理解符号执行-angr
符号类似于我们在计算未知数x的值,我们可以通过路径来求解未知数。通过判断条件来选择路径,当符号满足设定条件时即走了正确的路径。
我们需要通过符号执行来达到一个目标,假设这个目标是走到success这一步。
那么我们符号执行的步骤就相当明了了。
第一步:找到符号。一般这里我们的未知数,即符号就是我们的input。
第二步:找到路径(分支)。
第三步:评估每条路径。
由于路径和二进制文件的复杂度提升,我们给出了更好的选择来通过电脑来评估路径,于是就有了angr
Angr是一个符号执行引擎。
它可以:
遍历二进制文件(并遵循任何分支)
搜索符合给定条件的程序状态
解给定路径(和其他)约束的符号变量
在这里分为符号和执行路径两个板块
首先讨论执行路径
1.执行路径
Angr在一个模拟管理器对象中存储和处理一组给定程序的可能路径。
模拟管理器提供了逐步执行程序以生成可能路径/状态的功能。
构建一系列路径
1.Angr在你指定的地方启动程序(这是第一个激活状态)
2.在每个活动(非终止)状态下执行指令,直到到达分支点或状态终止
3.在每个分支点上,将状态拆分为多个状态,并将它们添加到活动状态集
4.重复步骤2 . .4,直到我们找到我们想要的,否则所有的州都会被终止
我们可以对活动状态进行标记,以排除错误的路径。
1.加载二进制
2.指定起点并创建模拟管理器
3.当我们还没有找到我们想要的……
1)步进所有激活状态
2)在每个活动状态上运行’ should_accept_state ‘谓词
3)如果有人接受,我们就找到了我们想要的!退出循环
4)在每个活动状态上运行’ should_avoid_state ‘谓词
5)对于每个被接受的状态,将其标记为终止
6)从活动状态集中移除所有标记为终止的状态
这个算法angr写了一个单独的explore函数
simulation.explore(find=should_accept_path, avoid=should_avoid_path)
将添加任何被接受的路径到列表’ simulation.found ‘
simulation.explore(find= 0 x80430a,avoid= 0x9aa442)
将搜索地址0x80430a并终止任何到达0x9aa442的内容。
2.符号和约束的引入
在某些情况下,当从stdin文件中查询用户输入时,Angr会自动注入符号。*它使用的是SimProcedures,我们将在后面介绍。
当Angr没有自动注入我们想要的符号时,我们可以手动这样做。
Angr的符号是用位向量来表示的
位向量有一个大小,即它们所代表的位数。
与编程中的所有数据一样,位向量可以表示任何适合的类型。通常,它们表示n位整数或字符串。
位向量和典型变量之间的区别是,典型变量存储单个值,而位向量存储满足一定约束条件的所有值。
在简单的例子中,angr会自动将您的input作为符号注入,但在一些复杂的情况下需要手动输入
下面是一些例子:
注入符号示例
1.寄存器
对于简单的情况,Angr会代替它,这样用户输入函数就会将符号值注入寄存器。
对于更复杂的情况,我们需要自己注入符号。在用户输入后启动程序,用符号值初始化寄存器。
情形:get_user_input函数通过将值写入寄存器来返回值(把输入存在内存中,并把地址写到rax寄存器并返回)。(angr不支持写入多个参数于不同地址。)
解决方案:不调用get_user_input,而是将符号值写入寄存器。
在Angr中,你可以用一个具体的或符号的值写入寄存器:
state.regs.eax = my_bitvector
将my_bitvector的值写入eax。
2.全局内存
情形:get_user_input函数通过将值写入编译时确定的地址来返回值。
解决方案:不调用get_user_input,而是将符号值写入地址。
3.栈
在Angr中,你可以用一个具体的或符号的值来推入堆栈:
state.stack_push (my_bitvector)
将my_bitvector的值推到堆栈的顶部。
你可能需要考虑你不关心的任何东西在堆栈的开始通过添加填充:
state.regs.esp - = 4
增加4个字节的填充。
4.动态内存
分配在堆上的内存
可以直接写入到指针覆盖原有指针
如果你不能确定scanf写入的地址,因为它存储在一个指针中,你可以覆盖指针的值,指向你选择的一个未使用的位置(在这个例子中,0x4444444):
state.memory.store(0xaf84dd8, 0x4444444) state.memory.store(0x4444444, my_bitvector)
此时,0xaf84dd8的指针将指向0x4444444,它将存储您的位向量。
5.文件系统
可以当作内存处理,但地址和内存的地址不同,要注意
特殊情况
1.hooks
如果想要跳一些地址,可以使用hook
您可以使用hooks来完成此操作。你可以指定一个‘hook’的地址,你想要跳过的指令的字节数,以及一个将运行的Python函数来替换跳过的指令。
注意:跳过的指令数可以为零。
Call: binary.hook(0x8048776, length=16, replacement_check_all_Z)
第一个是我们想要hook的地址,第二个是代表这些指令在内存中用16个字节表示(跳过的),第三个是代替运行这些指令的函数。
Hook可以用于:
在执行过程中注入符号值。
取代复杂的功能。
替换不支持的指令(例如,大多数系统调用)。
函数回顾
- 将参数推入堆栈
- 将返回地址推到堆栈
- 跳转到功能地址
- 处理参数*
- 执行函数
- 将返回值写入适当的位置
- 弹出返回地址并跳转到它
- 弹出参数
simprocedures
SimProcedures被用来替换任何你完全理解并且不想测试bug的东西,或者Angr不支持的东西。
因为问题的复杂性随着程序的长度呈指数级增长,任何满足上述条件的函数都应该用SimProcedure代替,以节省时间。
目前,在Angr中包含了libc子集(快速扩展)的重新实现。
Author: John Doe
Link: http://example.com/2022/02/16/angr-%E7%90%86%E8%A7%A3%E7%AC%A6%E5%8F%B7%E6%89%A7%E8%A1%8C/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.