二进制漏洞挖掘之angr‘s Reaching Definition Analysis(二)


前言

如何编写一个函数处理程序来模拟一个函数对分析状态的影响?

一、背景

在高层次上,我们可以使用静态分析来收集有关程序变量的数据流事实,而无需执行它们。为了做到这一点,这种分析在某种程度上,按顺序解释程序语句对它所跟踪的状态的影响

如果这样的语句是一个函数调用呢?

  • 可以继续对目标函数的语句进行分析,然后在函数返回时跳转到它原来的位置。

如果这个函数是一个外部函数呢?例如由动态链接库提供的?

  • 在这种情况下,组成目标函数内容的语句(它在二进制文件中的实现)不能直接用于分析
  • 需要注意的一点是,我们并不是真的想把分析一个外部库作为进程的一部分:我们想把重点放在手边的二进制文件上,并且更倾向于避免花费资源(计算时间和内存)来跟踪“外部”发生的事情

但大多数时候,我们“知道”库函数的作用。下面是一些我们“知道”的libc函数的例子:

  • printf: 使用几个参数确定地组成一个字符串,并将其写入stdout;
  • malloc: 分配一个大小由第一个参数决定的内存块,并返回一个指向它的指针;
  • strcpy: 将第二个参数的内容复制到第一个参数所指向的内存区域。

从程序的角度,以及分析的角度来看,这些函数是黑盒:它们的实现细节是隐藏的。然而,我们只关心这些函数在程序运行时对系统状态的影响;从分析的角度来看,它们对这种状态的表现所产生的影响。

在第一种情况下(局部函数),函数处理程序应该驱动被调用函数的分析,并充分返回;在第二种情况下(外部函数),函数处理程序应该根据“已知的”函数行为更新分析状态。

二、Usage and Description

我们使用angr的reachingdefintionsanalysis来实现我们的分析。如文档中所述,它接受一个可选的function_handler参数。function_handler需要从FunctionHandler继承,正如你在FunctionHandler的文档中看到的,这意味着给定的function_handler必须有以下方法:

  • hook: 处理程序对分析的引用的一种方式,能够访问有关其上下文的信息(架构,知识库中收集的事实,等等)。特别是,reachingdefintionsanalysis在初始化时调用它;

  • handle_local_function:当遇到对本地函数的调用时,分析将运行。
    然后,为了让reachingdefintionsanalysis能够处理printfmallocstrcpy,我们将向从FunctionHandler继承的具体类添加相应的方法:handle_printfhandle_mallochandle_strcpy

  • 例如,这样一个具体的类MyHandlers,将产生暴露handle_printf的实例,当在二进制文件中遇到对printf的调用时,将在分析期间调用该实例(对于对mallocstrcpy的调用,分别为handle_mallochandle_strcpy)

简而言之:

  • “函数处理程序”是一个(Python)方法,当遇到调用指令时,分析将调用它。
  • FunctionHandler是一个ABC类,它描述了一个具体的类(比如MyHandlers)必须与angr一起工作
  • function_handler是传递reachingdefintionsanalysis的参数名;它是一种MyHandlers,因此是FunctionHandler的一种。

三、示例

分析的二进制程序

我们将分析command_line_injection.c生成的二进制文件。

git clone git@github.com:Pamplemousse/bits_of_static_binary_analysis.git
cd bits_of_static_binary_analysis
make

如果一切正常,运行./build/command_line_injection ~/应该会列出你的主目录

最简单的分析

从函数main开始的最简单的分析如下所示:

from angr import Project

project = Project('./build/command_line_injection', auto_load_libs=False)
cfg = project.analyses.CFGFast(normalize=True, data_references=True)

main_function = project.kb.functions.function(name='main')
program_rda = project.analyses.ReachingDefinitions(
    subject=main_function,
)

# Do domething with `program_rda`
...

但是,分析是过程内的:它只运行在函数main上。令人愉快的是,当执行python analysis.py时,angr会警告我们:“Please implement the local function handler with your own logic.”所以我们知道它遇到了对一个本地函数的调用.

处理本地函数

我们可以改进analysis.py,为reachingdefintionsanalysis提供必要的handle_local_function,该函数将在分析main时被触发,精确地在指令调用检查时被触发

from angr import Project
from angr.analyses.reaching_definitions.function_handler import FunctionHandler


class MyHandler(FunctionHandler):
    def __init__(self):
        self._analysis = None

    def hook(self, rda):
        self._analysis = rda
        return self

    def handle_local_function(self, state, function_address, call_stack, maximum_local_call_depth, visited_blocks,
                              dependency_graph, src_ins_addr=None, codeloc=None):
        function = self._analysis.project.kb.functions.function(function_address)

        # Break point so you can play around with what you have access to here.
        import ipdb; ipdb.set_trace()
        pass

        return True, state, visited_blocks, dependency_graph

project = Project('./build/command_line_injection', auto_load_libs=False)
cfg = project.analyses.CFGFast(normalize=True, data_references=True)

handler = MyHandler()

main_function = project.kb.functions.function(name='main')
program_rda = project.analyses.ReachingDefinitions(
    function_handler=handler,
    observe_all=True,
    subject=main_function
)

