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. 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. 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. 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. 4.run with ltrace (Linux) to see library calls — strcmp, memcmp, strcasecmp calls often reveal what the binary is comparing your input to.
  5. 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.