盘古(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