Skip to content

Latest commit

 

History

History
98 lines (71 loc) · 10 KB

File metadata and controls

98 lines (71 loc) · 10 KB

自定义字节码-Base64字符串(BytecodeFromBase64)

  • 类别bytecode 别名:(无) 优先级:200
  • 作者:Ar3h
  • 实现com.ar3h.chains.common.Gadget(非抽象类,无基类)
  • 依赖@GadgetAnnotation 未声明 Maven 依赖;运行期实际调用 org.apache.commons.codec.binary.Base64(commons-codec,由 java-chains 自身携带),产出的字节码不为被测目标引入额外第三方依赖。

作用

把使用者手工提供的一段 Base64 编码的 Java class 字节码解码为 byte[],作为字节码链的终端字节码源交给上游的字节码装载器(如 TemplatesImpl)或字节码加工器(BytecodeConvert)。与 ExecSleep 等「由工具生成特定行为字节码」的 sink 不同,本 gadget 不生成任何逻辑,而是原样搬运使用者自带的字节码——即「Bring Your Own Bytecode」,用于自定义 payload、绕过对已知 sink 字节码的特征检测,或加载工具未内置的功能类。

链接标签

标签由 @GadgetTags(tags={"BytecodeConvertTag", "Bytecode", "END"}) 声明,nextTagsexcludes 均为空。

  • 入口 tags
    • Bytecode —— 声明「我就是最终字节码的产出者」,可填入任何期望下游是 Bytecode 的槽位(典型上游是 BytecodeConvert,其 nextTags={"Bytecode"};或直接是需要 _bytecodesTemplatesImpl 装载器)。
    • BytecodeConvertTag —— 同时声明自己可占据「字节码加工位」。由于使用者提供的往往已是成品 class(若做 TemplatesImpl 利用则应自行让该类 extends AbstractTranslet),因此本 gadget 可以替代 BytecodeConvert 直接接在装载器之后,跳过工具的类名/版本/接口改写环节。
    • END —— 链尾终结标签:本 gadget 自身产出最终字节码,其后不再挂接更内层 gadget。
  • 衔接 nextTags:(空)—— 作为叶子节点,invokechain.doCreate 拿不到更内层对象。
  • excludes:(空)—— 无互斥约束。

关于链构建方向:java-chains 由内向外构建,chain: [..., 装载器, BytecodeFromBase64] 时,最内层先执行的是本 gadget,产出 byte[] 后被外层装载器包装。三个 tag 的组合让它既能顶替 ExecBytecode,END)位,也能顶替 BytecodeConvertBytecodeConvertTag)位。

源码剖析

全类仅两个方法,逻辑极简(verbatim 摘自 ):

@GadgetTags(tags={"BytecodeConvertTag", "Bytecode", "END"})
@GadgetAnnotation(name="自定义字节码-Base64字符串",
    description="从参数中解码获取字节码字节流,适用于自定义字节码场合,增加了工具拓展性",
    priority=200, authors={"Ar3h"})
public class BytecodeFromBase64 implements Gadget {

    @Param(name="Base64字符串", description="会自动根据该字符串进行Base64解码获取字节码")
    public String base64String;

    public byte[] getObject(GadgetContext context) throws Exception {
        byte[] bytecode = Base64.decodeBase64((String)this.base64String);   // ① Base64 → 原始 class 字节
        JavassistHelper javassistHelper = new JavassistHelper(bytecode);     // ② 解析字节码
        context.put(ContextTag.CLASS_NAME_KEY, javassistHelper.getClassName()); // ③ 回填类名到上下文
        return javassistHelper.getBytecode();                               // ④ 交回字节码
    }

    @Override
    public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
        chain.doCreate(context);       // 叶子节点:本链无更内层,返回值被忽略
        return this.getObject(context);
    }
}

