雷锋网编者按:8月16日,第三届中国互联网安全领袖峰会(CSS 2017)在北京国家会议中心召开。作为九大分会场之一的腾讯安全探索论坛(TSec)以“安全新探索”为主题,云集了国际知名厂商及顶尖高校的资深安全专家,探讨全球信息安全领域前沿技术、研究成果及未来趋势。来自腾讯安全科恩实验室的方家弘、申迪分享了面对安卓内核中存在的UAF漏洞数量不断变小、利用难度逐渐变大的现状,将如何稳定高效地利用这类漏洞来完成操作系统提权。
方家弘:大家下午好!我是来自腾讯安全科恩实验室的方家弘,我和我的同事申迪给大家带来关于“安卓内核 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 的意思。Q 是队列,做堆管理器,总要有队列,申请的时候从这里拿。它取消了单独存在队列的结构,这样就使得完全空闲的 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 中一个对象被释放之后,仅仅是在对象的头部写入了指针,这个指针指向下一个可以使用的对象。