angr源码分析——库函数识别identify

在识别代码的同源性时,多数情况下采用的方法是依赖于代码本身的数据特征例如Flirt技术的前32位字节码,函数crc;diaphora的fuzzing hash等。而在二进制代码中,不同的编译环境或库版本都会对最终生成的库函数的代码带来巨大的变化,导致依赖于数据特征的库函数识别技术识别率极低,或者干脆无用。那么此时为了识别一个函数我们需要依赖的就是函数的语义

函数语义代表这个函数做了什么,而不是是什么。

做了什么包括对寄存器的操作,对内存的操作,对其他函数的调用,本身的调用流。

人工逆向的时候,我们通常分析的也是函数语义。

angr这个符号执行框架就提供给我们一个获取程序语义的接口,甚至我们可以通过动态符号执行两个函数来判断执行后的结果是否相同,来判断函数是否是相似的。这也是angr库函数识别的原理。下面我们就开始分析angr中已有的一个identify function的代码吧。

Ps:目前angr库函数识别只有几个库函数,这几个库函数的特征数据库(库函数的特征数据实际上是一个Python脚本,或者说一是一个类,存放了)是他们手动生成的,分析的二进制代码也是针对CGC竞赛的二进制数据集。所以需要改进的地方还有很多,例如,如何自动生成库函数的特征数据库?

1.库函数识别流程分析

从整体来看代码的指令流程为:
先初始化,初始化包括:cfg(主要就是判断cfg中的function集合),寄存器列表,用于记录和匹配的列表和字典,用于符号执行的符号状态,获取cfg中每个函数的func_info.
接下来就开始识别了,识别流程 run——》identify_func——》处理特殊函数。
identify_func就是我们识别的主要逻辑,它的判断流如下:
1.判断函数参数个数是否相等;
2.判断函数调用其他函数的个数是否相同;
3.调用check_tests判断两个函数相同状态为起点,相同输入下符号执行的结果是否相同。
如果相同就说明两个函数是相似的,或者说识别出了cfg中的这个函数。

2.细节处理

这里我们主要关注两点
2.1.如何获取func_info呢?func_info的定义里包括了参数个数,栈帧大小,压栈的寄存器,缓冲区等信息。如何从汇编代码或者符号执行获取这些信息是值得探究的,方遍我们以后人工逆向。
2.2.如何判断两个符号执行的输出是相等的,即符号执行结果的表示形式是什么?

1.符号执行获取栈,寄存器信息
这部分的主要逻辑代码在:find_stack_vars_x86函数里。这部分函数分析要求我们熟悉x86的函数调用约定,以后补充。

2.符号执行的结果表示
库函数直接调用gen_input_output_pair函数来获得执行结果 test_data。
执行结果包括的数据为(test_input, test_output, return_val, max_steps)
angr已有的库函数的test_input,要么是随机生成的要么是常量,所以看出来确实需要很多人工实现。
test_output代表输入数据运行后的结果,可不是返回值哦!
return_val才是返回值。
将执行结果传入runner.test(),判断是否相同。
分析runnet.test函数逻辑,发现也是简单的不得了,不过仍然有以下细节需要注意:

test_input和test_output都是实际值?这是为什么?

因为符号值可能会让程序陷入路径爆炸,而且运行结果为符号值的话判断较为复杂。

test_input作为程序输入是如何传入到目标函数里的?

这个我在自己给函数传值的时候也会遇到过,通常想法是把这个值直接复制给寄存器,当作参数传入,但是实际寄存器存放的是变量的指针呀。所以我们需要把实际值存入一个内存空间,然后把这个内存地址赋值给寄存器,这样才能让函数正常运行。

运行时的引擎为什么是unicorn?

这个可能跟输入输出为实际值有关,符号值用vex,实际值用unicorn?

angr库函数识别的代码在:angr/angr/analyses/identifier/identify.py

from collections import defaultdict
from itertools import chain
import logging

from networkx import NetworkXError

from cle.backends.cgc import CGC

from .errors import IdentifierException
from .functions import Functions
from .runner import Runner
from .. import Analysis
from ... import options
from ...errors import AngrError, SimSegfaultError, SimEngineError, SimMemoryError, SimError

