编译拾遗(二):揭秘污点追踪的困扰与对策

admin 2024年1月26日22:37:07评论16 views字数 6676阅读22分15秒阅读模式

编译拾遗(二):揭秘污点追踪的困扰与对策

编译拾遗(二):揭秘污点追踪的困扰与对策

前言

编译拾遗(一):代码静态行为分析中,我们介绍了基本的静态代码行为分析的思路。在文章中抛出了很多很有难度的技术话题,得到了很多用户比较正面的反馈。在和用户读者交流的过程中,“污点追踪”这个词被反复提及,包括我们团队内部的技术同学在 PoC 的时候,也编写了一个“Demo”去实现“污点追踪”的效果。

YAK

污点追踪:关注的是指定的“污点”数据从输入源流动到程序中可能危险的操作中的路径。在安全领域,污点追踪主要用于潜在的安全漏洞,如数据泄露或者注入攻击。

注意:污点分析是只有“安全领域”的概念,实际在编译领域并没有一个叫污点分析的概念。与之对应的过程应该叫“数据流分析”或者“变量支配分析”。

YAK

甚至特别有意思的是,做安全的同学大多数并不了解“污点分析”背后的基本计算机科学逻辑,很多安全领域的论文都把它当成研究课题,这其实是不合适的。

基本概念

编译拾遗(二):揭秘污点追踪的困扰与对策

我们使用一段伪代码来描述污点追踪的问题和挑战:

s = file.ReadFile("test.txt")~w = i => {    os.System(f"bash ${i}")}if e {    w(s)}

这段代码非常简单,那么我们总结一下这个过程:

编译拾遗(二):揭秘污点追踪的困扰与对策

要对这一段代码进行分析,需要思考如下几个问题:

1.上面的代码是伪代码,那么不完整代码如何分析行为?

2.函数过程间分析,我们应该怎么实现函数跳转?过程间分析带来的挑战有哪些?

3.IF 如何处理?每个分支都要步入吗?

4.输入从 file.ReadFile 中读取,那么我们应该从 ReadFile 开始分析吗?

在本篇文章中,我们尝试会对上面几个问题都有一个明确的解答,大家可以认真阅读,最后思考一下看看和自己想象的静态行为分析到底有没有差别。

从污点分析的角度看,这里存在两个重要概念:源(Source)和汇(Sink)。源是指数据可进入的地方,这里,源是 "ReadFile" 函数,用于读取外部用户可能控制的数据。汇,是指数据可以影响的地方,在这段代码中是 "System" 函数,它执行 bash 命令。

我们可以看到源(Source)是 ReadFile("test.txt"),这个读取的文件内容被赋值给了 s,然后 s 被传递给函数 w,最后作为命令参数在 System 这个汇(Sink)里执行。所以从源到汇形成了一个可能的污点传播路径。

污点追踪的“最大缺陷”

编译拾遗(二):揭秘污点追踪的困扰与对策

当我们明白了基本概念之后,本节内容将指出“污点追踪”这种思考逻辑的一个缺陷:思维方向固定是从 ReadFile(),但是我们有时候不能去观察所有的文件 IO 部分。如果读取了一个文件,并在内存中处理的链路非常长,那么分析过程将会无比痛苦。

很多时候,作为一个“人”,我们的思维实际上并不是“污点追踪”,而是“逆向污点追踪”,人去搜索源码,搜索到所有执行命令的地方,然后观察执行命令的地方在哪儿被使用了,一层一层向上追踪,观察输入的部分能不能控制执行命令。我们惊奇的发现,大多数人居然更愿意接受“逆向污点追踪”思考方式,毕竟面对动辄十几万行的源码,谁能从头说得清参数消亡在哪里了呢?

编译拾遗(二):揭秘污点追踪的困扰与对策

从安全代码审计引擎来说,“逆向污点追踪”一直是一个大家不愿意聊的话题,因为相对于正向思考的逻辑,逆向追踪的技术对 AST 分析太不友好了

如果代码审计系统的研发水平卡在 AST 的层面的话,注定了“逆向污点追踪”一定是一个非常痛苦的过程。但是既然有这篇文章,我们肯定还是会提出相应的对策和正确的解答。

编译视角下的正逆向污点追踪

编译拾遗(二):揭秘污点追踪的困扰与对策

