Gatekeeper picoCTF 2026 Solution

Published: March 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 1
    Run the binary
    Observation
    I noticed the challenge provides a compiled binary named gatekeeper with no source, which suggested the first step is to run it directly so that its prompts and error messages reveal what kind of input it expects before committing to disassembly.
    Execute gatekeeper and read the prompt. It asks for a number, and internally checks it against a threshold using an unsigned comparison on a signed input.
    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 sequences from the binary, but the flag is only produced at runtime by reading a server-side file - it does not exist inside the binary. The numeric threshold may appear as a constant, but without understanding the base-conversion logic you still cannot craft the right input, so strings alone leaves you stuck.

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

    The binary also enforces a string-length check of exactly 3 characters. A decimal 1000 is 4 characters long and fails the length gate immediately, printing an error before the numeric comparison is ever evaluated. You need a representation that is simultaneously 3 characters and decodes to a value 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 2
    Send a 3-digit hex number greater than 999 decimal
    Observation
    I noticed the binary enforces a 3-character length limit AND a numeric threshold above 999, two constraints that no decimal integer can satisfy simultaneously, which suggested that the conversion must accept hex so that a 3-character value like '3e8' encodes 1000 decimal.
    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. Connect to the server and send 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 3 characters via a %3ls format specifier. '0x3e8' is 5 characters, so scanf truncates it to '0x3', which strtol in base 16 parses as 0x03 = 3 decimal - far below the 1000 threshold. The 0x prefix is not needed because strtol is already called with base 16 hardcoded.

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

    The disassembly shows the base argument to strtol is hardcoded to 16, not 0. Base 0 would only work if the binary used auto-detection, which it does not. With a hardcoded base of 16, '3e8' is parsed correctly without any prefix, and any prefix characters would just eat into the 3-character budget.

    Learn more

    In Ghidra, the main function reads input with scanf using a %3ls 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 between 1000 and 10000.

    Hex digit strings like 3e8(3 characters) decode to 1000 decimal, satisfying both constraints simultaneously. This is why the hint says "reversing the string and cleaning out extra text may help you recover the flag."

  3. Step 3
    Decode the reversed and obfuscated flag output
    Observation
    I noticed the binary's output contained repeated 'picoCTF_' substrings interspersed through what looked like a scrambled flag, which suggested the reveal_flag function was printing the flag in reverse with 'picoCTF_' inserted at intervals, requiring a strip-then-reverse post-processing step.
    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{g4t3k33p3r_...}
    What didn't work first

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

    The reveal_flag function inserts 'picoCTF_' forward every 4 characters while printing the flag backwards, so a plain rev of the full output produces a garbled string with 'picoCTF_' fragments scattered throughout. You must remove all occurrences of 'picoCTF_' first, then reverse, to recover the clean flag.

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

    The flag is not printed in its final form - it is interleaved and reversed on the fly. grep 'picoCTF{' on the raw output will not find a match because the opening brace appears only after you apply both the strip and reverse transformations. You need to post-process the output rather than search it verbatim.

    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.

Flag

Reveal flag

picoCTF{g4t3k33p3r_...}

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

Input validation bugs arise when a program enforces constraints on two different representations of the same value without recognizing they can diverge. Here, the string-length check and the numeric-value check operate on different aspects of the input, and hexadecimal encoding exploits the gap: a 3-character hex string encodes a value that a 3-digit decimal string never could. The same class of confusion (base mismatch, encoding mismatch, signed versus unsigned interpretation) appears in real-world license-key validators, authentication tokens, and integer overflow vulnerabilities across compiled languages.

Related reading

Want more picoCTF 2026 writeups?

Useful tools for Reverse Engineering

What to try next