GREASE:一款用于发现二进制代码中隐藏漏洞的开源工具
介绍 GREASE:一款用于发现二进制代码中隐藏漏洞的开源工具
Langston Barrett, Ryan Scott, Ben Davis, and Matt Bauer
2025 年 3 月 19 日
主动且防御性地确保二进制代码中不存在漏洞,对于部署高保证系统至关重要。GREASE 是一款开源工具,利用欠约束符号执行来帮助软件逆向工程师分析二进制文件并发现难以发现的错误,最终增强系统安全性。这种二进制分析对于包含仅以二进制形式提供的 COTS 软件的系统尤其重要。
GREASE 可以作为 Ghidra 逆向工程框架的插件使用,也可以作为独立的命令行工具或 Haskell 库使用。GREASE 支持分析 AArch32、PPC32、PPC64 和 x86_64 Linux ELF 二进制文件以及 LLVM bitcode。
演示
GREASE 可以帮助软件逆向工程师发现二进制文件中的错误。例如,考虑以下代码,该代码源自 libpng,演示了 CVE-2018-13785。即使在源代码级别,该错误也很难发现。你能看到它吗? (不必担心详细研究代码,这对于理解本文的其余部分不是必需的。)
void/* PRIVATE */png_check_chunk_length(png_const_structrp png_ptr, const unsigned int length){
png_alloc_size_t limit = PNG_UINT_31_MAX;
# ifdef PNG_SET_USER_LIMITS_SUPPORTED
if (png_ptr->user_chunk_malloc_max > 0 &&
png_ptr->user_chunk_malloc_max < limit)
limit = png_ptr->user_chunk_malloc_max;
# elif PNG_USER_CHUNK_MALLOC_MAX > 0if (PNG_USER_CHUNK_MALLOC_MAX < limit)
limit = PNG_USER_CHUNK_MALLOC_MAX;
# endif
if (png_ptr->chunk_name == png_IDAT)
{
png_alloc_size_t idat_limit = PNG_UINT_31_MAX;
size_t row_factor =
(png_ptr->width * png_ptr->channels * (png_ptr->bit_depth > 8? 2: 1)
+ 1 + (png_ptr->interlaced? 6: 0));
if (png_ptr->height > PNG_UINT_32_MAX/row_factor)
idat_limit=PNG_UINT_31_MAX;
else idat_limit = png_ptr->height * row_factor;
row_factor = row_factor > 32566? 32566 : row_factor;
idat_limit += 6 + 5*(idat_limit/row_factor+1); /* zlib+deflate overhead */ idat_limit=idat_limit < PNG_UINT_31_MAX? idat_limit : PNG_UINT_31_MAX;
limit = limit < idat_limit? idat_limit : limit;
}
// ...}
GREASE 可以自动找到这个难以发现的错误:
$ clang test.c -o test
$ grease test
Finished analyzing 'png_check_chunk_length'. Possible bug(s):
At 0x100011bd:
div: denominator was zero
Concretized arguments:
rcx: 0000000000000000rdx: 0000000000000000rsi: 0000000000000000rdi: 000000+0000000000000000r8: 0000000000000000r9: 0000000000000000r10: 0000000000000000
000000: 5441444901000000 f9 ff ff ff 000000000080
此输出表明,当寄存器 rdi
保存指向包含字节 54 41 44
的分配的指针时,png_check_chunk_length
将除以零。实际上,如果我们添加以下 main 函数:
int main() {
char data[] = {0x54, 0x41, 0x44, 0x49, 0xf9, 0x00, 0x00, 0x00, 0x01, 0xb7, 0x3e, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80};
png_check_chunk_length((png_const_structrp)data, 0);
return0;
}
我们看到 GREASE 所描述的:
$ clang test.c -o test
$ ./test
Floating point exception (core dumped)
工作原理
从根本上讲,GREASE 的工作方式与 UC-Crux 非常相似,UC-Crux 是我们用于 LLVM 的欠约束符号执行工具。本质上,GREASE 通过在一组完全符号化的寄存器上运行目标二进制文件中的每个函数来分析该函数。如果发生错误(例如,如果程序从未初始化的内存中读取数据),GREASE 会使用启发式方法来改进此初始符号化前提条件(例如,通过初始化一些内存)并重新运行目标函数。此过程将继续,直到 GREASE 发现错误或得出结论:该函数在其输入的某些合理前提条件下是安全的。介绍 UC-Crux 的博客文章 详细描述了此算法。 GREASE 文档 中也提供了更多信息。
与上面来自 libpng 的示例相比,GREASE 的启发式方法 不会 将以下程序标记为可能存在问题。
$ cat test.c
int test(int *x) { return *x + 1; }
$ clang test.c -o test
$ grease test
– snip –
All goals passed!
如果我们向 GREASE 询问更多详细信息,我们可以看到它推断出 rdi
必须指向(至少)四个已初始化的字节。 启发式方法认为这是测试函数的一个合理前提条件。
$ grease test -v
rip: 0000000000401010– snip –
rdi: 000007+0000000000000000– snip –
000007: XX XX XX XX
(在上面的输出中,XX
表示初始化为符号值的内存字节。GREASE 输出中使用的语言的语法在文档中中有详细说明。)
局限性
GREASE 有几个关键的局限性。最重要的是,GREASE 依赖于启发式方法来确定是否有故障的内存访问应报告为错误。这些启发式方法可能会导致误报(将正常的程序行为报告为可疑)或漏报(遗漏真实错误)。
像大多数程序分析工具一样,GREASE 也受到大量约束和警告的限制。GREASE 受 路径爆炸 的影响。GREASE 的内存模型无法表达无界堆数据结构(例如,符号长度的链表)。与其他基于符号执行的工具一样,GREASE 会展开循环和递归(可以选择最多达到最大限制),并且当循环条件保持符号化时可能会卡住。GREASE 无法分析机器代码的某些病态行为,例如运行时代码生成 (JIT)、自修改代码或跳转到指令的“中间”。文档中可以找到对 GREASE 局限性的更完整说明。
与其他工具的比较
GREASE 在二进制分析、符号执行和软件逆向工程工具领域中处于什么位置?
- 诸如模糊测试器(例如,AFL)之类的具体测试工具具有报告导致崩溃的完整输入的优势。但是,它们更容易受到路径爆炸的影响(也就是说,它们可能难以到达目标内部深处的代码),并且只能测试程序可能输入的极小一部分。通过使用符号执行,GREASE 可以有效地覆盖更多的输入空间。
- angr 是一个用于二进制文件符号执行的工具包。angr 比 GREASE 成熟得多,并且适用于更广泛的用例。angr 首先是一个 Python API,逆向工程师可以使用它来构建工具,而 GREASE 提供了随时可用的 CLI。angr 默认执行传统的符号执行,而 GREASE 执行欠约束的符号执行。像模糊测试一样,由于路径爆炸,传统的符号执行可能难以到达程序内部深处的代码。angr 会积极地具体化指针,而 GREASE 的内存模型能够进行复杂的符号内存操作,例如涉及符号指针和符号大小的读取和写入。
- KLEE 是一个广泛使用的 LLVM 符号执行工具。与 KLEE 相比,GREASE 除了支持 LLVM 之外,还支持二进制文件分析,并执行欠约束的符号执行。
- UC-KLEE 是一种用于 LLVM 的欠约束符号执行工具,在“欠约束符号执行:真实代码的正确性检查”中进行了描述。据我们所知,它从未开源。与 UC-KLEE 相比,GREASE 除了支持 LLVM 之外,还支持二进制文件分析。UC-KLEE 使用了惰性内存模型,而 GREASE 的欠约束符号执行方法本质上更具迭代性。
- UC-Crux 是我们之前用于 LLVM 的欠约束符号执行工具。不幸的是,我们在 UC-Crux 的设计中犯了两个根本性错误:(1)它的核心专门用于 LLVM 分析,限制了其对二进制文件的潜在适用性,以及(2)它的分析利用了 LLVM 指针的类型。 LLVM 已经逐步淘汰了此类类型,转而使用“不透明指针”类型 ptr,因此很难使 UC-Crux 在最新版本的 LLVM 上运行良好。我们计划弃用 UC-Crux 以支持 GREASE。
- Macaw 是一个类似于 angr 的二进制分析框架,在预印本“Macaw:繁忙的二进制分析师的机器代码工具箱”中进行了描述。 GREASE 建立在 Macaw 之上。
- GREASE 可以作为插件集成到二进制分析平台中,例如 Ghidra 和 Binary Ninja。
结论
我们很高兴以 BSD 3-clause 许可证与二进制分析研究社区分享 GREASE。GREASE 正在积极开发中。欢迎提出请求、问题和疑问!请联系 grease@galois.com 以开始对话。
本材料基于国防高级研究计划局在合同编号 W31P4Q-22-C-0017 和 W31P4Q-23-C-0020 下支持的工作。 更深入: 软件与系统分析