Page 18 - 《软件学报》2024年第6期
P. 18
2594 软件学报 2024 年第 35 卷第 6 期
• 轻量快速. 该工具应该尽可能轻量化, 可以以较快的速度分析出结果, 提升效率.
• 适用性强. 该工具应该尽可能独立于 Go 的编译工具链, 可以支持不同 Go 版本编译器生成的二进制, 支持在
未修改编译运行时的原生 Go 二进制上进行检测.
• 低误漏报. 有效降低误报率可以大大减少人工筛选漏洞的时间. 同时, 减少漏报率可以确保该工具可以有效
发现潜在的漏洞.
为达到上述设计目标, 对 DBI-Go 的设计的主要思路是结合 Go 语言的特性来快速原型化. 整个设计中的关键
主要聚集于两点: (1) 程序分析方式的选择; (2) 如何结合并利用第 2 节抽象并总结出的违例判定规则.
• 分析方式的选择. 目前学术界已经开发了多种工具 [17,18,33,34] 来识别 Go 应用程序中的各类漏洞, 主要使用两
种分析技术——静态分析和动态分析. 静态分析无需执行即可分析 Go 源代码. 然而, 静态分析在识别方面的覆盖
范围有限. 此外, 由于不精确的指针分析, 静态分析可能会引入许多误报或漏报. 当在实际的 Go 应用程序中进行
大范围分析时, 这种不精确性会迅速累积. 因此, 单纯的静态分析难以满足 DBI-Go 的低误漏报的设计目标.
现有的动态分析通过额外的运行时信息监视 Go 程序的执行, 可以减少误报和漏报. 但是这些动态方法需要
修改 Go 编译器或运行时来收集必要的数据, 与 Go 的编译运行时强耦合. 当 Go 的编译运行时发生改变时, 这些
插桩信息
动态方法很容易失效. 这种要求很大程度上阻碍了这些工具在实际生产环境中的 Go 应用上的使用, 也不符合 DBI-Go
的适用性强的设计目标.
在 Go 应用程序的二进制代码上进行动态分析可以摆脱这些问题, 既可以在运行时监视 Go 程序的执行, 又无
需修改 Go 的编译运行时. 因此, DBI-Go 以动态二进制插桩作为主要的分析方式.
• 如何利用规则 1 和规则 2. 确定了程序分析方式之后, 就可以着手设计 DBI-Go. 第 2 节抽象总结的规则 1 和
规则 2 仅提供了判定的理论. 若要使用规则 1 和规则 2, 通过观察其形式, 不难发现, 在二进制代码上使用该规则
必须考虑以下两个关键问题.
(1) 如何识别存储指针的指令 store addr dst , addr: 这类指令改变内存之间的引用关系, 潜在可能违背逃逸不变式.
(2) 如何获取 Go 运行时 Goroutine 的栈信息: 由第 1.1.1 节可知, Go 程序执行期间 Goroutine 的栈地址区间不
是一成不变的, 因此需要有途径准确获取当前时刻的栈信息.
这两个关键点也是程序分析的难点. 第 3.2 节将介绍使用静态分析识别写入指针的 store 指令的挑战以及我
们的解决方案. 第 3.3 节将介绍如何从较为封闭的 Go 运行时中获取 Go 程序运行时 Goroutine 的栈信息.
DBI-Go 的整体架构如图 6 所示. DBI-Go 主要由两个组件组成.
规则1: 栈->栈外
Go ABI规范 规则2: 浅栈->深栈
先验知识 先验知识
StoreFinder bb1 cmp1 Go运行时栈 Go运行时 运行时
Store指针的 bb2 bb3 信息识别 栈信息 验证器
控制流识别 store ca11
bb4
回调函数的插桩 Log
函数、指令流 程序运行信息 验证信息
整理 文件
反汇编信息 运行时 输出
及符号信息 插桩信息 信息 StoreValidator
输入 程序
Go可执 反汇编API 开始执行 程序运行时信息API Pin
行程序 Pin 插桩API
图 6 DBI-Go 整体架构
(1) StoreFinder: 它使用静态分析提取 Go 二进制程序中存入指针的 store 指令, 并在二进制代码上插桩 (第
3.2 节).
(2) StoreValidator: 它以运行时回调函数的形式识别违反 Go 逃逸不变式的内存引用漏洞, 并输出错误日志信