July 11, 2026

The picoCTF Reverse Engineering Roadmap: From Disassembly to Decompiler

How to learn reverse engineering for CTF: an ordered, difficulty-tiered path through assembly, Ghidra, GDB, Frida, per-language reversing, and solver automation.

The whole roadmap in one screen

Reverse engineering is the one CTF category where beginners drown in tools before they can read a single instruction. People install Ghidra, IDA, radare2, Frida, and angr in the first week, then bounce off every challenge because they skipped the thing all of those tools exist to help you do: read what the program actually does. This page is the order to learn it in. Each step links the deep-dive post on this site and tells you what that step buys you.

The learning order, top to bottom, is: learn to read raw assembly, then learn the static tools that turn assembly back into something readable, then learn the dynamic tools that watch the program run, then learn the per-language tricks that matter once a binary is not plain C, and finally learn the automation that solves the math for you. Do not skip Tier 1. Every tool above it is a shortcut for a skill you still need when the shortcut breaks.

The ordered path

  1. Read the code. x86 assembly, then ARM assembly.
  2. Static tools. Ghidra, then radare2 / rizin.
  3. Dynamic analysis. GDB, then Frida.
  4. Per-language. Java, Python, Go / Rust, Android APK.
  5. Automation. angr, then z3.
Reverse engineering is not a tool you install. It is a reading skill the tools make faster. Learn to read first, then learn what reads for you.

Before any tool: identify the language and format first

The single most common beginner mistake is opening every file in Ghidra. Ghidra is built for compiled native code. Throw a Java class, a Python bundle, or an Android APK at it and you will read garbage for an hour. The first move on any reversing challenge is to identify what you are looking at, because the format decides the entire toolchain.

Run file ./target and strings ./target | head and let the answer route you:

If it is...You are reversingGo to
An ELF / PE compiled from C or C++Native machine codeTier 1 then Tier 2 (assembly, Ghidra)
A .class or .jarJVM bytecodeJava reversing
A .pyc or PyInstaller bundlePython bytecodePython reversing
A large stripped ELF with odd symbolsGo or RustGo / Rust reversing
An .apkAndroid (Dalvik plus native libs)Android APK reversing
Key insight: C and C++ decide whether you reach for assembly and a decompiler. Java and Python decide whether you reach for a bytecode decompiler instead. Go and Rust decide whether you fight fat static binaries with custom calling conventions. Android decides whether you start in a manifest and a Dalvik decompiler. Identify the format before you spend a single minute in a tool.

Tier 1: Read the code (the skill the tools accelerate)

Decompilers fail. They guess at types, miss inlined functions, and produce confident nonsense on obfuscated or hand-written code. When that happens, the only fallback is reading the disassembly directly, and you cannot do that if you never learned to. This tier is non-negotiable and it is where every reverser should start.

  • x86 assembly for CTF teaches the architecture you will meet most often: registers, the stack, calling conventions, and how a for loop or a string comparison looks once the compiler is done with it. Start here. Everything in Tier 2 is a friendlier view of exactly this.
  • ARM assembly for CTF covers the other architecture you will hit, especially on mobile and embedded challenges. Once you know x86, ARM is a translation exercise: the same ideas, fixed-width instructions, and more registers. Learn it second so that an ARM binary never stops you cold.
Tip: You do not need to memorize every instruction. You need to recognize the shapes: function prologue and epilogue, a comparison feeding a conditional jump, an indexed loop, an array access. Pattern recognition beats reference lookup every time you are racing a clock.

Tier 2: Static tools that read the binary for you

Once you can read assembly by hand, let a decompiler do the boring 90 percent so you can spend your attention on the 10 percent that matters. Static analysis means understanding the program without running it, which is safer and often faster than dynamic work.

  • Ghidra is the free decompiler to learn first. It turns native assembly into readable pseudo-C, lets you rename variables and retype structures as you understand them, and handles x86 and ARM out of the box. Most of your static reversing time will live here. Learn its decompiler view, its cross-references, and its function graph.
  • radare2 and rizin are the scriptable command-line counterpart. When you need to automate a patch, batch-analyze many binaries, or work over SSH on a remote box with no GUI, this is the tool. Learn it second, after Ghidra has taught you what the views mean.
Note: Ghidra and radare2 are complements, not competitors. A common workflow is to read in Ghidra for comprehension and drive radare2 for scripted patches and quick triage. Knowing both means no challenge format leaves you without a static option.

Tier 3: Dynamic analysis when static reading hits a wall

Some logic only reveals itself at runtime: a value computed from the environment, a check behind an anti-debug guard, a key derived in a loop you would rather watch than re-implement. Dynamic analysis runs the program under your control and lets you observe and change it live.

  • GDB is the debugger to learn first. Set breakpoints, step instruction by instruction, inspect registers and memory, and read the exact value a comparison is testing against. For most native CTF binaries, GDB plus a comprehension pass in Ghidra solves the challenge. This is the highest- leverage dynamic skill on the board.
  • Frida is dynamic instrumentation for when GDB is awkward: hooking functions in a running app, tracing calls across a large process, and reaching into Android and other managed runtimes where a classic debugger struggles. Learn it second, once GDB has taught you what to hook and why.
