rev ctf guide
how to approach reverse engineering challenges.
static analysis · dynamic analysis · decompilers · anti-debug · keygen logic
first steps for any rev challenge
- 1.run file on the binary — is it an ELF, PE, Python bytecode, Java class, or something else? the answer determines which tools to use.
- 2.run strings and grep for anything useful: flag-like patterns, interesting messages, hard-coded keys or paths. sometimes the flag is directly in the binary.
- 3.run the binary and observe its behavior. what input does it expect? what does it print? does it compare your input against something?
- 4.run with ltrace (Linux) to see library calls — strcmp, memcmp, strcasecmp calls often reveal what the binary is comparing your input to.
- 5.open in Ghidra. find main(), identify the validation function, and understand the logic. the goal is to determine what input produces the "correct" output.
common challenge types and approach
- crackme / license checkbinary validates a key or password. trace with ltrace to find strcmp calls. if obfuscated, use angr (symbolic execution) or z3 (constraint solving) to find the input that satisfies all checks.
- custom encoding / cipherbinary encodes your input and compares to a hard-coded value. reverse the encoding function and apply it to the target value. common: XOR with key, custom base encoding, byte shuffling.
- anti-debug techniquesbinary detects debuggers and exits early. look for ptrace(PTRACE_TRACEME) calls, timing checks, or IsDebuggerPresent (Windows). patch or nop the check, or use a bypass technique.
- virtual machine / bytecode interpreterbinary implements its own VM and runs custom bytecode. first understand the instruction set, then disassemble the bytecode. treat it as a new architecture.
- compiled Python / Java / .NETuse pycdc or uncompyle6 for Python bytecode, javap or jadx for Java class files, ILSpy or dnSpy for .NET assemblies. these produce near-source-level code.
- obfuscated / packed binaryrun and dump memory if the binary unpacks itself at runtime. use upx -d if UPX-packed. check for known packers with DIE (Detect It Easy).
useful tools
- Ghidra — free NSA decompiler. excellent for C/C++ binaries. decompiles assembly to readable pseudocode. use the function graph and cross-reference views.
- GDB — dynamic analysis on Linux. set breakpoints, inspect memory and registers, step through execution. use pwndbg or peda plugin for better UX.
- ltrace / strace — trace library calls and syscalls respectively without opening a debugger. fast way to spot strcmp, memcmp, open, read calls.
- angr — Python symbolic execution framework. give it the binary and a success condition, it finds input that reaches it. powerful for complex validation logic.
- z3 — SMT solver. express constraints from the binary as equations, solve for the input. pair with angr or use manually for simpler challenges.
- radare2 / iaito — open-source disassembler and binary analysis framework. steeper learning curve than Ghidra but scriptable and powerful.