Cve-2015-5774分析

盘古(blog.pangu.io)也讲解了这个漏洞.但不是很详细.因为这个漏洞比较简单,就写一篇blog鼓励大家反编译taig,毕竟具体很多东西还是在汇编里学习.这里仅给出这个漏洞的分析.
另外给出文章地址: http://cocoahuke.com/2015/09/18/describeCVE-2015-5774/
和利用代码的地址: http://cocoahuke.com/2015/09/18/CVE-2015-5774Code/

先逆向的分析吧:
漏洞存在于IOHIDResourceDeviceUserClient的methods中第三个函数_postReportResult

const IOExternalMethodDispatch IOHIDResourceDeviceUserClient::_methods[kIOHIDResourceDeviceUserClientMethodCount] = {
. . . . . .
{   // kIOHIDResourceDeviceUserClientMethodPostReportResult
        (IOExternalMethodAction) &IOHIDResourceDeviceUserClient::_postReportResult,
        kIOHIDResourceUserClientResponseIndexCount, -1, /* 1 scalar input: the result, 1 struct input : the buffer */
        0, 0
}
}

IOReturn IOHIDResourceDeviceUserClient::postReportResult(IOExternalMethodArguments * arguments)
{
    OSObject * tokenObj = (OSObject*)arguments->scalarInput[kIOHIDResourceUserClientResponseIndexToken];
    
    if ( tokenObj && _pending->containsObject(tokenObj) ) {
        OSData * data = OSDynamicCast(OSData, tokenObj);
        if ( data ) {
            __ReportResult * pResult = (__ReportResult*)data->getBytesNoCopy();
            
            // RY: HIGHLY UNLIKELY > 4K
            if ( pResult->descriptor && arguments->structureInput ) {
                pResult->descriptor->writeBytes(0, arguments->structureInput, arguments->structureInputSize);

                // 12978252:  If we get an IOBMD passed in, set the length to be the # of bytes that were transferred
                IOBufferMemoryDescriptor * buffer = OSDynamicCast(IOBufferMemoryDescriptor, pResult->descriptor);
                if (buffer)
                    buffer->setLength((vm_size_t)arguments->structureInputSize);
            
            }
                
            pResult->ret = (IOReturn)arguments->scalarInput[kIOHIDResourceUserClientResponseIndexResult];

            _commandGate->commandWakeup(data);
        }
            
    }

    return kIOReturnSuccess;
}

其中一句

if (buffer)
    buffer->setLength((vm_size_t)arguments->structureInputSize);

没有经过检查的用户态传来的参数直接用在了setLength(不分配,重新设置缓冲区的长度),所以上面的writeBytes本来是安全写入.会检查写入的大小是否小于IOBufferMemoryDescriptor对象内部的_length变量.

IOByteCount IOMemoryDescriptor::writeBytes
                (IOByteCount inoffset, const void *bytes, IOByteCount length)
{
    . . . . . .
    // Assert that this entire I/O is withing the available range
    assert(offset <= _length);
    assert(offset + length <= _length);//check 
    . . . . . .
}

嗯,那么我们回到postReportResult函数,想要可以设置buffer的长度的条件是buffer存在.这个缓冲区的初始化在其他地方.

IOBufferMemoryDescriptor * buffer = OSDynamicCast(IOBufferMemoryDescriptor, pResult->descriptor);
if (buffer)
                    buffer->setLength((vm_size_t)arguments->structureInputSize);

那么具体点:

