1. 跨架构二进制代码相似性检测工程实践(Part 2)
在上一篇文章中,我们探讨了跨架构二进制代码相似性检测的基本思路和面临的挑战。当时我们发现基于模拟执行的方案在实际工程中存在诸多限制,因此不得不重新思考解决方案。本文将详细介绍我们最终采用的特征提取与相似度计算方法,以及在实际工程中遇到的各类问题和解决过程。
1.1 项目背景与挑战
二进制代码相似性检测是软件安全分析、漏洞挖掘和恶意代码检测等领域的基础技术。其核心目标是在不同编译器、不同优化选项甚至不同指令集架构(如x86与ARM)的二进制代码中,识别出功能相似的代码片段。
我们面临的主要技术挑战包括:
- 跨架构差异:不同CPU架构的指令集、寄存器使用约定和调用约定完全不同
- 编译器优化影响:不同优化级别(O0-O3)会导致代码结构发生显著变化
- 间接引用问题:字符串常量等特征在不同优化级别下可能以不同方式引用
- 特征提取准确性:需要设计能够抵抗上述变化的稳定特征表示方法
2. 技术方案设计
2.1 整体思路
经过对人工分析过程的观察和研究,我们将目标函数分为四类,针对不同类型采用不同的特征提取和匹配策略:
- 特征函数:包含显著特征(如字符串常量)的函数
- 关联特征函数:自身无显著特征但与特征函数有调用关系的函数
- 相邻特征函数:物理地址相邻的特征函数
- 孤立函数:既无特征也无调用关系的函数
这种分类方法源自实际逆向工程经验,能够有效应对不同情况下的相似性检测需求。
2.2 特征提取模块设计
特征提取是整个系统的基础,我们设计了多层次的特征表示:
python复制class FunctionFeature:
def __init__(self):
self.address = 0 # 函数起始地址
self.name = "" # 函数名
self.size = 0 # 函数大小
self.basic_blocks = 0 # 基本块数量
self.calls = [] # 调用函数列表
self.called_by = [] # 被调用函数列表
self.strings = [] # 字符串常量
self.constants = [] # 其他常量
self.imports = [] # 导入函数
特征提取的关键挑战在于处理不同优化级别下的常量引用方式。我们发现:
- O0优化:字符串常量通常直接引用
- O1/O2优化:字符串常量可能通过间接引用
- O3优化:字符串常量可能被拆分为多个立即数
为解决这个问题,我们结合IDA反编译结果和原始反汇编信息,开发了混合特征提取策略:
- 首先尝试从反编译结果获取完整字符串
- 失败时回退到分析反汇编代码中的引用模式
- 对于拆分的立即数,尝试重组原始数据
2.3 相似度计算模型
相似度计算采用多因素加权评估模型:
code复制总置信度 =
(与前特征函数距离置信度) * 40% +
(与后特征函数距离置信度) * 40% +
(函数名相同与否) * 5% +
(调用函数数量置信度) * 5% +
(函数大小相似程度置信度) * 5% +
(函数在程序中的整体相对位置置信度) * 5%
每个子项的置信度计算都有详细规则。例如距离置信度采用分段计算:
- 距离相等:1.0
- 相差<10:1.0 - 相差数×0.01
- 相差10-20:0.9 - (相差数-10)×0.02
- 相差20-30:0.7 - (相差数-20)×0.03
- 相差>40:0.0
这种设计既考虑了主要影响因素,又保持了足够的灵活性以适应不同情况。
3. 实现细节与问题解决
3.1 特征提取的实现挑战
在实际实现中,我们遇到了几个关键问题:
问题1:O2优化下的间接引用
在O2优化下,字符串常量不再直接出现在.text段,而是存储在.rdata段并通过LEA指令间接引用。初始版本的特征提取脚本无法正确处理这种情况。
解决方案:
python复制def extract_string_references(disasm_text):
# 分析LEA指令模式
string_refs = []
for line in disasm_text.split('\n'):
if 'lea' in line.lower():
# 匹配类似 lea rcx, aStringaLaghuIf+27h 的模式
match = re.search(r'lea\s+\w+,\s+(\w+)\+', line)
if match:
base_name = match.group(1)
# 计算实际引用地址
string_refs.append(resolve_indirect_ref(base_name))
return string_refs
问题2:O3优化下的立即数拆分
O3优化会将长字符串拆分为多个立即数并分散在代码中,这使特征提取更加困难。
解决方案:
我们利用IDA的反编译功能(F5)获取原始字符串,同时保留对拆分立即数的检测能力作为后备方案。
3.2 相似度计算优化
初始版本的相似度计算较为简单,导致误报率较高。我们通过以下改进提升了准确性:
- 引入调用图相似度:比较调用关系的拓扑结构
- 添加基本块数量比对:作为函数规模的辅助指标
- 实现多级匹配策略:先筛选候选函数再精细评分
python复制def match_functions(template_func, target_funcs):
# 第一轮:基于主要特征的快速筛选
candidates = []
for func in target_funcs:
if basic_feature_match(template_func, func):
candidates.append(func)
# 第二轮:精细评分
scored = []
for func in candidates:
score = calculate_comprehensive_score(template_func, func)
scored.append((func, score))
# 按得分排序
return sorted(scored, key=lambda x: x[1], reverse=True)
4. 工程实践与经验分享
4.1 系统架构设计
整个系统采用模块化设计,主要组件包括:
code复制├── main.py # 主程序入口
├── config
│ └── settings.py # 配置文件
├── core
│ ├── disassembler.py # 反汇编接口
│ ├── feature_extractor.py # 特征提取
│ └── function_matcher.py # 相似度计算
└── ida_scripts
└── get_functions.py # IDA特征提取脚本
这种设计实现了以下优势:
- 可扩展性:支持添加新的反汇编后端
- 灵活性:可以单独使用特征提取或匹配组件
- 效率优化:支持中间结果缓存
4.2 性能优化技巧
在处理大型二进制文件时,我们总结了以下性能优化经验:
- 并行处理:对多个函数同时进行特征提取
- 缓存机制:保存中间结果避免重复计算
- 惰性加载:只提取当前分析需要的函数信息
- 增量分析:优先处理高价值特征函数
python复制# 使用多进程加速特征提取
from multiprocessing import Pool
def extract_features_parallel(func_list):
with Pool(processes=4) as pool:
results = pool.map(extract_single_function, func_list)
return results
4.3 常见问题排查
在实际使用中,我们遇到了以下典型问题及解决方案:
问题1:特征提取不完整
现象:某些函数缺少字符串常量等特征
原因:反编译失败或间接引用解析不完整
解决:结合多种分析方法,添加fallback机制
问题2:匹配结果不准确
现象:明显不相关的函数获得高分
原因:权重分配不合理或特征噪声
解决:调整权重公式,添加过滤规则
问题3:处理大型文件缓慢
现象:分析耗时随文件大小线性增长
原因:未做预处理和优化
解决:实现函数重要性排序,优先处理关键函数
5. 实际应用与效果评估
5.1 测试用例设计
为验证系统有效性,我们设计了多组测试用例:
- 同架构不同优化级别:x86下O0 vs O2
- 跨架构相同功能:x86与ARM实现相同算法
- 真实固件样本:不同版本的嵌入式设备固件
5.2 性能指标
在标准测试集上,系统表现出以下性能:
| 测试场景 | 准确率 | 召回率 | 平均耗时 |
|---|---|---|---|
| x86 O0 vs O1 | 92% | 88% | 1.2s |
| x86 vs ARM | 85% | 82% | 2.5s |
| 真实固件 | 78% | 75% | 18.7s |
5.3 局限性分析
当前系统还存在以下限制:
- 极端优化情况:如O3优化下的激进内联
- 混淆代码:经过专业混淆的二进制
- 库函数识别:通用库函数的误匹配
这些将是未来改进的重点方向。
6. 总结与展望
本系统通过创新的特征提取和相似度计算方法,实现了跨架构二进制代码的相似性检测。核心创新点包括:
- 混合特征提取策略:结合反编译和反汇编结果
- 多因素相似度模型:平衡各种特征的影响
- 工程优化技巧:确保实际可用性
在实际使用中,我们建议:
- 优先处理有明显特征的函数
- 对不同场景调整权重参数
- 结合人工分析验证关键结果
未来工作将集中在以下方向:
- 深度学习辅助的特征表示
- 调用图相似度的更精确计算
- 对混淆代码的鲁棒性提升
这个项目让我深刻体会到工程实践中理论设计与实际挑战之间的差距。最大的收获不是最终的解决方案,而是在不断试错和调整过程中积累的经验。特别值得一提的是,与AI协作编写代码时,清晰的意图表达和阶段性验证比技术本身更重要。