方家弘:大家下午好!
我是来自腾讯安全科恩实验室的方家弘,我和我的同事申迪给大家带来关于“安卓内核 UAF 漏洞利用探秘”的分享。
本日的分享紧张分成四个部分。首先由我先容在之前的一段韶光涌现的比较有代表性的安卓内核漏洞。其次要剖析像 UAF 这样一个特定类型的漏洞在利用方面有什么样的特点,它涉及到系统组件的根本知识,方便大家理解后续的内容。之后的韶光交给申迪,他会详细剖析在 perf 子系统中创造的 UAF 漏洞的详细利用过程。末了,分享一点我们的总结和思考。
首先先容安卓内核漏洞的简要历史。

为什么要对安卓内核进行攻击呢?
将安卓系统看做整体,最早系统中以不同的运用,利用不同的 UID ,对运用之间进行隔离。后来创造这并不能阻挡攻击,又产生了加密文件系统等等安全方法。
而绝大多数方法的实现都依赖于安卓的内核。攻击内核本身是非常有助益的事情。在 Windows 系统当中,攻击内核可以刀切斧砍的达到提权的目的。在安卓系统中,也可以绕过系统中设置的各类限定。对付坏人可以实现他不可告人的目的。
按照韶光轴,根据我个人的理解列出了比较有代表性的内核漏洞。当然,不包括我们本日要讲的漏洞。
2013 年 11 月份,CVE-2013-6282 是我们的友商写出的最早的漏洞,它是逻辑问题。在我们去年攻击特斯拉的时候,惊异地创造这个漏洞居然还存在。当时特斯拉比较自傲的认为车辆不可能被攻破,以是他们在内核方面没有做更多的防御。在漏洞被报告,修复之后,特斯拉在内核方面做了非常激进的更新和修复。加粗的漏洞都属于通用的安卓 root 漏洞。
2014 年,美国的两个天才少年创造了 towelroot,这是引起比较大反响的通用 root。这个漏洞类型在当时较新颖,这个漏洞的利用办法也非常创新,引起了不小的波澜。
2015 年 5 月份,我们创造了 CVE-2015-3636。
2016年8月份,我们利用了 CVE-2015-1805。
之后就是非常出名的 Dirty COW,它也是属于逻辑的问题。
2017年4月份,由 Google 研究员创造的 wifi Firmware 的漏洞,这是非常有创新特色的漏洞利用。
之条件到的是几个有代表性的漏洞,6282 属于业务逻辑的问题;1805 是属于越界访问;3153、3636 都属于 UAF 漏洞。本日我们紧张针对 UAF 漏洞进行研究和磋商。
顾名思义,UAF 漏洞先要 after Free,而要成功利用这个漏洞,有几个步骤是不能少掉的。一是如何按照顺序去触发 free 和 use。这看似比较大略,以 2015-3636 为例,use 的机遇和 free 的机遇非常可控,我们可以做任意想做的事情,比较好掌握被 free 掉的空间。
3636 这个漏洞的品相非常好,但在很多情形下的掌握环节上,可能会面临竞态的问题。在这种情形下,如何稳定的掌握好 free 和 use 的顺序,并且稳定的触发 use,便是比较困难的问题。对付不同的漏洞来讲,须要不同的技巧去处理。我们已经能够把时序上的问题清楚,之后便是如何改变内核的掌握力,如何掌握代码实行。掌握出来被 free 的空间方法,不同的漏洞,有不同的方法。
这里牵扯到最主要的便是 Linux 内核的内存管理。我们以图示的形式展示了 UAF 漏洞利用的办法。堆上有 ABCD 四个工具。这是一个比较大略的呈现办法,要做到能够稳定有效的 free 掉目标工具,并且把我们想要的东西填进去,须要对内核存储管理有比较深入的理解,也就牵扯到 Linux 内核的堆管理器。
在安卓系统当中,我们对常见的堆管理器叫 SLUB,它更换了之前常见的 SLAB。从管理构造上来讲,SLUB 是简化版的,U 便是 unqueued 的意思。queued 是行列步队,做堆管理器,总要有行列步队,申请的时候从这里拿。它取消了单独存在行列步队的构造,这样就使得完备空闲的 SLUB 被完备开释掉。它存在 per cpu 的 slab,它的开释和申请的过程会非常快。为了实现兼容性,Linux 的管理是抽象到 kmem 层。之前的内核代码、内核驱动,不须要做任何的修正。如果大家自己编译过 Linux 内核,只须要选择什么样的堆管理器就可以直策应用,其它代码都不用变动。
首先看一下 SLUB 的堆块长什么样子。Linux 内核当中物理内存的页面管理是通过 buddy 进行的。要符合物理页面管理的原则。在 SLUB 当中,都是二的 N 次方组成页组成的。它实际上是奥妙的利用了物理页面描述服的联合,实现了管理。我分配好给这个堆块的这些页,第一个页面的描述符上就会记录堆块中可利用的第一个工具。这便是空闲工具列表的头部。在空闲工具的头部,又会有一个指针,指到下一个空闲工具。
这个东西没有额外的元数据,所有的元数据只存在于原有的构造体当中,存在于你分配给这个堆块的页面当中。这也带来了一些特性,使得它可以帮助我们对特定的漏洞进行利用。
前面提到了 cpu_slub 的观点,分配和开释都是快速的过程。当前分配的工具在于 cpu 绑定 slub 上面,就会进入快速分配的流程。不管怎么样,对付利用堆管理器的用户来讲,肯定会得到空闲的 slub 工具供给用。详细怎么操作,就由 slub 的堆管理器进行。
为什么要设定 cpu_slub,大部分情形下在一个调度周期内会有频繁的工具分配操作。开释也是这样的情形,目前的工具便是从属于当前的 cpu_slub,这就带来了其余一个非常好的特性。当前 cpu 上开释的工具,我立时要申请的,肯定申请到刚刚开释的工具,这对付添补是非常好的特性。这个特性在其它的漏洞利用当中也会利用到。同时,开释也存在 slow path,这是不可避免的情形。
这里对 SLUB 的特性进行了归纳。按照工具的大小会做一个合并,这会对漏洞利用带来一些问题,你可能不知道这样的堆块当中放的还有其他什么工具。
接下来看两个漏洞的案例,这两个案例充分利用了 slub 堆管理器的特性。
首先是 CVE-2015-1805。
iovec 是数据内核中通报数据的构造。这个漏洞本身是 overrun,牵扯到我们在内核当中如何申请可控的overrun 数组。在安卓当中,很多 API 是被禁用的。终极我们找到 sendmmsg 的调用,你可以得到内容完备可控的数组。它的坏处是放完往后就被销毁掉了。
这个工具本身的生命周期不足长。看似这不是很好的工具,实际上可以回忆起之前的一点,在 slub 中一个工具被开释之后,仅仅是在工具的头部写入了指针,这个指针指向下一个可以利用的工具。
2015-1805 的代码路径当中,如果 iov 是 0,根本就不会被处理。如果说喷射的够快,漏洞利用过程够快,被开释掉的 iov 本身还是空的工具,或者又被另一个 iov 填上,根本不会对漏洞造成任何影响。我们只须要掌握填进去的第一个 iov 的长度是 0,它就会被忽略掉。即便被开释,这个结果还是有效的 iov。
第二个漏洞,该当是 2016-6187。通过这个方法,把一个品相非常差的漏洞,越界写一个字符节的 0,变成可能导致代码实行的情形。末了也是通过 freelist 指针的特性。
接下来的韶光交给申迪,由他剖析一下在 perf 子系统中创造的漏洞利用。
申迪:下面给大家先容我在去年创造的两个 perf 系统的 UAF。在安卓手机上有很多用户有一键 root 的需求。一些 UAF 漏洞的品相并不是特殊好,但我至少要写到达到 90% 的成功率。我紧张先容两个在去年年初创造,年底报给 Google 的漏洞。
第一个是 CVE-2016-6787,它是通过 Race触发的漏洞,内核立即崩溃。上周我在 BlackHat 讲了绕过三星 KNOX 的防护机制。
第二个漏洞是 CVE-2017-0403,这是今年才修复的漏洞。这个漏洞有它自己的难点。
在讲漏洞之前,先讲一下 perf 是什么东西,它是系统调用,任何安卓都可以调这个别系调用,攻击内核。这个别系调用有很多问题,我也创造不止两个问题,这边两个是比较好利用的,单独拿出来讲一下。这个别系调用的参数比较繁芜,个中一个是你怎么定义你想天生的统计事宜,你想定义代码实行的指定周期,或者是真正调用进入内核的时候又调用了哪些调用,它包含了千奇百怪的 perf 事宜,可能会进入到分支。
这个别系调用,不管中间发生了什么,末了总会天生 perf_event。每个都不是伶仃的,有可能是一组形式涌现的,每个 groun 都有一个 leader,每个进程里有多个组,又有大的容器包括这个组。每个进程里有两个 perf_event_context,把作为的 group leader 串联起来,这 list 把组内的串联起来,这三个内核对象有一个比较繁芜的连接的关系,实在便是由于这些关系搞得很繁芜,系统调用里出了一些 UAF 的问题。
Googl 在 2016 年下半年以为这个别系调用太危险,直接封掉了。去年年初,这个调用还可以被其它的调用调到,我利用这两个漏洞完成了手机上的 root。
第一个漏洞的问题紧张是由于系统调用里的 move_group。
如果 group leader 是软件的,你要插入一个硬件的,这个时候会挪动大量列表相连的关系。首先会把 group leader 从指针上提取出来,把每个 event 粘出来。这两个操作完成之后,又看到红框里的代码,减了 1,这个减 1 没有考虑到并发操作,把一个组摘除了两遍。正常情形下,只减 1,并发操作就要减 2,被多减了一个,引用减到 0,导致触发开释。
主线程先创建一个 group leader,线程 1 触发 move_group操作。右边涌现了崩溃,实际是由于 perf_event context 被提前开释。你的进程在第二次进入调度的时候,进入调度器会调入一行,周期实行完了会换出,换出之后再被进程调度器换入。
这个时候就会触发操作。关键是线程调度是很频繁的,基本不是肉眼可不雅观测的。漏洞一旦触发,内核立即就崩溃了。实际上它不是立即崩溃,实际上有一个微笑的操作,线程已经被换出,换入的时候又崩溃了。如果是一个立即的崩溃,对我来说便是毫无意义。这个时候就要集中办理一个问题,怎么让内核不立即崩溃,给我争取到足够的韶光去喷堆。
在讲这个问题之前,要先讲一下 Linux 内核的调度知识,包括很多线程,每个线程不可能同时实行。真正实行的是一个,其它都是等待实行。它实行一段韶光之后,会有几种情形。有一种情形便是往后再也不须要实行,它就会被杀掉。如果该想连续实行,合营的韶光片已经被用尽了,这个时候就被线程调度器切换出去。这种时候就会进入到 interruptible 或者是 uninterruptible。
futex 这个调用可以帮助我们完成真正的就寝。一旦线程进入就寝的状态,是不会被任何人主动唤醒。这对我们来说是最空想的状态。
有了这个函数的帮助,全体调用逻辑就变成这样的,从左到右,从触发漏洞,到喷堆、代码实行掌握完全的流程图。还是主线程创建 group leader,直接调 queue_me。线程1和线程2都同时触发 perf_event 的事情。调了 queue_me 之后,三个线程全部就寝掉了。这个时候我须要再建其余一个新进程,帮助我完成喷堆。喷堆并不是很难,1024 的工具,你直接喷堆,直接喷物理页面,也是可以的。可能须要十几秒的韶光,把堆喷满。这个时候我可以唤醒进程,或者直接杀掉进程,这都无所谓。线程调度器会调 sched_in。disable 可以指向你想掌握的任何一个地址。你可以实行内核任意代码,把权限提到 root。
其余一个 2017-0403 漏洞。末了一条是被开释的工具,每个方块都可以代表 0X10 的字节,它没有任何指针,你怎么掌握 PC 做代码实行呢?
free 之后你能锁 use 的部分便是赤色的两个框,是 0X10 的单位,可以写两个指针的地址。A 是工具地址本身,第二个是 A+0X20 的位置。你可以用内核的其它工具去喷对,在 0X20 这个位置恰好有一个 buffer,把它的地址覆盖成 A 地址本身,你真正写的是地址 A 本身,通过覆盖地址 A 本身的方法把 UAF写成堆溢出,再覆盖其它工具,这个时候就比较好写。如果不能掌握 PC 指针,UAF 并不好写。
总结
演讲后,科恩实验室在接管雷锋网采访时提到,Google 是非常重视安全的公司,在他们每月发的安全公告里面有很多安全漏洞,但绝大部分安全漏洞的利用场景非常困难,乃至是无法利用的,而个中真正威胁用户手机的漏洞是比较少的。
智好手机刚刚兴起时,众人对安全问题考虑不多。而随着攻击路子和攻击方向的增加,安全问题会被越来越重视。越来越多的人用新的方法去挖掘漏洞,其数量会变得越来越少。一旦创造新的攻击方法、新的漏洞模式,可能就会有一波漏洞攻击方法被爆发出来。随着逐步被重视,攻击路径被堵住。
因此,面对越来越健壮的 Android 内核,攻击面急剧缩紧,只剩下一部分系统调用可供攻击。在这个中,品相好的漏洞相对付品相不好的漏洞并不多。
但科恩实验室的方家弘见告雷锋网,一样平常来说只假如漏洞都须要被修补,事实上品相好的漏洞常日会更快修补,由于被利用的危险更大。这便是为什么精良的安全研究团队,比如 project zero,常常输出“高质量”的漏洞,也便是能够输出漏洞和利用思路乃至是完全的利用代码。漏洞的品相好坏有时候也很难判断,除非写出详细的利用代码。
当然,这个实在不是安卓的特例。漏洞和很多东西一样,比如玉石原石类,都有一个“品相”。漏洞的品相取决于很多成分,比如漏洞所在的攻击面是否随意马虎访问到、漏洞的触发是否随意马虎和稳定,等等。以是有无法利用的漏洞是很正常的。
申迪也总结了面对无法利用的漏洞做法,首先要确定核心问题到底是什么,是不是真的不能利用,还是有其它办法。也可以借助内核中的其它特性,或者参考过往的案例,毕竟有些思想是共通的,可以帮助你有些想法。
纵然末了真的创造无解,也可以丢开一段韶光。他回顾起自己写 2014-0403 那个漏洞,也是丢开了 1、2个月,溘然想到一个方法写出来的。
末了,不要盲目报告一些无法利用漏洞,没有人以为你真的厉害。