Skip to content
KitploitKITPLOIT
工具博客
Log in
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
AndroidKernelVulnerability — 触发与分析 Android 内核漏洞 CVE-2019-2215 | Kitploit
工具/GitHubGitHub/sharif-dev/androidkernelvulnerability
Android安全权限提升静态分析动态分析 (沙盒)漏洞利用学习与教育二进制利用实验室与实践

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
GitHub
sharif-dev/androidkernelvulnerability

AndroidKernelVulnerability

触发与分析 Android 内核漏洞 CVE-2019-2215

查看仓库
7219154年前Kitploit 审核通过

Android 内核漏洞

概述

2017 年 11 月,syzkaller 系统检测到 Linux 内核 的一个 use-after-free 漏洞。2018 年 2 月,一些 Linux 内核和 Android 版本中修补了此漏洞。

此修复从未包含在 Android 月度安全公告 中,因此许多新发布的设备(如 Pixel 和 Pixel2)并未修补此漏洞。

2019 年 9 月,Project Zero 向 Android 通报了此漏洞的安全影响。随后,Android 为此漏洞分配了 CVE-2019-2215,使其更为正式和公开。

CVE-2019-2215 是 binder.c 中的一个 use-after-free 漏洞,它允许从 Android 应用程序进行权限提升(获取 root 访问权限)。利用此漏洞无需用户交互。只需安装一个恶意的本地应用程序即可。

接下来,我们将更详细地介绍这个 Android 内核漏洞,并利用该漏洞获取整个 Android 设备的 root 访问权限(权限提升)。

我们将使用以下概念验证(PoC):

https://github.com/cloudfuzz/android-kernel-exploitation

触发漏洞

我们首先会向你展示一种在 Android 模拟器上触发此漏洞并导致内核崩溃的方法。然后,为了了解它的危险性,我们继续使用 PoC 在模拟的 Android 设备中获取 root 访问权限。随后,我们将分析内核代码,找出其中的原因(静态分析和动态分析)。

分析之后,我们将看到我们是如何获得 root 访问权限的。最后,我们将了解如何通过补丁缓解此漏洞。

