初识符号执行
符号执行的背景
软件中存在缺陷(漏洞)十分常见,一个项目中往往存在着大量的代码。为了查找和修复二进制文件漏洞。其中利用的关键技术都是基于符号执行,可以减少分析二进制文件代码的时间。
符号执行的基本概念
符号执行的概念
符号执行的过程类似于生成代数表达式。符号执行通过尽可能探索程序的执行路径找到潜在的漏洞为止,然后尝试解出利用该漏洞的特定输入。形象的来说就是设方程,然后解出设的未知数。这个未知数就是符号输入。
1 | 若x^2+2x+1=0,求其中的x为多少 |
约束的概念,举个例子
1 |
|
目标是执行到第8行的“you win!”,所以输入的变量i
要大于1
。代码中的每一个if
分支语句都可称之为一个约束
(简单说就是限制),符号执行的每个路径都可以看做是一个表达式,约束求解生成的路径win
表达式后得到的答案就是想要的输入。
再举个例子
-
符号化
程序的输入->探索程序的路径->发现指定的特殊路径->根据此路径约束求解->生成输入 -
符号状态(
symbolic state
):符号执行维护一个符号状态,它是一个<变量,符号表达式(symbolic expressions
)>的mapping
(映射) -
符号路径约束(
symbolic path constraint
):简单说就是逻辑条件
导致符号执行引擎效率低下的原因–路径爆炸
符号执行可能会导致状态空间的爆炸,深层次的原因是路径爆炸。比如说:符号执行一个动态链接的Linux
可执行文件,在引擎探索路径的过程中可能会探索到如libc
的动态链接库,因为libc
的路径十分复杂,因此会出现路径爆炸,然后把电脑内存挤爆,导致崩溃。
- 符号执行引擎是通过按行读取的方式模拟执行每条机器码,并更新对应变量,最后再通过约束求解的方式去逆推输入的初值
符号执行工具–angr
angr
是现在出名的基于符号执行和模拟执行的二进制框架。
安装
用python
虚拟环境安装
1 | sudo apt-get install python-dev libffi-dev build-essential virtualenvwrapper |
常用命令
导入angr
模块以及加载二进制程序
1 | import angr |
了解导入的二进制程序的基本信息
1 | print(proj.arch) #架构 |
二进制程序在虚拟地址空间的表示
1 | print(proj.loader)#通过CLE模块将二进制对象加载并映射到单个内存空间 |
Loader 模块主要是负责记录二进制程序的一些基本信息,包括段、符号、链接等。
1 | obj = proj.loader.main_object#加载main对象 |
同时也支持一些加载选项
auto_load_libs
:是否自动加载程序的依赖skip_libs
:避免加载的库except_missing_libs
:无法解析共享库时是否抛出异常force_load_libs
:强制加载的库ld_path
:共享库的优先搜索搜寻路径
一般情况下,加载程序都会将auto_load_libs
设置为False
,这是为了防止连外部库一起加载从而可能会导致路径爆炸。
factory模块
block
:用于从给定地址提取基本代码块,angr
以基本块为单位分析代码。
1 | block = proj.factory.block(proj.entry)#从程序的入口点提取一段代码 |
States
:proj
= angr.Project('./00_angr_find')
这是一个初始化映像,但是要执行符号执行,还需要simstate
,表示模拟程序状态的特定对象
1 | state = proj.factory.entry_state()#即就是simstate,Simstate包含程序的内存,寄存器,文件系统数据... |
blank_state
:构造一个“空白板”空白状态,其中大部分数据未初始化。当访问未初始化的数据时,将返回一个不受约束的符号值。entry_state
:造一个准备在主二进制文件的入口点执行的状态。full_init_state
:构造一个准备好通过任何需要在主二进制文件入口点之前运行的初始化程序执行的状态,例如,共享库构造函数或预初始化程序。完成这些后,它将跳转到入口点。call_state
:构造一个准备好执行给定函数的状态
这些构造函数都能够通过参数addr
来指定初始时的rip/eip
地址,而call_state
可以用这种方式来构造传参。
1 | state = proj.factory.entry_state()#即就是simstate,Simstate包含程序的内存,寄存器,文件系统数据... |
这些并不是python int
型,而是位向量(bitvectors
),需要对其置换
1 | bv = state.solver.BVV(0x1234,32)#创建一个32位宽,值为0x1234的位向量 |
eval
是一种通用方法,可以将任何位向量转换成python
原语。
Simulation Managers
模拟管理器
1 | simgr = proj.factory.simulation_manager(state)#首先创建将要使用的模拟管理器。构造函数可以接受状态或状态列表。 |
一些存储的类型:
active
:此存储区包含默认情况下将逐步执行的状态,除非指定了备用存储区deadended
:当一个状态在某些原因下无法被继续执行,则我们会将这个状态放入到deadended stash
中,这个原因包括没有有效的指令去执行,所有的约束状态都不可满足,或者是非法的指针pruned
:牵扯到了一个LAZY_SOLVES参数,主要是用来加快angr
的符号执行速度,但是这个参数要在恰当的情况下使用,否则可能求解出的答案是错误的,因为使用该参数后angr
不检查每个状态对约束是否满足。unconstrained
:如果save_unconstrained
选项被提供给SimulationManager
构造函数,则会将“不受约束的状态”放入到这个stash中。unsat
:如果向SimulationManager
构造函数中传入参数save_unsat
,则会将不满足的状态放入到unsat
中,比如某两个约束是互相矛盾的(输入必须同时是AAAA
和BBBB
)
SimProcedure
当符号执行遇到一些函数的时候,为了防止angr
将外部库也一并加载分析从而导致效率低下甚至可能是路径爆炸。可以利用hook
一些常用函数,从而提高效率。
它支持以下这些外部库:
1 | angr.procedures |
1 | SIM_LIBRARIES |
以Libc
为例,angr
支持了一部分libc
中的函数:
1 | angr.procedures.libc |
1 | __builtins__ |
所以如果程序中调用了这部分函数,默认情况下就会由angr.procedures.libc
中实现的函数进行接管。但是其中有一部函数实现的并不完善,需要我们自己编写函数来hook
它。
hook
第一种方案是直接对地址进行 hook,通过直接使用 project.hook(addr,function())
的方法直接钩取。
同时,Angr
对于有符号的二进制程序也运行直接对符号本身进行钩取:project.hook_symbol(name,function)
。