Description
Your task is to analyze and exploit a password-protected binary called bypassme.bin. 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 connect.
The binary bypassme.bin will be provided on the remote server.
Solution
Walk me through it- Step 1Analyse the binary staticallyRun
filefirst (ltrace only works on dynamically linked binaries), then strings, objdump, and radare2's auto-analysis. See Ghidra if you prefer a GUI decompiler.bashfile bypassme.bin # confirm dynamically linked before using ltracebashstrings bypassme.bin | grep -iE 'pass|secret|correct|wrong'bashobjdump -d bypassme.bin | grep -A 20 'strcmp\|cmp'bashr2 -A bypassme.binLearn more
Static analysis examines a binary without running it.
stringsis the fastest static check - any hardcoded password stored as a null-terminated C string will appear in the output. After that,objdump -ddisassembles the binary and lets you read the assembly. Searching forstrcmporcmpinstructions near string references usually locates the authentication logic quickly.Radare2 (
r2) is a full reverse engineering framework. After opening a binary withr2 -A(which auto-analyzes), you can useaflto list functions,pdf @ mainto print the disassembly of main, andizto list all strings. It also has a visual mode (V) and a graphing mode (VV) for following control flow.The difference between
strcmpand a custom comparison loop matters for exploitation:strcmpis interceptable by ltrace and its arguments are visible in a debugger, while a custom byte-by-byte loop requires reading the comparison address to find where the expected password lives in memory. Both are ultimately readable with the right tool. - Step 2Pick a path: leak, patch, or flip eflagsThree approaches. (A)
ltracethe dynamic strcmp call and read the second argument off the line. (B) Patch the conditional jump in radare2. (C) Force the success branch in GDB by toggling the zero flag. Concrete commands below.bash# (A) Leak via ltrace - sample line: strcmp("input", "correct_password") = -1bashltrace -e strcmp ./bypassme.bin <<< 'guess'bashbash# (B) Find the jump in r2 then patch itbashr2 -A bypassme.binbash# In r2:bash# pdf @ main # print decompiled mainbash# /c jne # find conditional jumpsbash# s 0xADDR_OF_JNE # seek to itbash# qbashr2 -w bypassme.binbash# In r2: s 0xADDR_OF_JNE ; wa jmp 0xSUCCESS_ADDRbashbash# (C) GDB runtime flip - no on-disk patch neededbashgdb ./bypassme.binbash# In GDB: b *0xADDR_OF_JNE ; r ; set $eflags |= 0x40 ; c # 0x40 sets ZFLearn more
What ltrace shows. A typical leak line looks like
strcmp("hunter2", "correct_password") = -1: ltrace prints both args because it hooked the PLT entry forstrcmp. The non-zero return tells you the strings differ; copy the second argument and feed it to the binary as input.Binary patching. In x86,
jneis75 XX. Replacing75with74flips it toje; withEBit becomesjmp(always taken). Radare2's write mode (-w) pluswa(write assembly) lets you write human-readable instructions and r2 encodes them.pwntools'ELF.write()is the scriptable alternative.GDB eflags trick. The zero flag (ZF) lives at bit 6 of the EFLAGS register. After the
cmpthat compares your input to the expected password, ZF is set if they were equal. Set ZF manually withset $eflags |= 0x40before thejneexecutes and the jump is not taken, falling through to the success branch. This is the cleanest path because it leaves no trace on disk.In real engagements, patching is rare (you need write access). It's standard in cracking and malware analysis. Defenses include code signing and runtime self-hash checks.
- Step 3Run the patched binary or use the passwordEither run the patched binary which skips the password check, or supply the extracted password to the original.bash
echo 'EXTRACTED_PASSWORD' | ./bypassme.binbash./bypassme_patched.binLearn more
Both approaches arrive at the same result through different paths. Password extraction keeps the binary unmodified and uses knowledge of the secret to authenticate legitimately. Binary patching modifies program logic to skip authentication entirely, which is more powerful because it works even when passwords are derived from complex computations.
In a real penetration test, binary patching is rarely used (you need write access to the binary), but it is a standard technique in software cracking (removing license checks) and malware analysis (disabling anti-analysis tricks). Modern defenses include code signing (the OS refuses to run tampered binaries) and integrity checks (the program hashes its own code at startup).
Flag
picoCTF{byp4ss_m3_...}
The authentication bypass works either by extracting the hardcoded password or patching the comparison branch.
How to prevent this
How to prevent this
Hardcoded secrets in client binaries are visible to anyone with strings(1). Defense lives on the server.
- Never put a meaningful secret in a binary you ship. Strings, ltrace, IDA, and Ghidra all surface it within minutes. Treat client-side code as untrusted.
- Authenticate against a server: the client sends credentials, the server checks them, the server returns the protected data. The success branch should not exist on the client at all.
- For local-only license / DRM use cases, combine code signing, runtime self-hash checks, and obfuscation as defense-in-depth. None of these are unbreakable, but together they raise the cost above the value of cracking.