radare2 是一个命令行逆向分析框架。它轻、快、适合 SSH 环境,也适合批处理。缺点是命令很多,刚开始容易记不住。
学习 radare2 的关键不是背全命令,而是先掌握一条稳定路线:
1 | 打开文件 |
启动方式
打开文件:
1 | r2 sample.bin |
打开并自动分析:
1 | r2 -A sample.bin |
以写模式打开:
1 | r2 -w sample.bin |
常用启动参数:
| 参数 | 说明 |
|---|---|
-A |
打开后自动分析 |
-w |
写模式,允许 patch |
-d |
调试模式 |
-q |
安静输出,适合脚本 |
-c |
启动后执行命令 |
示例:
1 | r2 -A -q -c "afl" sample.bin |
基础命令
| 命令 | 用途 |
|---|---|
? |
帮助 |
q |
退出 |
i |
文件信息 |
ii |
导入函数 |
iE |
入口点 |
izz |
字符串 |
aaa |
深度自动分析 |
afl |
函数列表 |
s addr |
seek 到地址 |
pdf |
打印当前函数反汇编 |
pdc |
打印伪 C 风格代码 |
VV |
进入可视化图模式 |
axt addr |
查看谁引用了地址 |
axf addr |
查看当前地址引用了谁 |
命令后加 ? 看帮助:
1 | af? |
自动分析
打开文件后先运行:
1 | aaa |
常见分析命令:
| 命令 | 说明 |
|---|---|
aa |
基础分析 |
aaa |
更完整分析 |
aaaa |
更激进分析 |
af |
在当前地址创建函数 |
afl |
列出函数 |
afi |
当前函数信息 |
afv |
当前函数变量 |
建议:
- 小样本用
aaa。 - 大固件先用
aa,避免等太久。 - 函数识别错时,手动
s 地址后af。
文件信息
进入 r2 后:
1 | i |
常用信息命令:
1 | ij # JSON 格式文件信息 |
命令行直接导出 JSON:
1 | r2 -A -q -c "iij" -c "q" sample.bin |
字符串与引用
列出字符串:
1 | izz |
搜索字符串:
1 | izz~token |
跳到字符串地址:
1 | s 0x00405000 |
查引用:
1 | axt 0x00405000 |
典型流程:
1 | izz~password |
含义:
- 找到
password字符串。 - 查看谁引用这个字符串。
- 跳到引用函数。
- 打印函数反汇编。
函数列表和反汇编
列函数:
1 | afl |
过滤函数:
1 | afl~main |
跳转并打印函数:
1 | s main |
打印指定行数反汇编:
1 | pd 20 |
打印伪代码:
1 | pdc |
如果安装了反编译插件,可能有:
1 | pdg |
不同环境插件不同,不能依赖所有机器都有 pdg。
重命名和注释
重命名函数:
1 | afn check_token 0x00401234 |
当前位置重命名:
1 | afn check_token |
添加注释:
1 | CC 验证 token 为空时返回 -1 |
查看注释:
1 | CC. |
删除注释:
1 | CC- |
命名建议和 Ghidra 一样:先用 maybe_,有证据再改精确。
可视化模式
进入图模式:
1 | VV |
常用按键:
| 按键 | 用途 |
|---|---|
p |
切换视图 |
g |
跳转 |
x |
查看引用 |
; |
注释 |
q |
退出当前视图 |
Tab |
切换基本块 |
可视化模式适合看条件分支和控制流。
搜索
搜索字符串:
1 | / token |
搜索十六进制:
1 | /x 89504e47 |
搜索指令:
1 | /a mov eax |
搜索结果列表:
1 | f~hit |
调试模式
启动调试:
1 | r2 -d ./sample |
常用调试命令:
| 命令 | 用途 |
|---|---|
ood |
重新打开并运行 |
db addr |
下断点 |
db- addr |
删除断点 |
dc |
继续运行 |
ds |
单步进入 |
dso |
单步越过 |
dr |
寄存器 |
px 64 @ rsp |
查看栈内存 |
dm |
内存映射 |
例子:
1 | aaa |
查看函数参数要结合架构调用约定:
| 架构 | 常见参数寄存器 |
|---|---|
| x86_64 Linux | rdi, rsi, rdx, rcx, r8, r9 |
| x86_64 Windows | rcx, rdx, r8, r9 |
| ARM64 | x0 - x7 |
| x86 32 位 | 多数走栈 |
Patch
写模式打开:
1 | r2 -w sample.bin |
把当前位置 patch 成 NOP:
1 | wa nop |
写汇编:
1 | wa jmp 0x401000 |
写 hex:
1 | wx 90909090 |
保存前先备份原文件。Patch 学习时可用,实际发布涉及签名、校验、完整性保护等问题。
r2pipe 自动化
安装:
1 | python3 -m pip install r2pipe |
列出函数:
1 | import r2pipe |
导出字符串和引用:
1 | import r2pipe |
批量处理目录:
1 | from pathlib import Path |
小案例:从错误字符串找校验逻辑
目标:找到 invalid license 的判断位置。
命令:
1 | r2 -A sample |
分析:
1 | 字符串 invalid license |
重命名:
1 | afn show_invalid_license 0x004012a0 |
记录:
1 | - `check_license` 返回值影响 `invalid license` 分支。 |
完整案例:分析一个口令校验小程序
假设有一个授权练习样本 checkpass:
1 | ./checkpass 123456 |
输出:
1 | wrong password |
目标:找出口令校验函数在哪里,以及它大概怎么判断。
1. 基础识别
1 | file checkpass |
可能输出:
1 | wrong password |
2. 打开并自动分析
1 | r2 -A checkpass |
进入后:
1 | iE |
如果没有 main 符号:
1 | s entry0 |
再顺着 __libc_start_main 的参数找真正的 main。
3. 从字符串定位分支
1 | izz~password |
假设得到:
1 | 0x00002020 ascii wrong password |
查看引用:
1 | axt 0x00002020 |
如果两者都指向 main,说明判断分支就在 main 附近。
4. 看反汇编
1 | s main |
可能看到:
1 | call sym.imp.strcmp |
能得到:
- 程序用
strcmp比较。 eax == 0进入成功分支。- 失败分支打印
wrong password。
5. 验证比较参数
x86_64 Linux 下 strcmp(a, b) 前两个参数通常在 rdi、rsi。
调试:
1 | r2 -d ./checkpass test |
下断:
1 | aaa |
断住后:
1 | dr rdi |
预期类似:
1 | test |
6. 重命名和注释
如果校验逻辑在 fcn.00001180:
1 | afn check_password 0x00001180 |
7. r2pipe 批量扫线索
1 | from pathlib import Path |
8. 复盘记录
1 | ## checkpass |
这个案例的核心是:izz 找提示字符串,axt 找引用,pdf 看分支,调试模式用寄存器验证真实参数。
常见问题
命令太多记不住
记住这些就够入门:
1 | aaa |
再记住 ?:
1 | pd? |
afl 没有 main
可能符号被去掉了。可以看入口:
1 | iE |
然后顺着启动逻辑找真正业务入口。
反汇编不对
可能架构识别错、ARM/Thumb 模式不对、数据被当代码。先看:
1 | ij |
必要时手动切换或指定架构打开。
pdc 很难看
pdc 不是完整反编译器,只是伪 C 风格输出。复杂函数建议用 Ghidra 看伪代码,radare2 用来快速定位和批处理。
调试时断不住
检查:
- 是否 PIE,地址是否要加基址。
- 断点地址是否在可执行映射里。
- 程序是否 fork 出子进程。
- 是否还没运行到目标分支。
查看映射:
1 | dm |
分析记录模板
1 | ## 样本 |
r2 -A sample izz~keyword axt 0x… pdf @ 0x…
1 |
|
案例二:分析一个菜单程序的隐藏命令
样本代码
1 |
|
编译:
1 | gcc -O0 menu.c -o menu |
1. 字符串找入口
1 | r2 -A ./menu |
输出类似:
1 | 0x0000202a 10 9 .rodata ascii debug-8848 |
2. 查字符串引用
1 | axt @ 0x0000202a |
得到:
1 | main 0x000011f4 [DATA:r--] lea rax, str.debug_8848 |
跳过去:
1 | s 0x000011f4 |
你会看到类似:
1 | lea rax, [str.debug_8848] |
3. 给函数命名
1 | afn check_menu_command @ main |
再看函数:
1 | pdf @ main |
4. 复现实验
退出 r2:
1 | q |
运行:
1 | printf 'debug-8848\n' | ./menu |
输出:
1 | 1. status |
5. 记录结论
1 | - 隐藏命令:`debug-8848` |
这个案例适合练 radare2 的三件事:izz 找字符串、axt 找引用、pdf 看分支。
案例三:用 radare2 调试确认 strcmp 参数
目标
样本运行时会比较输入密码,但静态看不清传给 strcmp 的两个参数。用调试模式在 strcmp 断住,直接看寄存器。
1. 启动调试
1 | r2 -d ./checkpass |
2. 找 strcmp PLT
1 | is~strcmp |
可能输出:
1 | 5 0x00000000 0x00001040 GLOBAL FUNC strcmp |
对导入函数下断点:
1 | db sym.imp.strcmp |
程序等待输入时,输入:
1 | hello |
3. x86_64 查看参数
Linux x86_64 下,前两个参数通常在 rdi、rsi:
1 | dr rdi |
可能得到:
1 | hello |
4. ARM64 查看参数
Android ARM64 下,前两个参数通常在 x0、x1:
1 | dr x0 |
5. 继续跑并观察返回值
1 | dso |
strcmp 返回 0 表示相等。记录:
1 | - 断点:`sym.imp.strcmp` |
案例四:r2pipe 批量导出字符串引用
目标
批量查所有包含 token、auth、secret 的字符串,并输出引用函数,适合对大量小样本快速扫入口。
Python 脚本
1 | import json |
运行:
1 | python3 scan_refs.py ./demo |
输出:
1 | STRING 0x402018 invalid token |
这个脚本可以和 Ghidra 分工:radare2 先批量扫,Ghidra 再打开高价值函数精读。