CVE-2014-4461

CVE-2014-4461 – IOSharedDataQueue映射内存漏洞

漏洞描述

IOSharedDataQueue类用于队列数据的管理,并且能够被映射到用户态下共享。由于返回的映射内存的大小设置问题,导致除了Data数据外还映射了notify msg的内存区域,从而可以在用户态下读取/改写msg内port的指针。

初始化时分配了足够大小的内存,并把notifyMsg成员放在尾部

Boolean IOSharedDataQueue::initWithCapacity(UInt32 size)
{
    ...

    allocSize = round_page(size + DATA_QUEUE_MEMORY_HEADER_SIZE + DATA_QUEUE_MEMORY_APPENDIX_SIZE);

    if (allocSize < size) {
        return false;
    }

    // 分配足够大小的内存给dataQueue
    dataQueue = (IODataQueueMemory *)IOMallocAligned(allocSize, PAGE_SIZE);
    if (dataQueue == 0) {
        return false;
    }

    ...

    // notifyMsg成员被放在dataQueue的尾部
    appendix            = (IODataQueueAppendix *)((UInt8 *)dataQueue + size + DATA_QUEUE_MEMORY_HEADER_SIZE);
    appendix->version   = 0;
    notifyMsg           = &(appendix->msgh);
    setNotificationPort(MACH_PORT_NULL);

    return true;
}

在返回映射内存的时候包含了整个数据区的大小

IOMemoryDescriptor *IOSharedDataQueue::getMemoryDescriptor()
{
    IOMemoryDescriptor *descriptor = 0;

    if (dataQueue != 0) {
        descriptor = IOMemoryDescriptor::withAddress(dataQueue, getQueueSize() + DATA_QUEUE_MEMORY_HEADER_SIZE + DATA_QUEUE_MEMORY_APPENDIX_SIZE, kIODirectionOutIn);
    }

    return descriptor;
}

通过IOConnectSetNotificationPort可以设置一个通知端口,用于当队列发生变化时接收到通知。notifyMsg中包含了一个内核结构port,该内核结构被暴露到了用户态下!

void IODataQueue::setNotificationPort(mach_port_t port)
{
    mach_msg_header_t * msgh = (mach_msg_header_t *) notifyMsg;

    if (msgh) {
        bzero(msgh, sizeof(mach_msg_header_t));
        msgh->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
        msgh->msgh_size = sizeof(mach_msg_header_t);
        msgh->msgh_remote_port = port;
    }
}

利用思路

通过映射内存后我们可以在用户态下修改port指针,而port指针在发送队列变化通知时会被使用

void IODataQueue::sendDataAvailableNotification()
{
    kern_return_t       kr;
    mach_msg_header_t * msgh;

    msgh = (mach_msg_header_t *) notifyMsg;
    if (msgh && msgh->msgh_remote_port) {
        kr = mach_msg_send_from_kernel_with_options(msgh, msgh->msgh_size, MACH_SEND_TIMEOUT, MACH_MSG_TIMEOUT_NONE);
        switch(kr) {
            case MACH_SEND_TIMED_OUT:    // Notification already sent
            case MACH_MSG_SUCCESS:
            case MACH_SEND_NO_BUFFER:
                break;
            default:
                IOLog("%s: dataAvailableNotification failed - msg_send returned: %dn", /*getName()*/"IODataQueue", kr);
                break;
        }
    }
}

sendDataAvailableNotification -> mach_msg_send_from_kernel_with_options -> ipc_kmsg_get_from_kernel 函数中存在一个内存拷贝的操作,通过精心构造数据控制port指向的内容,可以转换成任意的内存写的利用

mach_msg_return_t
ipc_kmsg_get_from_kernel(
    mach_msg_header_t   *msg,
    mach_msg_size_t size,
    ipc_kmsg_t      *kmsgp)
{
    ...

    dest_port = (ipc_port_t)msg->msgh_remote_port;

    msg_and_trailer_size = size + MAX_TRAILER_SIZE;

    /*
     * See if the port has a pre-allocated kmsg for kernel
     * clients.  These are set up for those kernel clients
     * which cannot afford to wait.
     */
    if (IP_VALID(dest_port) && IP_PREALLOC(dest_port)) {
        ...

        // 控制port的内容后可以控制kmsg
        kmsg = dest_port->ip_premsg;
        if (ikm_prealloc_inuse(kmsg)) {
            ip_unlock(dest_port);
            return MACH_SEND_NO_BUFFER;
        }

        ...
    }
    else
    {
        kmsg = ipc_kmsg_alloc(msg_and_trailer_size);
        if (kmsg == IKM_NULL)
            return MACH_SEND_NO_BUFFER;
    }

    // kmsg->ikm_header可控,msg就是notifyMsg结构,也可控,size也可控
    (void) memcpy((void *) kmsg->ikm_header, (const void *) msg, size);

    ...
}

触发条件

  • 首先需要找到使用IOSharedDataQueue来处理数据的对象
  • 通过IOConnectSetNotificationPort设置内核port
  • 通过IOConnectMapMemory获取映射内存,从而可以修改notifyMsg中的port数据
  • 内核heap风水控制数据后修改port,当队列发生变化时触发漏洞,达到任意写内核内存的目的

发表评论

电子邮件地址不会被公开。 必填项已用*标注