一个重写的概念验证 / 本地权限提升(LPE)漏洞利用,针对 CVE-2019-2215,即 Android Binder 驱动中的一个释放后使用(Use-After-Free)漏洞。
我已在 vulnerable_kernel_builds 文件夹中提供了存在漏洞的内核构建。创建一个 Android 10 模拟器 AOSP,并使用它运行该内核
emulator -show-kernel -no-window -no-snapshot -wipe-data -avd research -kernel bzImage
如需了解该漏洞的技术细节,可以从这篇精彩的博客中学习:https://projectzero.google/2019/11/bad-binder-android-in-wild-exploit.html
task_struct 结构有一个重要成员 addr_limit,其类型为 mm_segment_t。addr_limit 存储最高的有效用户空间地址。addr_limit 是 struct thread_info 或 struct thread_struct 的一部分,具体取决于目标架构。由于我们目前处理的是 x86_64 位系统,addr_limit 定义在 struct thread_struct 中。

如果我们能用 0xFFFFFFFFFFFFFFFF 覆盖这个 addr_limit,我们将能够读写内核空间内存的任何部分。为了使漏洞利用在 x86_64 和 arm64 上具有更好的兼容性,最好将 addr_limit 设置为 0xFFFFFFFFFFFFFFFE
struct iovec 用于 Vectored I/O,也就是 Scatter/Gather I/O。struct iovec 的主要问题之一是它们生命周期很短。它们在系统调用处理缓冲区时被分配,并在返回用户模式时立即释放。
我们希望 iovec 结构在触发 unlink 操作并用 binder_thread->wait.head 的地址覆盖 iov_base 指针时仍然驻留在内核中,从而获得有范围的读写能力。一种方法是在 pipe 文件描述符上使用 readv、writev 等系统调用,因为当 pipe 已满或为空时,这些调用会阻塞。pipe 是一种单向数据通道,可用于进程间通信。pipe 的阻塞特性为我们提供了显著的时间窗口来破坏内核空间中的 iovec 结构。
同理,我们可以使用 recvmsg 系统调用,通过将 MSG_WAITALL 作为标志参数传递来使其阻塞。
由于 binder_thread 结构的大小为 408 字节,它将最终位于 kmalloc-512 缓存中。

我们需要堆叠 25 iovec 结构来重新分配悬空的内存块。408 / 16 = 25.5

从上图我们可以看到,iovecStack[10].iov_len 和 iovecStack[11].iov_base 将被覆盖。

因此,我们希望先处理 iovecStack[10],阻塞 writev 系统调用,然后触发 unlink 操作。这将确保当 iovecStack[11].iov_base 被覆盖时,我们会恢复 writev 系统调用。最后,将 binder_thread 内存块的内容泄漏回用户空间,并从中读取 task_struct 指针

为了实现有范围的 write,我们将使用 recvmsg 系统调用,通过将 MSG_WAITALL 作为标志参数传递来使其阻塞。recvmsg 系统调用可以和 writev 系统调用一样阻塞。

由于 mm_segment_t 的大小为 0x8 字节,我们希望用 0xFFFFFFFFFFFFFFFE 覆盖它,因为它是内核空间最高的有效地址,并且如果在 arm64 系统中发生缺页错误,不会导致进程崩溃。

