使用多级中间表示和VAST在C代码中查找漏洞
中间表示(IR)是逆向工程师和漏洞研究人员用来"见树又见林"的工具。IR让我们能在不同抽象层级上观察程序,从而既能分析底层代码异常,也能发现高层逻辑错误。但现有工具的局限在于它们通常被限制在特定IR层级,而漏洞可能存在于任何抽象层级。
我们开发的VAST工具通过提供"IR抽象塔"解决了这个问题,允许分析从最适合的IR层级开始,然后根据需要上下切换。例如分析栈缓冲区溢出时:(1)在LLVM IR层级最容易识别;(2)在接近机器代码层级才能判断是否构成安全威胁;(3)在最高级的AST层级才能理解根本原因。
VAST的中间表示抽象塔
VAST的核心是基于MLIR(多级中间表示)项目构建的。MLIR是LLVM相关的基础设施项目,它通过"方言"(dialects)机制支持领域特定语言和IR的开发。VAST使用MLIR为C/C++编译过程的每个抽象层级构建对应的方言,形成从AST到LLVM IR的完整IR塔。
编写基于VAST的漏洞检测器
我们以Linux内核中的Sequoia漏洞为例,演示如何编写检测器。该漏洞的变体出现在seq_buf_path
函数中,它将无符号的size_t
值传递给d_path
函数的有符号int
参数,可能导致整数溢出。
- 首先设置基本的MLIR分析环境,注册必要的方言和检测Pass:
1
2
3
4
5
6
7
|
auto main(int argc, char** argv) -> int {
mlir::DialectRegistry registry;
vast::registerAllDialects(registry);
register_sequoia_checker_pass();
return mlir::failed(
mlir::MlirOptMain(argc, argv, "VAST Sequoia Bug Checker\n", registry));
}
|
- 实现检测Pass的核心逻辑,主要检查:
- 是否存在无符号到有符号的类型转换
- 转换后的参数是否用于指针运算
1
2
3
4
5
6
7
8
9
|
auto is_unsigned_to_signed_cast(mlir::Operation* opr) -> bool {
// 检查是否为整型转换且从无符号转为有符号
...
}
static auto has_ptr_arith_use(mlir::Operation* opr) -> bool {
// 递归检查是否用于指针运算
...
}
|
- 在函数操作中遍历所有调用点应用检测逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
|
void runOnOperation() override {
auto check_for_sequoia = [&](CallOp call) {
for (const auto& arg : llvm::enumerate(call.getArgOperands())) {
if (is_unsigned_to_signed_cast(arg.value().getDefiningOp())) {
if (llvm::any_of(param.getUsers(), has_ptr_arith_use)) {
// 报告漏洞
}
}
}
};
getOperation().walk(check_for_sequoia);
}
|
运行检测器成功发现了示例代码中的漏洞:
1
|
Call to `d_path` in `seq_buf_path` passes an unsigned value to a signed argument...
|
在各级抽象中寻找漏洞
不同漏洞在不同抽象层级更容易被发现。VAST让工具开发者可以选择最适合的IR层级进行定制化分析。我们展示了如何利用VAST的高层(hl)方言检测Sequoia漏洞,这种方法也可应用于其他逆向工程和漏洞研究项目。