Posted at

CVE-2015-5774 Analyze

More than 3 years have passed since last update.

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


利用代码