Reverse Engineering with radare2 and rizin: A Practical Walkthrough

RE & Pwn
Time it takes to read this article 6 minutes.

Introduction / Overview

This article is for education and authorized security testing only. Only analyze binaries you own, samples provided in a legal training environment, or files you are explicitly authorized to assess. Reversing licensed software may violate its EULA or local law, and detonating live malware outside an isolated lab is dangerous. You are responsible for your own actions.

radare2 (often shortened to r2) is a free, open-source reverse engineering framework that runs on Linux, macOS, Windows, and even constrained embedded systems. rizin is a fork of radare2 that prioritizes a cleaner internal API, stable command syntax, and a friendlier contributor experience. The two share most of their command language, so the muscle memory you build in one transfers almost entirely to the other. In this post I will use r2 syntax; nearly every command works identically under rizin.

If you have previously used a GUI-first tool, you may want to read my notes on Ghidra for beginners before diving into r2's terminal-centric workflow.

How it works / Background

radare2 is a collection of small command-line utilities glued together by a scriptable core. The pieces you will touch most often are:

  • r2 / rizin — the interactive analysis shell.
  • rabin2 / rz-bin — parses headers, imports, exports, strings, and sections.
  • rasm2 / rz-asm — assembles and disassembles instructions.
  • radiff2 / rz-diff — binary and function-level diffing (great for patch analysis).

The command language is terse but composable. Commands are read left to right, and suffixes change behavior: p prints, pd prints disassembly, pdf prints the disassembly of a function, and a trailing j or q switches almost any command to JSON or quiet output. That orthogonality is what makes r2 scriptable: you can pipe pdfj into jq and feed the result to your own tooling.

Prerequisites / Lab setup

Work inside an isolated VM with no network bridge to anything you care about. Install from source to get the newest analysis engine.

# radare2 (from source)
git clone https://github.com/radareorg/radare2
cd radare2
sys/install.sh

# rizin (Debian/Ubuntu package)
sudo apt install rizin

# verify
r2 -v
rizin -v
Bash

Create a tiny target so the output is predictable:

cat > target.c <<'EOF'
#include <stdio.h>
#include <string.h>
int check(const char *p){ return strcmp(p, "s3cr3t") == 0; }
int main(int argc, char **argv){
    if (argc > 1 && check(argv[1])) puts("OK");
    else puts("NO");
    return 0;
}
EOF
gcc -no-pie -fno-stack-protector -o target target.c
Bash

Walkthrough / PoC

Triage before opening the shell

Start with rabin2 to understand the file without running any analysis:

rabin2 -I target        # binary info: arch, bits, NX, PIE, canary
rabin2 -z target        # strings in data sections
rabin2 -i target        # imported symbols (strcmp, puts, ...)
rabin2 -s target        # exported symbols
Bash

The -I output tells you whether mitigations like canary, nx, and pic are present — exactly the fields you care about before writing an exploit.

Loading and analyzing

Open the binary. Use -A to run analysis on load, or run it manually from the prompt:

r2 -A target
Bash

Inside the shell, the single most important command is aaa ("analyze all, all"). It performs function discovery, names flags, identifies references, and propagates types. There are escalating variants:

aa     # basic analysis (functions from symbols/entry)
aaa    # aa + auto-name functions, find references, emulate where useful
aaaa   # experimental, more aggressive (slower, noisier)
Plaintext

Run aaa, then list what r2 found:

[0x00401050]> aaa
[0x00401050]> afl        # list all functions with addresses and sizes
[0x00401050]> afl~main   # grep the list for "main" (~ is r2's internal grep)
Plaintext

Disassembling a function with pdf

Seek to main and print the disassembly of the function. pdf = "print disassembly of function":

[0x00401050]> s main
[0x00401146]> pdf
Plaintext

You will see the prologue, the argc comparison, and a call sym.check. Jump straight to that function instead of scrolling:

[0x00401146]> s sym.check
[0x00401136]> pdf
Plaintext

Useful printing variants while you read:

pdf            # full function disassembly
pdf            # same, alias
pd 20          # disassemble 20 instructions from the current seek
pdc            # pseudo-C-like decompilation (built-in, lightweight)
pdr            # disassemble respecting control flow (recursive)
axt sym.check  # show all cross-references TO check (who calls it)
Plaintext

axt (analyze xrefs to) is how you build a call graph in your head: it tells you every site that references a function or string. Pair it with axf (xrefs from) to see what the current function reaches.

