Platform: BYUCTF Challenge: Intro 2 Category: Pwn Difficulty: Medium
Flag
byuctf{%p_yourself}
Target
<remote-host>:1367. dist.zip → intro + Dockerfile (pwn.red/jail, JAIL_TIME=60).
- ELF 64-bit, PIE, NX on, not stripped. Imports
system+/bin/sh; haswin(). - Disguised as a rev chal (embedded relic flag
byuctf{welcome_to_rev_fellas}decodes viamemfrob/XOR-0x2a inflag_check). The hint says solve it by pwning the remote. The rev flag isn't the real one.
Recon
win() @ 0x53c → system("/bin/sh") (sets up its own /bin/sh, so any redirected call = shell).
main loops up to 5 attempts:
Both branches call printf(buf) with no format string → format-string vulnerability, reachable just by sending a wrong flag. %99s and strcpy both stay in-frame (no stack overflow), so the format string is the bug.
Finding the offset & leaks
Send BBBBBBBB.%p.%p...: the marker 0x4242424242424242 lands on the 20th %p → format buffer is at arg offset 20. (Offsets 7 to 15 are the strcpy copy, mangled because flag_check runs memfrob on it in place.)
Scanning deeper:
%39$p→ pointer tomain=base + 0x5c9⇒ PIE base leak.%35$p→ a libc pointer (unused here).
Leak PIE, then GOT overwrite → win
Partial RELRO ⇒ GOT writable. puts@got = base + 0x3018. The plan fits the control flow perfectly: in the Incorrect branch, puts("Try again.") is called immediately after the vulnerable printf. So:
- Attempt 1: send
%39$p, leakmain, computebase. - Attempt 2: format-string write
win(base+0x53c) intoputs@got. When the trailingputs()fires, it jumps towin→system("/bin/sh").
Payload construction (manual, fits in 99 bytes):
- Write the low 6 bytes of
winas three%hnshorts toputs_got,+2,+4(the top 2 bytes are0x0000, same as the original entry, so skip them). - 64-bit target addresses contain null bytes, so place all addresses after every format directive.
scanf("%99s")happily reads embedded nulls (only whitespace stops it);printfexecutes all%hnwrites before hitting the first null in the address block and stopping. Order the shorts ascending so the cumulative%cwidths chain. - Reject payloads where the (ASLR-dependent) address bytes contain whitespace (
0x20/09/0a) and reconnect. Rare.
Result: base 0x559956339000 … PWNED_1000 … byuctf{%p_yourself} (flag at /app/flag.txt).
Takeaways
printf(user_buf)is the bug even when a fixed-formatprintf("%s", buf)sits right next to it. Check everyprintffor a missing format string.- PIE fmtstr workflow: first leak a binary pointer (here
%39$p→main) to recover the base, then write. One leak deterministically beats brute-forcing a partial overwrite (1/16 on the ASLR nibble). - GOT overwrite beats return-address overwrite here because a call to the target (
puts) happens in the same branch, right after the vulnerableprintf. Instant, deterministic trigger, no need to unwind the 5-attempt loop. - Null bytes in format-string addresses are fine if placed after all directives:
scanf("%s")reads nulls;printfdoes the%hnwrites before it stops at the null. - A
win()that loads its own argument means you only need to redirect one call. No register control required.
0xAdham
