pwndbg 中使用 r2pipe 调用 radare2:状态化反汇编与逆向分析实战
【免费下载链接】pwndbgExploit Development and Reverse Engineering with GDB & LLDB Made Easy项目地址: https://gitcode.com/GitHub_Trending/pw/pwndbg
导读
r2pipe是 pwndbg 提供的 radare2 深度集成命令:它会在调试会话中启动一个有状态(stateful)的 r2pipe 句柄,让你直接在 pwndbg 提示符下执行任意 radare2 命令,例如r2pipe pdf @ sym.main即可立刻得到当前二进制中main函数的反汇编与控制流图。读完本文,你将掌握 r2pipe 命令的完整用法、底层实现原理(PIE 自动重定位、QEMU 内核支持、r2ghidra 校验),以及它与交互式r2命令、rizin 系rzpipe命令的差异与选型。
r2pipe 命令概览:用法与参数
r2pipe命令由 pwndbg 自动生成文档,完整用法如下(见 r2pipe.md):
usage: r2pipe [-h] arguments [arguments ...]命令描述为:Execute stateful radare2 commands through r2pipe(通过 r2pipe 执行有状态的 radare2 命令)。
位置参数
| Positional Argument | Help |
|---|---|
| arguments | 要传递给 r2pipe 的参数(Arguments to pass to r2pipe) |
注意arguments使用nargs="+"(源码见 pwndbg/commands/radare2.py#L70),即至少需要传入一个参数,空调用会报错。
可选参数
| Short | Long | Help |
|---|---|---|
| -h | --help | 显示帮助信息并退出 |
命令注册上下文
该命令在源码中以如下方式注册(pwndbg/commands/radare2.py#L67-L100):
parser = argparse.ArgumentParser( description="Execute stateful radare2 commands through r2pipe.", ) parser.add_argument("arguments", nargs="+", type=str, help="Arguments to pass to r2pipe") @pwndbg.commands.Command( parser, category=CommandCategory.INTEGRATIONS, examples="""...""", ) @pwndbg.commands.OnlyWithFile def r2pipe(arguments: list[str]) -> None: print(pwndbg.aglib.radare2.r2cmd(arguments))两个关键细节:
- 命令归类为
CommandCategory.INTEGRATIONS(集成类命令),与r2、decomp、ai等并列; - 带有
@pwndbg.commands.OnlyWithFile装饰器,即只有在存在被调试目标文件(objfile)时才能执行——r2pipe 需要基于当前进程的可执行文件启动 radare2 实例。
前置依赖与安装
r2pipe并非开箱即用,底层实现(pwndbg/aglib/radare2.py)会在首次调用时检查三类依赖:
- radare2 本体:用于驱动 r2 的 r2pipe 需要 radare2 已安装并可用;
- r2pipe Python 库:代码中通过
import r2pipe检测,若缺失返回错误Could not import r2pipe python library. Is it installed?(对应ErrorCode.NO_IMPORT); - r2ghidra 插件:句柄打开后,pwndbg 会执行
LD命令(LD -> list supported decompilers)检查输出中是否包含pdg,若不存在则返回radare2 plugin r2ghidra must be installed and available from r2.(见 pwndbg/aglib/radare2.py#L63-L68)。
也就是说,一个完整可用的环境大致为:安装 radare2 →pip install r2pipe→ 安装并启用 r2ghidra 插件。若三者任一缺失,命令会给出明确的失败状态而非静默出错。
工作原理:从 pwndbg 命令到 r2pipe 句柄
r2pipe命令的执行链路很短但信息量很大:
pwndbg> r2pipe pdf @ sym.main └─ pwndbg.commands.radare2.r2pipe(arguments) # 命令层 └─ pwndbg.aglib.radare2.r2cmd(arguments) # 拼接并执行 └─ pwndbg.aglib.radare2.r2pipe() # 获取/创建句柄(带缓存) └─ r2pipe_mod.open(filename, flags) # 启动 radare2 管道 └─ r2.cmd("pdf @ sym.main") # 执行 radare2 命令句柄缓存:保证"状态化"
r2pipe()函数带有缓存装饰器(pwndbg/aglib/radare2.py#L22):
@pwndbg.lib.cache.cache_until("start", "objfile") def r2pipe() -> r2pipe_mod.open | Status:cache_until("start", "objfile")意味着:句柄在进程重新启动(start)或目标文件(objfile)发生变化时才会被重建,其余时间所有r2pipe调用都复用同一个 radare2 实例。这正是"有状态"的体现——你在某次调用中执行了s(seek)或修改了配置,后续调用仍然生效,就像一直坐在同一个 radare2 交互式会话里。
自动附加与 PIE 重定位
句柄创建时(pwndbg/aglib/radare2.py#L56-L62),非 QEMU 场景下:
flags = ["-e", "io.cache=true"] exe = pwndbg.aglib.elf.exe() if pwndbg.aglib.elf.get_elf_info(filename).is_pie and exe is not None: flags.extend(["-B", hex(exe.address)]) r2 = r2pipe_mod.open(filename, flags=flags)-e io.cache=true:开启 radare2 的 I/O 缓存,避免调试时对文件的实际写入;-B <base>:当检测到被调试目标为 PIE 时,pwndbg 会根据当前 ELF 的加载基址自动设置 radare2 的基地址。这样你在 r2pipe 里看到的地址就是与 gdb 中一致的真实运行地址,可以直接和 pwndbg 的telescope、vmmap等命令输出的地址对照,无需手动换算;- 打开句柄后,pwndbg 会自动对二进制进行分析(docstring 中注明 "the binary is automatically analyzed")。
QEMU 内核调试的特殊处理
如果当前处于 QEMU 内核模式(pwndbg.aglib.qemu.is_qemu_kernel()),则使用另一组 flags(pwndbg/aglib/radare2.py#L45-L55):
flags = ["-e", "bin.cache=true", "-e", "bin.relocs.apply=true"] if (kbase := pwndbg.aglib.kernel.kbase()) and filename == pwndbg.aglib.proc.exe(): urand = pwndbg.aglib.elf.get_vmlinux_unrand_base(filename) if urand is not None: flags.extend(["-e", "bin.baddr=" + hex(kbase - urand)])这里启用了bin.relocs.apply=true并利用内核基址kbase与 vmlinux 未随机化基址urand的差值计算bin.baddr,使 radare2 能够正确分析 KASLR 下的内核映像。
r2cmd:把参数拼成一条命令
最终执行由r2cmd完成(pwndbg/aglib/radare2.py#L72-L79):
def r2cmd(arguments: list[str]) -> str: r2 = r2pipe() if isinstance(r2, Status): return message.error(r2.message) return str(r2.cmd(" ".join(arguments)))所有位置参数被空格拼接后整体传给r2.cmd(),因此你可以使用 radare2 的完整命令语言,包括@地址操作符、;命令分隔、临时 seek 等语法。返回结果以字符串形式直接打印在 pwndbg 中。
实战示例:反汇编当前目标函数
原文档给出的官方示例即针对main函数的反汇编(pdf即 radare2 的 "print disassembly of function"):
pwndbg> r2pipe pdf @ sym.main ; ICOD XREF from entry0 @ 0x1d3e8(r) ┌ 6786: int main (uint32_t argc, char **argv, char **envp); │ `- args(rdi, rsi, rdx) vars(21:sp[0x40..0x18c]) │ 0x0001b920 f30f1efa endbr64 │ 0x0001b924 4157 push r15 │ 0x0001b926 4156 push r14 │ 0x0001b928 4155 push r13 │ 0x0001b92a 4154 push r12 [...] │ 0x0001b966 85c0 test eax, eax │ ┌─< 0x0001b968 7545 jne 0x1b9af │ │ 0x0001b96a e8311b0100 call sym.xtrace_init │ │ 0x0001b96f e80cff0000 call sym.check_dev_tty │ │ ; CODE XREF from main @ 0x1b988(x) │ ┌──> 0x0001b974 833dd1680f.. cmp dword [obj.debugging_login_shell], 0 ; [0x11224c:4]=0 │ ┌───< 0x0001b97b 743d je 0x1b9ba │ │╎│ 0x0001b97d bf03000000 mov edi, 3 [...]由于参数原样透传给 radare2,你可以在此之上组合出大量实用调用,例如:
# 列出所有函数(radare2 自动分析已完成) pwndbg> r2pipe afl # 查看字符串 pwndbg> r2pipe iz # 反编译当前 PC 附近的伪代码(依赖 r2ghidra,pdg 由 LD 校验) pwndbg> r2pipe pdg @ $PC # 以图形方式打印函数(输出适合在终端查看的控制流结构) pwndbg> r2pipe afi @ sym.main # 组合多个命令,以 ; 分隔 pwndbg> r2pipe s sym.main ; pdf因为句柄是状态化的,这些操作会互相影响——比如先s sym.main再执行pdf,会打印同一地址处的函数。这与 pwndbg 自身的decomp命令(decomp.md)互补:decomp走的是统一的"当前集成"抽象层,而r2pipe让你直接使用 radare2 原生命令语言。
状态化 vs 一次性调用:与r2命令的区别
同一个源文件里还定义了r2命令(pwndbg/commands/radare2.py#L14-L64,别名radare2),用法为:
usage: r2 [-h] [--no-seek] [--no-rebase] [arguments ...]两者的核心区别:
| 维度 | r2pipe | r2 |
|---|---|---|
| 交互方式 | 通过 r2pipe 管道,在 pwndbg 内直接返回命令输出 | 通过subprocess.call启动独立的交互式 radare2 终端 |
| 状态保持 | 是(句柄缓存,跨调用保持 seek/配置) | 否(每次启动新的 radare2 进程) |
| 额外选项 | 无(参数全部透传) | --no-seek(不跳转到当前 PC)、--no-rebase(不为 PIE 设置基址) |
| 适用场景 | 在 gdb 会话中快速查询反汇编/伪代码 | 进入完整 radare2 环境进行深度逆向 |
r2命令在启动时会自动 seek 到当前 PC(除非--no-seek),同样会为 PIE 传入-B <base>(除非--no-rebase),并使用-e io.cache=true。文档示例(r2.md):
pwndbg> r2 -- -S -AA WARN: Relocs has not been applied. Please use `-e bin.relocs.apply=true` or `-e bin.cache=true` next time INFO: Analyze all flags starting with sym. and entry0 (aa) [...] [0x0001d3d0]> help Welcome to radare2! [...]注意示例中--之后的-S -AA是传给 radare2 自身的参数(-AA表示深度自动分析),这正是r2与r2pipe在参数语义上的不同。
兄弟命令:rizin / rzpipe
pwndbg 对 rizin 提供了完全对称的集成(源码见 pwndbg/commands/rizin.py 与 pwndbg/aglib/rizin.py):
rz:交互式启动 rizin(别名rizin),选项与r2相同(--no-seek、--no-rebase);rzpipe:通过 rzpipe 执行有状态的 rizin 命令,用法与r2pipe完全一致(rzpipe.md)。
二者仅在底层实现上有细微差异:rizin 版本通过Lc(列出 core 插件)命令校验ghidra插件是否可用(对应 r2ghidra 的rzghidra)。rzpipe 示例(rzpipe.md):
pwndbg> rzpipe pdf @ sym.main ; DATA XREF from entry0 @ 0x1d3e8 ┌ int main(int argc, char **argv, char **envp); │ ; arg int argc @ rdi │ ; arg char **argv @ rsi │ ; arg char **envp @ rdx [...] │ ; var int64_t canary @ stack - 0x40 │ 0x0001b920 endbr64 │ 0x0001b924 push r15 [...]如果你的环境中同时安装了 radare2 与 rizin,可以根据个人偏好选择r2pipe或rzpipe;命令语言基本兼容(r2 与 rz 同源)。
常见问题与排查
结合实现代码,r2pipe可能出现的失败场景与对应信息如下:
| 现象 | 原因 | 处理建议 |
|---|---|---|
Could not find objfile to create a r2pipe for. | 当前没有已加载的被调试文件(pwndbg/aglib/radare2.py#L34-L36) | 先file <binary>或start加载目标 |
Could not import r2pipe python library. Is it installed? | 缺少 r2pipe Python 库(pwndbg/aglib/radare2.py#L38-L43) | pip install r2pipe |
radare2 plugin r2ghidra must be installed and available from r2. | radare2 中未安装/未启用 r2ghidra,LD输出不含pdg(pwndbg/aglib/radare2.py#L63-L68) | 安装 r2ghidra 插件(如通过r2pm -ci r2ghidra) |
OnlyWithFile限制 | 命令被@pwndbg.commands.OnlyWithFile装饰(pwndbg/commands/radare2.py#L98) | 先加载可执行文件再调用 |
另外需要注意:句柄一旦创建即被缓存(cache_until("start", "objfile")),因此如果调试过程中切换到另一个二进制,pwndbg 会在objfile变化后自动重建句柄,无需手动干预;如果你觉得缓存中的 radare2 状态已经混乱,也可以使用reinit-pwndbg或重启调试会话来刷新。
小结
r2pipe命令将 radare2 强大的反汇编、分析与伪代码能力无缝注入 gdb/LLDB 调试流程:
- 有状态管道:一次启动、复用句柄,地址经过 PIE 自动重定位,与 gdb 视角完全对齐;
- 完整命令语言:任意 radare2 命令原样透传,
@、;、变量等语法全部可用; - 可靠的前置校验:对 r2pipe 库与 r2ghidra 插件的缺失给出明确错误,方便排查;
- 生态对称:与
r2(交互式)、rz/rzpipe(rizin 系)互为补充,可覆盖从快速查询到深度逆向的完整工作流。
建议在调试环境准备好 radare2 + r2pipe + r2ghidra 后,用r2pipe pdf @ $PC或r2pipe afl作为第一个命令体验这一集成能力。相关的集成类命令文档可进一步参考 integrations 命令索引、r2 命令文档 与 decompiler-integration.md。
【免费下载链接】pwndbgExploit Development and Reverse Engineering with GDB & LLDB Made Easy项目地址: https://gitcode.com/GitHub_Trending/pw/pwndbg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考