CLE 是 angr 加载二进制文件的组建,在加载二进制文件的时候会分析病读取 binary 的信息,包括指令地址、shared library、arch information 等等。
- >>> import angr
- >>> b = angr.Project("./test")
angr 用 VEX IR 将指令转化为中间语言 IR,分析 IR 并且模拟,搞清楚它是什么并且做了什么。
如下的 ARM 指令
- subs R2, R2, #8
转化为 VEX IR
- t0 = GET:I32(16)
- t1 = 0x8:I32
- t3 = Sub32(t0,t1)
- PUT(16) = t3
- PUT(68) = 0x59FC8:I32
angr 的求解引擎叫 Claripy,具体这一步做什么呢,根据程序所需要的输入设置符号变量以及收集限制式等等。
- >>> import claripy
- >>> bv = claripy.BVV(0x41424344, 32)
这个例子说明了 angr 的简单用法,用例程序来自 https://github.com/angr/angr-doc/tree/master/examples/sym-write,其中有三个文件,一个是 C 源码,一个是二进制代码,还有一个是 python 脚本文件也就是我们的测试脚本。
源码如下
- #include
- char u=0;
- int main(void)
- {
- int i, bits[2]={0,0};
- for (i=0; i<8; i++) {
- bits[(u&(1<<i))!=0]++; }="" if="" (bits[0]="=bits[1])" {="" printf("you="" win!");="" else="" lose!");="" return="" 0;="" }
这个源码很简单就不介绍了。
接下来就是 python 脚本文件,GitHub 上面的脚本文件在我的虚拟机上面运行出错,据本人分析应该是 angr 更新之后对于有一些属性进行了修改,比如脚本文件中的 sm=p.factory.simgr(state) 这一行代码就会报错,因为没有 simgr 这个属性,以及在 main 函数 return 的地方 sm.found[0].state.se.any_int(u),这样修改才不会报错,不然会出现 path 没有 se 属性的错误。为了便于阅读,我自己写了一个 python 脚本对这个二进制代码进行分析。
- import angr
- import claripy
- def main():
- p = angr.Project('./issue', load_options={"auto_load_libs": False})
- state = p.factory.entry_state(add_options={"SYMBOLIC_WRITE_ADDRESSES"})
- u = claripy.BVS("u", 8)
- state.memory.store(0x804a021, u)
- sm = p.factory.path_group(state)
- sm.step(until = lambda lpg: len(lpg.active) > 1)
- for I in range(len(sm.active)):
- print "possible %d: " % I , sm.active[I].state.se.any_int(u)
- return sm.active[0].state.se.any_int(u)
- if __name__ == '__main__':
- print(repr(main()))
代码分析:
新建一个 angr 工程,导入二进制文件,./issue 是二进制文件的路径。后面选项是是否自动加载依赖。
- p = angr.Project('./issue', load_options={"auto_load_libs": False})
紧接着创建一个 SimState 对象,SimState 的对象在 angr 其中的一个子模块 SimuVEX 中,这个对象记录着符号信息,符号对应的内存信息,寄存器信息等等。
- state = p.factory.entry_state(add_options={"SYMBOLIC_WRITE_ADDRESSES"})
设置符号变量,读 C 源码可以知道,变量 u 其实可以视为一个外部输入,根据 u 的值的不同,最后得到的结果也不相同,如果 u 的二进制表示中 1 和 0 的个数相同就返回 win,否则返回 lose。所以在此设计符号变量 u
- u = claripy.BVS("u", 8)
将符号变量保存在内存的指定地址处
- state.memory.store(0x804a021, u)
接下来根据状态可以获取路径组对象,这个路径和你的状态参数有关,如果是入口状态那么你得到的路径就是入口处的路径。从获得的路径点开始,之后所有此路径分裂出去的路径都会包含在此路径组里面。
- sm = p.factory.path_group(state)
然后开始执行,一直执行到 active 状态的路径超过一条的时候停止,即执行到 C 源码的 if(bits[0]==bits[1]) 处,产生了两条路径,然后停止。
- sm.step(until = lambda lpg: len(lpg.active) > 1)
然后打印出所有路径下的的 U 的取值
- for I in range(len(sm.active)) : print "possible %d: " % I,
- sm.active[I].state.se.any_int(u)
返回结果是 win 的 u 的取值
- return sm.active[0].state.se.any_int(u)
到此主要代码分析结束,在 GitHub 上面有原始的 python 脚本,那个脚本只是输出结果为 win 时候的 U 的取值,但是那个脚本有错误,并且不太简介,由此我在此基础上做了修改,写了上述脚本,供大家参考分析。
以上就是 angr 的基本用法,本人对于 angr 的理解也还是很浅显,有些地方也不是特别清楚,如果有错的地方,希望大家指点。
来源: http://www.2cto.com/kf/201708/666415.html