cradare2
cradare2
A powerful, idiomatic Crystal library and high-level DSL for controlling radare2 via the r2pipe protocol across Windows, Linux, and macOS.
Includes low-level command framing, strongly typed JSON::Serializable models, rich DSL modules for binary analysis, memory manipulation, disassembly, Crystal runtime inspection, and native debugging (breakpoints, CPU registers, stack unwinding, memory maps, and demangled crash reporting).
Features
- Cross-Platform Transports:
- Subprocess Spawn: Spawns
r2 -q0child process with reliable null-byte (\0) stream framing on Windows, Linux, and macOS. - HTTP REST: Connects to remote radare2 webservers (
http://orhttps://). - TCP Socket: Connects directly to remote radare2 TCP instances (
tcp://). - In-Session (
#!pipe): Connects to the parent radare2 session when run as a script inside radare2 viaR2PIPE_IN/R2PIPE_OUTorR2PIPE_PATH. - Deterministic Mocking: Zero-dependency
MockTransportfor deterministic unit testing.
- Subprocess Spawn: Spawns
- Strongly Typed Models:
- Complete
JSON::Serializablemodels for binary metadata (ij), functions (aflj), symbols (isj), exports (iEj), imports (iij), sections (iSj), strings (izj), disassembly (pdj), CPU registers (drj), breakpoints (dbj), backtraces (dbtj), memory maps (dmj), and threads (dptj). - Dynamic JSON fallback (
r2.cmdj(...)) returningJSON::Any.
- Complete
- Expressive DSL:
r2.analyze: Fluent binary analysis (all,calls,functions,references,autoname,preludes,emulate).r2.disasm: Disassembly to formatted text, typed instructions, and decompilation (pdc/pdg).r2.memory: Direct reading & writing of bytes, integers, strings, hexdumps, and pattern searching.r2.debug: Process execution control (continue,step,step_over,step_until), breakpoints, registers, memory maps, and demangled crash reporting.
- Symbol & Function Querying:
- Convenient filters:
symbols_matching,functions_matching,exports_matching,imports_matching,strings_matching. - Multi-language symbol demangler supporting Crystal (
*Namespace::Class#method:Type), C++ (Itanium/MSVC), and Rust.
- Convenient filters:
Installation
Add cradare2 to your shard.yml:
dependencies:
cradare2:
github: sol-vin/cradare2
branch: master
Then run:
shards install
Make sure radare2 is installed on your system:
- Windows:
scoop install radare2orchoco install radare2 - macOS:
brew install radare2 - Linux:
apt install radare2or compile from source
Quick Start
require "cradare2"
# Open a binary (spawns r2 -q0 under the hood)
R2.open("my_app.exe") do |r2|
# Run analysis
r2.analyze.all
# Target metadata
info = r2.info
puts "Target: #{info.format} #{info.arch} (#{info.bits}-bit) on #{info.os}"
# Functions
r2.functions.first(5).each do |fn|
puts "Function: #{fn.name} @ 0x#{fn.offset.to_s(16)} (#{fn.size} bytes)"
end
# Sections
r2.sections.each do |sec|
puts "Section #{sec.name}: 0x#{sec.vaddr.to_s(16)} [#{sec.perm}]"
end
# Disassemble 5 instructions
puts r2.disasm.text(5)
end
Low-Level API (cmd and cmdj)
You can execute any radare2 command directly:
# Raw string output
puts r2.cmd("?e Hello from radare2")
# Dynamic JSON parsing (returns JSON::Any)
json = r2.cmdj("ij")
puts json["bin"]["arch"].as_s
# Type-safe deserialization directly into any JSON::Serializable model
functions = r2.cmdj("aflj", as: Array(Cradare2::Model::Function))
High-Level DSL Reference
Analysis (r2.analyze)
r2.analyze.all # "aaa"
r2.analyze.calls # "aac"
r2.analyze.functions # "aaf"
r2.analyze.references # "aar"
r2.analyze.autoname # "aan"
r2.analyze.preludes # "aap"
r2.analyze.emulate # "aae"
# Block form
r2.analyze do |a|
a.all
a.calls
end
Memory Manipulation (r2.memory)
# Reading
bytes = r2.memory.read(0x401000, size: 16)
str = r2.memory.read_string(0x402000)
u32 = r2.memory.read_u32(0x401000)
u64 = r2.memory.read_u64(0x401000)
# Writing
r2.memory.write(0x401000, Bytes[0x90, 0x90, 0xCC, 0xC3])
r2.memory.write_string(0x402000, "Hello World")
r2.memory.write_u32(0x401000, 0x1337BEEF_u32)
r2.memory.write_u64(0x401000, 0x1122334455667788_u64)
# Pattern Searching
hits = r2.memory.search_bytes(Bytes[0x90, 0x90])
string_hits = r2.memory.search_string("Query")
# Hexdump
puts r2.memory.hexdump(0x401000, size: 64)
Disassembly & Decompilation (r2.disasm)
# Text disassembly
puts r2.disasm.text(10, at: 0x401000)
# Typed instruction models
instructions = r2.disasm.instructions(10, at: 0x401000)
instructions.each do |ins|
puts "0x#{ins.offset.to_s(16)}: #{ins.opcode} (type: #{ins.type})"
end
# Decompilation (pdc or Ghidra pdg)
puts r2.disasm.decompile("main")
Debugger (r2.debug)
R2.open("my_app.exe", debug: true) do |r2|
r2.debug do |d|
d.breakpoint("main")
d.continue
# Architecture-neutral registers
puts "RIP / PC: 0x#{d.registers.pc.to_s(16)}"
puts "RSP / SP: 0x#{d.registers.sp.to_s(16)}"
# Specific registers
puts "RAX: 0x#{d.registers.rax.to_s(16)}"
# Call stack / Backtrace
d.backtrace.each do |frame|
puts "##{frame.frame} 0x#{frame.pc.to_s(16)} in #{frame.function}"
end
# Step instruction
d.step
end
end
Native Crash Diagnostics
When inspecting or debugging a crashed binary or DLL, r2.debug.crash_report formats a comprehensive diagnostic summary:
report = r2.debug.crash_report
puts report
Sample output:
=== Native Crash Diagnostic Report ===
Crash PC (Instruction Pointer): 0x401032
Stack Pointer (SP): 0x20c3c50718
Base/Frame Pointer (BP): 0x20c3c50748
Active Function: MyGame::Player#_process:Float64
Faulting Region: game.dll (0x400000 - 0x410000, -r-x)
Registers:
RAX: 0x401000 RBX: 0x64 RCX: 0x0
RDX: 0x409000 RSI: 0x3e8 RDI: 0x7d0
R8: 0x1 R9: 0x2 R10: 0x3
Call Stack (Demangled):
#0 0x401032 in MyGame::Player#_process:Float64
#1 0x4010a0 in entry_point
Crystal Binary & Runtime Inspection (r2.crystal)
cradare2 includes first-class tools for analyzing Crystal binaries, parsing mangled method signatures, and inspecting Crystal object layouts directly in memory:
R2.open("my_crystal_app") do |r2|
r2.analyze.all
# Detect Crystal binaries and locate entrypoint
if r2.crystal.crystal_binary?
puts "Entrypoint: 0x#{r2.crystal.entrypoint.try(&.to_s(16))}"
end
# Discover all Crystal classes and their analyzed methods
r2.crystal.classes.each do |cls|
methods = r2.crystal.methods_for_class(cls)
puts "Class #{cls}: #{methods.size} methods"
end
# Inspect runtime Crystal String in memory (offset 0: type_id, 4: bytesize, 8: length, 12: UTF-8 bytes)
str = r2.crystal.read_string(0x1000)
puts "String value: #{str.value} (length: #{str.length}, bytesize: #{str.bytesize})"
# Inspect Crystal Array(T) header (type_id, size, capacity, buffer pointer)
arr = r2.crystal.read_array_header(0x2000)
puts "Array size: #{arr.size}, capacity: #{arr.capacity}, buffer: 0x#{arr.buffer_address.to_s(16)}"
# Inspect Crystal Slice(T) header (size, read_only, pointer)
slice = r2.crystal.read_slice_header(0x3000)
puts "Slice size: #{slice.size}, read_only: #{slice.read_only?}"
# Crystal-specific demangled crash report with stack trace and registers
puts r2.crystal.crash_report
end
Building Custom Debuggers with cradare2
cradare2 is designed to serve as the low-level native debugger backend for higher-level frameworks and extensions (such as game engines, GDExtensions, or language runtimes).
External shards can easily build specialized inspectors or runners on top of cradare2:
require "cradare2"
class MyFrameworkDebugger
def initialize(@target : String)
@r2 = Cradare2.open(@target, debug: true)
end
def watch_entrypoint(entry_symbol : String)
@r2.debug.breakpoint(entry_symbol)
@r2.debug.continue
end
def handle_crash
puts @r2.debug.crash_report
end
def close
@r2.close
end
end
Testing
Run the test suite using Crystal's built-in spec runner:
crystal spec --verbose
All 68 specs run out-of-the-box using the internal MockTransport without requiring external network access.
License
MIT License. See LICENSE for details.
cradare2
- 0
- 0
- 0
- 2
- 1
- about 10 hours ago
- September 29, 2026
MIT License
Tue, 29 Sep 2026 12:50:58 GMT