4、angr核心概念:VEX、状态、符号执行与CFG/DFG

好,咱们进入正题。这一章要聊的,是angr的四个核心概念。说白了,这四个东西就是你用angr做固件分析时的四根柱子。我刚开始接触angr的时候,也一度被这些术语搞晕过。但后来发现,理解了它们,你就能真正驾驭angr,而不是被它牵着鼻子走。

4.1 VEX中间表示:angr的“通用语言”

VEX是什么?你可以把它理解成angr的“世界语”。不同的CPU架构,比如ARM、MIPS、x86,它们的指令集千差万别。angr不可能为每一种架构都写一套分析逻辑,那太累了。所以它把各种架构的机器码,都翻译成一种统一的中间表示——VEX。

我个人习惯把VEX叫做“IR”,也就是中间表示。它把一条复杂的机器指令,拆解成若干个更小、更简单的操作。比如一条ARM的LDR指令,在VEX里可能就变成了“计算地址”、“读取内存”、“存入寄存器”这几个步骤。

核心要点:VEX是angr进行架构无关分析的基础。你不需要关心底层是ARM还是MIPS,你只需要跟VEX打交道。

我在逆向一个MIPS路由器固件时,就吃过亏。当时直接用angr的默认设置去分析,结果符号执行跑得特别慢。后来我查了一下,发现是VEX对MIPS某些访存指令的翻译效率不高。我手动调整了VEX的优化选项,速度一下就上来了。嗯,这里要注意,VEX虽然强大,但也不是万能的,有时候需要你手动调优。

4.2 程序状态(State):angr的“快照”

程序状态,就是angr在模拟执行过程中,对CPU和内存的一个“快照”。它记录了当前时刻,所有寄存器的值、内存里的数据、程序计数器(PC)指向哪里,等等。

你可以把State想象成一个虚拟的CPU。angr每执行一条指令,就会更新这个State。而且,angr可以同时维护多个State,每个State代表程序执行的一条可能路径。这就是符号执行的基础。

State里最重要的几个东西:

  • regs:寄存器,比如state.regs.rax就是x86架构下的RAX寄存器。
  • mem:内存,比如state.mem[addr].int.resolved就是读取地址addr处的一个整数。
  • solver:约束求解器,用来判断某个条件是否可能成立。

我的经验:调试固件时,我经常用state.posix.dumps(0)来查看标准输入的内容。这招在分析处理用户输入的漏洞时特别管用。

4.3 符号执行引擎:angr的“大脑”

符号执行引擎,是angr最核心的部分。它负责驱动State的执行,处理分支,管理路径。

为什么叫“符号”执行?因为它的输入可以是符号化的,也就是一个变量,而不是一个具体的值。比如,你让angr分析一个函数,输入是一个符号化的缓冲区。angr会模拟执行这个函数,但不会去猜缓冲区里具体是什么内容。它会用符号来表示这些内容,然后跟踪这些符号在程序中的传播。

当程序遇到条件分支(比如if (input == 0xdeadbeef))时,引擎会创建两个State:一个走真分支,一个走假分支。每个State都会记录下对应的约束条件。比如,走真分支的State会记录“input == 0xdeadbeef”,走假分支的State会记录“input != 0xdeadbeef”。

最后,引擎会尝试求解这些约束,找到一组具体的输入值,让程序走到你想要的路径上。这就是符号执行的核心思想。

避坑指南:我曾经在一个复杂的固件里,符号执行跑了几个小时都没结束。后来发现是路径爆炸了——程序里循环太多,分支太多,State数量呈指数级增长。这时候就需要用一些技巧,比如路径剪枝、状态合并,来限制State的数量。

4.4 CFG与DFG:程序的“地图”与“数据流”

CFG(控制流图)和DFG(数据流图),是angr提供的两个静态分析工具。它们不执行代码,而是分析代码的结构。

4.4.1 CFG:控制流图

CFG,说白了就是程序执行路径的“地图”。它把程序中的基本块(一段连续的、没有分支的指令)作为节点,把基本块之间的跳转关系作为边。通过CFG,你可以直观地看到程序有哪些函数,函数之间怎么调用,有哪些循环,有哪些条件分支。

angr生成CFG的代码很简单:

import angr

proj = angr.Project('firmware.bin', auto_load_libs=False)
cfg = proj.analyses.CFGFast()
# 现在你可以遍历cfg.nodes()和cfg.edges()了

我个人习惯在分析一个陌生固件时,先跑一遍CFG。这能让我快速了解固件的整体结构,找到关键函数的位置。比如,我想找处理网络数据包的函数,就可以在CFG里搜索跟网络相关的字符串或者API调用。

4.4.2 DFG:数据流图

DFG关注的是数据在程序中的流动。它不关心控制流,只关心数据是怎么从一个地方传到另一个地方的。比如,一个变量是怎么被赋值的,怎么被传递到函数参数里的,怎么被写入内存的。

DFG在分析漏洞时特别有用。比如,你想知道一个用户输入的数据,最终会不会被用到memcpysize参数里。通过DFG,你可以追踪这个数据的流向,看它经过了哪些运算,有没有被过滤或检查。

angr的DFG通常跟CFG配合使用。先通过CFG找到目标代码区域,再用DFG分析数据流。

实战技巧:我在分析一个栈溢出漏洞时,就是先用CFG定位到处理用户输入的函数,然后用DFG追踪输入数据到strcpy的路径。结果发现中间有一个检查长度的逻辑,但那个检查有漏洞——它只检查了输入的前半部分,后半部分没检查。嗯,这就是典型的“检查不完整”漏洞。

4.5 知识体系总览

下面这张图,是我自己总结的angr核心概念关系图。你可以把它当作一个快速参考。

angr核心概念关系图 angr 框架 VEX 中间表示 程序状态 (State) 符号执行引擎 CFG / DFG 翻译 管理 驱动 静态分析 指令语义 路径探索 辅助分析 VEX提供统一指令语义 → State记录执行快照 → 符号执行引擎驱动路径探索 → CFG/DFG辅助静态分析

这张图里,angr是中心,四个概念围绕它展开。VEX负责翻译,State负责记录,符号执行引擎负责驱动,CFG/DFG负责辅助分析。它们互相配合,构成了angr的完整分析能力。

好了,这一章的内容就到这里。记住这四个概念,后面我们做联合调试的时候,会反复用到它们。


专注资料整理