现在,我们忘掉我们是一个安全工程师的大背景,污点追踪这个话题,本质上是数据流追踪。熟悉编译拾遗(一):代码静态行为分析内容的同学,可以很容易理解到“UD/DU链”这个层面,我们可以使用UD和DU链分析技术去追踪数据流。

  1. Use-Def链分析一般描述的是,从使用到定义的分析技术,这是一种“支配方向的向上分析”的技术。

  2. Def-Use链分析一般描述的是,从定义到使用到分析技术,一个变量在哪儿产生,最后消亡在哪里了,对应的就是“向下分析技术”。

我们解释到这里,我想读者已经明白为什么我们在前篇花了大量篇幅去解释基于Use-Def链和Def-Use链的静态分析技术和基本方法了。

编译拾遗(二):揭秘污点追踪的困扰与对策

向上分析经典案例

我们可以构造一个非常经典的案例,来展示“向上分析”“过程间分析”的惊人效果。针对如下代码,分析 f 的值应该取决于谁?或者说,f被谁支配?

a = 1b = (c, d, e) => {    a = c + d    return d, c}f = b(2,3,4); dump(f)

这段代码非常容易理解,我们通过人脑简单观察发现,f的值应该是[3,2]。代码段中出现了1,2,3,4四个值,我们的分析目标是应该是,f,那么,f 和 1,4是无关的,我们在程序分析结果中不应该包含14

我们使用 Yaklang SSA API 进行分析,通过UD关系,得到一个图:

strict digraph {  rankdir = "BT";  n0 [label="t9: f=main$1(t6,t7,t8)"]  n1 [label="main$1"]  n2 [label="t7: 3"]  n3 [label="t6: 2"]  n5 [label="c"]  n6 [label="d"]  n0 -> n3 [label=""]  n5 -> n3 [label=""]  n1 -> n3 [label=""]  n1 -> n5 [label=""]  n0 -> n1 [label=""]  n1 -> n6 [label=""]  n0 -> n2 [label=""]  n6 -> n2 [label=""]  n1 -> n2 [label=""]}

上图渲染之后为:

编译拾遗(二):揭秘污点追踪的困扰与对策

YAK

注意:这个图是程序生成的,并不是手写节点绘制的,展示支配关系大多数遵循 SSA 格式:全局唯一符号跨越过程,可以通过 b: main$1 进入。这个支配关系核心表示,f=main$1(t6,t7,t8) 中 f 的核心支配链,也可以认为是 SSA 中各项 Use-Def 链的整合,而不是 call main$1 指令的核心支配。

我们发现,如果要得出正确的结论,不去进行“过程间分析”是不可能的。如果我们不进入b函数,那么我们就会认为,2,3,4都是b的参数,都会支配f。这显然是不可以接受的。那么我们如何解决这个问题?

YAK

过程间分析,顾名思义,就是跨越单个过程(或函数,方法等)的边界进行分析的技术。它是编译器优化和程序理解的重要工具,可以帮助识别程序中跨越函数或过程边界的数据流和控制流。

相对于只在单个过程内进行分析的技术,如数据流分析或控制流分析,过程间分析可以提供更全局的视角,从而可能带来更深度的优化和更精确的程序行为理解。然而,过程间分析的难度也相对更大,需要处理更多的复杂性,例如函数指针,递归调用,动态分派等问题。

我们需要跨越b函数内部,并且还是从“返回值进入”,并且从“形式参数”穿越出来,才能确定结果到底是2,3还是2,3,4。那么我们具体的分析过程是什么,因为大家对 SSA IR 的熟悉程度有限,我们以 AST 为视角介绍这个过程:

如果你的目标是 AST 的话,首先,你需要知道 b 对应的 AST 的结构是什么,找到他的 RETURN 语句,RETURN 对应的变量分别为 b, c,我们分别分析 bc的用法,发现,a = c + bf几乎没有啥关联,跳过,c,d最终是通过形参传入的,那么就应该去b 对应的 (2,3,4)c,d 的位置2,3了,分析到常量了(Terminal Node)意味着已经没有再向寻找支配的必要了。

一般来说跨过程的 AST 需要能识别函数在 AST 中是在哪里定义的,AST 中的函数本身也十分复杂,比如说 lambda / anonymous 函数,标准函数,闭包函数,甚至每一个语言的 AST 对函数的定义都不一样,如果基于 AST 去分析过程间数据流,就需要多语言,多过程均支持。

