algoatson.github.io ~/posts
segfault@algoatson.github.io
back to index

rizin and radare2 in anger

2026-06-024 min readre tooling

Ghidra is where I read one gnarly function; rizin/radare2 is where I live — it's faster to orient, scriptable, and it disassembles, patches, and debugs from one prompt. The command grammar is verb-then-noun and relentlessly chainable, which is intimidating for a day and muscle memory after that. Here's the working set.

The two tools share a command language almost byte for byte. I'll write r2; rizin is the same session with renamed binaries — see the note at the end.

open and analyse#

console
1
2
3
$ r2 -A ./chal        # open and run full analysis up front
$ r2 -d ./chal        # open in debug mode (live process)
$ r2 -w ./chal        # open writable, for patching

Inside, if you didn't pass -A, analyse with aaa (or aaaa for the deep, slow pass). Analysis is what populates function names, xrefs, and strings.

info before instructions#

Read the binary's metadata first — r2 even gives you checksec for free:

command what it lists
iI binary info incl. nx / canary / pic / relro (checksec)
ii imports (the libc surface)
is symbols
iz / izz strings in data sections / in the whole file
iS sections
ie entrypoints
txt
1
2
3
4
5
6
afl                 # list every function analysis found
afl~main            # ...grep for one (~ is r2's internal grep)
s main              # seek to a symbol (or s sym.main, s 0x401234)
pdf                 # disassemble the function at the current seek
pd 20               # disassemble 20 instructions from here
pdf @ sym.check     # disassemble check() without moving the seek (@ = "at")

~ greps, @ runs a command at a temporary address, and they compose: pdf @ main ~call lists every call inside main. That composition is the whole reason to use r2 over clicking.

For the visual mode that feels like a TUI:

txt
V       # visual disassembly  (p / P cycle views, hjkl move, : for a command, q exits)
VV      # visual graph mode    (the control-flow graph; great for nested branches)

xrefs — the question you ask most#

"Who calls this, and what does this touch?"

txt
1
2
3
axt sym.imp.system     # cross-references TO system  -> where it's called
axt @ str.bin_sh       # who references the "/bin/sh" string
axf @ main             # references FROM main         -> what main calls/reads

axt sym.imp.system is often the fastest path to the bug: find the dangerous call, then read backwards to what feeds it.

search — strings, bytes, and gadgets#

txt
1
2
3
4
5
/ /bin/sh          # search for a string
/x 4831c0          # search for a byte pattern (hex)
/a jmp rax         # search for an assembled instruction
/R pop rdi         # ROP: gadget search ending in ret
/R/ pop r..        # ...as a regex

/R pop rdi makes r2 a serviceable gadget finder when you don't want to leave for ROPgadget.

decompile in place#

txt
pdc                # r2's built-in pseudo-C (rough but instant)
pdg                # Ghidra-quality decompile, via the r2ghidra / rz-ghidra plugin

pdg needs the plugin (r2pm -ci r2ghidra, or rizin's rz-ghidra), but it's the Ghidra decompiler in your terminal — worth the install.

patch without a hex editor#

Open writable (-w, or oo+ to reopen rw mid-session), then write hex or assembly straight over an instruction:

txt
1
2
3
4
5
oo+                # reopen current file read-write
s 0x401185         # seek to the branch you want gone
wao nop            # assemble-over: turn this instruction into nop(s)
wx 9090            # or write raw bytes
wa jmp 0x4011b0    # or write a new instruction (wa = "write assembly")

This is how you NOP a license check or flip a jz to a jmp and save the binary in one place.

debug from the same prompt#

r2 -d ./chal drops you in with the process live; the debug commands mirror the static ones with a d prefix:

command does
db 0x401234 set a breakpoint
dc continue
ds / dso step / step-over
dr show registers (dr rax=0 sets one)
dm / dmm memory maps / loaded modules
dbt backtrace

make it yours#

Drop your defaults in ~/.radare2rc (or ~/.rizinrc) so every session starts right:

txt
1
2
3
4
e asm.syntax = intel     # AT&T is the default; fix that
e scr.color = 3          # full colour
e asm.bytes = true       # show opcode bytes next to disasm
e pager = less

the only two help characters you need

Append ? to any command prefix to list its subcommands: i?, p?, d?, /?. Append ?? for the long form. You navigate the entire command tree by appending question marks — you don't memorise it, you discover it.

rizin vs radare2 — which one

rizin is a 2020 fork of radare2, aimed at a cleaner, stabler API and a more coordinated release process; the interactive command set is nearly identical, so everything above works in both. The differences you'll touch:

  • tools are renamed — rabin2rz-bin, radare2rizin, r2pmrz-pm.
  • the Ghidra decompiler is rz-ghidra (built in to the rizin ecosystem) vs r2ghidra (a radare2 plugin).
  • Cutter, the GUI, is built on rizin now — so learning rizin commands pays off in the GUI too.

Pick one and commit; the knowledge transfers either direction. I drifted to rizin for the saner plugin manager and never looked back.