LoginSignup
0

More than 5 years have passed since last update.

CVE-2015-5774 Analyze

Posted at

CVE-2015-5774 Analyze

2015-9-18 bycocoa

おかげで私のポストを見て、私はqittaについていくつか質問があります、私のLine id cocoahukeです

盘古(blog.pangu.io)也讲解了这个漏洞.但不是很详细.因为这个漏洞比较简单.鼓励大家反编译taig,毕竟具体很多东西还是在汇编里学习.这里仅给出这个漏洞的分析:
先逆向的分析吧:

漏洞存在于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函数

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的源码分析.实际使用请自行分析太极吧.^ ^

利用代码

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0