听到这个过程,可能你已经不是特别想去操作 AST 了,摸清楚一个语言的 AST 的分析过程都十分痛苦,更不用说实现一个通用编译器过程,并在过程中追踪数据流了。

YAK

注:分析AST不是说没有办法追踪支配关系,而是AST注重高级封装和过程,有多少种类型的AST节点,就需要针对多少种节点进行分析策略,而且需要AST本身做好“正向”和“逆向”关联。这些额外工作,注定了AST不具备普适性和工程价值,这也是大多数SAST方案的死亡之路。

难题对策:过程间分析

编译拾遗(二):揭秘污点追踪的困扰与对策

过程间分析经过我们最近的探索,实际上它并不适合 AST 视角去做,具体的原因我们在上节末尾有提到。实际我们更适合分析“指令集”的“跨过程”。

IR 如果不熟悉的话,我们可以以汇编举例子:函数参数压栈跳转实际上对应需要进行两个分析操作:

  1. 压栈的指令需要记下来,因为他们会弹出之后作为参数使用。

  2. 最后计算完成,执行完指令之后,返回值再压栈,跳回原位置,处理栈中返回值。

YAK

如果汇编这个例子和AST都没法理解过程间分析指的是什么,那可能说明你现在还不具备探讨“过程间分析”的基础知识,需要去补充一下这方面的基础知识。

最重要的是,“指令”级别的过程间分析基本只有一种形式,他的形参传递方式非常单一;同样的“返回值”的传递方式也十分单一。

我们使用“类汇编”的指令函数执行过程描述过程间分析,方便用户可以直观理解“指令函数间”和“AST函数间”分析的两个区别。当然我们知道这两个有区别,但是不一定必须使用汇编级过程间分析技术,因为这显然也并不是一个好分析方向,因为寄存器对数据流分析的干扰实在有点大。

如果我们可以有一种产物,可以同时兼具 AST 的“易理解”的优势,又同时具备“指令”的线性逻辑和过程间形式简单,那就可以提出通用解决方案来解决“过程间分析”的老大难题。当然,这个产物就是 SSA IR,他可以既保持中间产物的单一流向(不必受重复值干扰),同时也能把上层各式各样的 AST 抽象成同一种过程间转换逻辑。

编译拾遗(二):揭秘污点追踪的困扰与对策

重新审视过程间分析案例

a = 1b = (c, d, e) => {    a = c + d    return d, c}f = b(2,3,4)

经过我们上面的提示,对 IR 进行过程间分析实际上是正途,那么 IR 具体长什么样子呢?

main type: ( ) -> nullentry-0: (true)        <any> t10 = undefined-dump        <[]any> t9: f = call <(any,any,any ) -> []any> main$1<b> (<number> 2, <number> 3, <number> 4) []        ......        ......        <any> t11: _ = call <any> t10: dump (<[]any> t9(f)) []extern type:extern Value:main$1 <any> c, <any> d, <any> eparent: mainsideEffects: atype: (any,any,any ) -> []anyentry-0: (true)        <any> t4 = <any> c add <any> d        ret <any> d, <any> cextern type:extern Value:Values: 1        0:  Call: main$1(2,3,4)

在上述 Yaklang SSA HIR 指令集中,我们删除了一些干扰项,可以做如下解释,main$1指的是b函数,真正主程序入口只有三个相关指令:

1.声明一个 undefined dump <any>: t10 = undefined-dump

2.函数调用:f = call(2,3,4)编译为:t9(f) = call main$1(b) (2,3,4)

3.函数调用:dump

实际上,我们只从第二个指令分析,进入main$1后直接跟随d,c即可找到参数。我们只分析这个指令,完全不关心这个顶层语言是谁,因为在前置的编译过程中,我们已经实现了 AST 到 HIR 的编译。

并且我们这么去做过程间分析,只需要处理一种过程跳转,并且指令也相对不受寄存器干扰,非常简单易懂并且振奋人心。

编译拾遗(二):揭秘污点追踪的困扰与对策

过程间分析的工程化技巧

上述的过程实际不太依靠“人脑”,是完全可以编程实现这个分析过程的,因此我们可以编写一个可以进入 CI 的测试案例,在过程间分析技术迭代过程中,这个测试案例能运行通过,即可以认为我们这个过程间分析的基本技术是具备的,并且能得到一个比较好的效果:

