Challenge Setup
In a typical reverse engineering CTF challenge, you receive a compiled ELF binary (Linux) or PE executable (Windows) that behaves like a password checker: it prompts for input, validates it against some internal logic, and either prints the flag or rejects you. Your job is to determine the correct input — or to bypass the check entirely. Before writing a single line of code or opening Ghidra, gather basic information about the file.file tells you the architecture (x86, x86-64, ARM), linking type (static or dynamic), and whether the binary has been stripped of debug symbols. strings often surfaces hardcoded flags, error messages, and function names that immediately suggest where to look next. Running the binary itself shows you the interface — what input it expects and how it responds.
Analysis Workflow
1
Identify the binary type and gather static strings
Start with the broadest tools before drilling down. Run Look in the
file and strings to establish the basics, then use checksec to understand what mitigations are in place.strings output for anything that resembles a flag format, a password, a Base64 blob, or a telling error message like “Wrong password” or “Correct!”. These strings anchor your analysis — find the code that uses them and work outward from there.2
Load the binary into Ghidra and find main
Open Ghidra, create a new project, and import the binary. Let the auto-analysis run with default settings — this resolves function boundaries, identifies cross-references, and applies known library signatures.Once analysis completes, open the Symbol Tree panel and navigate to
Functions. Look for main, entry, or any function whose name contains “check”, “verify”, “validate”, or “password”. If the binary is stripped, navigate to the entry point and follow the call that takes the largest number of arguments — that’s typically main.In the Decompiler panel, Ghidra renders the selected function as readable C-like pseudocode. This is your primary working view. Read through main to understand the program’s flow: where it reads input, what it calls to validate it, and what it does when validation succeeds.3
Locate and analyze the flag check function
Follow calls from
main until you find the function that compares your input against the expected value. This function might use:strcmporstrncmp— direct string comparison against a hardcoded value- A loop with XOR, addition, or rotation operations — a custom encoding scheme
- A series of individual character checks — each byte compared independently
- A hash comparison — your input is hashed and compared to a stored digest
local_38 to user_input and local_20 to expected_flag. This makes the logic dramatically easier to follow as you work deeper into the function.Ghidra’s decompiler occasionally misinterprets the size of data types or the structure of loops. When the pseudocode looks wrong, switch to the Listing view and read the raw assembly alongside it. The assembly is always authoritative.
4
Set breakpoints and inspect values with GDB
For anything that’s easier to observe at runtime — the exact value being compared, the output of a transformation function, the contents of a register — switch to GDB.Set a breakpoint at the comparison instruction identified in Ghidra. When execution pauses, inspect the registers holding the two values being compared. On x86-64 Linux, One of those strings is the expected password or an intermediate transformed value. If it prints directly as a readable string, you’ve found the flag. If it looks like encoded bytes, note the values and proceed to the Python re-implementation step.
strcmp(a, b) passes a in rdi and b in rsi. Examine both:5
Re-implement the algorithm in Python
Once you understand the encoding or transformation the binary applies to your input, re-implement it in Python to recover the flag from the encoded ciphertext stored in the binary.You can extract the ciphertext byte array directly from the binary once you know its offset and length from Ghidra:Then reverse the transformation to recover the flag. Based on a Ghidra decompile showing Run the script and check whether the output matches the expected flag format. If it’s close but garbled, double-check the order of operations — XOR and arithmetic precedence in decompiler pseudocode are common sources of off-by-one errors. Try swapping
encoded[i] = (input[i] ^ 0x42) + i:(c - i) ^ 0x42 and (c ^ 0x42) - i and comparing outputs.6
Patch the binary to bypass the check
If re-implementing the algorithm is proving difficult, consider patching the binary instead. The goal is to flip the conditional jump after the comparison so the program always takes the “success” branch regardless of your input.Find the offset of the conditional jump instruction using Ghidra (right-click the instruction → Copy Special → Byte Offset) or by searching for the instruction bytes with
grep on a hex dump. Common patches:0x74(je) →0x75(jne) — invert the condition0x74(je) →0xeb(jmp) — make the jump unconditional0x75(jne) →0x74(je) — invert in the other direction
chmod +x challenge_patched && ./challenge_patched and provide any input. If the patch is correct, the binary prints the flag.Static vs Dynamic Analysis
Both analysis modes are essential. Use them together — static analysis gives you the map, dynamic analysis confirms what’s actually happening at runtime.- Static Analysis
- Dynamic Analysis
Static analysis means examining the binary without running it. Your primary tools are Extracting embedded byte arrays:Static analysis is safe, fast, and works on any binary regardless of how it behaves at runtime. It’s your starting point for every challenge.
strings, objdump, and Ghidra.Ghidra workflow:- Import the binary and run auto-analysis with default settings
- Use the Symbol Tree to navigate to known function names
- Use Search → Memory (Ctrl+S) to find string literals and byte patterns
- Use References → Show References To (Ctrl+Shift+F) to trace where a string or function is used
- Rename variables and functions as you understand them — this is the single most effective way to accelerate static analysis
objdump for quick disassembly:Recognizing Common Check Patterns
After reversing enough binaries, you’ll start recognizing recurring patterns in how flag checks are implemented.Tools Reference
Reverse Engineering Tools Cheat Sheet
Reverse Engineering Tools Cheat Sheet
Disassemblers and Decompilers
- Ghidra — free, open-source, excellent decompiler; NSA-developed
- IDA Free — industry-standard disassembler; free tier covers most CTF binaries
- Binary Ninja — commercial; cloud version available free for CTF use
objdump -d— quick command-line disassembly for orientation
gdbwith GEF or pwndbg — enhanced debugger TUIltrace— intercepts shared library calls at runtimestrace— intercepts system calls at runtime- x64dbg — Windows GUI debugger
file— identify binary type, architecture, and linkingstrings— extract printable strings from any binarychecksec— report enabled binary mitigations (PIE, NX, canaries)exiftool— metadata extraction for non-ELF file typesbinwalk— detect and extract embedded files within a binary