文章目录
前言
如何编写一个函数处理程序来模拟一个函数对分析状态的影响?
一、背景
在高层次上,我们可以使用静态分析来收集有关程序变量的数据流事实,而无需执行它们。为了做到这一点,这种分析在某种程度上,按顺序解释程序语句对它所跟踪的状态的影响
如果这样的语句是一个函数调用呢?
- 可以继续对目标函数的语句进行分析,然后在函数返回时跳转到它原来的位置。
如果这个函数是一个外部函数呢?例如由动态链接库提供的?
- 在这种情况下,组成目标函数内容的语句(它在二进制文件中的实现)不能直接用于分析
- 需要注意的一点是,我们并不是真的想把分析一个外部库作为进程的一部分:我们想把重点放在手边的二进制文件上,并且更倾向于避免花费资源(计算时间和内存)来跟踪“外部”发生的事情
但大多数时候,我们“知道”库函数的作用。下面是一些我们“知道”的libc函数的例子:
- printf: 使用几个参数确定地组成一个字符串,并将其写入stdout;
- malloc: 分配一个大小由第一个参数决定的内存块,并返回一个指向它的指针;
- strcpy: 将第二个参数的内容复制到第一个参数所指向的内存区域。
从程序的角度,以及分析的角度来看,这些函数是黑盒:它们的实现细节是隐藏的。然而,我们只关心这些函数在程序运行时对系统状态的影响;从分析的角度来看,它们对这种状态的表现所产生的影响。
在第一种情况下(局部函数),函数处理程序应该驱动被调用函数的分析,并充分返回;在第二种情况下(外部函数),函数处理程序应该根据“已知的”函数行为更新分析状态。
二、Usage and Description
我们使用angr的reachingdefintionsanalysis
来实现我们的分析。如文档中所述,它接受一个可选的function_handler
参数。function_handler
需要从FunctionHandler
继承,正如你在FunctionHandler
的文档中看到的,这意味着给定的function_handler
必须有以下方法:
-
hook
: 处理程序对分析的引用的一种方式,能够访问有关其上下文的信息(架构,知识库中收集的事实,等等)。特别是,reachingdefintionsanalysis
在初始化时调用它; -
handle_local_function
:当遇到对本地函数的调用时,分析将运行。
然后,为了让reachingdefintionsanalysis
能够处理printf
、malloc
或strcpy
,我们将向从FunctionHandler
继承的具体类添加相应的方法:handle_printf
、handle_malloc
和handle_strcpy
。 -
例如,这样一个具体的类
MyHandlers
,将产生暴露handle_printf
的实例,当在二进制文件中遇到对printf
的调用时,将在分析期间调用该实例(对于对malloc
和strcpy
的调用,分别为handle_malloc
和handle_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中调用的。