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.
Setup
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.
Step 1Analyse the binary in Ghidra to find the password
ObservationThe 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:bashghidra bypassme.bin &bash# In Ghidra: look at main() -> decode_password()bash# decode_password XORs each byte with 0xAAbash# The encoded bytes are: D9 DF DA CF D8 8A D9 CF C9 DF D8 CFWhat 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_passwordnear 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 CFbecomessuper secure. You can compute this by hand, or use CyberChef to do it visually.Step 2Decode the password with CyberChef
Observationdecode_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 readssuper 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.Step 3Alternative: use LLDB to leak the password at runtime
ObservationThe 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.bashlldb ./bypassme.binbash# In LLDB:bash(lldb) breakpoint set --name strcmpbash(lldb) runbash# Type any guess when prompted, hit Enterbash# When breakpoint triggers:bash(lldb) register read rsibash# RSI points to the decoded password string: 'super secure'bash(lldb) memory read --format s $rsibash(lldb) continuebash# 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,
$rsipoints 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.
Step 4Run bypassme.bin on the remote server with the password
ObservationThe 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.bashssh ctf-player@<HOST> -p <PORT_FROM_INSTANCE>bash./bypassme.binbash# Enter: super secureExpected 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
How to prevent this
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.