angr-doc 6: Execution Engines

执行引擎 Execution Engine

当你要求在angr中执行一步时,需要有什么东西实际执行这个步骤。angr使用一系列引擎(SimEngine类的子类)来模拟给定部分的代码对于一个输入状态的影响。angr的执行核心仅仅尝试队列中所有可用的引擎,使用第一个可以解决这步的引擎。下面是 默认引擎的列表,按照顺序是:

  • 当上一步将我们带到一些无法继续的状态时,失败引擎开始启动。
  • 当上一步在系统调用处结束时,系统调用引擎开始启动。
  • 当当前地址被钩住时,钩子引擎开始启动。
  • UNICORN状态选项被启用并且在状态中没有符号数据时,独角兽引擎开始启动。
  • VEX引擎作为最后的备选项启动。

符号执行后继 SimSuccessors

实际逐个尝试所有引擎的代码是project.factory.successors(state, **kwargs),它将它的参数传递给每个引擎。这个函数是state.step()simulation_manager.step()的核心。它返回一个符号执行后继对象,这在之前我们已经简要讨论过了。符号执行后继的目的是对后继状态进行简单的分类,被存储在不同的列表属性中。它们是:

属性 监视条件 指令指针 描述
successors 真(可以是符号化的,但是被约束为True) 可以是符号化的(但是不多于256个解;查看unconstrained_successor 一个被引擎处理状态的普通的可满足的后继状态。这个状态的指令指针可能是符号化的(比如,基于用户输入的计算跳转),所以这个状态可能实际上代表几个继续执行的可能的后继。
unsat_successors 假(可以是符号化的,但是被约束为False) 可以是符号化的 无法满足的后继。这些是监视条件只能为假(比如,非法跳转,或者必须执行的默认分支跳转)的后继状态。
flat_successors 真(可以是符号化的,但是被约束为True) 具体值 正如上面提到的,在successors列表中的状态可以拥有符号化的指令指针。这可能让人迷惑,因为在代码中的其他部分(比如在SimEngineVEX.process中,当要向前步进状态时),我们做出假设认为单个程序状态只代表在代码中单个点的执行。为了缓和这一点,当我们遇到了在successors中带有符号化指令指针的状态时,我们计算它们所有可能的具体解(门限高达大约256个),并且为每一个这种解制作一份状态的拷贝。我们把这个过程叫做“平展”。这些flat_successors中的每一个都是拥有一个不同的,具体的指令指针的状态。比如,如果在successors中的一个状态的指令指针为X+5,其中X的约束是X > 0x800000X <= 0x800010,我们可以将它平展到16个不同的flat_successors状态,其中一个的指令指针是0x800006,一个是0x800007,剩下的一直到0x800015
unconstrained_successors 真(可以是符号化的,但是被约束为True) 符号化的(带有超过256个解) 在上述的平展过程中,如果发现指令指针有超过256种可能的解时,我们假设指令指针被未约束的数据覆写了(比如,用户数据导致的栈溢出)。这个假设听上去不那么通用。这种状态被放置在unconstrained_successors中而不是在successors中。
all_successors 皆可 可以是符号化的。 这是successors + unsat_successors + unconstrained_successors

断点 Breakpoints

TODO: 重写这一部分以修复描述方式
像任何像样的符号执行引擎那样,angr支持断电。这非常酷!一个断点可以向下面这样设置:

>>> import angr
>>> b = angr.Project('examples/fauxware/fauxware')

# get our state
>>> s = b.factory.entry_state()

# add a breakpoint. This breakpoint will drop into ipdb right before a memory write happens.
>>> s.inspect.b('mem_write')

# on the other hand, we can have a breakpoint trigger right *after* a memory write happens. 
# we can also have a callback function run instead of opening ipdb.
>>> def debug_func(state):
...     print("State %s is about to do a memory write!")

>>> s.inspect.b('mem_write', when=angr.BP_AFTER, action=debug_func)

# or, you can have it drop you in an embedded IPython!
>>> s.inspect.b('mem_write', when=angr.BP_AFTER, action=angr.BP_IPYTHON)

除了内存写入以外,还有许多其他的地方来中断。这里有中断的列表。你可以在这些事件中每一个的BP_BEFORE和BP_AFTER处中断。

事件类型 事件含义
mem_read 内存被读取。
mem_write 内存被写入。
address_concretization 一个符号化内存访问被解析。
reg_read 一个寄存器被读取。
reg_write 一个寄存器被写入。
tmp_read 一个临时变量被读取。
tmp_write 一个临时变量被写入。
expr 一个表达式被创建(比如,一个算数操作的结果或者在IR中的常量)。
statement 一个IR状态被转译。
instruction 一个新的(本地)指令被转译。
irsb 一个新的基本块被转译。
constraints 新的约束被添加到状态中。
exit 一个后继正在从执行中被生成。
fork 一个符号执行状态生成了多个状态分支。
symbolic_variable 一个新的符号化变量被创建。
call 执行到一个call指令。
return 执行到一个ret指令。
simprocedure 一个符号执行过程(或者系统调用)被执行。)
dirty 一个脏IR回调被执行。
syscall 一个系统调用被执行(在符号执行过程事件以外调用的)。
engine_process 一个符号执行引擎将要执行一些代码。

这些事件暴露了不同的属性:

事件类型 属性名 可用属性 属性含义
mem_read mem_read_address BP_BEFORE or BP_AFTER 被读取的内存地址。
mem_read mem_read_expr BP_AFTER 位于那个地址的表达式。
mem_read mem_read_length BP_BEFORE or BP_AFTER 内存读取的长度。
mem_read mem_read_condition BP_BEFORE or BP_AFTER 内存读取的条件。
mem_write mem_write_address BP_BEFORE or BP_AFTER 被写入的内存地址。
mem_write mem_write_length BP_BEFORE or BP_AFTER 内存写入的长度。
mem_write mem_write_expr BP_BEFORE or BP_AFTER 被写入的表达式。
mem_write mem_write_condition BP_BEFORE or BP_AFTER 内存写入的条件。
reg_read reg_read_offset BP_BEFORE or BP_AFTER 被读取的寄存器的偏移量。
reg_read reg_read_length BP_BEFORE or BP_AFTER 寄存器读取的长度。
reg_read reg_read_expr BP_AFTER 寄存器中的表达式。
reg_read reg_read_condition BP_BEFORE or BP_AFTER 寄存器读取的条件。
reg_write reg_write_offset BP_BEFORE or BP_AFTER 被写入的寄存器的偏移量。
reg_write reg_write_length BP_BEFORE or BP_AFTER 寄存器写入的长度。
reg_write reg_write_expr BP_BEFORE or BP_AFTER 被写入的表达式。
reg_write reg_write_condition BP_BEFORE or BP_AFTER 寄存器写入的条件。
tmp_read tmp_read_num BP_BEFORE or BP_AFTER 被读取的临时变量的编号。
tmp_read tmp_read_expr BP_AFTER 临时变量的表达式。
tmp_write tmp_write_num BP_BEFORE or BP_AFTER 临时变量写入的编号。
tmp_write tmp_write_expr BP_AFTER 写入临时变量的表达式。
expr expr BP_BEFORE or BP_AFTER IR表达式。
expr expr_result BP_AFTER 表达式被计算的值(比如AST)。
statement statement BP_BEFORE or BP_AFTER IR声明(在IR基本块中)的索引。
instruction instruction BP_BEFORE or BP_AFTER 本地指令的地址。
irsb address BP_BEFORE or BP_AFTER 基本块的地址。
constraints added_constraints BP_BEFORE or BP_AFTER 被添加的约束表达式的列表。
call function_address BP_BEFORE or BP_AFTER 被调用的函数的名称。
exit exit_target BP_BEFORE or BP_AFTER 表示符号执行退出目标的表达式。
exit exit_guard BP_BEFORE or BP_AFTER 表示符号执行退出监视的表达式。
exit exit_jumpkind BP_BEFORE or BP_AFTER 表示符号执行退出种类的表达式。
symbolic_variable symbolic_name BP_AFTER 被创建的符号变量的名字。求解引擎可能会修改这个名字(通过附加一个独一无二的ID和长度)。检查symbolic_expr来获取最终的符号表达式。
symbolic_variable symbolic_size BP_AFTER 被创建的符号变量的大小。
symbolic_variable symbolic_expr BP_AFTER 表示新符号变量的表达式。
address_concretization address_concretization_strategy BP_BEFORE or BP_AFTER 被用来解析地址的符号执行具体化策略。这个可以通过断点处理器调整来改变将被应用的策略。如果你的断点处理器将这个设置为None,这个策略会被跳过。
address_concretization address_concretization_action BP_BEFORE or BP_AFTER 被用来记录内存操作的符号执行动作对象。
address_concretization address_concretization_memory BP_BEFORE or BP_AFTER 操作的符号执行内存对象。
address_concretization address_concretization_expr BP_BEFORE or BP_AFTER 代表被解析内存下标的AST。断点处理器可以修改这个来影响被解析的地址。
address_concretization address_concretization_add_constraints BP_BEFORE or BP_AFTER 对于这个读取是否需要添加约束。
address_concretization address_concretization_result BP_AFTER 被解析的内存地址(整数)的列表。断点处理器可以覆写这些来影响一个不同的解析结果。
syscall syscall_name BP_BEFORE or BP_AFTER 系统调用的名称。
simprocedure simprocedure_name BP_BEFORE or BP_AFTER 符号执行过程的名称。
simprocedure simprocedure_addr BP_BEFORE or BP_AFTER 符号执行过程的地址。
simprocedure simprocedure_result BP_AFTER 符号执行过程的返回值。你也可以在BP_BEFORE中 覆写 它,这会导致实际的符号执行过程被跳过,从而转而使用你的返回值。
simprocedure simprocedure BP_BEFORE or BP_AFTER 实际的符号执行过程对象。
dirty dirty_name BP_BEFORE or BP_AFTER 脏调用的名字。
dirty dirty_handler BP_BEFORE 为了处理脏调用将要执行的函数。你可以覆写它。
dirty dirty_args BP_BEFORE or BP_AFTER 脏的地址。
dirty dirty_result BP_AFTER 脏调用的返回值。你也可以在BP_BEFORE中覆写它,这将会导致实际的脏调用被跳过,从而转而使用你的返回值。
engine_process sim_engine BP_BEFORE or BP_AFTER 正在处理中的符号执行引擎。
engine_process successors BP_BEFORE or BP_AFTER 定义引擎结果的符号执行后继对象。