# Do domething with `program_rda`
...

运行python analysis.py时,由于handle_local_function中的断点,我们现在得到了一个shell。从这里开始,我邀请您调查和尝试您可以做什么;记住:你可以通过self._analysis访问很多angr收集的事实。项目是否为.arch.kb等。

处理外部函数

如前所述,处理程序也可以在调用库函数时被触发,并用于对不能直接分析的代码的效果进行建模。在我们的例子中,我们可以看到函数check调用libc函数sprintf。
下面是一个新的analysis.py,它展示了如何让分析来考虑这个调用;一个更丰富的MyHandler,包含一个handle_sprintf方法。

from angr import Project
from angr.analyses.reaching_definitions.function_handler import FunctionHandler


class MyHandler(FunctionHandler):
    def __init__(self):
        self._analysis = None

    def hook(self, rda):
        self._analysis = rda
        return self

    def handle_local_function(self, state, function_address, call_stack, maximum_local_call_depth, visited_blocks,
                              dependency_graph, src_ins_addr=None, codeloc=None):
        function = self._analysis.project.kb.functions.function(function_address)
        return True, state, visited_blocks, dependency_graph

    def handle_sprintf(self, state, codeloc):
        # Break point so you can play around with what you have access to here.
        import ipdb; ipdb.set_trace()
        pass

        return True, state

project = Project('./build/command_line_injection', auto_load_libs=False)
cfg = project.analyses.CFGFast(normalize=True, data_references=True)

handler = MyHandler()

sprintf_plt_stub = project.kb.functions.function(name='sprintf', plt=True)
program_rda = project.analyses.ReachingDefinitions(
    function_handler=handler,
    observe_all=True,
    subject=sprintf_plt_stub
)

# Do domething with `program_rda`
...

注意,为了示例的简单性,分析是从angr重新构建的sprintf PLT存根开始的。如果不是,这个例子将首先碰到handle_local_function,因为check有一个指向PLT位置的调用指令,而这个位置不是外部地址!换句话说,处理使用PLT机制调用的外部函数,需要使用适当的处理程序对目标PLT存根启动一个ReachingDefinitionsAnalysis
理想情况下,我们希望从函数检查开始分析,并期望handle_sprintf在某个时候被调用:特别是,分析应该使用handle_local_function将分析指向PLT存根,而PLT存根最终应该触发handle_sprintf.
巧合的是,这是一个更一般问题的特殊情况:如何执行过程间分析?

四、函数过程间分析

在现实世界的程序中,不太可能所有对分析师问题的回答都停留在浅层。大多数时候,我们希望从二进制文件的入口点开始分析,并期望它在各个函数调用之间继续进行,直到我们得到我们所寻找的信息。在我们的例子中,这意味着在main函数上启动reachingdefintionsanalysis,并期望它分析check,同时调用handle_sprintf。
因为我们希望一个分析能够运行多个函数,所以我们需要一个过程间的分析。遗憾的是,这目前还没有在angr主库中实现!
CSE545视频从一个“高层次”的角度介绍了如何将angr的reachingdefintionsanalysis转换为过程间的分析。
递归地运行它的想法是:

  • 每次遇到一个本地函数的调用,一个“孩子”ReachingDefinitionsAnalysis开始目标函数,一旦完成,分析国家在战争结束后回到父“复制”,为它继续(在调用指令)。

它的实现依赖于函数处理程序。特别地,handle_local_function是发生“递归”的地方:

  • 它在目标函数上启动子函数reachingdefintionsanalysis,并带有适当的参数(传递当前kb,使用init_state参数初始化子函数的父状态,转发function_handler);
  • 当子进程返回时,它更新父进程的observed_results,以便父进程知道在子进程运行期间捕获了什么;
  • 它返回父进程继续运行的状态(包含当前的live_definitions),以及分析记录的其他结构(visited_blocks, dep_graph)。

有些函数可以有几个出口(例如,在源文件中有多个return语句),因此从分析的角度来看有几个输出状态!在这种情况下,handle_local_function必须将这些状态合并在一起,以创建一个惟一的状态,以便父分析从该状态恢复。

五、Conclusion

函数处理程序是一个使用reachingdefiniationanalysis进行静态分析的方便工具:它们可以在不访问其实现的情况下应用外部函数的效果到状态
通过在局部函数上应用相同的原则,它们甚至使我们更进一步:过程间分析只不过是对调用指令的分析行为(递归性、状态管理和内部簿记)的定制。

  • 通过“污染”一个从用户输入获取值的变量,并在使用时传播这种污染,可以找到其他可能受用户输入影响的变量。被用于敏感操作(执行或系统的参数、对固定大小缓冲区的伪装等)的污染变量指出了潜在的安全漏洞
  • 如果您想了解关于分析如何工作的更多细节,以及此类分析的更具体的示例,强烈建议您去看看上面提到的演示
  • 对于那些对angr方面的底层机制感兴趣的人来说,handler的实例方法是在angr/analyses/reaching_definitions/ engine_vex.net .py中调用的。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值