要通过触发此漏洞使内核崩溃,我们使用以下步骤:

  1. 首先,你需要一个安装了 'gdb' 和 'python' 的 Linux 操作系统。
  2. 克隆 PoC 仓库。(https://github.com/cloudfuzz/android-kernel-exploitation)
  3. 安装 Android 模拟器和 Android NDK(通过安装 Android Studio)
  4. 克隆 Android 内核源代码。(将使用 'q-goldfish-android-goldfish-4.14-dev' 分支)
  5. 这个内核已经打过补丁,我们对其进行修改,以在该内核代码中重新引入该漏洞。
  6. 现在我们应该从源代码构建内核。我们使用 KASan 构建我们的内核。
  7. 我们启动构建好的内核,并用它运行我们的模拟器。
  8. 然后,通过使用 PoC 仓库中的 'trigger.cpp',我们触发崩溃(使用 'adb' 命令)。
  9. 我们将使用 PoC 中的 'root-me.py' 和 'gdb' 命令来获取模拟设备中的 root 访问权限。

观看以下视频:

[视频]

静态分析

在本节(以及下一节)中,我们将通过静态和动态分析来理解崩溃发生的原因。

在这里,我们将分析内核代码(静态分析)以理解该问题。在 crash_report.txt 中,有来自 KASan 的报告,指出这是一个 use-after-free 漏洞。这意味着一个对象在堆中被分配(并且我们有一个指向它的引用),然后我们从堆中释放了该对象,之后我们又错误地通过一个引用调用了它。该报告中打印了这三个阶段的堆栈跟踪。

如果你还记得上面的内容,我们在 PoC 中使用了 'trigger.cpp'。

以下是 trigger.cpp 的主要代码:``` int main() { //1 int fd, epfd; //2 struct epoll_event event = {.events = EPOLLIN}; //3 fd = open("/dev/binder", O_RDONLY); //4 epfd = epoll_create(1000); //5 epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); //6 ioctl(fd, BINDER_THREAD_EXIT, NULL); }

我们来看看 `trigger.cpp` 在做什么。

在 Android 中(与其他基于 Unix 的操作系统一样),我们有一些进程。我们运行的每个程序都会创建一个(或多个)进程,这些进程由操作系统(OS)管理。操作系统可以在它们之间切换(多任务处理),或者终止某个进程等。出于安全原因,进程默认彼此隔离。

在某些情况下,一个进程可能需要与另一个进程交换数据。这称为进程间通信(IPC)。在 Linux 中有多种方式可以让进程通信。Android 引入了一种名为 **'Binder'** 的特定 IPC 机制。Binder 是一个用于促进进程间通信的内核驱动程序。

在 Android 中,IPC 可以通过直接调用一些内核方法(其中大部分位于 drivers/binder.c)或使用高级实现(例如 Java)来完成。






![alt_text](https://raw.githubusercontent.com/sharif-dev/AndroidKernelVulnerability/master/images/binder.jpg)


为了使用 **binder**,我们应该打开内核的 binder 模块。这通过 trigger.cpp 的第 3 行完成。然后我们会得到一个文件描述符指针,通过这个 fd,可以识别 IPC 的内核发起方和接收方。

与驱动器的所有交互都将通过一小组 **'ioctl'** 命令(BINDER_THREAD_EXIT、BINDER_WRITE_READ 等)进行。

关于 binder 的更多信息:[link1](https://www.nds.ruhr-uni-bochum.de/media/attachments/files/2012/03/binder.pdf)、[link2](http://rts.lab.asu.edu/web_438/project_final/CSE_598_Android_Architecture_Binder.pdf)

在 Linux 中,我们有一个称为 '**event polling**' 的概念。当我们想要监视多个文件描述符(当我们打开驱动程序或处理 IO 等时,得到的就是文件描述符)时,会使用 '**epoll**' API。

**epoll** 是一个内核结构体,它有两个重要字段。



*   interest list = 我们想要监视的文件描述符列表。
*   ready list = 准备好进行 I/O 的文件描述符列表。

为了使用事件轮询,我们首先创建一个 epoll(第 4 行),然后通过调用内核的 **epoll_ctl** 方法,将一个与文件描述符(**fd**)相关联的事件(&event)添加或删除(EPOLL_CTL_ADD)到我们创建的 epoll(**epfd**)中。

=> `epoll_event event` 是一个事件,当关联的文件(fd)可用于读取操作时触发。

现在我们理解了 **trigger.cpp** 在做什么(不需要深入研究!)。它打开 **binder** 模块,创建一个 **epoll** 来在它准备好时监听它。然后在第 6 行,我们退出了从第 3 行开始的 binder。


#### 分配(Allocate):

通过调用 open(),我们实际上调用了 **open_binder()**(binder.c 中 open() 的实现),在 open_binder() 中,会创建一个新的 **'binder_proc'** 结构体,并且:

   ` fd->pricate_data = binder_proc`

通过调用 **epoll_create()**,会创建一个新的 epoll 结构体并添加到队列结构中。






![alt_text](https://raw.githubusercontent.com/sharif-dev/AndroidKernelVulnerability/master/images/event_poll.jpg)


通过调用 **epoll_ctrl(epdf, ADD, fd, event)**,会创建一个新的 **ep_item**,将 **fd**(要监听的描述符)关联到这个 **ep_item**,并将其插入到 event_poll 的 **红黑树**(epoll 中用于保存 ep_item 的数据结构)中。它还会调用 **ep_item_poll()**,该方法处理回调函数与 ep_item 的关联。

它会创建一个新的 **binder_thread** 结构体(**分配发生在这里**),将其链接到(上面创建的)**binder_proc**,然后创建一个 **epoll_entry** 结构体,它有两个链表,**epoll_entry->wait** 和 **epoll_entry->whead**,这两个链表都有一个指向之前创建的 **binder_thread** 的指针。

然后 **epoll_entry** 被链接到 ep_item(**ep_item->pwqlist** 是一个包含该 epoll_entry 的链表)。





![alt_text](https://raw.githubusercontent.com/sharif-dev/AndroidKernelVulnerability/master/images/even_poll.jpg)



#### 释放(Free):

通过调用 **ioctl(fd, ...)**,通过 fd->private_data 访问 binder_proc,然后 **binder_thread** 结构体会从内存中被**释放**。


#### 使用(Use):

当当前进程退出时,将会调用 **epoll_ctl(epfd, DEL, fd, event)**。

它会调用 **ep_remove(event_poll, ep_item)**。该方法从 **ep_item->pwqlist** 中获取 **epoll_entry**,然后获取 ep_item 的等待列表(**ep_tem->wait**),它是一个链表,它想要移除这个等待列表中的某一项。

它使用以下代码(使用伪代码):```
entry = wait->entry;
entry.next.prev = entry.prev;
entry.prev.next = entry.next;

这里 wait->entry 是指向 已从内存中移除的 binder_thread! 的指针。所以这是一个释放后使用(use-after-free)问题,会导致 bug!!

  • 在上面的代码中,我们使用了与内核实际代码类似的代码,它们在细节上可能有所不同。(在某些情况下,使用 binder_thread 而不是 binder_thread->wait)

总结:

我们创建了一个 event_poll,它包含一棵 红黑树(red_black_tree),每个节点是一个 ep_item,它有一个字段是 epoll_entry 列表,每个 epoll_entry 有两个指向 binder_thread 结构体的指针 (wait, whead).

通过调用 ioctl(),我们从内存中释放了 binder_thread,然后在退出时,这个结构体通过一个仍然可用的指针被访问了!

动态分析

动态分析是通过实时执行数据来测试和评估程序;以在程序运行时发现错误。

步骤:

  1. 构建不带 KASan 的 Android 内核

  2. 使用新构建的内核启动模拟器

  3. 启动模拟器

  4. 使用 GDB 附加到 QEMU 实例

  5. 构建漏洞触发器并将其推送到虚拟设备

  6. 在 GDB 中设置断点

    加载自定义 python 脚本(仓库中的 dynamic-analysis.py): 用于跟踪函数调用,并在 binder_thread 被释放前后转储其结构体内存块。还要在 unlink 操作前后转储相同的 binder_thread 结构体。

    在此文件中,我们首先删除所有断点,然后设置 2 个断点(BP);第一个符号是“binder_free_thread”(将跟踪 binder_free_thread 函数);在 binder_thread 被释放之前,会调用 stop 函数;因此参数和符号将通过(gb.write(....))显示,然后会调用回调方法(我们将其设置为 set_dump_binder_thread );在此函数中,binder_thread_address 将被设置到我们的全局变量中,gdb.execute 会将命令产生的任何输出发送到 GDB 的标准输出。

    第二个符号是“remove_wait_queue”(将跟踪 remove_wait_queue 函数),我们要观察的参数是 "wq_head"、"wq_entry",并且会在退出时设置 wait.c:52 断点。它们的回调函数是 dump_binder_thread 。这些断点将显示 unlink 操作前后发生的情况。

  7. 启动 adb shell 并运行触发 PoC

结果:

  • 结果的第一部分:
下载工具