BinDiff 概述
BinDiff 是一款高效的二进制文件比较工具,专为漏洞研究人员和软件工程师设计。它能够迅速识别反汇编代码中的差异和相似之处,从而提高分析效率。
通过应用 BinDiff,用户可以精确定位并分析供应商补丁中的漏洞修复。该工具还支持在同一二进制文件的多个版本的反汇编之间进行符号和注释的迁移。此外,BinDiff 在代码盗用或专利侵权调查中也发挥着重要作用,为取证提供有力支持。
BinDiff 使用方式
版本兼容性问题
BinDiff 存在多个版本,每个版本与特定的 IDA 版本相适配。在选择 BinDiff 版本时需要谨慎。例如,我初次尝试安装 BinDiff 7,但发现不兼容。随后卸载并重新安装 BinDiff 5 时,遇到了版本未能正确回退的问题。这通常是由于未完全清除之前版本的配置文件所致。解决此问题的详细说明可参考此技术博客。
BinDiff 官方手册提供了各版本 BinDiff 与 IDA 的兼容性信息。建议根据您使用的 IDA 版本选择相应的 BinDiff 安装包。值得注意的是,在 Mac 系统上可能会遇到兼容性问题,我最终在 Windows 平台上成功使用了 IDA 7.5 配合 BinDiff 7。
配置路径问题
在使用过程中,可能会遇到 BinDiff 报错:"Can't start disassembler. Please set correct path in the main settings first"。这通常是由于 BinDiff 未正确设置 IDA 的安装路径导致的。虽然之前的解决方案链接已失效,但这篇技术文章提供了可能的解决方案,建议尝试。
基本操作流程
使用 BinDiff 的基本步骤如下:首先创建新的 Workspace,然后选择 New Diff。需要特别注意的是,进行 Diff 比较时应选择 IDA 数据库文件,即 .i64 或 .idb 文件。
在Functions列表中,找到你感兴趣的函数,然后双击它。这会打开一个友好的对比视图,让你能轻松看到两个函数的具体差异。BinDiff很贴心地使用了控制流程图来进行比较,并用不同的颜色标记出了有变化的代码块。这样一来,你就可以一目了然地发现哪里发生了变化,省去了大量手动对比的时间!
实例分析之实战漏洞复现使用
WinRAR version 6.22 - Remote Code Execution via ZIP archive(CVE-2023-38831)
用bindiff比较存在漏洞的版本和更新的版本之间的差异,确定漏洞点位置在sub_1400EF508
关键代码段
// 0x140089948
char __fastcall compare(WCHAR *click_name, WCHAR *deFileName, int a3)
...
if ( (_WORD)a3 )
{
v7 = -1i64;
tName_len = -1i64;
do
++tName_len;
while ( click_name[tName_len] );
if ( (unsigned int)(unsigned __int16)a3 - 2 > 2 )
{
if ( a3 >= 0 )
v9 = sub_1400AF168(click_name, deFileName);
else
v9 = wcsncmp(click_name, deFileName, tName_len);
if ( !v9 )
{
tail = deFileName[tName_len];
if ( tail == '\\' || tail == '/' || !tail )
return 1;
}
if ( v3 == 1 )
return 0;
}
...
}
sub_1400EF508->sub_140009290->sub_14000A650->sub_1400D6070->0x1400D6478->0x1400D3474->0x1400CEBF4->0x140077054->0x140089948
-
sub_140009290(&unk_1401BA630, 0i64, 0i64);
该函数负责启动将文件提取至临时目录的过程。
__int64 __fastcall sub_140009290(__int64 a1, char a2, __int64 a3)
{
// 函数实现细节省略
if ( !byte_14018A806 )
{
if ( byte_1401A25E8 )
sub_14000A650(a1);
else
sub_14000A5A4(a1);
}
// 其他代码省略
}
函数调用序列如下:sub_1400EF508 → sub_140009290 → sub_14000A650
__int64 __fastcall sub_14000A650(__int64 a1)
{
__int64 v1; // rcx
v1 = *(_QWORD *)(a1 + 8i64 * dword_1401A4928 + 8);
return (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v1 + 56i64))(v1);
}
sub_14000A650
中的返回语句将控制权转移至sub_1400D6070
,后者开始遍历szShortPath目录下的所有文件。
//0x1400D6070
__int64 cmp()
{
// 函数实现细节省略
if ( (unsigned int)set_start_pos() )
return 8i64;
v1 = (int)call_cmp_name();
// 其他代码省略
}
cmp
函数调用set_start_pos()
为存储压缩档案中的文件名分配内存。
//0x1400D6478
__int64 set_start_pos()
{
// 函数实现细节省略
start_pos = (char *)j__malloc_base(0x2004ui64);
// 其他代码省略
}
随后,cmp
调用call_cmp_name
迭代文件名,填充deFileName并与用户选择的文件名(click_name)进行比较。
call_cmp_name
函数利用write_filenames
将档案中的所有文件名写入start_pos
内存区域,然后调用cmp_name_1
进行进一步比较。
//0x1400D3474
__int64 call_cmp_name()
{
// 函数实现细节省略
v0 = write_filenames();
// 其他代码省略
LODWORD(v1) = cmp_name_1(v2);
// 其他代码省略
}
cmp_name_1
遍历start_pos
区域中的所有文件名,对每个文件名调用cmp_name_2
进行比较。
//0x1400CEBF4
__int64 __fastcall cmp_name_1(__int64 a1)
{
// 函数实现细节省略
while ( !(unsigned __int8)sub_1400AE9EC() )
{
// 其他代码省略
while ( v1 < 64 || byte_1401EA658 )
{
// 其他代码省略
if ( !(unsigned int)cmp_name_2((unsigned int)&dword_14019B530, (unsigned int)v117, 0, v20, 0, 0i64, 0) )
goto LABEL_69;
// 其他代码省略
}
// 其他代码省略
}
// 其他代码省略
}
流程继续,cmp_name_2
获取deFileName和click_name,然后调用compare
函数进行最终比较。需要注意的是,click_name是在单独的"文件选择"过程中获取的,这部分将在后续分析中详细讨论。
//0x140077054
__int64 __fastcall cmp_name_2(__int64 a1, __int64 a2, bool *a3, unsigned int a4, char a5, _WORD *a6, unsigned int a7)
{
// 函数实现细节省略
deFileName = (const WCHAR *)(a2 + 40);
// 其他代码省略
click_name = sub_1400AC0CC(v15);
// 其他代码省略
if ( (unsigned __int8)compare(click_name, deFileName, a4) )
// 其他代码省略
}
总结上述函数流程:整个过程始于文件选择,随后进行临时目录的文件提取,最后执行相关操作。
案例分析:NEWstar-week4-洞OVO
首先,依据题目提供的信息,我们可以迅速定位到指定版本的 WinRAR。
该漏洞被确认为 WinRAR 6.22 版本中的远程代码执行漏洞,可通过 ZIP 压缩包触发(CVE-2023-38831)。
核心流程包括:文件选取 → 临时目录解压 → 执行操作。
基于此,题目所要求的函数段很可能与"临时目录解压"环节相关。
多数在线资料会提供以下代码片段作为参考:
C++
// 0x140089948
char __fastcall compare(WCHAR *click_name, WCHAR *deFileName, int a3)
...
if ( (_WORD)a3 )
{
v7 = -1i64;
tName_len = -1i64;
do
++tName_len;
while ( click_name[tName_len] );
if ( (unsigned int)(unsigned __int16)a3 - 2 > 2 )
{
if ( a3 >= 0 )
v9 = sub_1400AF168(click_name, deFileName);
else
v9 = wcsncmp(click_name, deFileName, tName_len);
if ( !v9 )
{
tail = deFileName[tName_len];
if ( tail == '\\' || tail == '/' || !tail )
return 1;
}
if ( v3 == 1 )
return 0;
}
...
}
然而,本题的解决方案并非如此直接。虽然上述代码接近正确答案,但通过向上追溯并结合 bindiff 工具的使用,我们能够识别出关键函数 sub_1400EF508
。
完整的函数调用链如下所示:
sub_1400EF508 → sub_140009290 → sub_14000A650 → sub_1400D6070 → 0x1400D6478 → 0x1400D3474 → 0x1400CEBF4 → 0x140077054 → 0x140089948
尽管预期的解决方案可能较为复杂,但本题还存在其他可行的解决途径。
额外说明:实际上,可疑函数的数量并不多,通过缩小范围后进行系统性尝试,通常能够较为迅速地得到正确答案。另一种策略性方法是:鉴于漏洞修复处新增了 3 条汇编指令,我们可以尝试提交所有在新旧版本对比中增加了 3 条汇编指令的位置,这种方法很可能会成功找到正确答案。
关于新版本修复漏洞
新版本增加了一些友好的筛查措施和逻辑调整,我们可以用BinDiff这个工具来看看几个关键函数的变化,就能轻松发现这些贴心的改进。
总结
本文详细介绍了BinDiff工具在RE中的应用,尤其是在漏洞分析方面的实用性。主要内容包括:
- BinDiff概述:解释了BinDiff的功能和用途,强调其在漏洞研究和代码分析中的重要性。
- 使用方法:讨论了版本兼容性问题、配置路径设置,以及基本操作流程,为读者提供了实用的使用指南。
- 实例分析:以WinRAR 6.22版本的远程代码执行漏洞(CVE-2023-38831)为例,展示了如何使用BinDiff定位和分析漏洞。
- 案例研究:通过"NEWstar-week4-洞OVO"题目,详细说明了如何运用BinDiff进行实际漏洞分析,包括函数定位和调用链分析。
- 漏洞修复分析:简要介绍了新版本中的漏洞修复方法,强调了使用BinDiff对比关键函数改动的重要性。
参考文章
https://paper.seebug.org/3036/#/