Visual mode

Typing commands is fast for triage, but for reading flow you want visual mode. Press V to enter it, then cycle print modes with p / P:

[0x00401136]> V        # enter visual mode (hex/disasm panels)
Plaintext

Key bindings inside visual mode:

  • p / P — cycle the panel type (disassembly, hexdump, debug, etc.).
  • V (again) — enter the graph view, an interactive control-flow graph.
  • hjkl — navigate; arrow keys also work.
  • Enter over a jump/call — follow it; u — go back (undo seek).
  • : — drop into a command line without leaving visual mode.
  • q — exit one level.

The graph view (VV from the prompt, or V then V) renders basic blocks as boxes with true/false edges — the closest r2 gets to the box-and-arrow experience of a GUI disassembler, entirely in the terminal.

Patching and scripting

r2 can write back to the binary. Open with -w for write mode and assemble a new instruction over an address:

r2 -w target
Bash
[0x00401050]> s sym.check
[0x00401136]> "wa mov eax, 1; ret"   # always-true check
[0x00401136]> wao nop                # NOP the current instruction
Plaintext

For repeatable analysis, save commands to a script and replay them:

echo -e "aaa\ns main\npdf" > analyze.r2
r2 -q -i analyze.r2 target
Bash

Cutter — the GUI front end

When you want a visual experience without leaving the ecosystem, use Cutter. Cutter is the official GUI built on rizin (earlier versions used radare2), bundling a graph view, hex editor, strings panel, and optional decompilers such as rz-ghidra (the Ghidra decompiler exposed through rizin). It is ideal for sharing findings or for newcomers who think better in panels, while still letting you drop into the rizin command bar for the precision of pdf, aaa, and axt.

Mermaid diagram

Reverse Engineering with radare2 and rizin: A Practical Walkthrough diagram 1

The diagram shows the analyst path from acquiring a sample to documenting indicators, branching on whether a graph view or a binary patch is needed.

Detection & Defense (Blue Team)

Reversing is a capability defenders need too — and the same tools attackers use to study your software help you study theirs. Equal weight to defense:

  • Treat r2/rizin/Cutter as triage tooling, not just attacker tooling. Use rabin2 -I, -z, and -i to extract IOCs (suspicious imports like VirtualAllocEx, WinExec, embedded URLs, mutex names) from quarantined samples. Map observed behaviors to MITRE ATT&CK, e.g. T1027 (Obfuscated Files or Information), T1140 (Deobfuscate/Decode), and T1620 (Reflective Code Loading).
  • Raise the cost of static analysis. Ship release builds with stripped symbols and enable hardening: ASLR/PIE (-fPIE -pie), stack canaries (-fstack-protector-strong), full RELRO (-Wl,-z,relro,-z,now), and NX. None of these stop a determined analyst, but they remove easy wins and break naive patching.
  • Make tampering detectable, not just hard. Code signing (Authenticode, codesign, GPG) plus integrity checks let you detect the kind of in-place patching shown above (wa, wao nop). Verify signatures at load time and alert on mismatches.
  • Detect dynamic analysis at the endpoint. Debugger attachment and ptrace are observable. EDR rules can flag ptrace(PTRACE_ATTACH), processes spawned under r2 -d, or unexpected memory protection changes (mprotect to RWX), aligning with T1622 (Debugger Evasion) detection.
  • Protect intellectual property pragmatically. For high-value logic, move secrets server-side, use attestation, or apply commercial obfuscation/packing — but assume a skilled reverser with r2 will eventually reach the logic. Defense in depth beats security-by-obscurity. See my notes on anti-debugging techniques for the cat-and-mouse details.

The takeaway: you cannot prevent reversing, but you can make it expensive, make tampering detectable, and use the very same tooling to accelerate your incident response.

Conclusion

radare2 and rizin reward a small amount of memorization with enormous leverage. Master a handful of commands — aaa to analyze, afl to list functions, pdf to read one, axt/axf to follow references, and V/VV for visual and graph modes — and you can triage most binaries entirely from the terminal. When you need pictures, Cutter gives you the rizin engine behind a GUI with optional Ghidra decompilation. For defenders, the same kit is a fast path to extracting IOCs and validating your hardening. Build the lab, reverse the toy binary above, and the workflow will quickly become second nature.

References

Comments

Copied title and URL