Need For Speed picoCTF 2019 Solution

Published: April 2, 2026

Description

The binary decodes the flag but it takes too long. Speed it up to get the flag.

Download the binary and make it executable.

bash
wget <url>/need-for-speed
bash
chmod +x need-for-speed

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Run the binary and observe the delay
    Observation
    I noticed the challenge description said the binary 'takes too long,' which suggested running it first to observe exactly how and when it stalls so I could identify the mechanism causing the delay.
    Execute the binary. It will print something like 'Calculating key...' and then hang for a very long time (or use alarm() to kill itself after a few seconds). The actual flag decoding logic is correct - only the artificial delay prevents you from seeing the output.
    bash
    ./need-for-speed
    What didn't work first

    Tried: Running the binary with 'time ./need-for-speed' hoping it would complete if left long enough

    The alarm(1) call delivers SIGALRM after exactly 1 second, which kills the process with a signal - not a timeout you can wait out. The busy-wait loop would take millions of seconds, so no amount of patience resolves this at the OS level.

    Tried: Using 'strace ./need-for-speed' to trace system calls and spot the delay

    strace shows the alarm() syscall and its argument, confirming the timer is there, but strace itself cannot suppress signal delivery. The process still dies from SIGALRM after 1 second; you need a debugger that can handle signals, not just observe them.

    Learn more

    Binaries can use sleep(), alarm(), or busy-wait loops to introduce artificial delays. alarm(n) sends SIGALRM to the process after n seconds, killing it by default. These are used in CTF challenges as anti-automation measures.

  2. Step 2
    Use GDB to bypass the SIGALRM timer and get the flag
    Observation
    I noticed from running the binary (and from strace output) that an alarm() syscall was scheduling a SIGALRM to kill the process after 1 second, which suggested using GDB's signal-handling capability to suppress SIGALRM delivery so the slow calculate_key loop could run to completion.
    Open the binary in GDB. Before running, tell GDB to ignore SIGALRM so the alarm handler never fires. Then simply run the program - the slow calculate_key loop (which counts from 0xec61038e down to 0xd8c2071c and returns 0xd8c2071c as the key) now has unlimited time to complete, and print_flag() prints the flag. An alternate approach is to break at main, call get_key() and print_flag() directly without running the full program flow.
    bash
    gdb ./need-for-speed
    bash
    handle SIGALRM ignore
    bash
    run
    bash
    # Alternate: call functions directly instead
    bash
    # break main
    bash
    # run
    bash
    # call (int) get_key()
    bash
    # call (int) print_flag()

    Expected output

    picoCTF{Good job keeping bus #b3a1d39c speeding along!}
    What didn't work first

    Tried: Using 'handle SIGALRM stop' instead of 'handle SIGALRM ignore' in GDB

    'handle SIGALRM stop' tells GDB to pause execution when SIGALRM arrives and then pass the signal to the program. The alarm still fires, the process still receives and dies from SIGALRM - you just get a GDB prompt at the moment of death. 'ignore' is required so GDB discards the signal entirely and never delivers it.

    Tried: Setting a breakpoint on print_flag and running without handling SIGALRM first

    GDB still forwards unhandled signals to the inferior by default. The alarm fires at 1 second, SIGALRM kills the process before the calculate_key loop finishes, and the breakpoint on print_flag is never reached. The signal must be suppressed with 'handle SIGALRM ignore' before issuing 'run'.

    Learn more

    The binary calls set_timer() which invokes alarm(1) - this schedules a SIGALRM to fire after 1 second, killing the program. Then get_key() calls calculate_key(), which runs a countdown loop from 0xec61038e down to 0xd8c2071c - far too slow to finish in 1 second. The loop returns 0xd8c2071c as the key used to decrypt the flag.

    handle SIGALRM ignore tells GDB to not stop, not print, and not pass SIGALRM to the program. With the timer defanged, the calculation loop runs to completion and print_flag() is reached normally. Calling call (int) get_key() then call (int) print_flag() is an equivalent shortcut that skips even the timer setup.

  3. Step 3
    Read the flag
    Observation
    I noticed GDB completed the run without terminating early after suppressing SIGALRM, which meant the print_flag() call had executed and the decrypted flag should appear in the terminal output.
    After bypassing the delay, the binary completes its flag decoding and prints it. Copy the output.
    Learn more

    Patching a binary with xxd + a hex editor is another approach: find the call alarm instruction bytes and overwrite them with NOP instructions (0x90). This permanently removes the delay from the binary file.

Interactive tools
  • Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
  • Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.

Flag

Reveal flag

picoCTF{Good job keeping bus #b3a1d39c speeding along!}

The binary kills itself with a SIGALRM timer before the (correct) decode finishes. Bypass the timer rather than racing it: in GDB run `handle SIGALRM ignore` (or set the key directly and call print_flag), then let it complete. The bus-ID hex in the flag is instance-specific.

Key takeaway

Anti-debugging and anti-automation tricks like alarm timers, signal handlers, and artificial busy loops are surface-level obstructions that do not change the underlying logic of a binary. A debugger like GDB can intercept and suppress signals before they reach the process, patch out branches in memory, or call internal functions directly, completely sidestepping the intended control flow. Real-world malware uses the same SIGALRM and ptrace-detection tricks to slow down sandbox analysis, and the same debugger techniques defeat them.

Related reading

Want more picoCTF 2019 writeups?

Useful tools for Reverse Engineering

What to try next