最新漏洞

高通加解密引擎提权漏洞解析

在函数 qcedev_hash_cmac 里, line 900 申请的堆内存 k_buf_src 的长度是 qcedev_areq->sha_op_req.data_len ,即请求数组里所有项的长度之和

然后在 line 911 ~ 920 的循环里,会将请求数组 qcedev_areq->sha_op_req.data[] 里的元素挨个拷贝到堆 k_buf_src 里,由于前面存在的整数溢出漏洞,这里会转变成为一个堆溢出漏洞,至此漏洞坐实。

CVE-2016-3935 漏洞补丁 [^]

图19

这个 漏洞补丁 也很直观,就是在做整数溢出时,将 ULONG_MAX 改成了 U32_MAX, 这种因为系统由32位升级到64位导致的代码漏洞,是 2016 年的一类常见漏洞

下面进入漏洞利用分析

漏洞利用 [^]

android kernel 漏洞利用基础

在介绍本文两个漏洞的利用之前,先回顾一下 android kernel 漏洞利用的基础知识

什么是提权 [^]

图20

linux kernel 里,进程由 struct task_struct 表示,进程的权限由该结构体的两个成员 real_cred 和 cred 表示

图21

所谓提权,就是修改进程的 real_cred/cred 这两个结构体的各种 id 值,随着缓解措施的不断演进,完整的提权过程还需要修改其他一些内核变量的值,但是最基础的提权还是修改本进程的 cred, 这个任务又可以分解为多个问题:

  • 怎么找到目标 cred ?
  • cred 所在内存页面是否可写?
  • 如何利用漏洞往 cred 所在地址写值?

利用方法回顾 [^]

图22

上图是最近若干年围绕 android kernel 漏洞利用和缓解的简单回顾,

  • 09 ~ 10 年的时候,由于没有对 mmap 的地址范围做任何限制,应用层可以映射0页面,null pointer deref 漏洞在当时也是可以做利用的,后面针对这种漏洞推出了mmap_min_addr 限制,目前 null pointer deref 漏洞一般只能造成
  • 11 ~ 13 年的时候,常用的提权套路是从/proc/kallsyms 搜索符号 commit_creds 和 prepare_kernel_cred 的地址,然后在用户态通过这两个符号构造一个提权函数(如下),

图23

可以看到,这个阶段的用户态 shellcode 非常简单, 利用漏洞改写内核某个函数指针(最常见的就是 ptmx 驱动的 fsync 函数)将其实现替换为用户态的函数, 最后在用户态调用被改写的函数, 这样的话从内核直接执行用户态的提权函数完成提权

这种方法在开源root套件 android_run_root_shell 得到了充分提现

后来,内核推出了kptr_restrict/dmesg_restrict 措施使得默认配置下无法从 /proc/kallsyms 等接口搜索内核符号的地址

但是这种缓解措施很容易绕过, android_run_root_shell 里提供了两种方法:

  1. 通过一些内存 pattern 直接在内存空间里搜索符号地址,从而得到commit_creds/prepare_kernel_cred 的值;
    libkallsyms:get_kallsyms_in_memory_addresses
  2. 放弃使用commit_creds/prepare_kernel_cred 这两个内核函数,从内核里直接定位到 task_struct cred 结构并改写
    obtain_root_privilege_by_modify_task_cred
  • 2013 推出 text RO 和 PXN 等措施,通过漏洞改写内核代码段或者直接跳转到用户态执行用户态函数的提权方式失效了, android_run_root_shell这个项目里的方法大部分已经失效, 在 PXN 时代,主要的提权思路是使用rop

具体的 rop 技巧有几种,

  1. 下面两篇文章讲了基本的linux kernel ROP 技巧

Linux Kernel ROP - Ropping your way to # (Part 1)/)

Linux Kernel ROP - Ropping your way to # (Part 2)/)

图24