OSObject * tokenObj = (OSObject*)arguments->scalarInput[kIOHIDResourceUserClientResponseIndexToken];
    
    if ( tokenObj && _pending->containsObject(tokenObj) ) {
        OSData * data = OSDynamicCast(OSData, tokenObj);
        if ( data ) {
            __ReportResult * pResult = (__ReportResult*)data->getBytesNoCopy();
            
            // RY: HIGHLY UNLIKELY > 4K
            if ( pResult->descriptor && arguments->structureInput ) {
                pResult->descriptor->writeBytes(0, arguments->structureInput, arguments->structureInputSize);

                // 12978252:  If we get an IOBMD passed in, set the length to be the # of bytes that were transferred
                pResult->descriptor
                IOBufferMemoryDescriptor * buffer = OSDynamicCast(IOBufferMemoryDescriptor,pResult->descriptor);

可以知道在_pending中存在tokenObj

ok.到这里为止.正向分析:
先需要创建一个IOHIDDevice对象.创建IOHIDDevice对象

const IOExternalMethodDispatch IOHIDResourceDeviceUserClient::_methods[kIOHIDResourceDeviceUserClientMethodCount] = {
    {   // kIOHIDResourceDeviceUserClientMethodCreate
        (IOExternalMethodAction) &IOHIDResourceDeviceUserClient::_createDevice,
        1, -1, /* 1 struct input : the report descriptor */
        0, 0
    }
. . . . . .
}

创建时需要传入一个struct.这个struct是有格式化的.看下面可以知道是一个XML的数据.解析后为一个字典对象,字典对象里有很多不同key对应着不同数据.解析函数在IOHIDReportDescriptorParser.c的PrintHIDDescriptor函数

IOReturn IOHIDResourceDeviceUserClient::createDevice(IOExternalMethodArguments * arguments)
{
    IOMemoryDescriptor *    propertiesDesc      = NULL;
    void *                  propertiesData      = NULL;
    IOByteCount             propertiesLength    = 0;
    OSObject *              object              = NULL;
    IOReturn                result;
    
    // Report descriptor is static and thus can only be set on creation
    require_action(_device==NULL, exit, result=kIOReturnInternalError);
    
    // Let's deal with our device properties from data
    propertiesDesc = createMemoryDescriptorFromInputArguments(arguments); //为用户态参数初始化IOMemoryDescriptor
    require_action(propertiesDesc, exit, result=kIOReturnNoMemory);
    
    propertiesLength = propertiesDesc->getLength();
    require_action(propertiesLength, exit, result=kIOReturnNoResources);
    
    propertiesData = IOMalloc(propertiesLength);
    require_action(propertiesData, exit, result=kIOReturnNoMemory);
    
    propertiesDesc->readBytes(0, propertiesData, propertiesLength);
    
    require_action(strnlen((const char *) propertiesData, propertiesLength) < propertiesLength, exit, result=kIOReturnInternalError);//ERROR POINT

    object = OSUnserializeXML((const char *)propertiesData, propertiesLength);
    require_action(object, exit, result=kIOReturnInternalError);

    _properties = OSDynamicCast(OSDictionary, object); //将用户态传来的XML解析出的字典赋给_properties
    require_action(_properties, exit, result=kIOReturnNoMemory);
    
    _properties->retain();
    
    if ( arguments->scalarInput[0] )
        result = createAndStartDeviceAsync();
    else
        result = createAndStartDevice(); //会进入这里.
    
    require_noerr(result, exit);

exit:
    
    if ( object )
        object->release();
    
    if ( propertiesData && propertiesLength )
        IOFree(propertiesData, propertiesLength);

    if ( propertiesDesc )
        propertiesDesc->release();
    
    return result;
}

正常执行会进入createAndStartDevice函数.

IOReturn IOHIDResourceDeviceUserClient::createAndStartDevice()
{
    IOReturn    result;
    OSNumber *  number = NULL;
    
    number = OSDynamicCast(OSNumber, _properties->getObject(kIOHIDRequestTimeoutKey));
    if ( number )
        _maxClientTimeoutUS = number->unsigned32BitValue();

    // If after all the unwrapping we have a dictionary, let's create the device
    _device = IOHIDUserDevice::withProperties(_properties); //
    require_action(_device, exit, result=kIOReturnNoResources);
    
    require_action(_device->attach(this), exit, result=kIOReturnInternalError);
    
    require_action(_device->start(this), exit, _device->detach(this); result=kIOReturnInternalError);
    
    result = kIOReturnSuccess;
    
exit:
    if ( result!=kIOReturnSuccess ) {
        IOLog("%s: result=0x%08x\n", __FUNCTION__, result);
        OSSafeReleaseNULL(_device);
    }

    return result;
}

可以看到初始化了一个IOHIDUserDevice对象.attach,start函数是继承自IOHIDDevice.

bool IOHIDDevice::start( IOService * provider )
{   . . . . . .
    // Call handleStart() before fetching the report descriptor.
    require_action(handleStart(provider), error, result=false); //调用handleStart为_provider赋值(IOHIDResourceDeviceUserClient)
    // Fetch report descriptor for the device, and parse it.
    require_noerr_action(newReportDescriptor(&reportDescriptor), error, result=false); 
    require_action(reportDescriptor, error, result=false); //check
    . . . . . . 
}

bool IOHIDUserDevice::handleStart( IOService * provider )
{   . . . . . . 
    _provider = OSDynamicCast(IOHIDResourceDeviceUserClient, provider);
    . . . . . .
}

IOReturn IOHIDUserDevice::newReportDescriptor(IOMemoryDescriptor ** descriptor ) const
{
    OSData *                    data;
    
    data = OSDynamicCast(OSData, _properties->getObject(kIOHIDReportDescriptorKey));
    if ( !data )
        return kIOReturnError;
            
    *descriptor = IOBufferMemoryDescriptor::withBytes(data->getBytesNoCopy(), data->getLength(), kIODirectionNone); //拷贝从用户态传来的数据(key为kIOHIDReportDescriptorKey)

    return kIOReturnSuccess;
}

然后我们在创建完后根据IOHIDUserDevice调用客户端的updateElementValues函数
记得之前调用IOHIDResourceDeviceUserClient::clientMemoryForType创建_queue对象.

const IOExternalMethodDispatch IOHIDLibUserClient::
sMethods[kIOHIDLibUserClientNumCommands] = {
{   . . . . . . 
    //    kIOHIDLibUserClientUpdateElementValues
    (IOExternalMethodAction) &IOHIDLibUserClient::_updateElementValues,
    kIOUCVariableStructureSize, 0,
    0, 0
    },
    . . . . . . 
}

IOReturn IOHIDLibUserClient::_updateElementValues (IOHIDLibUserClient * target, void * reference __unused, IOExternalMethodArguments * arguments)
{
    return target->updateElementValues(arguments->scalarInput, arguments->scalarInputCount);
}

IOReturn IOHIDLibUserClient::updateElementValues (const uint64_t * lCookies, uint32_t cookieCount)
{
    IOReturn    ret = kIOReturnError;
    
    if (fNub && !isInactive()) {
        uint32_t    cookies[cookieCount];
        
        deflate_vec(cookies, cookieCount, lCookies, cookieCount);
        
        ret = fNub->updateElementValues((IOHIDElementCookie *)cookies, cookieCount);
    }
    
    return ret;
}

deflate_vec会把用户态传来的ICookies整理成cookies数组.
fNub为IOHIDDevice,而IOHIDUserDevice继承自IOHIDDevice, IOHIDUserDevice下的函数多为获取字典中的各种key相应的值. 继承自IOHIDDevice函数使用这些值.key的宏来自IOHIDKeys.h
所以会调用到IOHIDDevice的updateElementValues

IOReturn IOHIDDevice::updateElementValues(IOHIDElementCookie *cookies, UInt32 cookieCount) {
    IOMemoryDescriptor *	report = NULL;
    IOHIDElementPrivate *		element = NULL;
    IOHIDReportType		reportType;
    IOByteCount			maxReportLength;
    UInt8			reportID;
    UInt32			index;
    IOReturn			ret = kIOReturnError;

    maxReportLength = max(_maxOutputReportSize,
                            max(_maxFeatureReportSize, _maxInputReportSize));

    // Allocate a mem descriptor with the maxReportLength.
    // This way, we only have to allocate one mem discriptor
    report = IOBufferMemoryDescriptor::withCapacity(maxReportLength, kIODirectionNone); //这里初始化了IOBufferMemoryDescriptor 
    . . . . . . 
    for (index = 0; index < cookieCount; index++) { 
        element = GetElement(cookies[index]);
        
        if (element == NULL)
            continue;

        if ( element->getTransactionState()
                != kIOHIDTransactionStatePending )
            continue;

        if ( !element->getReportType(&reportType) )
            continue;

        reportID = element->getReportID();
        // calling down into our subclass, so lets unlock
        WORKLOOP_UNLOCK;
        
        report->prepare();
        ret = getReport(report, reportType, reportID); //调用IOHIDUserDevice::getReport
        report->complete();
        
        WORKLOOP_LOCK;

        if ( ret != kIOReturnSuccess ) //ret来自IOHIDResourceDeviceUserClient::getReport
            break;
       . . . . . . 
}

作为for循环的条件.用户态传来的cookieCount一定要大于2.cookies的值也是可控的.
updateElementValues函数分配了IOBufferMemoryDescriptor,最后调用了getReport,这里指IOHIDUserDevice的getReport,因为一直在做IOHIDUserDevice内的操作. updateElementValues是继承自IOHIDDevice

IOReturn IOHIDUserDevice::getReport(IOMemoryDescriptor    *report,
                                    IOHIDReportType        reportType,
                                    IOOptionBits        options )
{
    return _provider->getReport(report, reportType, options); //调用IOHIDResourceDeviceUserClient的getReport
}

//还记得_provider哪来的吗? 就是IOHIDResourceDeviceUserClient.所以并把分配的IOBufferMemoryDescriptor传过去.

IOReturn IOHIDResourceDeviceUserClient::getReport(IOMemoryDescriptor *report, IOHIDReportType reportType, IOOptionBits options)
{
    ReportGatedArguments    arguments   = {report, reportType, options};
    IOReturn                result;
    
    require_action(!isInactive(), exit, result=kIOReturnOffline);

    result = _commandGate->runAction(OSMemberFunctionCast(IOCommandGate::Action, this, &IOHIDResourceDeviceUserClient::getReportGated), &arguments);
exit:
    return result;
}

IOReturn IOHIDResourceDeviceUserClient::getReportGated(ReportGatedArguments * arguments)
{
    IOHIDResourceDataQueueHeader    header;
    __ReportResult                  result;
    AbsoluteTime                    ts;
    IOReturn                        ret;
    OSData *                        retData = NULL;
    
    require_action(!isInactive(), exit, ret=kIOReturnOffline);
    
    result.descriptor = arguments->report; //结构体指向已经分配的IOMemoryDescriptor
    
    retData = OSData::withBytesNoCopy(&result, sizeof(__ReportResult)); //retData为__ReportResult结构体
    require_action(retData, exit, ret=kIOReturnNoMemory);
    
    header.direction   = kIOHIDResourceReportDirectionIn;
    header.type        = arguments->reportType;
    header.reportID    = arguments->options&0xff;
    header.length      = (uint32_t)arguments->report->getLength();
    header.token       = (intptr_t)retData;

    _pending->setObject(retData); //往_pending添加__ReportResult
    
    require_action(_queue && _queue->enqueueReport(&header), exit, ret=kIOReturnNoMemory);
    
    // if we successfully enqueue, let's sleep till we get a result from postReportResult
    clock_interval_to_deadline(kMicrosecondScale, _maxClientTimeoutUS, &ts);
    switch ( _commandGate->commandSleep(retData, ts, THREAD_ABORTSAFE) ) {
        case THREAD_AWAKENED:
            ret = result.ret; //返回值
            break;
        case THREAD_TIMED_OUT:
            ret = kIOReturnTimeout;
            break;
        default:
            ret = kIOReturnError;
            break;
    }
}

typedef struct {
    IOReturn                ret;
    IOMemoryDescriptor *    descriptor;
} __ReportResult;

然后调用IOHIDResourceDeviceUserClient的三号处理函数postReportResult
用户态传入的scalarInput为整形数组.

typedef enum {
    kIOHIDResourceUserClientResponseIndexResult = 0,
    kIOHIDResourceUserClientResponseIndexToken,
    kIOHIDResourceUserClientResponseIndexCount
} IOHIDResourceUserClientResponseIndex;

IOReturn IOHIDResourceDeviceUserClient::postReportResult(IOExternalMethodArguments * arguments)
{
    OSObject * tokenObj = (OSObject*)arguments->scalarInput[kIOHIDResourceUserClientResponseIndexToken];
    
    if ( tokenObj && _pending->containsObject(tokenObj) ) {
        OSData * data = OSDynamicCast(OSData, tokenObj);
        if ( data ) {
            __ReportResult * pResult = (__ReportResult*)data->getBytesNoCopy();
            
            // RY: HIGHLY UNLIKELY > 4K
            if ( pResult->descriptor && arguments->structureInput ) {
                pResult->descriptor->writeBytes(0, arguments->structureInput, arguments->structureInputSize);

                // 12978252:  If we get an IOBMD passed in, set the length to be the # of bytes that were transferred
                IOBufferMemoryDescriptor * buffer = OSDynamicCast(IOBufferMemoryDescriptor, pResult->descriptor);
                if (buffer)
                    buffer->setLength((vm_size_t)arguments->structureInputSize);
            
            }
                
            pResult->ret = (IOReturn)arguments->scalarInput[kIOHIDResourceUserClientResponseIndexResult]; 
            //作为IOHIDResourceDeviceUserClient::getReportGated的返回值.不想让for循环断就传0(KERN_SUCCESS)

            _commandGate->commandWakeup(data);
        }
            
    }

    return kIOReturnSuccess;
}

然后至少两次调用postReportResult,第一次会将buffer的长度设为任意值,第二次写入时就有溢出的可能.
到此为止.逻辑还是有些乱,最好自己结合IOHIDFamily的源码分析.实际使用请自行分析太极吧.^ ^1a

2 个赞

膜拜,不过这漏洞该怎么利用?可以利用这个实现越狱IOS系统?能大致说明下吗?

不能,得结合其他东西.KASLR,内核补丁…而且这又不是0day.这个当这个漏洞使设备崩溃的时候,你可以看到崩溃报告里明显被覆盖的值.你可以尝试在越狱设备上利用它.只是个学习过程

i should learn chinese now :3