漏洞描述
通过查看IOHIDFamily的代码,检查IOHIDLibUserClient的15号处理函数_getElements。该函数的入参是1个uint64,指定要获取的element是哪个队列的。出参是一个struct结构,无固定长度。
{ // kIOHIDLibUserClientGetElements
(IOExternalMethodAction) &IOHIDLibUserClient::_getElements,
1, 0,
0, kIOUCVariableStructureSize
},
在返回映射内存的时候包含了整个数据区的大小
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,当队列发生变化时触发漏洞,达到任意写内核内存的目的