- 类别:
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)。与 Exec、Sleep 等「由工具生成特定行为字节码」的 sink 不同,本 gadget 不生成任何逻辑,而是原样搬运使用者自带的字节码——即「Bring Your Own Bytecode」,用于自定义 payload、绕过对已知 sink 字节码的特征检测,或加载工具未内置的功能类。
标签由 @GadgetTags(tags={"BytecodeConvertTag", "Bytecode", "END"}) 声明,nextTags 与 excludes 均为空。
- 入口
tags:Bytecode—— 声明「我就是最终字节码的产出者」,可填入任何期望下游是Bytecode的槽位(典型上游是BytecodeConvert,其nextTags={"Bytecode"};或直接是需要_bytecodes的TemplatesImpl装载器)。BytecodeConvertTag—— 同时声明自己可占据「字节码加工位」。由于使用者提供的往往已是成品 class(若做 TemplatesImpl 利用则应自行让该类extends AbstractTranslet),因此本 gadget 可以替代BytecodeConvert直接接在装载器之后,跳过工具的类名/版本/接口改写环节。END—— 链尾终结标签:本 gadget 自身产出最终字节码,其后不再挂接更内层 gadget。
- 衔接
nextTags:(空)—— 作为叶子节点,invoke中chain.doCreate拿不到更内层对象。 excludes:(空)—— 无互斥约束。
关于链构建方向:java-chains 由内向外构建,
chain: [..., 装载器, BytecodeFromBase64]时,最内层先执行的是本 gadget,产出byte[]后被外层装载器包装。三个 tag 的组合让它既能顶替Exec(Bytecode,END)位,也能顶替BytecodeConvert(BytecodeConvertTag)位。
全类仅两个方法,逻辑极简(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]对应的类名,以便反序列化时defineClass后newInstance定位到正确的类。与BytecodeConvert会自己生成/覆盖className(FileHelper.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 回填模式,区别只在字节码来源。
| 字段 | 名称 | 说明 | 类型 | 默认 | 可选值 |
|---|---|---|---|---|---|
base64String |
Base64字符串 | 会自动根据该字符串进行 Base64 解码获取字节码;解码结果应为合法 class 文件(首 4 字节 CAFEBABE)。 |
String(public 字段) |
无(必填) | 无枚举 |
使用者需自行准备 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,则newTransformer时ClassCastException,利用失败。
usedInChains 为空——自由构件,未直接出现在 51 条预设链中。它作为「自定义字节码源」按需替换预设链末端的行为型 sink(如把 CB1 链 中的 Exec 换成 BytecodeFromBase64,即可投递任意自带 class 而非固定的命令执行字节码)。
- 上游(消费本 gadget 产出的字节码):BytecodeConvert(
nextTags=Bytecode,字节码加工器)、TemplatesImpl 及其变体 TemplatesImpl2、TransformerWithTemplatesImpl(字节码装载器)。 - 同族(可互换的字节码源):BytecodeFromHex、BytecodeFromLocalFile、BytecodeFromUploadFile。
- 同位替换对象(行为型 sink):Exec、Sleep、ReverseShell 等——它们由工具生成固定行为字节码,本 gadget 则由使用者自带。
- 检测特征:
- 反序列化流中出现
TemplatesImpl,其_bytecodes[0]以魔数CA FE BA BE开头——本 gadget 与所有 TemplatesImpl 系利用共有此指纹,可对_bytecodes/_name/_tfactory字段组合告警。 - 与
Exec等相比,payload 中不含明文命令字符串(如bash -c、cmd.exe),恶意逻辑藏于自带 class 的静态块/transform(),基于命令串的签名会失效;应改为对「非白名单类名的字节码装载」这一行为建模,而非匹配命令关键字。 - 若攻击者跳过
BytecodeConvert,字节码类名可能是非随机的原始类名(BytecodeConvert默认随机化),出现「可读但陌生」的 translet 类名亦可作弱信号。
- 反序列化流中出现
- 加固建议:
- 反序列化入口启用 look-ahead 过滤(
ObjectInputFilter/JEP 290、Apache CommonsValidatingObjectInputStream),黑名单com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl及等价 xalan 装载类。 - 升级运行时到 JDK 17+,利用模块封装阻断对内部 xalan 类的反射写入,使 TemplatesImpl 系装载链整体失效。
- 移除/隔离不必要的 xalan、commons-collections、commons-beanutils 等可作外层触发器的依赖;对不可信数据一律避免原生
readObject,改用白名单化的数据格式。
- 反序列化入口启用 look-ahead 过滤(