名称: 二进制逆向静态分析 描述: 用于分析二进制结构、反汇编代码或反编译函数。通过radare2 (r2) 和 Ghidra headless 进行深度静态分析 - 函数枚举、交叉引用 (xrefs)、反编译、控制流图。关键词 - “反汇编”, “反编译”, “此函数功能”, “查找函数”, “分析代码”, “r2”, “ghidra”, “pdg”, “afl”
静态分析 (阶段 2-3)
目的
在不执行的情况下理解二进制结构及逻辑。映射函数、追踪数据流、反编译关键代码。
使用时机
- 在初步分类确定架构和ABI之后
- 为理解被识别为有趣的具体函数时
- 当动态分析不切实际或存在风险时
- 在进行动态验证前建立假设时
预分析:首先比较已知输入/输出
关键: 在深入反汇编之前,检查是否存在已知的输入/输出。
⚠️ 需要人工批准 - 任何执行(即使是用于I/O比较)前都需要获得明确批准。
# 安全:使用模拟器处理跨架构二进制文件(在人工批准后)
# ARM32:
qemu-arm -L /usr/arm-linux-gnueabihf -- ./binary < input.txt > actual.txt
# ARM64:
qemu-aarch64 -L /usr/aarch64-linux-gnu -- ./binary < input.txt > actual.txt
# 基于Docker (macOS/跨架构 - 参见动态分析选项 D):
docker run --rm --platform linux/arm/v7 -v ~/samples:/work:ro \
arm32v7/debian:bullseye-slim sh -c '/work/binary < /work/input.txt' > actual.txt
# x86-64 原生 (仍需批准):
./binary < input.txt > actual.txt
# 比较输出:
diff expected.txt actual.txt
cmp -l expected.txt actual.txt | head -20 # 字节级差异
# 记录发现:
# - 输出在何处首次出现分歧?
# - 文件大小是否匹配?(逻辑错误 vs 截断)
# - 损坏中出现了什么模式?
此步骤通常在代码分析之前就揭示了漏洞类别。
两阶段方法
阶段 1 (轻量): 函数枚举、字符串、导入 - 快速,覆盖广泛 阶段 2 (深度): 针对性反编译、CFG分析 - 慢速,聚焦
阶段 1:轻量分析 (radare2)
分析深度选择
| 二进制大小 | 命令 | 权衡 |
|---|---|---|
| < 500KB | aaa |
完整分析,可能较慢 |
| 500KB - 5MB | aa; aac |
函数 + 所有调用目标 |
| > 5MB | aa + 针对性的 af @addr |
快速,手动控制深度 |
会话设置
# 启动 r2 并控制分析
r2 -q0 -e scr.color=false -e anal.timeout=120 -e anal.maxsize=67108864 binary
# 在 r2 内部 (根据二进制大小选择):
aa # 基础分析
aac # 同时分析所有调用目标 (推荐用于大多数二进制文件)
关键设置:
anal.timeout=120- 防止分析失控anal.maxsize=67108864- 最大函数大小 64MB- 中型二进制文件使用
aa; aac,小型文件仅使用aaa
处理未分析的调用目标
如果 axtj 对已知导入返回空结果:
# 导入可能被间接调用或分析深度不足
# 选项 1: 更深层次的分析
aac # 分析所有调用
# 选项 2: 在调用目标处手动创建函数
af @0x8048abc
# 选项 3: 搜索对导入地址的引用
axtj @sym.imp.connect
函数枚举
# 所有函数 (JSON格式)
aflj
# 按名称模式过滤
aflj~main
aflj~init
aflj~network
aflj~send
aflj~recv
# 函数计数
afl~?
交叉引用分析
# 谁调用了这个函数?
axtj @sym.imp.connect
# 这个函数调用了什么?
axfj @sym.main
# 对地址的数据引用
axtj @0x12345
字符串-函数关联
# 查找哪个函数包含某个字符串
izj~api.vendor.com
# 记下 vaddr,然后查找包含它的函数
afi @0xVADDR
# 或者搜索并映射
"/j api" # 搜索字符串
axtj @@hit* # 对所有命中结果的交叉引用
导入/导出映射
# 导入及其地址
iij
# 导出及其地址
iEj
# 符号 (如果未剥离)
isj
快速反汇编
# 将函数反汇编为 JSON
pdfj @sym.main
# 从地址开始反汇编 N 条指令
pdj 20 @0x8400
# 打印函数摘要
afi @sym.main
阶段 2:深度分析
r2ghidra 可用性检查
在尝试反编译之前,验证 r2ghidra 是否已安装:
# 检查 r2ghidra 是否可用
r2 -qc 'pdg?' - 2>/dev/null | grep -q Usage && echo "r2ghidra OK" || echo "SKIP: r2ghidra 未安装"
# 如果缺失,使用以下命令安装:
r2pm -ci r2ghidra
如果 r2ghidra 不可用: 依赖反汇编 (pdf) 和交叉引用分析 (axt/axf)。
针对性反编译 (r2ghidra)
# 反编译特定函数
pdgj @sym.target_function
# 或命名函数
pdgj @sym.main
Ghidra Headless (大型二进制文件)
对于复杂函数或当 r2ghidra 处理困难时:
# 创建分析项目并运行脚本
analyzeHeadless /tmp/ghidra_proj proj \
-import binary \
-overwrite \
-processor ARM:LE:32:v7 \
-postScript ExportDecompilation.java sym.target_function \
-deleteProject
处理器字符串:
- ARM 32位:
ARM:LE:32:v7或ARM:LE:32:Cortex - ARM 64位:
AARCH64:LE:64:v8A - x86_64:
x86:LE:64:default - MIPS LE:
MIPS:LE:32:default - MIPS BE:
MIPS:BE:32:default
控制流分析
# 函数中的基本块
afbj @sym.main
# 函数调用图 (dot 格式)
agCd @sym.main > callgraph.dot
# 控制流图
agfd @sym.main > cfg.dot
数据结构恢复
# 分析局部变量
afvj @sym.main
# 栈帧布局
afvd @sym.main
# 全局数据引用
adrj
分析模式
模式:网络函数追踪
# 查找所有网络相关调用
axtj @sym.imp.socket
axtj @sym.imp.connect
axtj @sym.imp.send
axtj @sym.imp.recv
axtj @sym.imp.SSL_read
axtj @sym.imp.SSL_write
# 追踪调用者链
for func in $(aflj | jq -r '.[].name'); do
axfj @$func | grep -q "socket\|connect" && echo $func
done
模式:配置文件分析
# 查找文件操作
axtj @sym.imp.open
axtj @sym.imp.fopen
# 追踪字符串参数
"/j /etc"
"/j .conf"
"/j .json"
# 检查哪些函数引用了这些路径
模式:加密算法识别
# 常见加密导入
axtj @sym.imp.EVP_EncryptInit
axtj @sym.imp.AES_encrypt
axtj @sym.imp.SHA256
# 硬编码密钥 (检查加密调用附近的字符串)
izj | jq '.strings[] | select(.length == 16 or .length == 32)'
r2 JSON 命令参考
| 命令 | 输出 | 使用场景 |
|---|---|---|
aflj |
函数列表 | 映射代码结构 |
axtj @addr |
对地址的引用 | 谁使用了这个? |
axfj @addr |
从地址出发的引用 | 它调用了什么? |
pdfj @addr |
反汇编 | 理解指令 |
pdgj @addr |
反编译 | 伪C代码输出 |
afbj @addr |
基本块 | 控制流 |
izj |
数据字符串 | 配置、URL |
iij |
导入 | 外部依赖 |
iEj |
导出 | 公共接口 |
afvj @addr |
局部变量 | 栈分析 |
输出格式
将分析发现记录为结构化事实:
{
"functions_analyzed": [
{
"name": "sub_8400",
"address": "0x8400",
"size": 256,
"calls": ["socket", "connect", "send"],
"called_by": ["main", "init_network"],
"strings_referenced": ["api.vendor.com"],
"hypothesis": "network_initialization"
}
],
"call_graph": {
"main": ["init_config", "init_network", "main_loop"],
"init_network": ["sub_8400", "SSL_CTX_new"]
},
"data_flow": [
{
"source": "config_file_read",
"through": ["parse_config", "extract_url"],
"sink": "connect_to_server"
}
]
}
知识记录
静态分析后,为情景记忆记录发现:
[二进制逆向:静态] {文件名} (sha256: {哈希值})
已分析函数: {数量}
已执行反编译: {是|否}
关键函数:
事实: 地址 {addr} 处的函数调用了 {imports} (来源: r2 axfj)
事实: 地址 {addr} 处的函数引用了字符串 "{string}" (来源: r2 axtj)
事实: 函数 {name} 似乎用于 {purpose} (来源: 反编译)
交叉引用:
事实: {caller} 调用了 {callee} (来源: r2 axtj)
假设更新: {精炼后的理论} (置信度: {新值})
支持: {事实ID}
矛盾: {事实ID}
新问题:
问题: {发现的未知项}
已回答问题:
已解决: {问题} → {答案}
示例记录条目
[二进制逆向:静态] thermostat_daemon (sha256: a1b2c3d4...)
已分析函数: 47
已执行反编译: 是 (函数 0x8400)
关键函数:
事实: 函数 0x8400 调用 curl_easy_perform, curl_easy_setopt (来源: r2 axfj)
事实: 函数 0x8400 引用字符串 "api.thermco.com/telemetry" (来源: r2 axtj)
事实: 函数 0x9200 使用 jsmn 库解析 JSON (来源: 反编译)
事实: 函数 0x10800 是主循环,在 sleep(30) 后调用 0x8400 (来源: r2 pdf)
交叉引用:
事实: main 调用 init_config (0x9000) 然后 main_loop (0x10800) (来源: r2 axtj)
事实: main_loop 在循环中调用 send_telemetry (0x8400) (来源: r2 pdf)
假设更新: 遥测客户端每30秒向 api.thermco.com 发送数据 (置信度: 0.85)
支持: URL 字符串, curl 导入, 循环中的 sleep(30)
矛盾: 无
新问题:
问题: 遥测负载中包含哪些数据字段?
问题: 是否存在任何身份验证/API密钥?
已回答问题:
已解决: "什么端点?" → 通过 HTTPS 访问 api.thermco.com/telemetry
决策点
静态分析后:
- 识别出关键函数? → 准备进行动态验证
- 行为不明确? → 尝试动态分析以进行运行时观察
- 检测到加密? → 记录密钥处理方式,供安全审查参考
- 存在反分析模式? → 考虑使用 Unicorn 片段模拟
后续步骤
→ binary-re-dynamic-analysis 通过运行时观察验证假设
→ binary-re-synthesis 如果达到足够理解