我在学习angr时,先是阅读了开发者发布在IEEE上的论文IEEE Xplore Full-Text PDF:该文章讲述了自动化漏洞挖掘的背景和方法,并对angr的架构和核心模块进行了介绍,非常经典值得一读。
而后,我阅读了angr官方文档和API文档,对angr有了总体的、晕晕乎乎的了解。最后,我发现了github上的项目angr_ctf,并使用该项目在解题过程中不断补充、修正我对angr的了解,以下是涉及的相关连接,你可能用得上:
angr官方文档README - angr Documentation
angr的API文档angr API documentation — angr 9.2.26 documentation
angr_ctf项目GitHub - jakespringer/angr_ctf
本系列教程是angr的入门教程,将通过做angr_ctf中的题目的形式来介绍angr,在每篇开头会先介绍该篇所写题目时要用到的知识,并且尽量进行详细的补充和扩展,以便于你了解angr是一个多么伟大的项目。观点均来自作者的论文、angr官方文档以及本人在实践时的思考。
angr和angr_ctf简介
angr是一个支持多处理架构的用于二进制文件分析的工具包,它提供了动态符号执行的能力以及多种静态分析的能力。项目创建的初衷,是为了整合此前多种二进制分析方式的优点,并开发一个平台,以供二进制分析人员比较不同二进制分析方式的优劣,并根据自身需要开发新的二进制分析系统和方式。
也正是因为angr是一个二进制文件分析的工具包,因此它可以被使用者扩展,用于自动化逆向工程、漏洞挖掘等多个方面。
angr_ctf则是一个专门针对angr的项目,里面有17个angr相关的题目。这些题目只有一个唯一的要求:你需要找出能够使程序输出“Good Job”的输入,这也是符号执行常见的应用场景。
项目中序号开头的文件夹里面是题目的源码和题解。
dist中保存了各个题目编译后的可执行文件,均是ELF-32bit
solutions中集合了所有题目的题解,也是所有序号开头文件夹的合集。题解文件solve包含完整题解,而scaffold则是待填充的题解,需要使用者根据程序在“???”处填入合适的内容。
angr核心概念
顶层接口
Project类是angr的主类,也是angr的开始,通过初始化该类的对象,可以将你想要分析的二进制文件加载进来,就像这样:
import angr p = angr.Project('/bin/true')
参数为待分析的文件路径,它是唯一必须传入的参数,此外还有一个比较常用的参数load-options,它指明加载的方式,如下:
名称 | 描述 | 传入参数 |
auto_load_libs | 是否自动加载程序的依赖 | 布尔 |
skip_libs | 希望避免加载的库 | 库名 |
except_missing_libs | 无法解析库时是否抛出异常 | 布尔 |
force_load_libs | 强制加载的库 | 库名 |
ld_path | 共享库的优先搜索路径 | 路径名 |
使用angr时最重要的就是效率问题,少加载一些无关结果的库能够提升angr的效率,如下:
import angr p = angr.Project('/bin/true', auto_load_libs=False)
任何附加的参数都会被传递到angr的加载器,即CLE.loader中(CLE 即 CLE Loads Everything的缩写)
Project类中有许多方法和属性,例如加载的文件名、架构、程序入口点、大小端等等:
>>> print(p.arch, hex(p.entry), p.filename, p.arch.bits, p.arch.memory_endness ) <Arch AMD64 (LE)> 0x4023c0 /bin/true 64 Iend_LE
状态State
Project实际上只是将二进制文件加载进来了,要执行它,实际上是对SimState对象进行操作,它是程序的状态。用docker来比喻,Project相当于开发环境,State则是使用开发环境制作的镜像。
要创建状态,需要使用Project对象中的factory,它还可以用于创建模拟管理器和基本块(后面提到),如下:
init_state = p.factory.entry_state()
预设状态有四种方式如下:
预设状态方式 | 描述 |
entry_state | 初始化状态为程序运行到程序入口点处的状态 |
blank_state(addr=) | 大多数数据都没有初始化,状态中下一条指令为addr处的指令 |
full_init_state | 共享库和预定义内容已经加载完毕,例如刚加载完共享库 |
call_state | 准备调用函数的状态 |
状态包含了程序运行时的一切信息,寄存器、内存的值、文件系统以及符号变量等,这些信息的使用等用到时再进一步说明。
entry_state和blank_state是常用的两种方式,后者通常用于跳过一些极大降低angr效率的指令,它们间的对比如下:
>>> state = p.factory.entry_state() >>> print(state.regs.rax, state.regs.rip) <BV64 0x1c> <BV64 0x4023c0>
>>> state = p.factory.blank_state(addr=0x4023c0) >>> print(state.regs.rax, state.regs.rip) <BV64 reg_rax_42_64{UNINITIALIZED}> <BV64 0x4023c0>
在blank_state方式中,我们仍将地址设定为程序的入口点,然而rax中的值由于没有初始化,它现在是一个名字,也即符号变量,这是符号执行的基础,后续在细说。
此外,可以看到寄存器中的数据类型并不是int,而是BV64,它是一个位向量(Bit Vector),有关位向量的细节之后再说。
模拟管理器(Simulation Manager)
上述方式只是预设了程序开始分析时的状态,我们要分析程序就必须要让它到达下一个状态,这就需要模拟管理器的帮助(简称SM).
使用以下指令能创建一个SM,它需要传入一个state或者state的列表作为参数:
simgr = p.factory.simgr(state)
SM中有许多列表,这些列表被称为stash,它保存了处于某种状态的state,stash有如下几种:
stash | 描述 |
active | 保存接下来可以执行并且将要执行的状态 |
deadended | 由于某些原因不能继续执行的状态,例如没有合法指令,或者有非法指针 |
pruned | 与solve的策略有关,当发现一个不可解的节点后,其后面所有的节点都优化掉放在pruned里 |
unconstrained | 如果创建SM时启用了save_unconstrained,则没有约束条件的state会放在这里 |
unsat | 如果创建SM时启用了save_unsat,则被认为不可满足的state会放在这里 |
默认情况下,state会被存放在active中。
stash中的state可以通过move()方法来转移,将fulter_func筛选出来的state从from_stash转移到to_stash:
simgr.move(from_stash='deadended', to_stash='more_then_50', filter_func=lambda s: '100' in s.posix.dumps(1))
stash是一个列表,可以使用python支持的方式去遍历其中的元素,也可以使用常见的列表操作。但angr提供了一种更高级的方式,在stash名字前加上one_,可以得到stash中的第一个状态,加上mp_,可以得到一个mulpyplexed版本的stash
此外,稍微解释一下上面代码中的posix.dumps:
- state.posix.dumps(0):表示到达当前状态所对应的程序输入
- state.posix.dumps(1):表示到达当前状态所对应的程序输出
上述代码就是将deadended中输出的字符串包含'100'的state转移到more_then_50这个stash中。
可以通过step()方法来让处于active的state执行一个基本块,这种操作不会改变state本身:
>>> state = p.factory.entry_state() >>> simgr = p.factory.simgr(state) >>> print(state.regs.rax, state.regs.rip) <BV64 0x1c> <BV64 0x4023c0> >>> print(simgr.one_active) <SimState @ 0x4023c0> >>> simgr.step() <SimulationManager with 1 active> >>> print(simgr.one_active) <SimState @ 0x529240> >>> print(state.regs.rax, state.regs.rip) <BV64 0x1c> <BV64 0x4023c0>
最后也是SM最常用的技术:探索技术(explorer techniques)
可以使用explorer方法去执行某个状态,直到找到目标指令或者active中没有状态为止,它有如下参数:
- find:传入目标指令的地址或地址列表,或者一个用于判断的函数,函数以state为形参,返回布尔值
- avoid:传入要避免的指令的地址或地址列表,或者一个用于判断的函数,用于减少路径
此外还有一些搜索策略,之后会集中讲解,默认使用DFS(深度优先搜索)。
explorer找到的符合find的状态会被保存在simgr.found这个列表当中,可以遍历其中元素获取状态。
符号执行
angr作为一个二进制分析的工具包,但它通常作为符号执行工具更为出名。
符号执行就是给程序传递一个符号而不是具体的值,让这个符号伴随程序运行,当碰见分支时,符号会进入哪个分支呢?
angr的回答是全都进入!angr会保存所有分支,以及分支后的所有分支,并且在分支时,保存进入该分支时的判断条件,通常这些判断条件时对符号的约束。
当angr运行到目标状态时,就可以调用求解器对一路上收集到的约束进行求解,最终得到某个符号能够到达当前状态的值。
例如,程序接收一个int类型的输入,当这个输入大于0小于5时,就会执行某条保存在该程序中,我们希望执行的指令(例如一个后门函数backdoor),具体而言如下图所示:
angr会沿着分支按照某种策略(默认DFS)进行状态搜索,当达到目标状态(也就是backdoor能够执行的状态),此时angr已经收集了两个约束(x>0 以及x<=5),那么angr就通过这两个约束对x进行求解,解出来的x值就是能够让程序执行backdoor的输入。
在复杂的程序当中,从一个符号到backdoor的路径可能十分复杂,甚至包含一些加密解密的过程,这时就是angr大显身手的时候了。
实战 00_angr_find
使用angr一般分为如下步骤:
- 创建Project,预设state
- 创建位向量和符号变量,保存在内存/寄存器/文件或其他地方
- 将state添加到SM中
- 运行,探索满足条件的路径
- 约束求解获取执行结果
先逆向查看逻辑
程序接收一个8字节的输入,对它使用complex_function函数进行转换,比较转换后的字符串是否等于JACEJGCS,现在我们要找出能够让程序执行puts("Good Job.")的输入。
以下是能够获取结果的angr脚本:
import angr #加载文件,预设状态,执行状态 p = angr.Project('./dist/00_angr_find', auto_load_libs=False) init_state = p.factory.entry_state() simgr = p.factory.simgr(init_state) #puts Good的指令地址 target = 0x08048678 #搜索能够执行目标指令的状态 simgr.explore(find = target) if simgr.found: solution_state = simgr.found[0] #打印出符号条件的状态的输入 print(solution_state.posix.dumps(0))
由于该题比较简单,因此跳过了第2和第5步。
执行结果如下:
┌──(venv)─(kali㉿kali)-[~/angrfile] └─$ python 00.py 148 ⨯ 2 ⚙ WARNING | 2022-11-27 22:37:44,598 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing register with an unspecified value. This could indicate unwanted behavior. WARNING | 2022-11-27 22:37:44,598 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by: WARNING | 2022-11-27 22:37:44,598 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state WARNING | 2022-11-27 22:37:44,598 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null WARNING | 2022-11-27 22:37:44,598 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages. WARNING | 2022-11-27 22:37:44,598 | angr.storage.memory_mixins.default_filler_mixin | Filling register edi with 4 unconstrained bytes referenced from 0x80486b1 (__libc_csu_init+0x1 in 00_angr_find (0x80486b1)) WARNING | 2022-11-27 22:37:44,601 | angr.storage.memory_mixins.default_filler_mixin | Filling register ebx with 4 unconstrained bytes referenced from 0x80486b3 (__libc_csu_init+0x3 in 00_angr_find (0x80486b3)) WARNING | 2022-11-27 22:37:46,904 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7ffeff50 with 4 unconstrained bytes referenced from 0x8100000 (strcmp+0x0 in extern-address space (0x0)) b'JXWVXRKX'
事实上,上述脚本能够解决一切有关"为了执行某条目标语句,我应该用怎样的输入”这样的问题,是一个万能脚本。区别在于由于程序的复杂程度和逻辑不同,耗费的时间不同,因此在解决这类问题上,编写angr脚本的本质是在使用angr提供的各种二进制分析方法去优化路径,提高它的运行效率。
实战 01_angr_avoid
这个题使用IDA打开时就很慢,在对main进行反汇编时会提示函数过大,因此直接shift+F12查找调用了Good的指令,发现是函数maybe_good
此外查看函数avoid_me的交叉引用
可以发现该函数被main函数调用了多次,应该是导致main函数过大的原因,因此要对它进行避免,也就是使用explorer的avoid的参数。
整个angr脚本如下:
import angr p = angr.Project('./dist/01_angr_avoid') init_state = p.factory.entry_state() simgr = p.factory.simgr(init_state) good = 0x080485e5
#avoid_me的函数起始的位置(并非调用该函数的位置,因为调用该函数的地方太多了) bad = 0x080485a8 simgr.explore(find= good,avoid = bad) if simgr.found: solution = simgr.found[0] print(solution.posix.dumps(0))else: raise Exception("Could not find solution")
运行结果如下:
┌──(venv)─(kali㉿kali)-[~/angr] └─$ python 01.py 1 ⨯ 2 ⚙ WARNING | 2022-11-27 22:54:49,287 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing register with an unspecified value. This could indicate unwanted behavior. WARNING | 2022-11-27 22:54:49,287 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by: WARNING | 2022-11-27 22:54:49,287 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state WARNING | 2022-11-27 22:54:49,287 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null WARNING | 2022-11-27 22:54:49,287 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages. WARNING | 2022-11-27 22:54:49,288 | angr.storage.memory_mixins.default_filler_mixin | Filling register edi with 4 unconstrained bytes referenced from 0x80d4591 (__libc_csu_init+0x1 in 01_angr_avoid (0x80d4591)) WARNING | 2022-11-27 22:54:49,293 | angr.storage.memory_mixins.default_filler_mixin | Filling register ebx with 4 unconstrained bytes referenced from 0x80d4593 (__libc_csu_init+0x3 in 01_angr_avoid (0x80d4593)) WARNING | 2022-11-27 22:54:55,575 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7ffeff2d with 11 unconstrained bytes referenced from 0x8197e20 (strncmp+0x0 in libc.so.6 (0x97e20)) WARNING | 2022-11-27 22:54:55,576 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7ffeff50 with 4 unconstrained bytes referenced from 0x8197e20 (strncmp+0x0 in libc.so.6 (0x97e20)) b'HUJOZMYS'
实战:02_angr_find_condition
该题逻辑和前两题差不多,区别在于此时使用了字符串“Good Job”和字符串“Try again”的指令多了很多,因此需要考虑给find和avoid参数传入函数来筛选出所有这些使用了这两个字符串的指令。
对前两题的脚本进行如下修改:
def good(state): tag = b'Good' in state.posix.dumps(1) return True if tag else False def bad(state): tag = b'Try' in state.posix.dumps(1) return True if tag else False simgr.explore(find=good, avoid=bad)
上述函数使用state作为形参,在函数内部进行判断后返回布尔值,当值为true时触发相应的操作(find或者avoid),这样就能筛选出所有能够打印出字符串“Good Job”和字符串“Try again的状态了。
其他说明:路径搜索——avoid为何能提高效率
angr在模拟执行指令时,对于遇到的分支和跳转,会全部进行保留,并且记录用于判断分支的条件(即约束),如下图所示
之前说过,这些状态都是程序运行到某些阶段时的信息,包括了内存、寄存器、文件系统等多个方面,这些状态中有满足条件的状态,就会被放入到found列表当中。
而在路径搜索时,对于满足avoid条件的状态,则会被丢弃,也就是说,该状态及该状态的后续路径都不会被进行搜索,因此简化了angr的搜索路径,从而提高效率。
标签:11,状态,simgr,简介,state,ctf,angr,memory From: https://www.cnblogs.com/level5uiharu/p/16925991.html