angr分析程序的CFG
概述
在二进制上实现基本的分析一般是通过控制流图。一个CFG是一个基本块为节点,jump/ret/call为边的图。
在angr里,主要有两种CFG,一种是静态的CFG(CFGFast)和动态的CFG(CFGEmulated)
CFGFast使用静态分析来产生CFG。生成的速度很快,但是理论上有些控制流的转换只在执行的时候确定。这个和其他逆向工具差不多,结果也匹配输出。
CFGEmulated使用符号执行来去获取CFG。理论上更精准,但是比较慢。目前还不是完整的,因为模拟执行时的准确性不是很好。
可以用下面的代码来构建CFG:
>>> import angr
# load your project
>>> p = angr.Project('/bin/true', load_options={'auto_load_libs': False})
# Generate a static CFG
>>> cfg = p.analyses.CFGFast()
# generate a dynamic CFG
>>> cfg = p.analyses.CFGEmulated(keep_state=True)
使用CFG
CFG是基于NetworkX di-graph实现的。意味着NetworkX 的API都可以用。
>>> print("This is the graph:", cfg.graph)
>>> print("It has %d nodes and %d edges" % (len(cfg.graph.nodes()), len(cfg.graph.edges())))
CFG图的节点是CFGNode
类的实例。由于上下文的敏感性,一个给定的基本块可以有多个节点在图中。
# this grabs *any* node at a given location:
>>> entry_node = cfg.get_any_node(p.entry)
# on the other hand, this grabs all of the nodes
>>> print("There were %d contexts for the entry block" % len(cfg.get_all_nodes(p.entry)))
# we can also look up predecessors and successors
>>> print("Predecessors of the entry point:", entry_node.predecessors)
>>> print("Successors of the entry point:", entry_node.successors)
>>> print("Successors (and type of jump) of the entry point:", [ jumpkind + " to " + str(node.addr) for node,jumpkind in cfg.get_successors_and_jumpkind(entry_node) ])
查看CFG
控制流图render是一个复杂的问题。angr不会去修改CFG分析的输出,而是用传统的图修改库,比如matplotlib。
要可视化CFG可以在这里查看:axt’s angr-utils repository
共享库
angr默认情况下会自动分析加载的共享库。这有可能导致分析需要好几天的时间。如果要加载一个没有共享库的二进制,需要在Project那边加个参数。
load_options={'auto_load_libs': False}
函数管理器
cfg提供一个对象叫Function Manager,这个对象可以通过cfg.kb.functions
来访问。这个对象最常用的使用方法是通过字典来访问。它会映射地址到Function对象,也就能告诉你函数的属性。
>>> entry_func = cfg.kb.functions[p.entry]
函数有几个重要的属性:
entry_func.block_addrs
:属于函数开始的基本块的地址entry_func.blocks
:属于这个函数基本块的集合。entry_func.string_references
:列出函数里引用的常量字符串entry_func.returning
:bool值,看函数是否有返回值entry_func.callable
:与函数相关的可调用对象entry_func.transition_graph
:entry_func.name
:函数名entry_func.has_unresolved_calls
:用来检测CFG的不准确性entry_func.has_unresolved_jumps
:用来检测CFG的不准确性entry_func.get_call_sites
:返回一个列表,包含所有基本块的地址的结束是call的entry_func.get_call_target(callsite_addr)
:给定callsite_addr,返回callsite的目标entry_func.get_call_return(callsite_addr)
:给定callsite_addr,返回callsite要return回去的地方
上面都是文档的内容,实际上自己去用angr去构造控制流图,抽取信息,还需要看network X和angr的源码来获取相关的接口。
下面讲个具体的例子。就比如你要获取cfg里所有基本块的跳转的目的基本块地址。可以用下面的代码实现:
import angr
result = {}
proj = angr.Project("hello",load_options={"auto_load_libs":False})
cfg = proj.analyses.CFGFast()
#获取cfg里的所有节点,放在一个列表里,列表每个元素都是CFGNode对象
cfg_node_list = list(cfg.graph.nodes)
for cfg_node in cfg_node_list:
src_addr = hex(cfg_node.addr)
#初始化保存结果的字典
result[src_addr]=[]
#获取当前节点的后继及跳转属性,这个successors_and_jumpkinds()函数在angr源码的CFGNode.py有提及
succ_list = cfg_node.successors_and_jumpkinds()
for succ_item in succ_list:
#将后继基本块的地址保存在字典里
result[src_addr].append(hex(succ_item[0].addr))
print(result)
要可视化CFG图,可以用这个开源工具实现:https://github.com/axt/angr-utils
只需要一行代码就能生成cfg的png文件了。
plot_cfg(cfg, "ais3_cfg", asminst=True, remove_imports=True, remove_path_terminator=True)
总结
angr文档虽然已经写得很详细了,但实际去应用angr写点什么东西(程序分析,逆向,利用等等),还需要去看他源码怎么写的,看他对象有哪些属性,方法可以调用。