l = logging.getLogger("identifier.identify")


NUM_TESTS = 5


class FuncInfo(object):
    def __init__(self):
        self.stack_vars = None #栈变量
        self.stack_var_accesses = None 
        self.frame_size = None  #栈帧大小
        self.pushed_regs = None #已经压栈的寄存器
        self.stack_args = None  #栈参数
        self.stack_arg_accesses = None
        self.buffers = None   #缓冲区
        self.var_args = None  #变量参数
        self.bp_based = None  #基址寄存器
        self.bp_sp_diff = None #bp和sp差值?
        self.accesses_ret = None #返回值
        self.preamble_sp_change = None #sp变化


class Identifier(Analysis):

    _special_case_funcs = ["free"]
    #这是初始化函数,初始化函数的基本信息
    def __init__(self, cfg=None, require_predecessors=True, only_find=None):
        # self.project = project
        if not isinstance(self.project.loader.main_object, CGC):
            l.critical("The identifier currently works only on CGC binaries. Results may be completely unexpected.")
        #可以看出是需要cfg的
        if cfg is not None:
            self._cfg = cfg
        else:
            self._cfg = self.project.analyses.CFGFast(resolve_indirect_jumps=True)
        self._runner = Runner(self.project, self._cfg)
 
        # only find if in this set
        self.only_find = only_find
        初始化寄存器列表
        # reg list
        a = self.project.arch
        self._sp_reg = a.register_names[a.sp_offset]
        self._bp_reg = a.register_names[a.bp_offset]
        self._ip_reg = a.register_names[a.ip_offset]
        self._reg_list = a.default_symbolic_registers
        self._reg_list = filter(lambda r: r != self._sp_reg, self._reg_list)
        self._reg_list = filter(lambda r: r != self._ip_reg, self._reg_list)
        #记录匹配结果
        self.matches = dict()
        #记录调用
        self.callsites = None
        self.inv_callsites = None
        self.func_info = dict()#记录函数信息
        self.block_to_func = dict() #记录属于function的block信息

        self.map_callsites()#匹配调用

        if self._too_large():  #判断函数是不是太大了
            l.warning("Too large")
            return
        #初始化一个符号状态 
        self.base_symbolic_state = self.make_symbolic_state(self.project, self._reg_list)
        #关闭状态中支持浮点运算的选项
        self.base_symbolic_state.options.discard(options.SUPPORT_FLOATING_POINT)
        self.base_symbolic_state.regs.bp = self.base_symbolic_state.se.BVS("sreg_" + "ebp" + "-", self.project.arch.bits)
        
        for f in self._cfg.functions.values():
            #跳过系统调用和已经被hook的函数
            if f.is_syscall:
                continue
            if self.project.is_hooked(f.addr):
                continue

            # skip if no predecessors 如果要求有前继点,而函数本身没有的话就跳过
            try:
                if require_predecessors and len(list(self._cfg.functions.callgraph.predecessors(f.addr))) == 0:
                    continue
            except NetworkXError:
                if require_predecessors:
                    continue

            # find the actual vars
            try:#获得函数信息 信息为在上面声明的func info里面的
                func_info = self.find_stack_vars_x86(f)#针对的是x86指令的
                self.func_info[f] = func_info
            except (SimEngineError, SimMemoryError) as e:
                l.debug("angr translation error: %s", e.message)
            except IdentifierException as e:
                l.debug("Identifier error: %s", e.message)
            except SimError as e:
                l.debug("Simulation error: %s", e.message)

    #如果cfg中的函数数目超过400就返回
    def _too_large(self):
        if len(self._cfg.functions) > 400:
            return True
        return False

    #实际识别函数
    def run(self, only_find=None):
        if only_find is not None:
            self.only_find = only_find

        if self._too_large():
            l.warning("Too large")
            return

        for f in self._cfg.functions.values():
            if f.is_syscall:
                continue
            match = self.identify_func(f)#识别的关键函数!!!
            if match is not None:
                match_func = match
                match_name = match_func.get_name()
                if f.name is not None:
                    l.debug("Found match for function %s at %#x, %s", f.name, f.addr, match_name)
                else:
                    l.debug("Found match for function %#x, %s", f.addr, match_name)
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值