Seccomp技术在Android应用中的滥用与防护
作者:三六零天御
安全攻防是一个永不停息的持续性话题,没有任何一种防御办法是一劳永逸的。今天三六零天御针对一个比较流行的攻击手段进行阐述,并介绍该手段的防御方法。该文章无法囊括所有内容,为了更好地去体验效果,可以使用360加固保产品做进一步的探究。接下来我们对该技术进行阐述,希望为广大技术爱好者带来帮助。
一、Seccomp技术简介
Seccomp全称(Secure Computing Mode),在2005年的Linux内核版本2.6.12中引入。主要目的是为了限制进程的系统调用权限,换句话说,只允许进程进行read/write/_exit/sigreturn的调用。
随后,在Linux内核3.5版本中引入了Seccomp过滤模式(Seccomp filtering),Seccomp过滤模式的语法是基于BPF (Berkeley Packet Filter)。可以将此模式简称为Seccomp-BPF。Seccomp-BPF可以使用系统调用号、CPU架构类型等作为条件,对接下来的操作进行允许、禁止、TRAP等操作。
故Seccomp-BPF是对最初的Seccomp的一次升级,将原有的“暴力执法“转成了”有条件的执行“。
Seccomp的应用场景比较常见,比较有代表性的就是沙箱。沙箱一般使用隔离进程(Isolated Process)来保障应用安全。隔离进程内部机制就是使用了Seccomp。也就是说在该进程内部可以进行的操作非常有限。
刚刚我们提到了,Seccomp-BPF可以使用系统调用号作为条件,来对接下来的操作进行过滤。这种特性,最终会被别有用心的人恶意利用。完成对APP系统调用的拦截,并修改应用内业务逻辑。
我们通过一个例子来看一下Seccomp-BPF的特性。
图1
图2
图3
在图 1中,有两个filter。第一个filter一共有4条BPF指令。第一条BPF指令,代表取seccomp_data(图 2)中的nr字段。nr字段含义可见图 3,意思是下一条BPF指令的判断条件是系统调用号。第二条BPF指令,第二个参数是openat系统调用,第三个参数数字N,表示如果系统调用是openat,跳过N条BPF指令,第四个参数数字N,标识如果系统调用不是openat,跳过N条BPF指令。按照这个逻辑,如果是系统调用openat,则跳过0条指令,执行第三条BPF。如果不是openat,则跳过1条指令,执行第四条BPF。第二个filter同理。
二、Seccomp-BPF技术在Android应用中的滥用
随着Android攻击技术的不断深入,越来越多的开发者意识到APP本身的安全风险。一些常用的黑客技术也被越来越多的人熟知。在过去,Inline Hook技术可能会被少部分人使用。但是随着技术的发展,出现了很多的Inline Hook开源框架。例如从早期的Cydia substrate hook到HookZz,Sandhook等。使用方法也是非常简单,容易上手。
Inline Hook可以完成对函数指令级别的拦截,从而监控应用内部的文件操作、网络请求等。这样的Hook手段,可能会被黑灰产利用。从而实现破解、篡改、仿冒、攻击等目的。
所以为了防止这样的Hook手段,应用可以使用svc调用的方式。使用内联汇编的方式来代替直接的系统调用,可以避免一些业务被拦截。举例如下:
ENTRY(__openat_self) mov x8, __NR_openat svc #0 cmn x0, #(MAX_ERRNO + 1) cneg x0, x0, hi b.hi __set_errno_internal ret END(__openat_self) .hidden __openat_self |
表格1
使用表格 1中SVC调用的方式,由于本身采用内联汇编的形式,代码地址分配不固定。另外还可以采取动态分配内存,存储并执行代码的方法。使得Inline Hook变的更加困难。
但是,这并不能阻止Seccomp-BPF的拦截方案。因为Seccomp-BPF提供了一个钩子函数,在SVC系统调用执行之前会进入到这个函数,对系统调用进行检查,并做业务逻辑的修改和绕过。故任何SVC调用,都无法逃离Seccomp-BPF的魔掌。
那么,Seccomp-BPF在Android应用攻击中是怎么做的呢?举例如下。
有的APP为了保障自身的安全,不允许APP动态调试、二次签名、运行在框架环境等。采用的方案一般使用open系统调用的方式打开特定文件检查有无异常特征,例如/proc/self/map或者/proc/self/status等。有些开发者比较注重安全,对open采用了svc的实现方式。但是利用Seccomp-BPF完全可以对open操作进行拦截,重定位参数路径,完成逻辑的绕过。
三、Seccomp-BPF技术的防护
1. 基于prctl调用的检测
prctl方法也是一个系统调用。使用Seccomp-BPF前,会调用prctl,并传入特定参数作为前置条件。如图 4所示。
图 4
为了检测这种方式。我们同样可以调用prctl,传入PR_GET_NO_NEW_PRIVS。根据返回值来判断是否NO_NEW_PRIVS被设置为1。见表格 2。
if(prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) == 1) { do something… } |
表格 2
但是该方案有个缺陷,因为prctl本身是个系统调用。所以仍然可以被Seccomp-BPF拦截修改进而绕过。
2. 基于Signal的捕获检测
将SECCOMP_RET_TRAP传入BPF指令中,当执行该指令时,将会产生SIGSYS信号。我们可以提前通过sigaction方法来注册SIGSYS信号,当捕获到信号时,会执行对应的handler方法。在正常情况下,都是可以正常接收到信号,并执行handler方法。但是如果该进程已经提前使用了Seccomp-BPF,将会无法正常接收到信号,handler方法也不会执行。所以可以通过这种逻辑来判断该应用是否处在Seccomp-BPF环境中。
不过该方法依然有被绕过的可能性,攻击者可以通过Hook的方式,拿到handler方法地址并主动去执行。则可以绕过校验。
3. 基于status文件的检测方案
当调用图 4中的代码时,会在对应进程的status文件中留下痕迹。如图 5所示,NoNewPrivs对应的值变为1。
图 5
我们可以使用open打开/proc/self/status文件,检测该值是否为1。当然,open系统调用,我们之前说过了,可以被绕过。但这并不意味着防御到此结束,我们依然可以通过open返回的句柄值,来进一步验证句柄的合法性。办法有很多,在此不一一赘述。
四、小结
安全攻防是个无止境、无硝烟的战斗,我们只有不断地去深挖攻击手段,才可以做出更高级的对抗。这个过程充满挑战,需要持久的耐心以及高涨的热情。然而,幸运的是,360加固保在这方面会持续地开展下去。对行业是利好,对开发者们更是福音。同时360加固保也会持续不断的利用黑科技来造福开发者,更多更高级的功能可以在高级防篡改和企业版中有体现,欢迎大家来感受并玩转黑科技。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2023-3-22 10:35
被三六零SRC编辑
,原因: