从无符号 VelaOS 固件(小米基于 NuttX 的可穿戴设备操作系统)中自动恢复函数名称。组合多种分析技术,从纯二进制固件中尽可能多地识别函数。
当前最佳结果:小米手表 S3 v4.8.0 — 19,010 / 22,172 个函数已命名(85.7%)
工具使用五种互补技术识别函数名:
-
__func__字符串交叉引用 — C 编译器在使用__func__时会将函数名存为字符串常量。找到这些字符串并追踪引用,即可 100% 确定函数名。 -
跨版本字节签名匹配 — 不同固件版本中许多函数的机器码完全相同。从已命名版本提取字节签名,在目标版本中匹配。支持多版本迭代交叉传播。
-
跨架构特征匹配 — 模拟器(ARM-A 架构,有完整符号)与真机固件(Cortex-M 架构,无符号)共享架构无关特征。通过字符串指纹(Jaccard 相似度)和调用图拓扑进行匹配。
-
反向调用图投票 — 对于无字符串引用的函数(如
malloc、memcpy):如果一个未知函数的大部分调用者都映射到模拟器中调用某个已知函数的调用者,则它们很可能是同一函数。 -
高扇入函数排名匹配 — 对仍未命名的高价值函数,按真机/模拟器 fan-in 排名对齐,并结合 fan-out 与函数大小相似度进行筛选。该方法通常提供中等置信度候选,适合人工复核后确认(例如
memset、abort、malloc/free相关函数)。
- Python 3.8+
- Ghidra 11.x+(用于步骤 1-2)
- Python 依赖:
pip install pyelftools capstone
git clone <repo-url>
cd ReverseVelapip install pyelftools capstone从 https://ghidra-sre.org/ 下载并解压到以下任意位置(自动检测):
~/Documents/Dev/Tools/ghidra_*(Windows)~/ghidraC:\ghidra或/opt/ghidra
firmware/
S3/
4.8.0/
vela_ap.bin # 主 AP 固件二进制文件
4.6.10/
vela_ap.bin
...
simulator/
vela-pre-4.0/
nuttx # 带完整调试符号的 ELF
vela-release-4.0/
nuttx
vela-watch-5.0/
nuttx
...
系统会根据固件中的 NuttX 版本字符串自动选择匹配的模拟器。
python pipeline.py --info显示已发现的设备、固件版本、模拟器和 Ghidra 路径。
python pipeline.py --device S3 --version 4.8.0默认运行多版本迭代增强模式(质量最高):
- 运行步骤 1-3(Ghidra 导入/导出 + 模拟器特征提取)处理所有版本
- 运行步骤 4-7(匹配 + 编译)处理每个固件版本
- 用增强后的符号表重新进行跨版本匹配(各版本互相交叉传播函数名)
- 生成最终输出
output/S3/4.8.0/symbols.csv
python pipeline.py --device S3 --version 4.8.0 --no-enrich更快但结果更少(不进行多版本交叉传播)。
# 只运行步骤 4 到 7
python pipeline.py --device S3 --version 4.8.0 --steps 4-7
# 只运行步骤 5 和 7
python pipeline.py --device S3 --version 4.8.0 --steps 5,7python pipeline.py --device S3 --version 4.8.0 --force忽略所有缓存,重新运行全部步骤。
| 步骤 | 工具 | 脚本 | 说明 |
|---|---|---|---|
| 1 | Ghidra | RenameByStringXref.java |
导入固件 + __func__ 交叉引用重命名 |
| 2 | Ghidra | Export*.java |
导出函数特征和字节签名 |
| 3 | Python | extract_sim_features.py |
提取模拟器 ELF 特征 + 签名 |
| 4 | Python | cross_version_match.py |
跨版本字节签名匹配 |
| 5 | Python | cross_arch_match.py |
跨架构特征匹配 |
| 6 | Python | reverse_cg_match.py + high_fanin_match.py |
反向调用图投票 + 高扇入函数排名匹配 |
| 7 | Python | compile_results.py |
合并所有结果 → 最终符号表 |
步骤 1-3 处理所有固件版本和模拟器(已有缓存自动跳过)。步骤 4-7 处理目标版本。
其中步骤 6 会同时产出 matches/reverse_cg.csv 和 matches/high_fanin.csv,后者默认作为“中置信候选池”参与最终合并。
运行成功后,生成以下文件:
output/S3/4.8.0/
symbols.csv # 最终符号表(地址、名称、置信度、来源、证据)
for_ghidra.csv # 简化版 CSV,用于导入 Ghidra
report.txt # 人类可读的分析报告
address,name,confidence,source,evidence
0x2c301234,nx_start,100,xref_confirmed,__func__ unique xref
0x2c305678,sched_lock,95,binary_crossver,4 versions agree (24-byte sig)
0x2c30abcd,some_func,75,cross_arch,string_match: 3 shared strings jaccard=0.67置信度等级:
- >=80%(高)— 非常可能正确,可直接使用
- 60-79%(中)— 大概率正确,关键函数建议人工验证
- <60%(低)— 最佳猜测,谨慎使用
# 方式一:在 Ghidra Script Manager 中运行 ApplyNames.java,参数为 CSV 路径
# 方式二:使用 headless 模式:
analyzeHeadless ghidra_projects S3_4.8.0 -process -noanalysis \
-scriptPath ghidra_scripts -postScript ApplyNames.java output/S3/4.8.0/for_ghidra.csv每个步骤检查输出文件是否已存在,已有则 [CACHED] 跳过:
- 重新运行流水线很快(仅执行未缓存的步骤)
- 使用
--force强制重新生成全部 - 步骤 7(compile)总是重新执行(毫秒级合并操作)
可安全删除的目录: output/、sim_features/、ghidra_projects/ 均可完全删除,流水线会自动重建。
-
在
config.py的DEVICES字典中添加设备参数:DEVICES = { "NewDevice": { "name": "设备显示名称", "chip": "芯片型号", "arch": "ARM:LE:32:v8-m", # Ghidra 处理器 ID "base_addr": 0x2C300000, # Flash 基地址 "header_size": 16, "firmware_binary": "vela_ap.bin", }, }
-
将固件放入
firmware/NewDevice/{version}/ -
验证:
python pipeline.py --info -
运行:
python pipeline.py --device NewDevice --version 1.0.0
将模拟器目录放入 simulator/,确保其中包含 nuttx ELF 文件:
simulator/
my-new-sim/
nuttx # 带调试符号的 ELF
系统会根据固件中的 NuttX 版本字符串自动匹配模拟器。如需添加新的版本检测规则,修改 config.py 中的 NUTTX_VERSION_SIGNATURES。
config.py # 设备配置、路径发现、NuttX 版本检测
pipeline.py # 主流水线调度器
scripts/
extract_sim_features.py # 模拟器 ELF → 函数特征 + 字节签名
cross_version_match.py # 跨版本字节签名匹配
cross_arch_match.py # 跨架构(模拟器 → 真机)匹配
reverse_cg_match.py # 反向调用图投票
high_fanin_match.py # 高扇入函数排名匹配(libc/高价值候选)
compile_results.py # 合并所有结果 → 最终符号表
utils.py # 共用工具函数
ghidra_scripts/
RenameByStringXref.java # __func__ 字符串交叉引用自动重命名
ExportFunctionFeatures.java # 导出 device_functions/calls/strings.csv
ExportNamedFunctions.java # 导出 named_functions.csv + stripped_functions.csv
ExportCFG.java # 导出控制流图特征
ApplyNames.java # 从 CSV 将函数名应用到 Ghidra 项目
- 目标芯片:BES2700BP(ARM Cortex-M55,ARMv8-M Mainline,Thumb-2 指令集)
- Flash 基地址:
0x2C300000,16 字节固件头 - 模拟器:ARM-A 架构 ELF,带完整符号表
- 跨架构大小比例:Thumb-2 与 ARM-A 函数大小通常在 0.2x-5.0x 范围内(匹配时验证)
- 字节签名:每个函数的前 N 字节(12 或 24 字节),hex 编码
- NuttX 版本检测:扫描固件二进制中的版本字符串(
NuttX-12.3.0、curl 7.86.0-DEV等)
本项目仅用于研究和教育目的。