首页 > 编程知识 正文

uaf(二进制漏洞)

时间:2023-05-05 04:56:12 阅读:100886 作者:4920

雷锋编者按。com:8月16日,第三届中国互联网安全领袖峰会(CSS 2017)在北京国家会议中心举行。九大突围会场之一的腾讯安全探索论坛(TSec)以“新安全探索”为主题,汇聚了国际知名厂商和顶尖高校的资深安全专家,共同探讨全球信息安全领域的前沿技术、研究成果和未来趋势。腾讯安全科恩实验室的Simple Belt和Shendi分享了面对安卓内核中UAF漏洞数量不断减少、利用难度逐渐增加的现状,如何稳定高效地利用这些漏洞完成操作系统权限提升。

简单带:大家下午好!我是腾讯安全科恩实验室的简单带,我和我的同事沈迪将与大家分享“利用安卓内核中的UAF漏洞”。

今天的分享主要分为四个部分。首先,我来介绍一些前段时间出现的典型安卓内核漏洞。其次,需要从利用方面分析UAF这种特定类型漏洞的特点,这涉及到系统组件的基础知识,让大家了解后续内容。之后交给神迪,神迪具体分析perf子系统中发现的UAF漏洞的具体利用过程。最后,分享我们的总结和思考。

首先介绍安卓内核漏洞的简史。

为什么要攻击安卓内核?

安卓系统被视为一个整体。在最早的系统中,不同的应用程序和uid被用来隔离应用程序。后来发现这并不能阻止攻击,于是产生了加密文件系统等安全措施。

大多数措施的实现都依赖于安卓的内核。攻击内核本身非常有帮助。在Windows系统中,攻击内核可以直接达到提升实力的目的。在安卓系统中,你也可以绕过系统中设置的限制。对于坏人来说,他们可以达到他们不可告人的目的。

根据时间线和我个人的理解,列出了有代表性的内核漏洞。当然不包括我们今天要讲的漏洞。

2013年11月,CVE-2013-6282是我们朋友写的最早的漏洞。这是一个逻辑问题。当我们去年攻击特斯拉时,我们惊讶地发现这个漏洞仍然存在。当时特斯拉自信车辆无法攻破,所以他们没有在内核做更多的防御。bug被举报修复后,特斯拉在内核上做了非常激进的更新修复。大胆漏洞是常见的安卓根漏洞。

2014年,美国两个天才少年发现了towelroot,这是一个引起巨大反响的万能根。这种类型的漏洞在当时是比较新的,利用这种漏洞的方法也很有新意,引起了不小的轰动。

2015年5月,我们找到了CVE-2015-3636。

2016年8月,我们使用了CVE-2015-1805。

然后就是非常著名的Dirty COW,这也是一个逻辑问题。

2017年4月,谷歌研究人员发现的wifi Firmware漏洞是一个非常创新的漏洞。

我之前提到过几个有代表性的漏洞,6282是业务逻辑问题;1805年是跨境访问;353和3636都是UAF漏洞。今天,我们主要研究和讨论UAF漏洞。

hsjdyj和UAF的漏洞需要先Free后用,成功利用这个漏洞有几个步骤不能省略。一是如何依次触发免费和使用。这似乎比较简单。以2015-3636为例。使用和免费的时机是非常可控的。我们可以为所欲为,更好地控制自由空间。

336漏洞的出现很好,但在很多情况下,可能会面临种族的问题。在这种情况下,如何稳定控制免费使用的顺序,稳定触发使用是一个难题。不同的漏洞需要不同的技能来应对。我们已经能够阐明定时问题,然后如何改变内核的控制能力以及如何控制代码执行。有不同的方法来控制自由空间,不同的洞和不同的方法。

这里涉及到的最重要的是Linux内核的内存管理。我们生动地展示了利用UAF漏洞的方法。堆上有四个ABCD对象。这是一种相对简单的表示方法。为了能够稳定有效地丢弃目标对象,并填充我们想要的内容,我们需要对内核存储管理有更深入的了解,这也涉及到Linux内核的堆管理器。

在安卓系统中,我们称之为公共堆管理器SLUB,它取代了之前常见的SLAB。在管理结构上,SLUB是简化版,U表示不引用。Qued是一个队列。作为一个堆管理器,必须总有一个队列,当你申请的时候可以从这里取。它取消了单独队列的结构,使得完全空闲的SLUB被完全释放。它存在于每cpu的平板中,它的发布和应用过程会非常快。为了实现兼容性,将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个月,突然想到一个方法写出来的。

最后,不要盲目报告一些无法利用漏洞,没有人觉得你真的厉害。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。