符号执行之angr学习-最简单的angr分析

本文主要通过一个简单的案例来说明angr的使用,同时,尽可能的将上文中提到的一些指令、代码实践下。

一 案例

在上文中,我才发现如果只是利用angr实现间的分析的话,真的很容易做到,上文中对angr的一些指令有了充分的解释,本文就利用这些解释来实现我们的第一个程序分析之旅。(转载请注明出处)

首先,安装好angr后,我们需要一个二进制文件,为方便起见,我直接用了https://github.com/angr/angr-doc/tree/master/examples/sym-write里面提供的例子,仅需要issue.c这个C文件就好。(感谢胖胖鹏鹏胖胖鹏

第一:基操勿六(皮一下)

符号执行之angr学习-最简单的angr分析

第二,开始编写python脚本

# -*- coding: utf-8 -*-  
import angr
import claripy

def main():
    proj = angr.Project('./issue',load_options={"auto_load_libs":False})
    #创建一个工程并导入二进制文件——issue,选择不自动加载依赖项(可选)
    #auto_load_libs,用CLE自动解析共享库依赖关系,默认为on。
    #except_missing_libs,当二进制文件有无法解析的共享库依赖项时,将引发异常默认为on。
    #将字符串列表传递给force_load_libs,所列出的内容将作为未解析的共享库依赖项处理,
    #将字符串列表传递给skip_libs,以防止将该名称的任何库解析为依赖项。
    #main_opts是一个从选项名到选项值的映射
    #lib_opts是一个从库名到字典的映射,将选项名映射到选项值

    #以下是对二进制文件的一些基本操作(可选)
    print proj.arch #架构
    #proj.entry #二进制程序入口点 
    #proj.filename #程序名称以及位置
    #proj.loader#是通过CLE模块将二进制对象加载并映射带单个内存空间
    #proj.loader.min_addr#proj.loader 的低位地址
    #proj.loader.max_addr#proj.loader 的高位地址
    #proj.loader.all_objects #CLE加载的对象的完整列表
    #proj.loader.shared_objects#这是一个从共享对象名称到对象的字典映射
    #proj.loader.all_elf_objects#这是从ELF文件中加载的所有对象
    #proj.loader.all_pe_objects#加载一个windows程序
    #proj.loader.main_object#加载main对象
    #proj.loader.main_object.execstack#这个二进制文件是否有可执行堆栈
    #proj.loader.main_object.pic#这个二进制位置是否独立
    #proj.loader.extern_object#这是“externs对象”,我们使用它来为未解析的导入和angr内部提供地址
    #proj.loader.kernel_object#此对象用于为模拟的系统调用提供地址
    #proj.loader.find_object_containing(0x400000)#获得对给定地址的对象的引用
    
    #以下是对确定的对象进行基本操作(可选)
    obj = proj.loader.main_object#指定main对象
    print obj.entry#获取地址
    #obj.min_addr, obj.max_addr#地址的地位和高位
    #obj.segments#检索该对象的段
    #obj.sections#检索该对象的节
    #obj.find_segment_containing(obj.entry)#通过地址获得单独的段
    #obj.find_section_containing(obj.entry)#通过地址获得单独的节
    #addr = obj.plt['abort']#通过符号获取地址
    #obj.reverse_plt[addr]#通过地址获取符号
    #obj.linked_base
    #obj.mapped_base#显示对象的预链接基以及CLE实际映射到内存中的位置
    
    state = proj.factory.entry_state(add_options={angr.options.SYMBOLIC_WRITE_ADDRESSES})
    #返回一个simstate,SimState包含程序的内存、寄存器、文件系统数据……任何可以通过执行更改的“实时数据”均在SimState。
    #.entry_state的替换:
    #.blank_state()#构造了一个“空白石板”空白状态,其大部分数据未初始化。
    #.full_init_state()构造一个状态,该状态可以通过需要在主二进制的入口点之前运行的任何初始化程序执行,例如共享库构造函数或预初始化程序。
    #.call_state()构造准备执行给定函数的状态。

    #通过state来访问一些寄存器的数值(可选)
    #state.regs.rip
    #state.regs.rax
    #state.regs.rbp = state.regs.rsp#将寄存器rsp的值给rbp
    #注意:这儿采用的bitvectors,并不是python值,后面会说明python和bitvectors的转换

    u = claripy.BVS("u",8)#建立一个名称为u,8位宽的符号变量
    #claripy.BVS和state.solver.BVS有什么区别还不清楚,需要验证
    #可以通过.eval(A)的方法将A(bitvectors)转化位python int型
    #a = state.solver.FPV(3.2, state.solver.fp.FSORT_DOUBLE)#通过FPV来创建浮点型向量
    #raw_to_bv和raw_to_fp方法将位向量解释为浮点数,反之亦然:

    state.memory.store(0x601041,u)##使用.memory.store(addr,val)方法将数据val保存在地址位addr的内存中,0x601041代表的是二进制文件中的某个变量的地址,具体获取见下文

    simgr = proj.factory.simulation_manager(state)#使用的模拟管理器来管理状态或状态列表。state被组织成stash,可以forward, filter, merge, and move 。
    #simgr.active#存储操作
    #simgr.step()#执行一个基本块的符号执行,即就是所有状态向前推进一个基本块(类似单步操作,这儿是单块操作)
    #simgr.active#以列表的形式更新存储
    #simgr.run()#.run()方法直接执行程序,直到一切结束,运行simgr会查看返回deadended数目
    
    #active#此存储区包含默认情况下将逐步执行的状态,除非指定了备用存储区,可替换的方法如下
    #deadended#当一个状态由于某种原因不能继续执行时,包括没有任何有效指令、所有后续的未sat状态或无效的指令指针,它就会进入死区隐藏。
    #pruned#当在lazy_resolve存在的情况下发现一个状态未sat时,将遍历该状态层次结构,以确定在其历史上,它最初何时成为unsat。所有这一观点的后裔州(也将被取消sat,因为一个州不能成为取消sat)都将被修剪并放入这一储备中。
    #unconstrained#无约束的状态(即,由用户数据或其他符号数据来源控制的指令指针)放在这里。
    #unsat#不可满足的状态(即,它们有相互矛盾的约束,比如输入必须同时为“AAAA”和“BBBB”)。

    while len(simgr.active)!=2:
        simgr.step()#循环运行直到通过.active来判断是否进入了分支,这是因为angr在遇到分支时,会将每个分支作为一个状态来保存。

    return simgr.active[0].state.se.eval(u)#返回结果为win的u值,要是想返回lose的u值,将active[0]中0变为1即可

