name: binary-re-static-analysis description: 用于分析二进制结构、反汇编代码或反编译函数。通过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 # 字节级差异
# 记录发现:
# - 输出首先在哪里出现分歧?
# - 文件大小匹配吗?(逻辑错误与截断)
# - 损坏中出现了什么模式?
此步骤通常在代码分析之前就揭示了错误类别。
两阶段方法
阶段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 not installed"
# 如果缺失,使用以下命令安装:
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"
}
]
}
知识记录
静态分析后,为情景记忆记录发现:
[BINARY-RE:static] {文件名} (sha256: {哈希值})
已分析函数:{数量}
已执行反编译:{是|否}
关键函数:
事实:地址{addr}处的函数调用了{imports}(来源:r2 axfj)
事实:地址{addr}处的函数引用了字符串"{string}"(来源:r2 axtj)
事实:函数{name}似乎用于{purpose}(来源:反编译)
交叉引用:
事实:{caller}调用了{callee}(来源:r2 axtj)
假设更新:{精炼的理论}(置信度:{新值})
支持:{事实ID}
矛盾:{事实ID}
新问题:
问题:{发现的未知点}
已回答问题:
已解决:{问题} → {答案}
示例记录条目
[BINARY-RE:static] 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 如果达到足够的理解