转载:细数iOS上的那些安全防护
发布日期:2016-08-24序言
随着苹果对iOS系统多年的研发,iOS上的安全防护机制也是越来越多,越来越复杂。这对于刚接触iOS安全的研究人员来说非常不友好,往往不知从何入手。因此,为了让大家能够更加系统性的了解iOS上的安全机制,我们从三个方面着眼:代码签名(CodeSign)、沙盒机制(SandBox) 和利用缓解(Exploit Mitigation),对iOS的系统安全机制做了一个总结。希望能够给大家的学习以及研究带来一定的帮助。注意,以下内容是以最新版的iOS 9.3.4做为标准进行讲解。
代码签名(CodeSign)
为了保护开发者的版权以及防止盗版应用,苹果系统拥有非常严格的签名保护机制。想要开发iOS程序,必须先注册开发者账号,并向苹果申请相关的证书,否则程序只能在模拟器上运行,无法在真机上调试,也无法上架App Store。除了传统的签名机制以外,苹果还额外增加了Team ID的安全防护措施,用来增强iOS系统的安全性。
(1). 传统签名机制 – 数字证书
传统的签名机制即iOS系统中使用的数字证书机制。数字证书是一种对数字内容进行校验的方法,它首先对内容使用摘要算法(例如MD5,SHA1)生成一段固定长度的hash值(可以理解为原内容的摘要),然后利用私钥对这个摘要进行加密,得到原内容的数字签名。接受方一并接收到原内容和数字签名,首先用相同的摘要算法生成原内容的摘要,同时用公钥解密数字签名,得到摘要2,然后比较摘要1和摘要2,若相同,则验证原内容有效。我们从苹果MC(Member Center)中获得的数字证书就是被苹果CA签过名的合法的证书。而iOS设备在执行app前,首先要先验证CA的签名是否合法,然后再通过证书中我们的公钥来验证app是否的确是开发者发布的,且中途没有对程序进行过篡改。理论上想要破解或者绕过这个签名机制,需要能够获取到苹果的私钥,或者能够找到签名校验过程中的漏洞。
(2). 签名校验的实现
iOS在运行代码前,都会对即将运行的代码进行签名校验。签名的校验机制是运行在内核里的。因此想要关闭这个校验的话,需要对系统进行越狱才行。内核在vm_fault_enter中规定了绝大部分情况下,具有执行位的页需要进行签名有效性检查,如果检查到该页签名无效会为进程设置kill flag。签名校验分两种情况;如果binary是platform binary,系统会直接校验binary的哈希值是否存在于trustcache中。如果binary是第三方应用程序,会先在内核在检查执行页对应hash值,而页hash对应的签名由用户态进程amfid校验其正确性。
(3). Team ID
Team ID 最早在iOS 8中被提出,在iOS 9中得到了进一步的加强。Team ID的出现主要是为了阻止攻击者将自己的动态库加载到不属于自己的executable中,常见例子:越狱过程中将动态库加载到系统进程,获得沙箱外的任意代码执行能力;恶意应用通过沙箱逃逸将自己的动态库加载到别人的app运行环境,盗取账号密码等有价值的信息。所以Team ID的具体的校验逻辑就是根据这个原则来设计。除了特殊情况,系统的进程只能加载系统的动态库。第三方app根据自己的Team ID来决定哪些具有相同Team ID的dylib能被加载。
沙盒机制(SandBox)
很多系统都有沙盒机制,但是像iOS这么复杂的却很少。iOS从UID/GID permission,MAC和entitlement三个维度实现了整个系统的沙盒机制:
(1). UID/GID permission
一般情况下,iOS会将进程的权限分为root和mobile,一些特殊的模块(比如基带)会有自己的用户组。需要注意的是,所有第三方的app都是运行在mobile权限下的。
(2). iOS Mandatory Access Control
iOS的MAC在TrustedBSD Mac Framework基础上实现,在内核具体接口、具体位置插入权限hook check(mac_** call),在发生调用时检查当前进程是否满足调用的MAC police。
而进程的MAC police主要是通过sandbox profile。Sandbox profile是苹果为每个系统进程或app预设的,例如:哪些文件可读可写,哪些不能;哪些system call可以调用,哪些不能等等。
对于系统进程,一般情况下苹果会为不同的系统进程配备不同的sandbox profile,既满足业务需求,又遵循权限最小化原则。
对于第三方app,则是统一配备名为 Container 的sandbox profile,这个profile里面的内容限制可达数千条。限制非常严格,以致于只有很少数的syscall能在第三方app内访问。一些安卓中非常普通的调用,例如fork,exec等创建子进程的系统调用,在第三方app内都是无法生效的。我们常说的沙盒逃逸,其实目的就是跳出container的sandbox profile。
(3). Entitlement
Entitlement的出现主要是为了上面两个维度都无法解决的权限检查问题。
假设有这样的场景:
进程 A 是 service 、进程 B 是 client,两者通过IPC通讯。
进程A提供的服务接口分别有:a1 , a2 ,其中只希望接口a1能被B访问。
因为检查发生在用户态,不能直接使用TrustedBSD Mac Framework,同时需要有更简单的查询方式,这样就需要在a2接口的代码中加入权限校验。基于entitlement的校验框架就是在这个需求背景下被提出来的。业务进程只需要关注entitlement的内容,而entitlement的正确性由签名保证。比如想要访问提供了能删除app的接口的”com.apple.mobile.installd”服务就必须拥有对应的”com.apple.private.mobileinstall.allowedSPI” entitlement才行。而lockdownd这个service是用于和iTunes交互来进行安装、升级、删除应用的,所以这个服务为了能与installd服务通讯,进行删除app操作,就需要拥有”com.apple.private.mobileinstall.allowedSPI” 这个entitlement:
利用缓解(Exploit Mitigation)
除了常见的Stack Canaries、 ASLR和DEP等利用缓解技术之外,iOS还有很多高级的或者独有的利用缓解技术:
(1).栈金丝雀保护 (Stack Canaries)
栈金丝雀保护是已知的放置在缓冲器和控制数据之间的一个随机值。当缓冲器溢出时,最先被破坏通常是金丝雀值。因此当金丝雀的数据的验证失败的时候,就表示出现了缓冲区溢出,从而触发保护机制,并使程序停止运行。
(2).地址随机化 (ASLR/KASLR)
为了增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,用户态进程在每次启动时的执行文件基址都是随机生成的。并且,在每次手机重启后,内核kernel mach-o的基址也是随机的。
(3).数据执行保护 (DEP)
DEP是为了防止数据页执行代码。通常情况下,默认不从堆和栈执行代码。DEP会检测从这些位置运行的代码,并在发现执行情况时引发异常。在mprotect对应的内核实现中,不允许page被同时赋予执行和写这两种权限。当page的权限发生变化或一个新的page mmap到内存中的时候,vm_fault_enter会检查这个页是否有执行位,如果有执行位,会对这个页做签名检查。
(4). 堆释放元素保护 (Heap Free Element Protection)
在iOS中,如果修改一个zone中已释放的free element,当内存管理器再次分配内存到这个free element的时候会发生随机panic。具体的逻辑是,当element被释放后,内核会根据重启时创建的token生成一些内容填充在element中。这样一方面用户态无法得知填充的内容是什么,另一方面内核在分配内存的时候可以根据token知道这个element有没有被修改,如果被修改就产生panic。
(5).堆元素地址随机化 (Random Heap Element Address)
iOS系统在释放内存块的过程中,会对内存释放后在free队列中的顺序进行随机化处理,这个安全措施主要是使用攻击者无法根据堆喷接口调用的时序来预测对应元素在内核的布局。
(6).内核补丁保护 (Kernel Patch Protection)
ARMv8-A架构定义了四个例外层级,分别为EL0到EL3,其中数字越大代表特权(privilege)越大:
EL0: 无特权模式(unprivileged)
EL1: 操作系统内核模式(OS kernel mode)
EL2: 虚拟机监视器模式(Hypervisor mode)
EL3: TrustZone monitor mode
KPP就是运行在Application Process 的 EL3中,目的是用来保证:只读的页不可修改、page table 不可修改、执行页不可修改。
文章来源:阿里聚安全博客,本文链接:http://www.wosign.com/News/ios-sec.htm