我更关心的问题其实是,执行一条 stmt 后,如何反映到内存和寄存器上,如何记录约束信息,以及 VEX IR 的变量如何和机器的寄存器对应起来。
VEX IR 参考链接:
知乎专栏的这篇文章 说得已经很详细了。这里主要想引用的部分是,GET 和 PUT 指令都是用来操作寄存器的。而 offset 表示的是寄存器对应的序号,VEX 并没有为 CPU 寄存器创建一个新的存储类型,而是将 CPU 指令对寄存器的访问描述成对内存的访问,实现对寄存器操作的中间表示。VEX 在内存中为寄存器分配了存储空间,为每个寄存器分配了一个索引地址。
现在我想知道应该去哪查这个索引对应哪个寄存器,比如 offset=28 到底对应啥。看了这篇博客 我回想起来了 irsb.pp() 方法可以打印当前的 IRSB:
text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 IRSB { t0:Ity_I32 t1:Ity_I32 t2:Ity_I32 t3:Ity_I32 t4:Ity_I32 t5:Ity_I32 t6:Ity_I32 t7:Ity_I32 t8:Ity_I32 t9:Ity_I32 t10:Ity_I32 t11:Ity_I32 t12:Ity_I32 t13:Ity_I32 t14:Ity_I32 t15:Ity_I32 t16:Ity_I32 t17:Ity_I32 t18:Ity_I32 t19:Ity_I32 t20:Ity_I32 t21:Ity_I32 t22:Ity_I32 t23:Ity_I32 t24:Ity_I32 t25:Ity_I32 00 | ------ IMark(0x8048450, 2, 0) ------ 01 | PUT(ebp) = 0x00000000 02 | PUT(eip) = 0x08048452 03 | ------ IMark(0x8048452, 1, 0) ------ 04 | t4 = GET:I32(esp) 05 | t3 = LDle:I32(t4) 06 | t15 = Add32(t4,0x00000004) 07 | PUT(esi) = t3 08 | ------ IMark(0x8048453, 2, 0) ------ 09 | PUT(ecx) = t15 10 | ------ IMark(0x8048455, 3, 0) ------ 11 | t5 = And32(t15,0xfffffff0) 12 | PUT(cc_op) = 0x0000000f 13 | PUT(cc_dep1) = t5 14 | PUT(cc_dep2) = 0x00000000 15 | PUT(cc_ndep) = 0x00000000 16 | PUT(eip) = 0x08048458 17 | ------ IMark(0x8048458, 1, 0) ------ 18 | t8 = GET:I32(eax) 19 | t17 = Sub32(t5,0x00000004) 20 | PUT(esp) = t17 21 | STle(t17) = t8 22 | PUT(eip) = 0x08048459 23 | ------ IMark(0x8048459, 1, 0) ------ 24 | t19 = Sub32(t17,0x00000004) 25 | PUT(esp) = t19 26 | STle(t19) = t17 27 | PUT(eip) = 0x0804845a 28 | ------ IMark(0x804845a, 1, 0) ------ 29 | t12 = GET:I32(edx) 30 | t21 = Sub32(t19,0x00000004) 31 | PUT(esp) = t21 32 | STle(t21) = t12 33 | PUT(eip) = 0x0804845b 34 | ------ IMark(0x804845b, 5, 0) ------ 35 | t23 = Sub32(t21,0x00000004) 36 | PUT(esp) = t23 37 | STle(t23) = 0x08048460 NEXT: PUT(eip) = 0x08048483; Ijk_Call }
这么看来 28 对应 ebp,68 对应 eip。对应关系保存在 irsb.arch.register_size_names
中,可以调用 irsb.arch.translate_register_name()
来解析 offset。和程序的架构有关。具体这个表怎么构建的就不看了(其实是因为太麻烦了)。
IR 的执行 简单例子 1 —— “PUT(ebp) = 0x00000000” 上一篇文章提到了,执行一条 stmt 是通过调用 _handle_vex_stmt()
函数:
1 2 3 4 5 6 def _handle_vex_stmt (self, stmt: pyvex.stmt.IRStmt ): handler = self._vex_stmt_handlers[stmt.tag_int] handler(stmt)
执行 PUT 操作对应的 handler 就是 _handle_vex_stmt_Put()
函数:
1 2 3 4 5 def _handle_vex_stmt_Put (self, stmt ): self._perform_vex_stmt_Put( self._handle_vex_const(pyvex.const.U32(stmt.offset)), self._analyze_vex_stmt_Put_data(stmt.data))
对于 PUT(offset=28) = 0x00000000,stmt.offset 是寄存器的编号即 28,stmt.data 是一个 pyvex.expr.Const 对象,其值为 0x0。重点在 self._perform_vex_stmt_Put
函数。这个函数的具体实现在 SimStateStorageMixin 中:
1 2 3 4 5 6 7 8 9 class SimStateStorageMixin (VEXMixin ): ... ... def _perform_vex_stmt_Put (self, offset, data, action=None , inspect=True ): self.state.registers.store(offset, data, action=action, inspect=inspect) def _perform_vex_stmt_Store (self, addr, data, endness, action=None , inspect=True , condition=None ): self.state.memory.store(addr, data, endness=endness, action=action, inspect=inspect, condition=condition) ... ...
可以看到 SimStateStorageMixin 中定义的全都是和内存,寄存器存储有关的操作。PUT 操作的实现也非常简单,就是单纯的调用了 self.state.registers.store()
方法将表达式(BVV 0x0)保存到寄存器(EBP)里。
简单例子 2 —— “t4 = GET:I32(esp)” 由于 VEX IR 并没有新定义寄存器,所以 PUT/GET 操作直接对通用寄存器操作即可。不过 VEX IR 使用了临时变量如 t3,t4,我想看看这些中间变量是如何保存的。
对临时变量的读写操作定义在 VEXMixin 中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class VEXMixin (SimEngineBase ): ... ... def _analyze_vex_stmt_WrTmp_data (self, *a, **kw ): return self._handle_vex_expr(*a, **kw) def _handle_vex_stmt_WrTmp (self, stmt ): self._perform_vex_stmt_WrTmp( stmt.tmp, self._analyze_vex_stmt_WrTmp_data(stmt.data) ) ... ... def _handle_vex_expr (self, expr: pyvex.expr.IRExpr ): handler = self._vex_expr_handlers[expr.tag_int] result = handler(expr) return self._instrument_vex_expr(result)
这里明显只是入口函数,stmt.tmp 表示临时变量的序号。在这里 t4 对应的值就是 4。这里提一下,有一个成员变量 self.tmps 是一个数组,长度为 26,负责保存 26 个临时变量 (t0~t25)。另一个函数 self._analyze_vex_stmt_WrTmp_data()
负责解析出最终向临时变量保存什么值。在这个例子中,就是将 esp 的值取出来。真正的解析工作由 _handle_vex_expr()
实现。
之前在 VEXMixin 中看到 _handle_vex_expr 和 _handle_vex_stmt 这两个函数的时候还有点分不清楚,现在来看 stmt 表示的就是一个完整的语句,而 expr 则表示其内部取值取地址使用的表达式。
对于 GET:I32 表达式,expr.tag = Iex_Get
,找到的 handler 是 _handle_vex_expr_Get 函数:
1 2 3 4 5 6 7 class VEXMixin (SimEngineBase ): ... ... def _handle_vex_expr_Get (self, expr: pyvex.expr.Get ): return self._perform_vex_expr_Get( self._handle_vex_const(pyvex.const.U32(expr.offset)), expr.ty)
_handle_vex_const 之前已经见过了,就是把 expr.offset 转成 BVV 然后再转回整数。_perform_vex_expr_Get 函数还是在 SimStateStorageMixin 实现的。
1 2 3 4 5 class SimStateStorageMixin (VEXMixin ): def _perform_vex_expr_Get (self, offset, ty, action=None , inspect=True ): return self.state.registers.load(offset, self._ty_to_bytes(ty), action=action, inspect=inspect) ... ...