苹果2017年漏洞学习总结
一. 漏洞资料列举:
1. Yalu102
漏洞编号:CVE-2017-2370
作者:kpwn https://github.com/kpwn/yalu102
作者:ianbeer https://bugs.chromium.org/p/project-zero/issues/detail?id=1004
中文分析文章: https://hello-sherlock.github.io/2017/04/17/night-Blog/
2. Mach_portal攻击链:
漏洞编号:CVE-2016-7637CVE-2016-7644 CVE-2016-7661
作者:ianbeer https://bugs.chromium.org/p/project-zero/issues/detail?id=965
中文分析文章:https://www.anquanke.com/post/id/85908
3. Tripe fetch
漏洞编号:CVE-2017-7047
作者ianbeer https://bugs.chromium.org/p/project-zero/issues/detail?id=1247
中文分析文章:https://paper.seebug.org/366/
4. Async_wake
漏洞编号:CVE-2017-13861
作者ianbeer https://bugs.chromium.org/p/project-zero/issues/detail?id=1417
作者 盘古 http://blog.pangu.io/iosurfacerootuserclient-port-uaf/
这里面,每个漏洞都是谷歌的ianbeer 最先公布并且放出了漏洞利用代码,而有些漏洞虽然是其它人先发现(比如盘古),但他们并没有公布出来,更别说放出代码。多亏ianbeer大神放出的代码以及后来其他大神写的分析文章,才能让我等有机会了解到苹果的底层知识。当然,这些代码和分析目前并没有完全弄懂,以后有机会再继续学习。
二. 苹果内核概述
我们平常听得最多的IOS,是苹果手机的操作系统,并不是内核。IOS包含了内核,同时也包含了驱动,UI框架等,而真正的内核名叫XNU,它也是UNIX家族中的一员,属于BSD分支,而LINUX属于UNIX-LIKE分支。此外,苹果电脑的MACOS操作系统,其内核也是XNU。
IOS是一个封闭的系统,里面的内容是完全不开源的。但MACOS却相对要开放得多,其内核xnu(x86版本)很早就开源了,这等于间接开源了IOS的内核。除了内核,MACOS还开源了操作系统中一些模块,由于MACOS除内核外,其他很多模块也是和IOS相同的设计思路,甚至代码都一样,这大大方便了对IOS的研究,所以IOS的研究者(包括黑客和安全研究)一般都先从MACOS入手。当然,对于UI这部分,苹果是没有开源的,这也是苹果的UI无法被山寨的重要原因。
三. 苹果安全体系和越狱概述
除了和LINUX一样拥有权限控制机制,苹果还有两个重要的安全机制:
首先是沙盒机制,普通的应用程序只能访问系统规定某些资源,例如只能在某个文件夹内活动,没法跳出这个文件夹去访问其它应用或系统的文件夹。
在MACOS里,苹果并没有强制实行沙盒机制,这是因为台式机需要更多交互性的功能,比如你总不能不让word APP不能访问外界的文件吧。于是,苹果在MAC上只规定进入APP STORE的应用必须强制进沙盒,这一规定导致了很多APP都退出了APP STORE。
而在IOS系统里,所有的APP都强行被沙盒所禁锢,只能在“狭小”的空间内活动,就像被关起来一样,而打破沙盒获得自由的行为,被黑客形象的称为“越狱”。
如何打破沙盒呢?最直接的办法就是获取root权限,具有root权限的进程是不受沙盒限制的。但这并不意味着“越狱”成功,因为苹果还有一层安全机制:代码签名。
所谓代码签名,就是程序在运行时,一个叫AMFID的进程会分段对其进行hash校验,用来与苹果授权的证书进行比对,看是否符合,如果不符合,AMFID进程就会杀掉这个进程。(大概是这样,还没搞太清楚,AMFID不开源)
那么如何获得这个证书呢?有两种方式,第一种是申请成为个人开发者,并将开发的APP代码发给苹果审核,审核通过后,苹果就对该APP授予证书,并让它在APP STORE上架(据说越狱祖师Saurik就是因为自己做的APP被苹果拒绝,一怒之下走上了与苹果死磕到底的道路)。第二种是申请成为企业开发者,以企业信用为担保发布APP,苹果对其授予企业证书,而无需审核,但这种APP不能在APP STORE上架,且须承诺只能在企业内部使用,例如大公司开发的公司内部通信软件或者手机打卡软件等。
在这种机制下,对于个人证书,由于需经过APP STORE的审核,苹果可以把含有恶意软件的APP剔除,甚至将个人开发者封号。而对于企业证书,苹果虽然不审核,但因为其不上APPSTORE,影响面是很小的,当然如果企业APP被检测出了有恶意软件,苹果同样也可以吊销企业开发者。
所以,突破沙盒后,下一步就是要破除代码签名机制,这样APP才可以得到真正的自由。
这里有个问题,如果没有证书,“越狱”的初始代码根本都无法运行,连走出沙盒这一步都没法实现呢!理论上是这样,但实际上从电脑直接下载到手机上的APP有7天的临时证书,这是因为成为个人开发者需要每年都向苹果交99美元(企业证书更贵),苹果不想让这个门槛将初学者挡在门外。而由于必须是从电脑直接下载到手机中,苹果也不须担心会有恶意软件通过该方式大规模传播。
这样,越狱软件就能成功运行起来,在依次破除沙盒和代码签名后,7天的临时证书限制同样被破除,这时候貌似就没有什么东西能阻挡它对整个系统的控制了——除了重启机器。重启机器后,越狱软件需要重新执行才能重新控制系统,这样越狱称为不完美越狱。但是,如果黑客找到了苹果芯片或BOOTROM的BUG,在系统加载阶段就注入越狱代码,就能使苹果每次重启都自动越狱,这称为完美越狱,由于芯片BUG几乎已被苹果封死,最近的越狱都不是完美越狱,所以本文不讨论完美越狱(当然我也不懂:)。
四. XNU内核权限控制机制
在LINUX体系里,几乎所有功能都被抽象为文件,VFS虚拟文件系统不仅抽象了各种驱动,也抽象了进程,线程,内存的访问。与之相对应,XNU将这些功能抽象成了“端口”(PORT),进程,线程,驱动等都是通过端口访问。
当然,普通的端口还是如其字面意思,用于消息收发,两个进程,一个进程申请了一个端口,并为其赋予读写权限,然后将其读权限发送给另外个进程,两个进程间就可以通信了。在这里,端口的读权限被赋予给对端的进程时,这个“读权限”本身又被抽象成了端口,进程在读取消息时,是手握这个“读权限”端口,才能收到消息。
事实上,在XNU里,所有的权限都是端口,包括较高权限的host_priv (这个貌似是具有了root的权限,但还不是root,这个我还不是很明白), 还有进程端口,线程端口,内核端口,如果获取了相应的端口,就可以控制对应的进程,线程,甚至内核。而控制了内核端口,就拥有了对内核的读写权限,可以将自己的进程直接改为root进程,一般到了这一步,越狱基本上就成功了。
权限也如上所说,可以由一个进程发送给另外个进程。所以,大部分漏洞都是围绕这一点做文章,设法持有更高权限的端口,包括root进程的任务端口,host_priv端口,内核端口。
五. 从其它进程”骗取”高权限的端口
Mach_port攻击链就是利用了XNU对端口的处理不当,让另外一个root进程主动将其持有的任务端口发送了过来。
在XNU端口权限里,一个进程对某个端口进行使用时,就会增加一个引用计数,这个引用计数有个上限,而突破这个上限后,XNU未对其做任何处理,只是简单的把这个引用数控制在上限值。这样,如果设法不停增加某个PORT的使用,使其引用值超过上限值,再等使用完成,就会使引用减到0,导致这个port被释放。
在IOS中,有个launchd进程,它握有所有应用或服务的“写权限”端口,当一个进程试图联系某个应用或服务时,就先向launchd申请这个端口,launchd就将这个写权限端口复制一份,发给进程。而利用这个漏洞使用非法流程,大量向launchd发送一个特殊服务的读端口,使其引用值溢出,最终导致释放。然后又向launchd注册自己的服务,占用这个释放掉的端口。这样其它进程发送给这个服务的所有消息都会被劫持到漏洞进程。恰好有个root权限的power进程会在重启时将自己的任务端口发送给该服务,这样就顺利地骗取了这个任务端口,并从中提取了host_priv进行下一步动作。
Tripe_fetch漏洞也是如此,它利用对方的root进程想当然认为漏洞进程提供的共享内存不会发生改变,不断地改变内存中数据,使root进程发生了内存越界。又巧妙地使越界影响范围控制在这片共享内存中,通过对该内存一连串眼花缭乱的布局,使该root进程最终执行了主动发送自己端口的代码。
六. 泄露内核信息
前面说了,得到root进程任务端口,并提取了host_priv权限,只能获得更多root相关的权限,但自身并没有变成root,也就不能越过沙盒。要称为完全的root,还要获得内核端口权限并把自己改成root进程。这里关键的一步就是泄露内核信息。
Mach_port攻击链在漏洞进程获得host_priv后,得以触发高权限的另外个漏洞,及端口权限处理中一个互斥锁保护不周的地方,致使一批端口被内核端口管理模块释放,而在进程管理确没有释放,而这个进程当然是漏洞进程。也就是说漏洞进程掌握了一批悬空的端口。然后漏洞进程又申请了一些端口并发送给自己,其中就包含了host_priv的用户态值,这些端口在内核态中转换成内核态地址,host_priv端口同样也会转换成其内核态的真正地址(端口的内核态结构为ipc_object,其指针就是所谓内核态地址),他们一起填充了悬空端口所在的内存(ipc_object),然后漏洞进程再合法的通过系统调用获取了这个悬空端口ipc_object里面的内容,但里面的值却实际上已经变成了一个个ipc_objectptr,这样host_priv的内核真正地址ipc_objectptr就暴露了。而内核port和host_priv的ipc_object ptr非常接近,漏洞进程再通过反向方式,通过悬空端口修改这些内核态值,然后接受这些端口。一个个试出了内核port并接收了内核端口,使自己获得了内核权限。
与mach_port相比,yalu102要简单粗暴得多,并且效率也要高得多。Mach_port只泄露了ipc_object ptr,而yalu102直接伪造了一个ipc_object以及其对应的task结构体。因为苹果的程序员在内核态犯了一个踩内存的低级错误,yalu102巧妙地使其踩掉了一个正常ipc_object ptr指针,使其指向了自己伪造的内存结构,而且相比mach_port三大漏洞的繁复,这个漏洞只需要一句系统系统调用就可以触发。不过苹果的对策也很简单粗暴,新版本禁止了内核指针向用户态的映射,而且硬件上对内核内存分配做了保护,将不再可能踩到想要的内存段。因此,此后想要利用同类漏洞,将变得更困难。
总结下内核信息泄露的作用:上述两个内存泄露都是围绕端口在内核态的内容进行操作,苹果提供一些系统调用可以合法获取到这些内容的某些部分,这也是内核信息泄露的前提。但这些合法获取的信息对于越狱来说是无用的,因此,mach_port通过UAF(use after free)的方式将这些内容替换成了有价值的信息再进行获取,而yalu102则自己伪造了这块内存,在里面按内核的规定填上一些值,通过系统调用不停地愚弄内核,让其把自己感兴趣的内容送出来。此外,在获得内核端口相关的信息后,还需要将这些信息设置进端口对应的内核内存中去,这样才能在用户态获取到内核端口。Mach_port和yalu102都不知道什么是正确的内核端口信息,但他们根据内核信息泄露所得到的一个点(host_priv和clock_task),不停地把点周围的信息设置进去进行试探,最终试出了真正的内核信息。
不过即使没有找到泄露内核信息的漏洞,也有其它绕过的方法,不过按照大神们的说法,这种方法不那么“优雅”,triple_fetch漏洞就是如此。照之前所说,triple_fetch已经获取了一个root进程的任务的端口,因此就获得了其控制权,可以控制这个root进程来执行任何想执行的代码。这样,虽然自身不是root进程,但却有个root进程为自己服务,虽然很麻烦,但还是能得到较高的权限。
七. 劫持代码签名进程
要获得root权限,不仅要有技术,还得有灵感(你得找到一个漏洞,并把它利用起来)。而获得root权限后,所需要的就只有技术了。
代码签名进程有个异常处理流程,这个异常流程也是个端口,拥有root权限和内核端口的漏洞进程接管了它,并且破坏了代码签名的部分代码使其在进行代码签名时进入已经伪造的异常处理流程,这样成功的绕过签名。到这里,越狱基本就成功了,当然还有些琐碎的工作,比如支持一些插件和把一些APP加入系统之类的。
八. ROP系统攻击
虽然triple_fetch这个漏洞利用是最不优雅,效率最低的(手机发烫了还没触发成功)。但它所采用的ROP(Return-oriented Programming)攻击方式,应该是黑客里常用的一种渗透方法。
所谓ROP,攻击者从已有的库或可执行文件中提取指令片段(gadget),构建恶意代码。这是因为,现在的操作系统里都为自己的指令,数据等段设置了不同的权限,而只有指令段才有可执行权限。因此,攻击的时候如果把自己想要执行的指令通过数据的方式传过去,即使使用漏洞将PC指针指了过来,仍然没有权限执行的。但却利用被攻击者已有的各个代码片段来执行想要的代码,并利用其中的跳转指令在各个片段间跳转。特别需要利用的是ret指令,进行出栈操作,就可以把想要的内容设置到寄存器里,特别是SP寄存器,因为它可以把堆栈地址设置到攻击者想要的地址上去,那里有攻击者精心准备的参数数据。
Triple_fetch首先利用漏洞踩掉了OC的isa指针,获取了PC,然后利用_longjmp函数所带的返回功能,替换掉了SP指针,控制了堆栈。再在mach_sendmsg函数中提取代码片段,使其反复向自己发送带有权限的消息,最终成功获取了被攻击进程的任务权限。
九. 堆整理
Mach_port想把UAF的内存替换成其他内容,yalu102想踩到正确的指针地址,Triple_fetch也想踩到OC的isa指针。但内存并不是按漏洞的心愿老老实实呆在合适的地方,随便乱踩只会把系统踩死机而已。
yalu102进行大量端口分配并发送,而只间隔接收了其中一小部分端口,使里面空出一些“槽位”,再运行漏洞就会使漏洞申请的内存大概率“卡”进这些槽位中,而踩掉其他端口内存。
Mach_port更是调用了mach_zone_force_gc让内核重新整理内存,以达到占用。
(Triple_fetch创造槽的方式更加复杂,我一直没看懂)
通过堆整理,可以使内存踩到正确位置的几率大大增加,堆整理也叫“堆风水”(不知道老外是怎么想到风水这个词的)