◆刘 星 魏丽珍 梁煜麓 罗 佳
安卓沙箱中ELF文件行为检测技术研究
◆刘 星 魏丽珍 梁煜麓 罗 佳
(厦门安胜网络科技有限公司 福建 361000)
随着安卓的发展和普及,安卓平台恶意软件的功能实现开始由java层向native层转移,因此检测方法也由单一静态转变到动、静态结合。本文在传统的动态检查方法基础上,提出一种新的动态检测方案,通过修改系统内核中断处理函数,在目标程序使用系统调用时,获取其使用的系统调用函数名称、参数、上下文信息、返回值等,通过对这些信息分析处理,判断出程序是否为恶意软件。
移动安全;沙箱;ELF文件行为;安卓;检测技术
在安卓发展初期,恶意软件基本都是以java语言开发,由于java语言逆向分析简单的特点,恶意软件很容易被安全厂商逆向分析、检测并查杀。随着时间推移,恶意软件开发者水平的提高,恶意功能代码由java层转移到了native层,由NDK开发编译成ELF文件,再加上o-llvm的普遍使用,给静态分析造成了很大困难。因此,实现一种高效、灵活的动态检测系统,在恶意软件检测方面具有非常重要的意义。
为解决静态分析、检测遇到的问题,动态检测系统开始广泛应用,通过动、静态信息综合判断程序是否有恶意行为。
安卓系统是由Linux内核层和以应用程序、Framework、本地运行库和安卓运行时组成的用户层构成。内核和用户层通过系统调用衔接,所以安卓的动态检测系统包含两部分,分别是Linux系统调用检测和用户层代码调用检测。
用户层主要是通过修改源代码(如xposed框架[1])或者采用注入的方式,修改虚拟机中java类方法对应的数据结构,达到修改执行流程目的。
传统系统调用检测通常有两种,一种在用户层利用内核提供的相关机制或者HOOK,另一种是修改系统调用函数实现。
使用内核机制实现比较简单,如内核提供的inotify/fanotify机制、连接器[2]等。基于此类机制实现的系统调用监控,虽然实现起来比较容易,但是监控粒度比较粗,无法精确到具体函数参数、操作进程等信息,无法满足通过动态行为来甄别恶意软件的需求。
使用机制实现,通常是将监控模块注入到目标进程,使用导入表或者内联HOOK目标函数。因为所有操作都在用户层实现,很容易被检测和被绕过。再者,如果恶意程序采用静态链接,或自己实现系统调用过程,就无法通过此方式来监控相关信息。
除HOOK外,strace等工具也是可以用来监控系统调用,作为第三方工具,输出结果可定制性差,容易被检测和绕过。
鉴于传统动态检测系统的不足,本文提出了以下动态检测方案。
为了解决传统方案在系统调用监控中存在的缺点,本文从系统内核出发,根据系统调用原理,提出了一种基于中断的系统调用检测方案。
在Arm64架构下,系统调用的流程大致如下:
(1)把系统调用号放到X8寄存器,通过svc指令进入内核层。
(2)内核层通过中断向量表找到对应的中断服务程序,el0_sync或el0_sync_compat [3]。
(3)中断服务程序从esr_el1 [4]寄存器中获取异常原因,如果由svc指令产生,则进入el0_svc或el0_svc_compat流程。
(4)el0_svc_或el0_svc_compat流程中,通过系统调用号在 sys_call_table或compat_sys_call_table找到对应的函数地址,进入系统调用程序。
(5)系统调用完成后,返回到中断服务程序,然后再返回到用户空间。
通过以上分析可以看出,系统调用的分发流程在el0_svc中(el0_svc和el0_svc_compat流程会在获取系统调用表后合二为一),因此可以通过加入代码,修改el0_svc的逻辑,得到系统调用参数等信息。
在系统调用前后加入代码,分别命名为before和after,因为涉及到的寄存器可能被污染,需要对相关寄存器进行保存。
Arm64参数传递时[5],前8个使用X0到X7寄存器传递,其余通过栈传递。由于Arm64最大系统调用参数个数为6,所以使用X0 到X5。因此before的函数参数为7个,分别对应系统调用的X0 到X5,即系统调用参数,外加一个系统调用号,使用X6传递。由于使用了X6、LR(使用BL指令,导致LR寄存器被污染)和X0(函数返回值),所以需要备份X0到X8以及LR。在进入before时寄存器值和栈结构如图1所示。
图1 进入before前寄存器和栈信息
在before返回后,依次按照入栈顺序恢复各个寄存器值,使流程进入系统调用函数,系统调用返回后进入after函数。由于系统调用会污染相关寄存器,因此参数无法从寄存器直接获取。但是,系统在进入中断后,各个寄存器会被系统以pt_regs的结构备份在栈中,因此可以从栈中获取到相关信息。所以,after的函数参数个数可以简化为2个,分别是系统调用返回值X0和指向pt_regs的指针,使用X1传递,先要备份X1,然后使X1指向pt_regs地址。在进入after时,系统堆栈如图2所示。
图2 进入after前寄存器和栈信息
通过以上步骤,虽然可以拦截到系统调用信息,但是获取到的是所有用户层的系统调用请求,重要信息被大量无用信息干扰,不利于后续处理。为了解决以上问题,需要在before和after函数中对一些系统调用做特殊处理。
在before中,需要对以下系统调用做特殊处理:
(1)SyS_execve时,需要检测文件路径是不是被监控文件,如果是,则将当前进程PID加入到监控队列(通过链表实现)。
(2)SyS_exit时,从监控队列里面移除当前PID。
在after中,需要对以下系统调用做特殊处理:
(1)sys_fork时,如果父进程在监控队列,就将自身PID加入到监控队列。
(2)SyS_ptrace时,如果系统调用返回成功,需要将目标进程PID加入到监控队列。
(3)函数返回时,需要将系统调用的返回值作为当前函数的返回值。
其余情况下,检测当前PID是否在监控队列,如果在就输出日志,流程如图3所示。
图3 调用流程图
通过以上步骤,可以完整获取目标进程系统调用信息,为恶意行为检测提供依据。
恶意程序为了躲避检测,各种手段层出不穷,使得静态检测难度不断提高。但是,无论恶意软件如何从静态层面隐藏自身,其功能的实现必须依靠系统提供的接口。本文提出的系统调用检测方法,从内核层系统调用中断过程实现,有以下优点:实现起来比较方便,不需要修改每个系统调用实现代码;更加底层,恶意样本无法绕过;利用PID作为过滤条件,能准确获取其行为,减少无用信息干扰;对特殊系统调用特殊处理,既能获取到与目标进程有关联进程信息,也可以排除无关进程。动态检测作为恶意软件检测的一种手段,需要在实践中不断改进、完善,从而使其发挥更大作用。
[1]rovo89. Xposed[J/OL]. https://github. com/rovo89/Xposed.
[2]杨燚.连接器(Netlink Connector)及其应用[J/OL]. https://www.ibm.com/developerworks/cn/linux/l-connector/.
[3]linuxer. Linux kernel的中断子系统之(六):ARM中断处理过程[EB/OL]. http://www. wowotech.net/irq_subsy stem/irq_handler.html.
[4]ARM.ARM Cortex-A Series Programmer’s Guide for ARMv8-A[J/OL]. http://infocenter.arm.com.