louys louys

angr 小笔记

in 不务正业read (456) 文章转载请注明来源!

前言

最近在angr的基础上写了一个小玩具。总体而言,angr还是非常强大和好用的,能够让我这种Web选手都能自动化的分析二进制程序,但是缺点就是angr的文档真的是十分不全,使用方法基本靠猜。还有一些在论文里面提到的方法,实际的angr中竟然找不到一个使用实例。本文就是写一些写玩具时碰到的坑和解决方法

正文

cfg生成

大多数人用angr都是希望实现自动化分析,那么程序流图的生成就是必不可少的,后续很多工作都要在此基础上进行。

angr提供了两种程序流图的生成方法,CFGFast和CFGAccurate。其中fast是真的fast,而accurate不是真的accurate。

原因是fast的生成模式是基于静态分析的,而accurate是基于符号执行的。当分析复杂的程序比如固件的时候,fast的效果要远好于accurate。

fast生成

self.project = angr.Project(self.path, load_options={'auto_load_libs': False})
self.cfg = self.project.analyses.CFGFast()

这样就可以十分快速的得到一个程序的cfg。

当然accurate也不是毫无用处,angr的一些分析是基于accurate生成的cfg上进行的,而直接对一个比较复杂的程序进行分析需要耗费大量时间,我们可以通过starts参数和base_graph参数避免分析整个程序。

sub = target.cfg.graph.subgraph(maybe_path)
cfg = target.project.analyses.CFGAccurate(context_sensitivity_level=3,keep_state=True,base_graph=sub,starts=[xx.addr])

比如这样,另外推荐使用pypy来运行CFGAccurate,可以提升一定的速度。

cfg的基本用法

angr分析完后的cfg是由一个个程序块组成的图,图本身是基于networkx的DiGraph。

下面介绍一些cfg的基本用法

1.通过地址找到对应的节点

self.cfg.get_any_node(addr)

有时我们想要获得某个函数所对应的节点,我们可以这样进行查找

self.cfg.get_any_node(self.project.loader.main_object.plt[func])

2.寻找节点的前继后继

定位到某个块或者程序所对应的节点后,我们往往想要知道改节点的前继后继。在实际情况中就是知道某个函数的入口和出口。

node = self.cfg.get_any_node(addr)
xx = self.cfg.graph.predecessors(node)
yy = self.cfg.graph.successors(node)

这样我们就可以实现类似ida中的x键功能,找到某函数所有的交叉引用点。

需要注意的是不同的angr版本,返回的结果不同,有的版本是直接返回了list,而有的版本返回的是一个生成器。

另外在处理函数的入口和出口的时候要注意一一对应关系,直接调用networkx的相关图算法分析出来的路径经过函数后就是乱的需要重写。

字符串引用

通常我们在分析二进制程序时,程序块中的字符串引用可以很好的帮助理解逻辑。

angr中提供了一个string_references函数,在文档中声称能够找到字符串引用,但是不知道为什么我在实际使用的过程中,返回的永远都是空列表。

于是经过简单的研究后,自己实现了查找字符串引用的功能。

rodata_header = self.project.loader.main_object.sections_map['.rodata']
self.rodata_start = rodata_header.min_addr
self.rodata_end = rodata_header.max_addr
'''
其他代码
'''
    def read_addr_string(self,addr):
    #读取某个地址的字符串
        result = ""
        memory = self.project.loader.memory
        while memory[addr] in string.printable:
            result += memory[addr]
            addr += 1
        return result
    def read_function_string(self,addr):
        #读取一个函数内的字符串引用
        #本来angr是有api的,不过返回万年为[],所以先这么写吧
        func = self.cfg.functions[addr]
        strings = []
        for block in func.blocks:
            for insn in block.capstone.insns:
                if insn.mnemonic == 'mov':
                    if insn.operands[1].reg > self.rodata_start and insn.operands[1].reg < self.rodata_end:
                        s = self.read_addr_string(insn.operands[1].reg)
                        if len(s) > 1:
                            strings.append(s)
        return strings

其实就是在程序内寻找mov操作,如果操作对象在rodata段就开始读取字符串,emmmm这么实现可能在某些情况中考虑的不完善,不过本人使用过程中还没碰到什么问题。

需要注意的是这种查找方法是在AMD64下的,如果分析的是mips的话可能就比较难搞,首先它的操作就不是mov,其次以dlink某款型号(就是有那个后门的)为例,

.text:0043F294                 lw      $a0, 0xC($s0)
.text:0043F298                 la      $a1, aNotPermitted  # " not permitted.\n"
.text:0043F29C                 nop
.text:0043F2A0                 addiu   $a1, (aTmp - 0x470000)  # "tmp"
.text:0043F2A4                 la      $t9, strcmp

在ida中轻松识别出了相关引用和函数,而在angr中它长这样

0x43f294:   lw  $a0, 0xc($s0)
0x43f298:   lw  $a1, -0x7fe4($gp) #string
0x43f29c:   nop
0x43f2a0:   addiu   $a1, $a1, 0x78dc
0x43f2a4:   lw  $t9, -0x7a8c($gp)

我目前只能猜出来字符串时怎么识别的,函数调用还不知道怎么识别,orz。

还有一些诸如ddg,后向切片但是我都找不到较好的资料进行学习使用,而且目前符号执行这一块也还没引入,还有很多值得学习的地方。

jrotty WeChat Pay

微信打赏

jrotty Alipay

支付宝打赏

文章二维码

扫描二维码,在手机上阅读!

此处评论已关闭

博客已萌萌哒运行
© 2018 由 Typecho 强力驱动.Theme by Yodu
前篇 后篇
雷姆
拉姆