quarkslab/bolt-stackinit-scanner
GitHub: quarkslab/bolt-stackinit-scanner
基于 LLVM BOLT 的二进制静态分析 pass,用于检测 x86-64 二进制文件中未初始化的栈变量读取操作。
Stars: 0 | Forks: 0
# bolt-stackinit-scanner
(附带的[博客文章](https://blog.quarkslab.com/extending-llvms-bolt-based-binary-analyser-to-validate-stack-variable-initialisation.html)和[完整项目报告](https://github.com/quarkslab/public-reports/blob/main/Reports/26-04-2682-REP-OSTIF-LLVM-BOLT-validating-automatic-variable-initialisation-V1.1.pdf))
这是一个 BOLT pass,用于对 x86-64 二进制文件进行静态分析,以标记出无法证明已初始化的栈加载。它的开发旨在验证编译器的 `-ftrivial-auto-var-init` 标志,同时也可用作检测未初始化栈读取的通用工具。
扫描器首先会收集它检测到的所有栈加载和存储。对于每次加载,它会检查在每一条到达该加载操作的控制流路径上,读取的每一个字节是否都已被先前的存储操作写入。该检查首先在函数内部运行,随后通过分析被调用者和调用者跨越函数边界进行扩展。无法证明已初始化的加载将被输出为诊断报告;而无法精确建模的结构将被输出为限制报告。
该扫描器目前仍在开发中。有关理论基础、设计选择以及已知的局限性(主要是静态分析固有的局限),请参阅完整的项目报告。
## 仓库结构
本仓库“fork 了” [llvm/llvm-project](https://github.com/llvm/llvm-project)。
对上游文件的修改被保持在最低限度。可以使用以下命令列出更改内容,例如:
```
$ git diff master bolt-stackinit-scanner
```
### 关键文件
| 文件 | 用途 |
| ---- | ------- |
| [`bolt/include/bolt/Passes/StackInitScanner.h`](bolt/include/bolt/Passes/StackInitScanner.h), [`bolt/lib/Passes/StackInitScanner.cpp`](bolt/lib/Passes/StackInitScanner.cpp) | 主要实现 |
| [`bolt/test/binary-analysis/stackinit/`](bolt/test/binary-analysis/stackinit/) | 测试套件 |
## 构建
有关要求和完整说明,请参阅 [LLVM 的文档](https://llvm.org/docs/GettingStarted.html#getting-the-source-code-and-building-llvm)。
作为快速入门指南,请使用以下示例配置启用 BOLT 支持的 LLVM:
```
$ mkdir build.RELEASE
$ cmake -G Ninja -B build.RELEASE/ \
-DLLVM_ENABLE_PROJECTS="clang;lld;bolt" \
-DLLVM_TARGETS_TO_BUILD="X86;AArch64" \
-DBOLT_TARGETS_TO_BUILD="X86;AArch64" \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_OPTIMIZED_TABLEGEN=ON \
-DLLVM_PARALLEL_LINK_JOBS=4 \
-DLLVM_ENABLE_ASSERTIONS=ON \
llvm/
```
或者对于 debug 构建:
```
$ mkdir build.DEBUG
$ cmake -G Ninja -B build.DEBUG/ \
-DLLVM_ENABLE_PROJECTS="clang;lld;bolt" \
-DLLVM_TARGETS_TO_BUILD="X86;AArch64" \
-DBOLT_TARGETS_TO_BUILD="X86;AArch64" \
-DCMAKE_BUILD_TYPE=Debug \
-DLLVM_OPTIMIZED_TABLEGEN=ON \
-DLLVM_PARALLEL_LINK_JOBS=4 \
-DLLVM_ENABLE_ASSERTIONS=ON \
-DLLVM_ENABLE_DUMP=ON \
llvm/
```
然后,使用以下命令进行构建:
```
$ ninja -C build.RELEASE/
```
## 运行
通常按如下方式调用扫描器:
```
$ build.RELEASE/bin/llvm-bolt-binary-analysis \
-scanners=stackinit \
-allow-stripped \
-experimental-shrink-wrapping \
-assume-abi \
-log-loads-stores \
/path/to/target/binary
```
若要进行 debug 运行,还需包含 `-debug-only=bolt-stackinit-scanner -no-threads`。
## 测试
可以使用以下命令运行扫描器的测试套件:
```
$ build.RELEASE/bin/llvm-lit -vva bolt/test/binary-analysis/stackinit/
```
以下小节尝试按复杂度对大多数测试进行分组,以便于初学者了解该主题。
### 基础测试
直接加载与存储、简单的跨基本块模式、块初始化模式以及分类类别。
| 文件 | 描述 |
| ---- | ----------- |
| [`alignment-pops.s`](bolt/test/binary-analysis/stackinit/X86/alignment-pops.s) | 退出基本块出栈的对齐出栈分类类别 |
| [`basic.s`](bolt/test/binary-analysis/stackinit/X86/basic.s) | 单基本块存储/加载顺序,帧指针与 RSP 的对比,XMM 存储 |
| [`byte-coverage.s`](bolt/test/binary-analysis/stackinit/X86/byte-coverage.s) | 加载范围内的每一个字节都必须被存储覆盖 |
| [`interproc-basic.s`](bolt/test/binary-analysis/stackinit/X86/interproc-basic.s) | 基础的被调用者端跨过程:被调用者初始化、晚期调用、其中一条路径缺失初始化的菱形控制流图、存储大小不匹配 |
| [`lods-stos.s`](bolt/test/binary-analysis/stackinit/X86/lods-stos.s) | `lods` 加载和 `stos` 存储检测 |
| [`memset.s`](bolt/test/binary-analysis/stackinit/X86/memset.s) | `memset` 块初始化模式检测 |
| [`noreturn.s`](bolt/test/binary-analysis/stackinit/X86/noreturn.s) | 不返回的调用传播、包装器检测、glibc `error()` 识别 |
| [`possibly-unused-pop.s`](bolt/test/binary-analysis/stackinit/X86/possibly-unused-pop.s) | 针对向已死亡的调用者保存寄存器执行出栈操作的死寄存器出栈分类类别 |
| [`redundant-loads.s`](bolt/test/binary-analysis/stackinit/X86/redundant-loads.s) | 冗余加载优化:同基本块、跨基本块、菱形控制流图 |
| [`rep-stos.s`](bolt/test/binary-analysis/stackinit/X86/rep-stos.s) | `rep stos` 块初始化模式检测 |
| [`stack-clash-probe.s`](bolt/test/binary-analysis/stackinit/X86/stack-clash-probe.s) | Stack Clash 探测分类类别 |
| [`unsatisfied-dedup.s`](bolt/test/binary-analysis/stackinit/X86/unsatisfied-dedup.s) | 诊断报告去重:同基本块、跨基本块、不同范围 |
### 中级测试
派生访问、条件路径、指向栈的指针解析以及跨过程边缘情况。
| 文件 | 描述 |
| ---- | ----------- |
| [`cmov.s`](bolt/test/binary-analysis/stackinit/X86/cmov.s) | `cmov` 派生加载:无路径/单路径/双路径栈源、循环、被忽略的情况、内存源操作数 |
| [`cmov-interproc.s`](bolt/test/binary-analysis/stackinit/X86/cmov-interproc.s) | 跨过程的保存/恢复、达成一致的 `cmov` 路径、非栈 `cmov` 操作数(限制)、不一致的 `cmov` 偏移量(限制) |
| [`derived-arithm.s`](bolt/test/binary-analysis/stackinit/X86/derived-arithm.s) | 位于派生寄存器链中间的 MOV/ADD/SUB/INC/DEC |
| [`derived-basic.s`](bolt/test/binary-analysis/stackinit/X86/derived-basic.s) | 简单派生加载:LEA/MOV 传播、偏移量 |
| [`derived-conditional.s`](bolt/test/binary-analysis/stackinit/X86/derived-conditional.s) | 条件路径、多基本块寄存器链、2 跳路径约束 |
| [`derived-dedup.s`](bolt/test/binary-analysis/stackinit/X86/derived-dedup.s) | 重复的派生访问、路径约束去重 |
| [`derived-indexed.s`](bolt/test/binary-analysis/stackinit/X86/derived-indexed.s) | 索引加载与存储:已知常量索引寄存器、不支持的透明索引 |
| [`derived-pointer.s`](bolt/test/binary-analysis/stackinit/X86/derived-pointer.s) | 指向栈的指针解析 |
| [`derived-stores.s`](bolt/test/binary-analysis/stackinit/X86/derived-stores.s) | 派生存储:菱形控制流图、偏移量跟踪 |
| [`interproc-arg-forwarding.s`](bolt/test/binary-analysis/stackinit/X86/interproc-arg-forwarding.s) | 通过寄存器重排进行的跨过程参数转发 |
| [`interproc-callee-init.s`](bolt/test/binary-analysis/stackinit/X86/interproc-callee-init.s) | 被调用者初始化正确性:参数寄存器覆盖、部分初始化(单退出和多退出被调用者)、全路径初始化 |
| [`interproc-csr.s`](bolt/test/binary-analysis/stackinit/X86/interproc-csr.s) | 被调用者保存寄存器保护 (`-assume-abi`) |
| [`interproc-derived.s`](bolt/test/binary-analysis/stackinit/X86/interproc-derived.s) | 带有派生寄存器的跨过程(拷贝+偏移量、ADD、LEA) |
| [`interproc-multilevel.s`](bolt/test/binary-analysis/stackinit/X86/interproc-multilevel.s) | 不同深度的跨过程调用链 |
| [`interproc-recursion.s`](bolt/test/binary-analysis/stackinit/X86/interproc-recursion.s) | 跨过程的自递归调用 |
| [`interproc-tailcall.s`](bolt/test/binary-analysis/stackinit/X86/interproc-tailcall.s) | 通过尾调用进行的跨过程调用链 |
| [`loops.s`](bolt/test/binary-analysis/stackinit/X86/loops.s) | 已初始化和未初始化的循环计数器、跨基本块直接加载 |
| [`stackargs.s`](bolt/test/binary-analysis/stackinit/X86/stackargs.s) | 调用者栈帧加载(超出函数栈帧的加载) |
### 高级测试
路径约束机制、禁止基本块、存储消除以及其他边缘情况。
| 文件 | 描述 |
| ---- | ----------- |
| [`constraint-path.s`](bolt/test/binary-analysis/stackinit/X86/constraint-path.s) | 具有未满足路径约束的卡住路径、在相同基本块但不同约束进度下的不同访问状态 |
| [`derived-edge-cases.s`](bolt/test/binary-analysis/stackinit/X86/derived-edge-cases.s) | 循环寄存器定义、与路径约束交互的不可达前驱、外部调用切断 RBP 的到达定义链 |
| [`derived-loop-carried.s`](bolt/test/binary-analysis/stackinit/X86/derived-loop-carried.s) | 循环携带的寄存器更新:在同一路径上具有不同范围的多次访问 |
| [`forbidden-bb.s`](bolt/test/binary-analysis/stackinit/X86/forbidden-bb.s) | 禁止基本块:单跳和 2 跳寄存器链场景 |
| [`path-conflict.s`](bolt/test/binary-analysis/stackinit/X86/path-conflict.s) | 指向栈的指针解析中的基本块内和跨基本块被消除的存储 |
标签:Bash脚本, C/C++, LLVM/BOLT, 事务性I/O, 二进制分析, 云安全监控, 云安全运维, 代码安全审计, 编译器工具链, 静态分析