可以看到这两篇文章的方法是搜索一些 rop 指令 ,然后用它们串联 commit_creds/prepare_kernel_cred, 是对上一阶段思路的自然延伸。

  1. 使用 rop 改写addr_limit 的值,破除本进程的系统调用 access_ok 校验,然后通过一些函数如 ptrace_write_value_at_address 直接读写内核来提权, 将 selinux_enforcing 变量写0关闭 selinux
  2. 大名鼎鼎的Ret2dir bypass PXN
  3. 还有就是本文使用的思路,用漏洞重定向内核驱动的xxx_operations 结构体指针到应用层,再用 rop 地址填充应用层的伪 xxx_operations 里的函数实现
  4. 还有一些 2017 新出来的绕过缓解措施的技巧,参考
  • 进入2017年,更多的漏洞缓解措施正在被开发和引进,谷歌的nick正在主导开发的项目Kernel_Self_Protection_Project 对内核漏洞提权方法进行了分类整理,如下

 

 

针对以上提权方法,Kernel_Self_Protection_Project 开发了对应的一系列缓解措施,目前这些措施正在逐步推入linux kernel 主线,下面是其中一部分缓解方案,可以看到,我们回顾的所有利用方法都已经被考虑在内,不久的将来,这些方法可能都会失效

  • Split thread_info off of kernel stack (Done: x86, arm64, s390. Needed on arm, powerpc and others?) * Move kernel stack to vmap area (Done: x86, s390. Needed on arm, arm64, powerpc and others?)
  • Implement kernel relocation and KASLR for ARM
  • Write a plugin to clear struct padding
  • Write a plugin to do format string warnings correctly (gcc’s -Wformat-security is bad about const strings)
  • Make CONFIG_STRICT_KERNEL_RWX and CONFIG_STRICT_MODULE_RWX mandatory (done for arm64 and x86, other archs still need it)
  • Convert remaining BPF JITs to eBPF JIT (with blinding) (In progress: arm)
  • Write lib/test_bpf.c tests for eBPF constant blinding
  • Further restriction of perf_event_open (e.g. perf_event_paranoid=3)
  • Extend HARDENED_USERCOPY to use slab whitelisting (in progress)
  • Extend HARDENED_USERCOPY to split user-facing malloc()s and in-kernel malloc()svmalloc stack guard pages (in progress)
  • protect ARM vector table as fixed-location kernel target
  • disable kuser helpers on arm
  • rename CONFIG_DEBUG_LIST better and default=y
  • add WARN path for page-spanning usercopy checks (instead of the separate CONFIG)
  • create UNEXPECTED(), like BUG() but without the lock-busting, etc
  • create defconfig “make” target for by-default hardened Kconfigs (using guidelines below)
  • provide mechanism to check for ro_after_init memory areas, and reject structures not marked ro_after_init in vmbus_register()
  • expand use of __ro_after_init, especially in arch/arm64
  • Add stack-frame walking to usercopy implementations (Done: x86. In progress: arm64. Needed on arm, others?)
  • restrict autoloading of kernel modules (like GRKERNSEC_MODHARDEN) (In progress: Timgad LSM)

有兴趣的同学可以进入该项目看看代码,提前了解一下缓解措施,

比如 KASLR for ARM, 将大部分内核对象的地址做了随机化处理,这是以后 android kernel exploit 必须面对的;

另外比如 __ro_after_init ,内核启动完成初始化之后大部分 fops 全局变量都变成 readonly 的,这造成了本文这种利用方法失效, 所幸的是,目前 android kernel 还是可以用的。

本文使用的利用方法 [^]

对照 Kernel_Self_Protection_Project 的利用分类,本文的利用思路属于 Userspace data usage

Sometimes an attacker won’t be able to control the instruction pointer directly, but they will be able to redirect the dereference a structure or other pointer. In these cases, it is easiest to aim at malicious structures that have been built in userspace to perform the exploitation.

具体来说,我们在应用层构造一个伪 file_operations 结构体(其他如 tty_operations 也可以),然后通过漏洞改写内核某一个驱动的 fops 指针,将其改指向我们在应用层伪造的结构体,之后,我们搜索特定的 rop 并随时替换这个伪 file_operations 结构体里的函数实现,就可以做到在内核多次执行任意代码(取决于rop) ,这种方法的好处包括:

  1. 内核有很多驱动,所以 fops 非常多,地址上也比较分散,对一些溢出类漏洞来说,选择比较多
  2. 内核的 fops 一般都存放在 writable 的 data 区,至少目前android 主流 kernel 依然如此
  3. 将内核的 fops 指向用户空间后,用户空间可以随意改写其内部函数的实现
  4. 只需要一次内核写