逐段解释:

  • Base64.decodeBase64(base64String):把使用者填入的 base64String 解码为原始字节数组。这段字节应是一个合法的 .class 文件——其前 4 字节即 class 文件魔数 0xCAFEBABE。工具不校验也不生成任何业务逻辑,字节内容完全由使用者掌控(这正是 description 所称「增加了工具拓展性」的含义)。
  • new JavassistHelper(bytecode):用 Javassist 把字节码载入为可解析对象。JavassistHelper 未包含在反编译源码中(属 com.ar3h.chains.common.util 工具类),从其在本类及 BytecodeConvert 中的用法可确定它的构造入参为 byte[],并对外暴露 getClassName() / getBytecode() 等方法。此处未做任何改写,仅用于读取类名。
  • context.put(ContextTag.CLASS_NAME_KEY, javassistHelper.getClassName()):把解析出的真实类名写入 GadgetContext。这是与上游装载器协作的关键——例如 TemplatesImpl 利用需要知道 _bytecodes[0] 对应的类名,以便反序列化时 defineClassnewInstance 定位到正确的类。与 BytecodeConvert 会自己生成/覆盖 classNameFileHelper.getRandomClassName())不同,本 gadget 尊重使用者字节码里原有的类名。
  • return javassistHelper.getBytecode():返回(可能经 Javassist 规整过的)字节码 byte[] 交给外层 gadget 包装。
  • invoke:先 chain.doCreate(context)——因本 gadget 是链尾(END),内层为空,返回值被丢弃;随后调用 getObject 产出字节码。这一「先 doCreate 再产出」的写法与接口约定保持一致,即便作为叶子也无害。

与同族对照BytecodeFromHex 结构完全一致,仅把 ① 换成 Hex.decodeHex(hexString)BytecodeFromLocalFile / BytecodeFromUploadFile 则从文件读取字节。四者共享同一套 JavassistHelper + CLASS_NAME_KEY 回填模式,区别只在字节码来源。

参数(@Param)

字段 名称 说明 类型 默认 可选值
base64String Base64字符串 会自动根据该字符串进行 Base64 解码获取字节码;解码结果应为合法 class 文件(首 4 字节 CAFEBABE)。 Stringpublic 字段) 无(必填) 无枚举

使用者需自行准备 class 字节码。若用于 TemplatesImpl 系利用链,该类应 extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,并把恶意逻辑放在静态初始化块transform() 中,以便在 newInstance 时触发。

适用版本与原理要点

  • 无版本/依赖绑定:本 gadget 只做 Base64 解码与字节码搬运,不依赖任何目标侧第三方库,理论上适配所有场景;实际能否 RCE 取决于外层装载器的约束。
  • 典型约束来自装载器:最常见的外层是 TemplatesImpl,其通过 TransformerFactoryImpl.newTransformer()getTransletInstance()defineClass 加载 _bytecodes,要求目标 JDK < 17(JDK 17+ 因模块封装无法反射写入内部 xalan 类)。若走 BytecodeConvert 加工,还可指定字节码 version(如 JDK6=50)以规避 java.lang.VerifyError
  • 可跳过 BytecodeConvert:因携带 BytecodeConvertTag,可直接接在装载器后,把使用者的成品字节码当作「已转换字节码」,省去类名随机化/接口注入。代价是使用者需自行保证字节码满足装载器要求(继承 AbstractTranslet、无参构造、类名与 CLASS_NAME_KEY 一致等)。
  • 坑点:若字节码不是合法 class,JavassistHelper 构造即抛异常;若目标类未继承 AbstractTranslet 而外层是 TemplatesImpl,则 newTransformerClassCastException,利用失败。

所属预设链

usedInChains 为空——自由构件,未直接出现在 51 条预设链中。它作为「自定义字节码源」按需替换预设链末端的行为型 sink(如把 CB1 链 中的 Exec 换成 BytecodeFromBase64,即可投递任意自带 class 而非固定的命令执行字节码)。

关联

防御与检测

  • 检测特征
    • 反序列化流中出现 TemplatesImpl,其 _bytecodes[0] 以魔数 CA FE BA BE 开头——本 gadget 与所有 TemplatesImpl 系利用共有此指纹,可对 _bytecodes/_name/_tfactory 字段组合告警。
    • Exec 等相比,payload 中不含明文命令字符串(如 bash -ccmd.exe),恶意逻辑藏于自带 class 的静态块/transform(),基于命令串的签名会失效;应改为对「非白名单类名的字节码装载」这一行为建模,而非匹配命令关键字。
    • 若攻击者跳过 BytecodeConvert,字节码类名可能是非随机的原始类名BytecodeConvert 默认随机化),出现「可读但陌生」的 translet 类名亦可作弱信号。
  • 加固建议
    • 反序列化入口启用 look-ahead 过滤(ObjectInputFilter/JEP 290、Apache Commons ValidatingObjectInputStream),黑名单 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 及等价 xalan 装载类。
    • 升级运行时到 JDK 17+,利用模块封装阻断对内部 xalan 类的反射写入,使 TemplatesImpl 系装载链整体失效。
    • 移除/隔离不必要的 xalan、commons-collections、commons-beanutils 等可作外层触发器的依赖;对不可信数据一律避免原生 readObject,改用白名单化的数据格式。