if __name__ == "__main__":
    print repr(main())#repr() 函数将对象转化为供解释器读取的形式。

代码 state.memory.store(0x601041,u)中地址来源

将生成的test二进制文件在ida中打开,找到.bss段,可以发现

符号执行之angr学习-最简单的angr分析

这个u就是我们关注的变量,其地址就是0x601041

程序运行结果:

符号执行之angr学习-最简单的angr分析

分别打印出了架构,main地址(并非时16进制),以及打印win时的u值

二 存在的一些问题

1 state = proj.factory.entry_state(add_options={angr.options.SYMBOLIC_WRITE_ADDRESSES})不同于胖胖鹏鹏胖胖鹏中的 state = proj.factory.entry_state(add_options={"SYMBPLIC_WRITE_ADDRESS"}),在初次运行时,我采用了add_options={"SYMBPLIC_WRITE_ADDRESS"}这个,却报错:缺失SYMBPLIC_WRITE_ADDRESS,而改为add_options={angr.options.SYMBOLIC_WRITE_ADDRESSES}后正常运行。

2 obj = proj.loader.main_object#指定main对象
              print obj.entry#获取地址,打印的地址为4195488,转换为十六进制为0x4004A0,在ida中的0x4004A0是:

符号执行之angr学习-最简单的angr分析

不知道是否有问题。

3 simgr.active[0].state.se.any_int(u)中使用any_int()也会报错,会提示使用.eval()来打印,即就是simgr.active[0].state.se.eval(u)

三 总结

从上述过程来看,利用angr分析程序时有个一般的流程

1 导入angr

2 导入二进制文件

3 建立状态

4 定义符号变量并二进制文件相联系

5 建立simgr,用于管理state

6 通过.active的变化来找满足我们要求的目标状态

7 获取目标状态的数值

四 感谢以及参考

小型程序分析并不难,但是其中也遇见一些挫折,感谢胖胖鹏鹏胖胖鹏Badrerangr的博文。