func TestFunctionTrace_FormalParametersCheck_2(t *testing.T) {    prog, err := Parse(`a = 1b = (c, d, e) => {    a = c + d    return d, c}f = b(2,3,4);dump(f)`)    if err != nil {       t.Fatal(err)    }    prog.Show()    check2 := false    check3 := false    noCheck4 := true    prog.Ref("f").Show().ForEach(func(value *Value) {       value.GetTopDefs().ForEach(func(value *Value) {          d := value.Dot()          _ = d          value.ShowDot()          if value.IsConstInst() {             if value.GetConstValue() == 2 {                check2 = true             }             if value.GetConstValue() == 3 {                check3 = true             }             if value.GetConstValue() == 4 {                noCheck4 = false             }          }       })    })    if !noCheck4 {       t.Fatal("literal 4 should not be traced")    }    if !check2 {       t.Fatal("the literal 2 trace failed")    }    if !check3 {       t.Fatal("the literal 3 trace failed")    }}

这个案例中,我们会对 f进行顶级定义的追踪,如果追踪过程中,发现缺少 2,3字面量,说明基本分析流程失效,如果发现分析结果包含4说明过程间分析失效。

我们可以用同样的技术,构建很多的代码段(代码案例):MVP,然后这些 MVP 必须明确审计出正确的结果,以证明我们的分析技术实际上都生效了,并且可以追踪到特殊的情况。

当然,因为篇幅问题,我们省略掉了一些具体代码如何保持上下文传递的技术方案,你可以随时查看我们的开源代码获得这方面的信息。

结语

编译拾遗(二):揭秘污点追踪的困扰与对策

文章描述到这里,我想你对污点追踪应该有了非常清醒的认知,原本各种模糊的含糊其辞,充满公式的污点追踪过程应该可以变成了“代码”的过程。并且实际上,你应该抛弃掉“污点追踪”带给你的误导,直接看到污点追踪技术的分析本质。

END

更新日志

Yaklang  1.3.0-sp3

1. 修复 Windows 自定义安装路径中 HOME 不同步的问题

2. 重构 http 包底层,统一使用与 poc 相同的 HTTP 库功能

3. 分离 SSA 中自由变量和捕获变量

4. 修复 HTTP Fuzzer 中数据包变换的 BUG

5. 新增一个提取 Favicon URL 的小接口 @TimWhiteZ

6. Synscan 新增自定义网卡的接口

7. 静态分析新增提示“弃用”

8. 优化 CLI 与参数的展示/解析的功能

9. Vulinbox 更新逻辑漏洞测试靶场

10. 新增 For-Range Zero 的特性

11. 优化 tls.Inspect 接口

12. 新增 sandbox 包,可以在yak脚本中创建安全执行代码的沙箱

13. 新增新的批量扫描接口

14. 静态分析新增 include / eval 的支持

15. web fuzzer matcher 和 extractor 新增 ID 以保证生成的数据的匹配顺序

Yakit  1.2.9-sp1

1. 插件批量执行上线全新UI

2. 修复Historytag筛选抖动问题,增加tag搜索

3. 将菜单栏与设置功能优化为点击出现,避免影响操作

4. 修复菜单栏展开收起缓存问题

5. MITM增加过滤Websocket功能

6. 编写代码页增加弃用标识和提示

  YAK官方资源

Yak 语言官方教程:
https://yaklang.com/docs/intro/
Yakit 视频教程:
https://space.bilibili.com/437503777
Github下载地址:
https://github.com/yaklang/yakit
Yakit官网下载地址:
https://yaklang.com/
Yakit安装文档:
https://yaklang.com/products/download_and_install
Yakit使用文档:
https://yaklang.com/products/intro/
常见问题速查:
https://yaklang.com/products/FAQ

编译拾遗(二):揭秘污点追踪的困扰与对策
长按识别添加工作人员
开启Yakit进阶之旅

编译拾遗(二):揭秘污点追踪的困扰与对策

原文始发于微信公众号(Yak Project):编译拾遗(二):揭秘污点追踪的困扰与对策

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月26日22:37:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   编译拾遗(二):揭秘污点追踪的困扰与对策https://cn-sec.com/archives/2435512.html

发表评论

匿名网友 填写信息