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
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.
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 1
Analyse the binary in Ghidra to find the passwordObservationI noticed the challenge description said the binary performs input sanitation and hides a password, which suggested loading bypassme.bin into a decompiler like Ghidra to trace the authentication logic and find where the password is constructed 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 something like: F9 DF DA DF C9 DF ...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 only shows literal strings embedded in the binary. Because the password bytes are XOR-encoded constants, they appear as raw byte arrays, not as a string reference. The decoded value only materialises in memory at runtime, so you must read the decode_password() function body and compute the XOR manually or with CyberChef.
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. You can compute this by hand, or use CyberChef to do it visually.Step 2
Decode the password with CyberChefObservationI noticed the Ghidra analysis revealed that decode_password() XORs 12 hardcoded bytes with the fixed key 0xAA, which suggested using CyberChef's 'From Hex' and 'XOR' operations to recover the plaintext password without writing any custom 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 3
Alternative: use LLDB to leak the password at runtimeObservationI noticed the binary must compare the user's input against the decoded password in memory, which suggested setting a breakpoint on strcmp under LLDB and reading the RSI register to capture the expected password without needing to reverse the decode algorithm manually.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
In the x86-64 System V calling convention, RDI holds the first argument and RSI holds the second. At strcmp, RDI points to your own input string (whatever you typed), while RSI points to the decoded expected password. Reading RDI just echoes back your own guess, not the secret.
Tried: Run bypassme.bin under LLDB locally with the discovered password expecting to see the flag
The flag file on the local copy is absent, and even if it were present, LLDB strips the setUID bit when launching the process. The binary reads the flag using elevated privileges that only exist on the remote server. You must SSH to the challenge server and run the binary there without a debugger to obtain the flag.
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 it runs without the setUID permissions. Run outside the debugger with the real password to get the flag.
Step 4
Run bypassme.bin on the remote server with the passwordObservationI noticed the setup described bypassme.bin as a setUID executable on the remote server, which suggested the flag file is only accessible with elevated privileges and that the password must be entered on the remote host 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{byp4ss_m3_...}Learn more
The binary is setUID on the remote server, meaning it runs as root 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{byp4ss_m3_...}
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.