Angr-Learn-0x4
注意
本文可以理解为官方文档的简单翻译+一部分个人理解
程序状态
本篇主要讲的是程序状态,比如内存与寄存器等,然后简单介绍angr操作的基本概念。
比如说如何读写内存:
import angr, claripy
>>> proj = angr.Project('/bin/true')
>>> state = proj.factory.entry_state()
# copy rsp to rbp
>>> state.regs.rbp = state.regs.rsp
# store rdx to memory at 0x1000
>>> state.mem[0x1000].uint64_t = state.regs.rdx
# dereference rbp
>>> state.regs.rbp = state.mem[state.regs.rbp].uint64_t.resolved
# add rax, qword ptr [rsp + 8]
>>> state.regs.rax += state.mem[state.regs.rsp + 8].uint64_t.resolved
基本符号执行
我们可以使用state.step()
来进行一次符号执行。该方法将执行一步符号执行并返回一个名为angr.engines.successors.SimSuccessors
的对象 。在这有几个要点需要理解:
-
第一:与仿真不同,符号执行的对象可以产生多个后继状态,这些后继状态可以通过多种方式进行分类。而
.successors
这个对象的属性他是一个包含所有“正常”后继状态的列表。我们在这,要清楚的理解为啥这返回的是列表而不是一个单一的元素。 -
第二:我们选择“真”分支还是“假”分支?答案是,我们两者都采用!我们生成两个完全独立的后继状态: 一种模拟条件为真的情况,另一种模拟条件为假的情况。在第一个状态中,我们添加模拟情况为真的符号比较作为约束,在第二个状态中,我们添加模拟情况为假的符号比较作为约束。这样,每当我们使用这些后继状态中的任何一个执行约束求解时,状态上的条件都会确保我们获得的任何解决方案都是有效的输入,这将导致执行遵循给定状态所遵循的相同路径
>>> proj = angr.Project('examples/fauxware/fauxware')
>>> state = proj.factory.entry_state(stdin=angr.SimFile) # ignore that argument for now - we're disabling a more complicated default setup for the sake of education
>>> while True:
... succ = state.step()
... if len(succ.successors) == 2:
... break
... state = succ.successors[0]
>>> state1, state2 = succ.successors
>>> state1
<SimState @ 0x400629>
>>> state2
<SimState @ 0x400699
>>> input_data = state1.posix.stdin.load(0, state1.posix.stdin.size)
>>> state1.solver.eval(input_data, cast_to=bytes)
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00SOSNEAKY\x00\x00\x00'
>>> state2.solver.eval(input_data, cast_to=bytes)
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00S\x00\x80N\x00\x00 \x00\x00\x00\x00'
状态预设
到目前为止,每当我们使用状态时,我们都是使用project.factory.entry_state()
来创建它的。这只是项目工厂中可用的几个状态构造函数之一:
.blank_state()
构造一个“blank slate”空白状态,其中大部分数据未初始化。当访问未初始化的数据时,将返回一个不受约束的符号值。.entry_state()
构造一个准备在主二进制文件的入口点执行的状态。.full_init_state()
构造一个准备通过需要在主二进制文件入口点之前运行的任何初始化程序执行的状态,例如共享库构造函数或预初始化程序。当完成这些后,它将跳转到入口点。.call_state()
构造一个准备执行给定函数的状态。
您可以通过这些构造函数的几个参数来自定义状态:
- 这些构造函数都可以接受一个 addr 参数来指定开始的确切地址。
- 如果你在一个可以接受命令行参数或环境变量的环境中执行,你可以通过 args 传入一个参数列表,并通过 env 传入一个环境变量的字典到 entry_state 和 full_init_state 中。这些结构中的值可以是字符串或位向量,并将作为参数和环境变量序列化到模拟执行的状态中。默认的 args 是一个空列表,所以如果你分析的程序期望至少找到一个 argv[0],你应该总是提供它!
- 如果你想让 argc 成为符号化的,你可以将一个符号位向量作为 argc 传递给 entry_state 和 full_init_state 构造函数。但要小心:如果你这样做,你还应该在结果状态中添加一个约束,以确保你为 argc 设置的值不会大于你传入 args 的参数数量。
- 要使用 call state,你应该用 .call_state(addr, arg1, arg2, ...) 调用它,其中 addr 是你想要调用的函数的地址,argN 是该函数的第 N 个参数,可以是 Python 整数、字符串或数组,或者是位向量。如果你想要分配内存并实际传入一个对象的指针,你应该将其包装在 PointerWrapper 中,即 angr.PointerWrapper("point to me!")。这个 API 的结果可能有点不可预测,但我们正在努力改进。
- 为了指定使用 call_state 为函数调用的约定,你可以传递一个 SimCC 实例作为 cc 参数。我们试图选择一个合理的默认设置,但对于特殊情况,你需要帮助 angr 进行调整。
内存接口
状态选项
可以对 angr
的内部进行很多小调整,这些调整在某些情况下会优化行为,但在其他情况下会产生损害。这些调整是通过状态选项控制的。
在每个 SimState
对象上,都有一组 ( state.options
) 其所有启用的选项。每个选项(实际上只是一个字符串)都以某种微小的方式控制 angr
执行引擎的行为。完整的选项域列表以及不同状态类型的默认值可以在附录中找到。您可以通过 访问添加到状态的单独选项angr.options
。各个选项均以大写字母命名,但也有一些常见的对象分组,您可能希望将它们捆绑在一起使用,并以小写字母命名。
通过任何构造函数创建 SimState
时,您可以传递关键字参数add_options
和remove_options
,它们应该是修改默认初始选项集的选项集。
# Example: enable lazy solves, an option that causes state satisfiability to be checked as infrequently as possible.
# This change to the settings will be propagated to all successor states created from this state after this line.
>>> s.options.add(angr.options.LAZY_SOLVES)
# Create a new state with lazy solves enabled
>>> s = proj.factory.entry_state(add_options={angr.options.LAZY_SOLVES})
# Create a new state without simplification options enabled
>>> s = proj.factory.entry_state(remove_options=angr.options.simplification)
状态插件
除了刚才讨论的一组选项之外,存储在 SimState
中的所有内容实际上都存储在附加到该状态的插件中。到目前为止,我们讨论的状态的几乎每个属性都是一个插件 - memory
、 registers
、mem
、regs
、solver
等。这种设计允许代码模块化以及为模拟状态的其他方面轻松实现新型数据存储的能力,或者提供插件的替代实现的能力。
例如,普通memory
插件模拟平坦内存空间,但分析可以选择启用“抽象内存”插件,该插件使用地址的替代数据类型来模拟独立于地址的自由浮动内存映射,以提供state.memory
. 相反,插件可以降低代码复杂性:state.memory
并且state.registers
实际上是同一插件的两个不同实例,因为寄存器也使用地址空间进行模拟。
全局插件
state.globals
是一个非常简单的插件:它实现了标准 Python 字典的接口,允许您在状态上存储任意数据。
历史记录插件
为了更方便地使用这个结构,历史记录还提供了几个高效的迭代器,用于遍历某些值的历史记录。一般来说,这些值存储为 history.recent_NAME
,而遍历它们的迭代器就是 history.NAME
。例如,for addr in state.history.bbl_addrs: print hex(addr)
将打印出二进制文件的基本块地址跟踪,而 state.history.recent_bbl_addrs
是最近一步执行的基本块列表,state.history.parent.recent_bbl_addrs
是上一步执行的基本块列表等。如果你需要快速获得这些值的平面列表,你可以访问 .hardcopy
,例如 state.history.bbl_addrs.hardcopy
。不过要记住,基于索引的访问是在迭代器上实现的。
以下是历史记录中存储的一些值的简要列表:
history.descriptions
是对状态执行的每一轮次的字符串描述的列表。
history.bbl_addrs
是状态执行的基本块地址的列表。每轮执行可能有多个地址,而且不是所有地址都对应于二进制代码 - 有些可能是挂钩了 SimProcedures
的地址。
history.jumpkinds
是状态历史中每个控制流转移的处置的列表,作为 VEX 枚举字符串。
history.jump_guards
是列表,列出了状态遇到的每个分支的保护条件。
history.events
是在执行过程中发生的“有趣事件”的语义列表,如存在符号跳转条件、程序弹出消息框,或执行以退出代码终止。
history.actions
通常为空,但如果你向状态添加 angr.options.refs
选项,它将记录程序执行的所有内存、寄存器和临时值访问的日志。
调用堆栈插件
angr
将跟踪模拟程序的调用堆栈。在每个调用指令上,都会将一个帧添加到跟踪的调用堆栈的顶部,并且每当堆栈指针下降到调用最顶层帧的点以下时,都会弹出一个帧。这使得angr
能够稳健地存储当前模拟函数的本地数据。
与历史记录类似,调用堆栈也是一个节点链接列表,但没有提供对节点内容的迭代器 - 相反,您可以直接迭代以state.callstack
获取每个活动帧的调用堆栈帧,按从大多数到顺序最近到最旧。如果您只想要最上面的框架,那就是state.callstack
。
callstack.func_addr
是当前正在执行的函数的地址callstack.call_site_addr
是调用当前函数的基本块的地址callstack.stack_ptr
是从当前函数开头开始的堆栈指针的值callstack.ret_addr
是当前函数返回的位置
复制和合并
这部分主要了解状态的复制与合并:
proj = angr.Project('/bin/true')
s = proj.factory.blank_state()
s1 = s.copy()
s2 = s.copy()
s1.mem[0x1000].uint32_t = 0x41414141
s2.mem[0x1000].uint32_t = 0x42424242
(s_merged, m, anything_merged) = s1.merge(s2)
aaaa_or_bbbb = s_merged.mem[0x1000].uint32_t
标签:状态,插件,Angr,0x4,state,angr,Learn,x00,options
From: https://www.cnblogs.com/7resp4ss/p/18077048