Skip to main content

Bypass Me picoCTF 2026 Solution

Analyze a password-protected binary, understand its authentication logic, and find a way in.

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

Description

Your task is to analyze and exploit a password-protected binary called bypassme.bin. The binary performs input sanitation. Instead of guessing the password, reverse engineer or debug the program to bypass the authentication logic and retrieve the hidden flag.

Launch the challenge instance and SSH to the provided host.
The binary bypassme.bin is a setUID executable on the remote server.
Use SCP to copy the binary to your local machine for analysis.
bash
scp -P <PORT_FROM_INSTANCE> ctf-player@<HOST>:bypassme.bin .

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Analyse the binary in Ghidra to find the password
    Observation
    The description mentions input sanitation and a hidden password. Load the binary into Ghidra and trace the authentication logic to where the password is built or compared.
    Load bypassme.bin into Ghidra and analyse it. In main(), you will see a call to decode_password() before the intro sequence. decode_password() takes 12 hardcoded bytes and XOR-decodes each one with 0xAA to produce the actual password.
    bash
    # After downloading the binary locally:
    bash
    ghidra bypassme.bin &
    bash
    # In Ghidra: look at main() -> decode_password()
    bash
    # decode_password XORs each byte with 0xAA
    bash
    # The encoded bytes are: D9 DF DA CF D8 8A D9 CF C9 DF D8 CF
    What didn't work first

    Tried: Run 'strings bypassme.bin' to find the password directly in the binary

    strings outputs printable ASCII sequences, but decode_password() stores the password as XOR-obfuscated bytes that are not printable ASCII. The plaintext 'super secure' never exists in the binary on disk, only in memory after decoding, so strings returns nothing useful for the password.

    Tried: Search for the password in Ghidra by looking at string references in main() instead of following the call to decode_password()

    Ghidra's string table lists literal strings only. The password bytes are XOR-encoded constants, so they appear as a raw byte array with no string reference, and the decoded value exists only at runtime. Read the decode function and compute the XOR yourself.

    Learn more

    Ghidra is a free reverse engineering suite developed by the NSA. Load the binary, let it analyze, then browse to the main function in the Listing view. You will see a call to decode_password near the top of main. Navigate into that function to see the array of bytes and the XOR loop.

    The decoded password is the XOR of each encoded byte with 0xAA: D9 DF DA CF D8 8A D9 CF C9 DF D8 CF becomes super secure. You can compute this by hand, or use CyberChef to do it visually.

  2. Step 2Decode the password with CyberChef
    Observation
    decode_password() XORs 12 hardcoded bytes against a fixed key of 0xAA. CyberChef's From Hex and XOR operations recover the plaintext without writing any code.
    Use CyberChef to XOR the encoded bytes with key 0xAA and read the plaintext password. The password is 'super secure'.

    In CyberChef: (1) paste the encoded hex bytes, (2) add "From Hex", (3) add "XOR" with key AA (hex). The output reads super secure.

    Learn more

    XOR with a fixed key is the simplest obfuscation scheme. Applying the same key twice returns the original: byte XOR 0xAA XOR 0xAA == byte. So encode and decode use the exact same operation. CyberChef (gchq.github.io/CyberChef) handles this visually with "From Hex" then "XOR" operations.

  3. Step 3Alternative: use LLDB to leak the password at runtime
    Observation
    The binary has to compare your input against the decoded password in memory. Break on strcmp in LLDB and read RSI, and you get the expected value without reversing the decoder at all.
    Run bypassme.bin under LLDB and set a breakpoint on strcmp. When the breakpoint hits, inspect RSI (the second argument) which contains the expected password. This works even if you don't understand the decode algorithm.
    bash
    lldb ./bypassme.bin
    bash
    # In LLDB:
    bash
    (lldb) breakpoint set --name strcmp
    bash
    (lldb) run
    bash
    # Type any guess when prompted, hit Enter
    bash
    # When breakpoint triggers:
    bash
    (lldb) register read rsi
    bash
    # RSI points to the decoded password string: 'super secure'
    bash
    (lldb) memory read --format s $rsi
    bash
    (lldb) continue
    bash
    # Now run outside the debugger with the real password:
    What didn't work first

    Tried: Read RDI instead of RSI at the strcmp breakpoint to get the expected password

    Under the System V calling convention, RDI is the first argument and RSI the second. At strcmp, RDI points at whatever you typed and RSI at the decoded password. Read RDI and you get your own guess back.

    Tried: Run bypassme.bin under LLDB locally with the discovered password expecting to see the flag

    There is no flag file locally, and even with one, the kernel ignores the setUID bit for a process launched under a debugger (a ptraced exec drops the elevated ID). The elevated privileges the read needs exist only on the challenge server. SSH there and run the binary without a debugger.

    Learn more

    In x86-64, function arguments are passed in registers: RDI is the first argument (your input), RSI is the second argument (the expected password). When the breakpoint hits at strcmp, $rsi points to the string the program is comparing against - which is the decoded password.

    Note: when running under LLDB, the binary cannot read the flag file because the kernel drops the setUID elevation for a traced process. Run outside the debugger with the real password to get the flag.

  4. Step 4Run bypassme.bin on the remote server with the password
    Observation
    The binary is setUID on the remote server, so the flag file is only readable with elevated privileges. Enter the password there rather than locally.
    SSH back to the challenge server and run bypassme.bin directly. Enter the password 'super secure' when prompted to get the flag.
    bash
    ssh ctf-player@<HOST> -p <PORT_FROM_INSTANCE>
    bash
    ./bypassme.bin
    bash
    # Enter: super secure

    Expected output

    picoCTF{d3bugg3r_p0w3r_is_4w3s0m3_...}
    Learn more

    The binary is setUID on the remote server, meaning it runs with the privileges of its owner (root here) regardless of who executes it. This is why you must run it on the server (not locally) to get the flag - only the server-side binary has the setUID bit set and can read the protected flag 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.

Flag

Reveal flag

picoCTF{d3bugg3r_p0w3r_is_4w3s0m3_...}

The password is 'super secure'. Find it by XOR-decoding the 12 bytes in decode_password() with key 0xAA in Ghidra/CyberChef, or by setting a breakpoint on strcmp in LLDB and reading RSI.

Key takeaway

A secret compiled into a binary is not hidden; anyone can open a decompiler or attach a debugger. XOR obfuscation delays discovery by seconds, because the decoded value has to exist in memory at runtime and any comparison function is a place to catch it. Real authentication happens on the server, where the client receives a yes or no and never the secret.

How to prevent this

Hardcoded secrets in client binaries are visible to anyone with a decompiler. Defense lives on the server.

  • Never put a meaningful secret in a binary you ship. Ghidra, IDA, and strings all surface XOR-obfuscated secrets within minutes. Treat client-side code as untrusted.
  • Authenticate against a server: the client sends credentials, the server checks them. The flag should only exist server-side and never be readable by an unprivileged user.

Related reading

Tools used in this challenge

Where to go next