这些属性可以在合适的断点回调至对合适的值访问的过程中作为state.inspect的成员来访问。你甚至可以修改这些值来调整未来对这些值的使用!

>>> def track_reads(state):
...     print('Read', state.inspect.mem_read_expr, 'from', state.inspect.mem_read_address)
...
>>> s.inspect.b('mem_read', when=angr.BP_AFTER, action=track_reads)

除此之外,这些属性中的每一个都可以被用作一个传递给inspect.b的关键词参数来让断点条件化:

# This will break before a memory write if 0x1000 is a possible value of its target expression
>>> s.inspect.b('mem_write', mem_write_address=0x1000)

# This will break before a memory write if 0x1000 is the *only* value of its target expression
>>> s.inspect.b('mem_write', mem_write_address=0x1000, mem_write_address_unique=True)

# This will break after instruction 0x8000, but only 0x1000 is a possible value of the last expression that was read from memory
>>> s.inspect.b('instruction', when=angr.BP_AFTER, instruction=0x8000, mem_read_expr=0x1000)

酷的东西!事实上,我们甚至可以指定一个函数作为条件:

# this is a complex condition that could do anything! In this case, it makes sure that RAX is 0x41414141 and
# that the basic block starting at 0x8004 was executed sometime in this path's history
>>> def cond(state):
...     return state.eval(state.regs.rax, cast_to=str) == 'AAAA' and 0x8004 in state.inspect.backtrace

>>> s.inspect.b('mem_write', condition=cond)

那真的是一些很酷的东西!

有关mem_read断点的注意事项

在任何时候通过执行程序或者是二进制文件分析产生的内存读取都会触发mem_read断点。如果你正在mem_read上使用断点并且也在使用state.mem来从内存地址中加载数据,那就要了解到只要你在确切的意义上读取内存,那么断点就会被触发。

所以如果你想要从内存中加载数据并且不触发任何你已经设置的mem_read断点,那就通过disable_actions=Trueinspect=Falase关键字参数使用state.memory.load

这对于state.find也是成立的,你可以使用相同的关键字参数来防止触发mem_read断点。

上一篇
下一篇