PS3 Payload Development
Preface
Assembler is dead -.-
It's true, almost no one need assembler anymore but for reversing we need it, because we need to understand what the CPU does. In alot cases we just have an dump to extract this information. I use the PL3 lv2_dump_analyser.idc script to reverse my lv2 dump with IDA.
Resources
Thanks to KaKaRoToKS
Why do we develope Payloads?
There are alot reasons. I give you some:
- Want to have a better knowledge about the GameOS/Hypervisor
- Want to play backups (Hermes Gamepad patch etc.)
- Want to cheat or modify a game
- Want to have fun
- ...
PL3
Structure of PL3 payload_dev.S Is the main "File", this includes everything and has the jailbreak in it (payload_main:)
pl3.h.S Defines the Macros and the "hack" entry point (payload_entry:), which branches payload_main. Also includes the firmware_symbols and config.
firmware_symbols.h.S Defines the most common firmware Symbols (ex. strncmp)
map_open_path.h.S Base function for the Syscall 35 which is added. (dunno much about it at the moment)
memory_patching.h.S dunno at the moment
send_eth.h.S Function used to init the eth device.
send_eth_res.h.S Function for sending data via eth. (Protocol is 0x1337)
memory_patching_res.h.S dunno at the moment
dev_syscalls.h.S Peek and Poke functions
open_hook.h.S Is the Syscall 35. Hooks the open function.
print_debug.h.S Provides a function for sending debug information via send_eth.
trace_helpers.h.S - Function for saving r3-r12 - Function for loading back r3-r12 - Function for sending information of r3-r12 via eth
patch_table.h.S You can imagine this as an array of patches which are applied to the memory. It says
/** * patch_table: * * The patch table used by exploit_main to patch the kernel * it format is .long address, .long new_value * * it will patch its content until the destination address is 0 * */
Handy Macros
MEM_BASE2 is defined in the firmware_symbols.h.S
ADDR_IN_PAGE
#define ADDR_IN_PAGE(target) (PAYLOAD_OFFSET_IN_PAGE + (target) - payload_entry)
ADDR_IN_MEM2 calculates the adress relative to the RESIDENT_AREA_OFFSET (resident_area_start)
#define ADDR_IN_MEM2(target) ((target) - RESIDENT_AREA_OFFSET)
ABSOLUTE_MEM2 calculates the adress relative to the current position. So you can give your banches an absolute memory address and it calculates the branch address to it for you.
// Absolute branching #define ABSOLUTE_MEM2(target) (target - (MEM_BASE2 + ADDR_IN_MEM2(.)))
// Dynamic macros to load a label into a register 1. define MEM_BASE(dest) \ li dest, 1; \ rldicr dest, dest, 63, 0;
#define LOAD_LABEL(base, dest, source, address) \ oris dest, source, ((base) + (address))@h; \ ori dest, dest, ((base) + (address))@l;
#define LOAD_LABEL2(dest, source, address) \ LOAD_LABEL(MEM_BASE2, dest, source, ADDR_IN_MEM2 (address))
#define LOADI_LABEL2(dest, address) \ LOAD_LABEL2(dest, dest, address)
#define LOAD_MEM_BASE2(dest) \ MEM_BASE (dest) \ LOAD_LABEL (MEM_BASE2, dest, dest, 0)
// Add system calls. Use only in exploit_main because of registers used... 1. define ADD_SYSCALL(source, ptr, num) \ LOAD_LABEL2 (%r3, source, ptr); \ LOAD_ABS (%r4, source, syscall_table); \ std %r3, 0x08*num(%r4); \
// For loading an absolute value 1. define LOAD_ABS(dest, source, address) LOAD_LABEL(0, dest, source, address) 2. define LOADI_ABS(dest, address) LOAD_ABS(dest, dest, address)
// Absolute .quads // HACK ALERT: the open toolchain bugs during compilation when trying to add // a 'bignum' with address or MEM_BASE1.. so we split it here into two .long // makes it easy since PPC is big endian. 1. define QUAD_MEM2(address) \ .long 0x80000000; \ .long MEM_BASE2 + ADDR_IN_MEM2(address);
/* Patch Table Macros */ 1. define PATCH_INST(offset, instruction...) \ .long offset; \ instruction;
#define PATCH_DATA(offset, data...) \ .long offset; \ .long data;
#define PATCH_BRANCH(offset, op, target) \ .long offset; \ op ((target) - (offset));
#define PATCH_BRANCH_MEM2(offset, op, target) \ PATCH_BRANCH (offset, op, (MEM_BASE2 + ADDR_IN_MEM2(target)));
#define BRANCH_ABSOLUTE(dest, target) \ MEM_BASE (dest); \ oris dest, dest, target@h; \ ori dest, dest, target@l; \ mtctr dest; \ bctrl;
#define DEFINE_FUNC_PTR(function) \ function##_ptr: \ .quad 0; \ function: \ mflr %r0; \ stdu %r1, -0x80(%r1); \ std %r31, 0x70(%r1); \ std %r0, 0x90(%r1); \ BRANCH_FUNC_PTR(%r31, function); \ ld %r31, 0x70(%r1); \ ld %r0, 0x90(%r1); \ addi %r1, %r1, 0x80; \ mtlr %r0; \ blr;
#define BRANCH_FUNC_PTR(dest, function) \ MEM_BASE (dest); \ LOAD_LABEL2 (dest, dest, function ##_ptr); \ ld dest, 0(dest); \ mtctr dest; \ bctrl;
#define LOAD_FUNC_PTR(function) \ ALLOC_AND_COPY_PROC(%r31, function ##_start, \ (function ## _end - function##_start)); \ LOAD_LABEL2 (%r6, %r30, function ##_ptr); \ std %r3, 0(%r6);
#define GET_CURRENT_PAGE(temp, dest) \ bl get_current_page; \ b got_current_page; \ get_current_page: \ mflr dest; \ blr; \ got_current_page: \ li temp, 0xfff; \ nor temp, temp, temp; \ and dest, dest, temp;
#define PANIC() \ li %r3, 0; \ li %r11, 255; \ sc 1;
#define ALLOCATE_BUFFER(base, variable, size) \ li %r3, size; \ li %r4, 0x27; \ BRANCH_ABSOLUTE(%r5, alloc); \ LOAD_LABEL2 (%r4, base, variable); \ std %r3, 0(%r4);
// Allocate new memory and copy a function to it. R3 to R11 will be lost // pl3_memcpy must be included! define ALLOC_AND_COPY_PROC(base_reg, function, size) \
// Copy functions that need to stay resident in memory to MEM_BASE2 1. define COPY_RESIDENT_AREA(base, page) \ LOAD_LABEL (MEM_BASE2, %r3, base, 0); \ addi %r4, page, ADDR_IN_PAGE(RESIDENT_AREA_OFFSET); \ li %r5, RESIDENT_AREA_SIZE; \ bl pl3_memcpy; \
Source codes are from PL3
Understanding the Hack
Patching
Hooking a function (strncmp)
I try to explain how to do it with the PL3. PL3 loads a patch_table (see below). This patches are applied to the memory.
I used this way for the hook of strncmp: Orginal PL3:
... patch_table: PATCH_DATA(patch_data1, 0x01000000) PATCH_INST(patch_func1 + patch_func1_offset, ld %r4, rtoc_entry_1(%r2)) //hang PATCH_INST(patch_func1 + patch_func1_offset + 4, ld %r3, 0x20(%r28)) PATCH_INST(patch_func1 + patch_func1_offset + 8, std %r3, 0(%r4)) #ifdef __MEMORY_PATCHING_H_S__ PATCH_BRANCH_MEM2 (patch_func2 + patch_func2_offset, bl, memory_patching) #endif #ifdef __OPEN_HOOK_H_S__ PATCH_BRANCH_MEM2 (patch_func3 + patch_func3_offset, b, hook_open) #endif PATCH_INST(patch_func4 + patch_func4_offset, li %r4, 0) //80010009 error PATCH_INST(patch_func4 + patch_func4_offset + 4, stw %r4, 0(%r3)) PATCH_INST(patch_func4 + patch_func4_offset + 8, blr) #ifndef NO_UNAUTH_SYSCALL PATCH_INST(patch_func5 + patch_func5_offset, li %r3, 1) //check feature? PATCH_INST(patch_func5 + patch_func5_offset + 4, blr) PATCH_INST(patch_func6 + patch_func6_offset, li %r3, 0) PATCH_INST(patch_func7 + patch_func7_offset, li %r3, 0) #endif // force lv2open return 0 PATCH_INST(patch_func8 + patch_func8_offset1, li %r3, 0) // disable calls in lv2open to lv1_send_event_locally which makes // the system crash PATCH_INST(patch_func8 + patch_func8_offset2, nop) PATCH_INST(patch_func9 + patch_func9_offset, nop) #ifdef __SYSCALL_HANDLER_H_S__ PATCH_BRANCH_MEM2 (patch_syscall_func, bl, syscall_handler) #endif #ifdef __PRINT_DEBUG_H_S__ //PATCH_BRANCH_MEM2(lv2_printf_null + 8, b, print_debug) //PATCH_BRANCH_MEM2(lv2_printf_null, b, print_debug) PATCH_BRANCH_MEM2(hvsc107_1, bl, print_hvsc107) PATCH_BRANCH_MEM2(hvsc107_2, bl, print_hvsc107) PATCH_BRANCH_MEM2(hvsc107_3, bl, print_hvsc107) #endif .long 0 ...
I added before the end of the Patch Table
#ifdef __STRNCMP_HACK__ PATCH_BRANCH_MEM2 (strncmp, b, hook_strncmp) #endif STRNCMP Included Version, if __STRNCMP_HACK__ is defined this is added to the PatchTable.
... patch_table: PATCH_DATA(patch_data1, 0x01000000) PATCH_INST(patch_func1 + patch_func1_offset, ld %r4, rtoc_entry_1(%r2)) //hang PATCH_INST(patch_func1 + patch_func1_offset + 4, ld %r3, 0x20(%r28)) PATCH_INST(patch_func1 + patch_func1_offset + 8, std %r3, 0(%r4)) #ifdef __MEMORY_PATCHING_H_S__ PATCH_BRANCH_MEM2 (patch_func2 + patch_func2_offset, bl, memory_patching) #endif #ifdef __OPEN_HOOK_H_S__ PATCH_BRANCH_MEM2 (patch_func3 + patch_func3_offset, b, hook_open) #endif PATCH_INST(patch_func4 + patch_func4_offset, li %r4, 0) //80010009 error PATCH_INST(patch_func4 + patch_func4_offset + 4, stw %r4, 0(%r3)) PATCH_INST(patch_func4 + patch_func4_offset + 8, blr) #ifndef NO_UNAUTH_SYSCALL PATCH_INST(patch_func5 + patch_func5_offset, li %r3, 1) //check feature? PATCH_INST(patch_func5 + patch_func5_offset + 4, blr) PATCH_INST(patch_func6 + patch_func6_offset, li %r3, 0) PATCH_INST(patch_func7 + patch_func7_offset, li %r3, 0) #endif // force lv2open return 0 PATCH_INST(patch_func8 + patch_func8_offset1, li %r3, 0) // disable calls in lv2open to lv1_send_event_locally which makes // the system crash PATCH_INST(patch_func8 + patch_func8_offset2, nop) PATCH_INST(patch_func9 + patch_func9_offset, nop) #ifdef __SYSCALL_HANDLER_H_S__ PATCH_BRANCH_MEM2 (patch_syscall_func, bl, syscall_handler) #endif #ifdef __PRINT_DEBUG_H_S__ //PATCH_BRANCH_MEM2(lv2_printf_null + 8, b, print_debug) //PATCH_BRANCH_MEM2(lv2_printf_null, b, print_debug) PATCH_BRANCH_MEM2(hvsc107_1, bl, print_hvsc107) PATCH_BRANCH_MEM2(hvsc107_2, bl, print_hvsc107) PATCH_BRANCH_MEM2(hvsc107_3, bl, print_hvsc107) #endif #ifdef __STRNCMP_HACK_H_S__ PATCH_BRANCH_MEM2 (strncmp, b, hook_strncmp) #endif .long 0 ...
strncmp is already defined in the firmware_symbols.h.S so we can easy use it. hook_strncmp is our "injected" function.
For example:
#ifndef __STRNCMP_HACK_H_S__ #define __STRNCMP_HACK_H_S__ hook_strncmp: b ABSOLUTE_MEM2(strncmp + 4) #endif
Now our strncmp gets hooked and our function will get called and we jump back to the strncmp.
Why this won't work that easy and why we jump to "strncmp+4" will be more clear in "Recreate overwritten code".
Recreate overwritten code
First we have to know what really happens in the memory.
The strncmp function on my 3.41 dump looks like this:
ROM:0004D344 strncmp: ROM:0004D344 cmpdi %r5, 0 ROM:0004D348 beq loc_4D398 ROM:0004D34C lbz %r11, 0(%r4) ROM:0004D350 lbz %r9, 0(%r3) ROM:0004D354 clrlwi %r0, %r11, 24 ROM:0004D358 cmpw cr7, %r9, %r11 ROM:0004D35C bne cr7, loc_4D3A4 ROM:0004D360 cmpwi cr7, %r0, 0 ROM:0004D364 mtctr %r5 ROM:0004D368 bne cr7, loc_4D38C ROM:0004D36C b loc_4D3A4 ROM:0004D370 loc_4D370: ROM:0004D370 lbz %r11, 0(%r4) ROM:0004D374 lbz %r9, 0(%r3) ROM:0004D378 clrlwi %r0, %r11, 24 ROM:0004D37C cmpw cr7, %r9, %r11 ROM:0004D380 cmpwi cr6, %r0, 0 ROM:0004D384 bne cr7, loc_4D3A4 ROM:0004D388 beq cr6, loc_4D3A4 ROM:0004D38C loc_4D38C: ROM:0004D38C addi %r3, %r3, 1 ROM:0004D390 addi %r4, %r4, 1 ROM:0004D394 bdnz loc_4D370 ROM:0004D398 loc_4D398: ROM:0004D398 li %r0, 0 ROM:0004D39C extsw %r3, %r0 ROM:0004D3A0 blr ROM:0004D3A4 loc_4D3A4: ROM:0004D3A4 clrlwi %r9, %r9, 24 ROM:0004D3A8 clrlwi %r0, %r11, 24 ROM:0004D3AC subf %r0, %r0, %r9 ROM:0004D3B0 extsw %r3, %r0 ROM:0004D3B4 blr
After the patch is applied it looks like this:
ROM:0004D344 strncmp: //This is removed //ROM:0004D344 cmpdi %r5, 0 //gets overwritten with our patchtable hack ROM:0004D344 b hook_strncmp ROM:0004D348 beq loc_4D398 ROM:0004D34C lbz %r11, 0(%r4) ROM:0004D350 lbz %r9, 0(%r3) ROM:0004D354 clrlwi %r0, %r11, 24 ROM:0004D358 cmpw cr7, %r9, %r11 ROM:0004D35C bne cr7, loc_4D3A4 ROM:0004D360 cmpwi cr7, %r0, 0 ROM:0004D364 mtctr %r5 ROM:0004D368 bne cr7, loc_4D38C ROM:0004D36C b loc_4D3A4 ROM:0004D370 loc_4D370: ROM:0004D370 lbz %r11, 0(%r4) ROM:0004D374 lbz %r9, 0(%r3) ROM:0004D378 clrlwi %r0, %r11, 24 ROM:0004D37C cmpw cr7, %r9, %r11 ROM:0004D380 cmpwi cr6, %r0, 0 ROM:0004D384 bne cr7, loc_4D3A4 ROM:0004D388 beq cr6, loc_4D3A4 ROM:0004D38C loc_4D38C: ROM:0004D38C addi %r3, %r3, 1 ROM:0004D390 addi %r4, %r4, 1 ROM:0004D394 bdnz loc_4D370 ROM:0004D398 loc_4D398: ROM:0004D398 li %r0, 0 ROM:0004D39C extsw %r3, %r0 ROM:0004D3A0 blr ROM:0004D3A4 loc_4D3A4: ROM:0004D3A4 clrlwi %r9, %r9, 24 ROM:0004D3A8 clrlwi %r0, %r11, 24 ROM:0004D3AC subf %r0, %r0, %r9 ROM:0004D3B0 extsw %r3, %r0 ROM:0004D3B4 blr
The Problem: We overwritte code to call our function. We have to recreate the code. Easy if you know what you have overwritten:
We have overwritten this: ROM:0004D344 cmpdi %r5, 0 So our Code changes to:
#ifndef __STRNCMP_HACK_H_S__ #define __STRNCMP_HACK_H_S__ hook_strncmp: cmpdi %r5, 0 b ABSOLUTE_MEM2(strncmp + 4) #endif
That we jump to "strncmp+4" should be clear now, we have to "overjump" our branch to hook_strncmp.
Saving the Register
The Problem is we hook into a function and play around. This will modify our register and if the programm uses those register, we may change the outcome. To prevent this, we save all register we modify and reload them before we jump back to our real code.
KaKaRoToKS has made code for us, we can use it. I explain you how this works, so you dont need to use it blind.
We look at these lines partial found in trace_helpers.h.S. This simply stores all register and loads them to/from the address of r1.
store_regs: std %r3, 0x70(%r1) std %r4, 0x78(%r1) std %r5, 0x80(%r1) std %r6, 0x88(%r1) std %r7, 0x90(%r1) std %r8, 0x98(%r1) std %r9, 0xA0(%r1) std %r10, 0xA8(%r1) std %r11, 0xB0(%r1) std %r12, 0xB8(%r1) blr load_regs: ld %r3, 0x70(%r1) ld %r4, 0x78(%r1) ld %r5, 0x80(%r1) ld %r6, 0x88(%r1) ld %r7, 0x90(%r1) ld %r8, 0x98(%r1) ld %r9, 0xA0(%r1) ld %r10, 0xA8(%r1) ld %r11, 0xB0(%r1) ld %r12, 0xB8(%r1) blr
How to use this: As simple as understanding everything :D. (Background knowledge: r1 is used as stack pointer).
First we save the link register in r0.
mflr %r0
After that we get space on the stack for our register data.
stdu %r1, -0x100(%r1)
Saving the register
bl store_regs
Loading our saved register
bl load_regs
Free our stack memory
addi %r1, %r1, 0x100
Reset the link register
mtlr %r0
[Not tested] We use this in our code, so we can do our own crap without destroying something:
#ifndef __STRNCMP_HACK_H_S__ #define __STRNCMP_HACK_H_S__ #include "trace_helpers.h.S" hook_strncmp: //Save the link Register mflr %r0 //Get space on the stack (r1 = StackPointer) stdu %r1, -0x100(%r1) //Save the register bl store_regs //... OUR CODE //Restore register bl load_regs //Restore stack addi %r1, %r1, 0x100 //Restore link Register mtlr %r0 //Our overwritten code cmpdi %r5, 0 //Branch original strncmp b ABSOLUTE_MEM2(strncmp + 4) #endif
Modify a function
Doing our own crap
Debug via ETH
Creating a Syscall (explained on Peek & Poke)
What next?
Thanks to
Hermes
Retrieved from "http://ps3wiki.lan.st/index.php/PS3_Payload_Developement"