Tip: The fastest reversing loop alternates static and dynamic: read a function in Ghidra to form a hypothesis, then set a GDB breakpoint to confirm the value at that exact point. Do not pick a camp. Bounce between the two until the logic is obvious.

Tier 4: Per-language tracks for when it is not plain C

The decision rule sent you here. Each of these runtimes needs its own toolchain because the binary is not native machine code, or it is native code with conventions that confuse a generic decompiler. Learn the track that matches the challenge in front of you; you do not need all four before you start.

  • Java reverse engineering covers JVM bytecode. A .jar or .class decompiles back to near-original source with the right tool, so the work shifts from reading assembly to reading recovered Java and defeating obfuscation.
  • Python reversing covers.pyc files and PyInstaller bundles. You unpack the bundle, decompile the bytecode, and read Python. The hard part is reconstruction when the bytecode has been tampered with or the Python version is unusual.
  • Go and Rust reversing covers the modern compiled languages. Both produce large static binaries with their own calling conventions, string handling, and runtime, which trip up tools tuned for C. This track is what keeps a stripped Go binary from being a wall.
  • Android APK reversing covers the mobile stack: the manifest, Dalvik bytecode, Smali, and the native libraries an app ships. It pulls in Tier 1 assembly for the native parts and Frida from Tier 3 for live hooking.

Tier 5: Automation that solves the puzzle for you

Many reversing challenges reduce to a question: what input makes this function return true? Once you have the constraints, you should not solve them by hand. Two tools turn a tedious algebra problem into a one-shot script, and they are the last tier because they are most powerful once you can already read the code well enough to extract the constraints.

  • angr is a symbolic execution engine. You point it at a binary, mark the success state, and let it compute an input that reaches that state. It shines on input-validation crackmes where the checks are complex but the binary is small enough to explore.
  • z3 is a constraint solver. When you can read the checks but they form a system of equations (XORs, modular arithmetic, a chain of byte constraints), you transcribe them into z3 and it hands back the satisfying input. Learn it alongside angr; they overlap and complement each other.
Warning: Automation is a force multiplier, not a substitute for reading. angr can blow up on large binaries (path explosion), and z3 needs you to extract the constraints correctly by hand first. If you skipped Tier 1, these tools will fail in ways you cannot debug. That is the whole reason they come last.

Where to practice, easy to hard

Reading about reversing teaches you nothing until you break a binary yourself. Here is a ladder of challenges on this site, ordered so each one stretches you a little past the last. Solve them in order and you will have touched every tier above.

  • picoCTF 2019 asm1 is pure Tier 1: trace a small block of x86 assembly by hand and report what it returns. No tools required. Start here to prove you can read.
  • picoCTF 2021 ARMssembly 0 does the same for ARM. It confirms the Tier 1 skill transfers to the second architecture and that ARM does not scare you.
  • picoCTF 2019 Vault Door 1 is a Java source-and-logic challenge: read the check and invert it. A gentle on-ramp to the per-language Java track.
  • picoCTF 2025 Rust FixMe 1 pushes you into the Go and Rust track, where the binary and tooling look different from plain C.
  • picoCTF 2024 Classic Crackme 0x100 is a native crackme that rewards a static-plus-dynamic loop, and it is a strong candidate for an angr or z3 finish once you read the checks.
  • picoCTF 2022 keygenme is the hard rung: a keygen-style binary that ties together static reading, dynamic confirmation, and constraint solving. If you can clear this, the roadmap worked.

Picking the right tool without thrashing

Note: A simple rule that saves hours: identify the format, then pick the tool that format demands, then prefer static reading before dynamic running. Reach for automation only after you can state the constraints in plain words. If you find yourself opening four tools on one binary, you skipped the format-identification step. Go back to the decision rule and route once, deliberately.

Quick reference

The roadmap as a checklist

  1. Run file and strings. Identify C/C++ vs Java vs Python vs Go/Rust vs Android.
  2. Learn to read: x86, then ARM.
  3. Decompile statically: Ghidra, then radare2 / rizin.
  4. Watch it run: GDB, then Frida.
  5. Match the language: Java, Python, Go / Rust, Android.
  6. Automate the solve: angr, then z3.
  7. Practice in order: asm1, ARMssembly 0, Vault Door 1, Rust FixMe 1, Classic Crackme 0x100, keygenme.

Reverse engineering is just reading, with progressively better glasses: learn the alphabet first, then earn each tool by knowing what it reads for you.

Keep reading

Guides that build on the same ideas, plus the roadmap this topic sits under.