Skip to main content

Gatekeeper picoCTF 2026 Solution

Reverse engineer a binary to determine the exact input property it requires before granting access to the flag.

Published: March 20, 2026Updated: September 20, 2026

Description

What's behind the numeric gate? You only get access if you enter the right kind of number. Download gatekeeper, reverse the numeric checks, and enter the value that passes.

Download the binary and make it executable.
Run it and observe what kind of input it expects.
bash
chmod +x gatekeeper
bash
./gatekeeper

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Run the binary
    Observation
    There is a compiled binary and no source. Run it first: its prompts and error messages say what input it wants, before you commit to a disassembler.
    Execute gatekeeper and read the prompt. It asks for a number, and internally applies two separate checks to it: a length check on the string you typed and a value check on the number that string parses to.
    bash
    ./gatekeeper
    What didn't work first

    Tried: Running strings on the binary hoping to find the flag or the expected numeric value in plaintext.

    strings prints readable text from the binary, and the flag is not in there: it is read from a server-side file at runtime. The threshold may show up as a constant, but without understanding the base conversion you still cannot craft the input.

    Tried: Entering a normal 4-digit decimal number like 1000 to satisfy the 'greater than 999' check.

    There is also a length check of exactly 3 characters, and 1000 in decimal is four. It fails the length gate and errors out before the numeric comparison runs at all. You need three characters that decode above 999.

    Learn more

    When approaching an unknown binary, always start by running it with normal input to understand its interface. The program's prompts, error messages, and exit codes give you immediate information about what it expects. In this case the binary asks for a number, which suggests it performs integer comparisons internally.

    If the binary doesn't reveal enough from runtime behaviour, the next steps are static analysis (disassembling with objdump or Ghidra, or reading the source with strings / cat) and dynamic analysis (running under strace to see system calls, or ltrace to see library calls). For this challenge the vulnerability class (integer type confusion) is inferable from the prompt alone and confirmed quickly.

  2. Step 2Send a 3-digit hex number greater than 999 decimal
    Observation
    The binary wants at most 3 characters and a value above 999, which no decimal integer satisfies at once. So the conversion must read hex, where three characters reach 1000 easily.
    The binary reads the input as a string, checks the string length must be 3, and separately converts it to a long integer that must be greater than 999. Run the binary and feed it a 3-character hex value like 3e8 (hex for 1000). The program accepts hex via strtol.
    bash
    echo '3e8' | ./gatekeeper
    What didn't work first

    Tried: Sending '0x3e8' (with the 0x prefix) instead of bare '3e8' to make the hex value explicit.

    The binary reads exactly three characters. '0x3e8' is five, so it truncates to '0x3', which parses as 3 in base 16, nowhere near the threshold. The prefix is unnecessary anyway, since the base is hardcoded to 16.

    Tried: Trying strtol with base 0 and sending a string like '0x' prefix to auto-detect hex.

    The disassembly shows the base argument fixed at 16, not 0. Auto-detection would need base 0, which the binary does not use. With 16 hardcoded, '3e8' parses correctly on its own, and a prefix would only consume part of your three characters.

    Learn more

    In Ghidra, the main function reads input with scanf using a %3s format (limiting to 3 characters). It then converts the string with a base-16 parse (e.g. strtol(input, NULL, 16)), so a bare 3-character hex string like 3e8 (no 0x prefix needed) becomes 0x3e8 = 1000 decimal. The conversion must be base 16: with base 0, strtol would treat 3e8 as decimal and stop at the e, yielding just 3. The binary checks two conditions: string length must be 3, AND the numeric value must be greater than 999.

    Hex digit strings like 3e8 (3 characters) decode to 1000 decimal, satisfying both constraints simultaneously. The largest three-digit hex value, fff, is 4095, so any three hex characters from 3e8 upward pass the gate.

  3. Step 3Decode the reversed and obfuscated flag output
    Observation
    The output scatters repeated picoCTF_ substrings through what looks like a scrambled flag. reveal_flag prints the flag backwards with that string spliced in at intervals, so strip it out, then reverse.
    The reveal_flag function prints the flag content in reverse order, with 'picoCTF_' (inserted forward by the function) interspersed every 4 characters. Strip the 'picoCTF_' occurrences from the raw output, then reverse the remaining string to get the real flag.
    bash
    # Copy the output from the binary, strip out the 'picoCTF_' interspersed text
    bash
    # Then reverse what remains to read the flag
    bash
    echo '<output>' | sed -e 's/picoCTF_//g' | rev
    bash
    # Or: paste the output into Python and process it
    python
    python3 -c "output = '<paste output here>'; print(output.replace('picoCTF_', '')[::-1])"

    Expected output

    picoCTF{3_digit_hex_GT_999_...}
    What didn't work first

    Tried: Reversing the raw binary output directly without first stripping the interspersed 'picoCTF_' substrings.

    reveal_flag splices picoCTF_ in forward every four characters while printing the flag backwards, so reversing the raw output leaves those fragments scattered through it. Remove every occurrence first, then reverse.

    Tried: Using strings on the binary output file or piping the binary's stdout directly to grep for the flag format.

    The flag is never printed in its final form; it is interleaved and reversed as it goes. Grepping for the opening brace finds nothing, because that shape only exists after both transformations. Post-process the output rather than searching it.

    Learn more

    The reveal_flag function in this binary opens the flag file and prints it backwards, inserting the string "picoCTF_" (forward) every 4 characters as additional obfuscation. To recover the real flag: strip the repeated picoCTF_ interleaving and reverse the remaining characters.

    This two-layer obfuscation (reversal + interleaving) is a common CTF technique to prevent straightforward string extraction with strings. The hint in the challenge description explicitly tells you to reverse the string and clean out extra text.

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.
  • Base64 & Base32 DecoderDecode Base64 and Base32 strings with auto-detection. Multi-layer mode unwraps nested encodings automatically.

Flag

Reveal flag

picoCTF{3_digit_hex_GT_999_...}

Send a 3-character hex value greater than 999 decimal (e.g. '3e8' = 1000). The flag output is reversed and obfuscated with interspersed 'picoCTF_' text - strip that and reverse to get the real flag.

Key takeaway

Validation bugs appear when a program constrains two different representations of the same value without noticing they can diverge. Here a length check and a value check look at different aspects of the input, and hex encoding lives in the gap: three hex characters reach a value three decimal digits never could. The same confusion, base mismatch or encoding mismatch or signed versus unsigned, shows up in license-key validators, authentication tokens, and integer overflow bugs.

Related reading

Useful tools for Reverse Engineering

Where to go next