下面结合漏洞说明怎么利用

CVE-2016-6738 漏洞利用 [^]

CVE-2016-6738 是一个任意地址写任意值的漏洞,利用代码已经提交在 EXP-CVE-2016-6738

我们选择重定向 /dev/ptmx 设备的 file_operations, 先在用户态构造一个伪结构,如下

图25

根据前面的分析,伪结构的值需要先做一次加密,再使用

图26

下面是核心的函数

图27

参数 src 就是 fake_ptmx_fops 加密后的值,我们将其地址放入 qcedev_cipher_op_req.vbuf.src[0].vaddr 里,目标地址 qcedev_cipher_op_req.vbuf.dst[0].vaddr 存放 ptmx_cdev->ops 的地址,然后调用 ioctl 触发漏洞,任意地址写漏洞触发后,目标地址 ptmx_cdev->ops 的值会被覆盖为 fake_ptmx_fops.

此后,对 ptmx 设备的内核fops函数执行,都会被重定向到用户层伪造的函数,我们通过一些rop 片段来实现伪函数,就可以被内核直接调用。

图28

比如,我们找到一段 rop 如上,其地址是 0xffffffc000671a58, 其指令是 str w1, [x2] ; ret ;

这段 rop 作为一个函数去执行的话,其效果相当于将第二个参数的值写入第三个参数指向的地址。

我们用这段 rop 构造一个用户态函数,如下

图29

9*8 是 ioctl 函数在 file_operations 结构体里的偏移,

*(unsigned long*)(fake_ptmx_fops + 9 * 8) = ROP_WRITE;

的效果就是 ioctl 的函数实现替换成 ROP_WRITE, 这样我们调用 ptmx 的 ioctl 函数时,最后真实执行的是 ROP_WRITE, 这就是一个内核任意地址写任意值函数。

同样的原理,我们封装读任意内核地址的函数。

有了任意内核地址读写函数之后,我们通过以下方法完成最终提权:

图30

搜索到本进程的 cred 结构体,并使用我们封装的内核读写函数,将其成员的值改为0,这样本进程就变成了 root 进程。
搜索本进程 task_struct 的函数 get_task_by_comm 具体实现参考 github 的代码。

CVE-2016-3935 漏洞利用 [^]

这个漏洞的提权方法跟 6738 是一样的,唯一不同的地方是,这是一个堆溢出漏洞,我们只能覆盖堆里边的 fops (cve-2016-6738 我们覆盖的是 .data 区里的 fops )。

在我测试的版本里,k_buf_src 是从 kmalloc-4096 分配出来的,因此,需要找到合适的结构来填充 kmalloc-4096 ,经过一些源码搜索,我找到了 tty_struct 这个结构

图31

在我做利用的设备里,这个结构是从 kmalloc-4096 堆里分配的,其偏移 24Byte 的地方是一个 struct tty_operations 的指针,我们溢出后重写这个结构体,用一个用户态地址覆盖这个指针。

图32

4128 + 536867423 + 7 * 0x1fffffff = 632

溢出的方法如上,我们让 entry 的数目为 9 个,第一个长度为 4128, 第二个为 536867423, 其他7个为0x1fffffff

这样他们加起来溢出之后的值就是 632, 这个长度刚好是 struct tty_struct 的长度,我们用 qcedev_sha_op_req.data[0].vaddr[4096] 这个数据来填充被溢出的 tty_struct 的内容

主要是填充两个地方,一个是最开头的 tty magic, 另一个就是偏移 24Bype 的 tty_operations 指针,我们将这个指针覆盖为伪指针 fake_ptm_fops.

之后的提权操作与 cve-2016-6738 类似,

图33

如上,ioctl 函数在 tty_operations 结构体里偏移 12 个指针,当我们用 ROP_WRITE 覆盖这个位置时,可以得到一个内核地址写函数。

图34

图35

同理,当我们用 ROP_READ 覆盖这个位置时,可以得到一个内核地址写函数。

最后,用封装好的内核读写函数,修改内核的 cred 等结构体完成提权。

参考 [^]

android_run_root_shell

xairy

New Reliable Android Kernel Root Exploitation Techniques

 

上一页 1 2
(0)

本文由 安全周 作者:空心 发表,转载请注明来源!

热评文章

发表评论