Private unique device identifier in iOS

前端 未结 8 1182
不知归路
不知归路 2021-01-30 01:57

We\'re working on a project with my colleagues which involves using a lot of private and non official code. This is not intended for AppStore use.

The f

相关标签:
8条回答
  • 2021-01-30 02:30

    After some digging, I've found that all private APIs use libMobileGestalt for getting any hardware identifiers, which in turn uses IOKit. MobileGestalt checks sandbox rules for current pid and looks for com.apple.private.MobileGestalt.AllowedProtectedKeys entitlement.

    See the code below:

    signed int __fastcall sub_2EB8803C(int a1, int a2, int a3, int a4)
    {
      int v4; // r5@1
      int v5; // r4@1
      int v6; // r10@1
      int v7; // r2@1
      int v8; // r0@3
      int v9; // r6@3
      int v10; // r11@4
      int v11; // r4@4
      int v12; // r0@5
      signed int v13; // r6@6
      int v14; // r6@7
      char *v15; // r0@7
      int v16; // r1@7
      int v17; // r1@14
      int v18; // r3@16
      int v19; // r5@16
      signed int v20; // r1@17
      int v21; // r0@17
      __CFString *v22; // r2@19
      int v23; // r4@27
      __CFString *v24; // r2@27
      int v26; // [sp+8h] [bp-428h]@1
      char v27; // [sp+10h] [bp-420h]@1
      int v28; // [sp+414h] [bp-1Ch]@1
    
      v26 = a2;
      v4 = a1;
      v5 = a3;
      v6 = a4;
      v28 = __stack_chk_guard;
      memset(&v27, 0, 0x401u);
      v7 = *(_DWORD *)(dword_32260254 + 260);
      if ( !v7 )
        v7 = sub_2EB8047C(65, 2);
      v8 = ((int (__fastcall *)(int, _DWORD))v7)(v4, "com.apple.private.MobileGestalt.AllowedProtectedKeys");
      v9 = v8;
      if ( !v8 )
        goto LABEL_12;
      v10 = v5;
      v11 = CFGetTypeID(v8);
      if ( v11 != CFArrayGetTypeID() )
      {
        v14 = (int)"/SourceCache/MobileGestalt/MobileGestalt-297.1.14/MobileGestalt.c";
        v15 = rindex("/SourceCache/MobileGestalt/MobileGestalt-297.1.14/MobileGestalt.c", 47);
        v16 = *(_DWORD *)(dword_32260254 + 288);
        if ( v15 )
          v14 = (int)(v15 + 1);
        if ( !v16 )
          v16 = sub_2EB8047C(72, 2);
        ((void (__fastcall *)(int))v16)(v4);
        _MGLog(3, v14);
    LABEL_12:
        v13 = 0;
        goto LABEL_13;
      }
      v12 = CFArrayGetCount(v9);
      if ( CFArrayContainsValue(v9, 0, v12, v26) )
        v13 = 1;
      else
        v13 = sub_2EB7F948(v9, v26, v10, "MGCopyAnswer");
    LABEL_13:
      if ( !v6 )
        goto LABEL_30;
      v17 = *(_DWORD *)(dword_32260254 + 288);
      if ( !v17 )
        v17 = sub_2EB8047C(72, 2);
      v19 = ((int (__fastcall *)(int))v17)(v4);
      if ( v13 != 1 )
      {
        v21 = *(_DWORD *)v6;
        if ( *(_DWORD *)v6 )
        {
          v22 = CFSTR(" and IS NOT appropriately entitled");
          goto LABEL_22;
        }
        v23 = CFStringCreateMutable(0, 0);
        *(_DWORD *)v6 = v23;
        sub_2EB7F644(v19, &v27);
        v24 = CFSTR("pid %d (%s) IS NOT appropriately entitled to fetch %@");
        goto LABEL_29;
      }
      v20 = MGGetBoolAnswer((int)CFSTR("LBJfwOEzExRxzlAnSuI7eg"));
      v21 = *(_DWORD *)v6;
      if ( v20 == 1 )
      {
        if ( v21 )
        {
          v22 = CFSTR(" but IS appropriately entitled; all is good in the world");
    LABEL_22:
          CFStringAppendFormat(v21, 0, v22, v18);
          goto LABEL_30;
        }
        v23 = CFStringCreateMutable(0, 0);
        *(_DWORD *)v6 = v23;
        sub_2EB7F644(v19, &v27);
        v24 = CFSTR("pid %d (%s) IS appropriately entitled to fetch %@; all is good in the world");
    LABEL_29:
        CFStringAppendFormat(v23, 0, v24, v19);
        goto LABEL_30;
      }
      if ( v21 )
      {
        CFRelease(v21);
        *(_DWORD *)v6 = 0;
      }
      *(_DWORD *)v6 = 0;
    LABEL_30:
      if ( __stack_chk_guard != v28 )
        __stack_chk_fail(__stack_chk_guard - v28);
      return v13;
    }
    
    signed int __fastcall sub_2EB88228(int a1, int a2, int a3)
    {
      int v3; // r4@1
      int v4; // r10@1
      int v5; // r0@1
      int v6; // r6@1
      int v7; // r5@5
      signed int result; // r0@6
      char v9; // [sp+8h] [bp-420h]@5
      int v10; // [sp+40Ch] [bp-1Ch]@1
    
      v3 = a1;
      v4 = a3;
      v10 = __stack_chk_guard;
      v5 = sandbox_check();
      v6 = v5;
      if ( v5 )
        v5 = 1;
      if ( v4 && v5 == 1 )
      {
        memset(&v9, 0, 0x401u);
        v7 = CFStringCreateMutable(0, 0);
        *(_DWORD *)v4 = v7;
        sub_2EB7F644(v3, &v9);
        CFStringAppendFormat(v7, 0, CFSTR("pid %d (%s) does not have sandbox access for %@"), v3);
      }
      result = 0;
      if ( !v6 )
        result = 1;
      if ( __stack_chk_guard != v10 )
        __stack_chk_fail(result);
      return result;
    }
    

    As described here, UDID is calculated like this:

    UDID = SHA1(serial + ECID + wifiMac + bluetoothMac)
    

    MobileGestalt gets these values via IOKit like this:

    CFMutableDictionaryRef service = IOServiceMatching("IOPlatformExpertDevice");
        io_service_t ioservice = IOServiceGetMatchingService(kIOMasterPortDefault, service);
        CFTypeRef entry = IORegistryEntryCreateCFProperty(ioservice, CFSTR("IOPlatformSerialNumber"), kCFAllocatorDefault, 0);
    
        const UInt8 * data = CFDataGetBytePtr(entry);
        CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, data, kCFStringEncodingUTF8);
    

    If you try to do it yourself, it will fail because new sandbox rules in iOS 8.3 are very strict and deny access to all hardware identifiers like this:

    deny iokit-get-properties IOPlatformSerialNumber
    

    Possible Solution

    It looks like the only way you can get UDID is the following:

    1. Launch a web server inside the app with two pages: one should return specially crafted MobileConfiguration profile and another should collect UDID. More info here, here and here.
    2. You open the first page in Mobile Safari from inside the app and it redirects you to Settings.app asking to install configuration profile. After you install the profile, UDID is sent to the second web page and you can access it from inside the app. (Settings.app has all necessary entitlements and different sandbox rules).

    Confirmed working solution

    Here is an example based on RoutingHTTPServer:

    import UIKit
    import RoutingHTTPServer
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
        var bgTask = UIBackgroundTaskInvalid
        let server = HTTPServer()
    
        func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
            application.openURL(NSURL(string: "http://localhost:55555")!)
            return true
        }
    
        func applicationDidEnterBackground(application: UIApplication) {
            bgTask = application.beginBackgroundTaskWithExpirationHandler() {
                dispatch_async(dispatch_get_main_queue()) {[unowned self] in
                    application.endBackgroundTask(self.bgTask)
                    self.bgTask = UIBackgroundTaskInvalid
                }
            }
        }
    }
    
    class HTTPServer: RoutingHTTPServer {
        override init() {
            super.init()
            setPort(55555)
            handleMethod("GET", withPath: "/") {
                $1.setHeader("Content-Type", value: "application/x-apple-aspen-config")
                $1.respondWithData(NSData(contentsOfFile: NSBundle.mainBundle().pathForResource("udid", ofType: "mobileconfig")!)!)
            }
            handleMethod("POST", withPath: "/") {
                let raw = NSString(data:$0.body(), encoding:NSISOLatin1StringEncoding) as! String
                let plistString = raw.substringWithRange(Range(start: raw.rangeOfString("<?xml")!.startIndex,end: raw.rangeOfString("</plist>")!.endIndex))
                let plist = NSPropertyListSerialization.propertyListWithData(plistString.dataUsingEncoding(NSISOLatin1StringEncoding)!, options: .allZeros, format: nil, error: nil) as! [String:String]
    
                let udid = plist["UDID"]! 
                println(udid) // Here is your UDID!
    
                $1.statusCode = 200
                $1.respondWithString("see https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/iPhoneOTAConfiguration/ConfigurationProfileExamples/ConfigurationProfileExamples.html")
            }
            start(nil)
        }
    }
    

    Here are the contents of udid.mobileconfig:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
        <dict>
            <key>PayloadContent</key>
            <dict>
                <key>URL</key>
                <string>http://localhost:55555</string>
                <key>DeviceAttributes</key>
                <array>
                    <string>IMEI</string>
                    <string>UDID</string>
                    <string>PRODUCT</string>
                    <string>VERSION</string>
                    <string>SERIAL</string>
                </array>
            </dict>
            <key>PayloadOrganization</key>
            <string>udid</string>
            <key>PayloadDisplayName</key>
            <string>Get Your UDID</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <key>PayloadUUID</key>
            <string>9CF421B3-9853-9999-BC8A-982CBD3C907C</string>
            <key>PayloadIdentifier</key>
            <string>udid</string>
            <key>PayloadDescription</key>
            <string>Install this temporary profile to find and display your current device's UDID. It is automatically removed from device right after you get your UDID.</string>
            <key>PayloadType</key>
            <string>Profile Service</string>
        </dict>
    </plist>
    

    The profile installation will fail (I didn't bother to implement an expected response, see documentation), but the app will get a correct UDID. And you should also sign the mobileconfig.

    0 讨论(0)
  • 2021-01-30 02:30

    I'm sorry to say that apparently from iOS 8.3, to get any unique identifier you need a higher access level than normal user.

    Without exploiting anything, just with private frameworks, libraries and kernel requests, any request to unique identifiers returns null.

    Illustrating:

    Trying to use IOKit:

    void *IOKit = dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", RTLD_NOW);
    if (IOKit)
    {
        mach_port_t *kIOMasterPortDefault = dlsym(IOKit, "kIOMasterPortDefault");
        CFMutableDictionaryRef (*IOServiceMatching)(const char *name) = dlsym(IOKit, "IOServiceMatching");
        mach_port_t (*IOServiceGetMatchingService)(mach_port_t masterPort, CFDictionaryRef matching) = dlsym(IOKit, "IOServiceGetMatchingService");
        CFTypeRef (*IORegistryEntryCreateCFProperty)(mach_port_t entry, CFStringRef key, CFAllocatorRef allocator, uint32_t options) = dlsym(IOKit, "IORegistryEntryCreateCFProperty");
        kern_return_t (*IOObjectRelease)(mach_port_t object) = dlsym(IOKit, "IOObjectRelease");
    
        if (kIOMasterPortDefault && IOServiceGetMatchingService && IORegistryEntryCreateCFProperty && IOObjectRelease)
        {
            mach_port_t platformExpertDevice = IOServiceGetMatchingService(*kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
            if (platformExpertDevice)
            {
                CFTypeRef platformSerialNumber = IORegistryEntryCreateCFProperty(platformExpertDevice, CFSTR("IOPlatformSerialNumber"), kCFAllocatorDefault, 0);
                if (platformSerialNumber && CFGetTypeID(platformSerialNumber) == CFStringGetTypeID())
                {
                    serialNumber = [NSString stringWithString:(__bridge NSString *)platformSerialNumber];
                    CFRelease(platformSerialNumber);
                }
                IOObjectRelease(platformExpertDevice);
            }
        }
        dlclose(IOKit);
    }
    

    Fails. Reason: IOPlatformSerialNumber is not accessible. Many other requests work fine.

    Trying to use Mach calls to get network adapters HW IDs:

    int         mib[6], len;
    char            *buf;
    unsigned char       *ptr;
    struct if_msghdr    *ifm;
    struct sockaddr_dl  *sdl;
    
    mib[0] = CTL_NET;
    mib[1] = AF_ROUTE;
    mib[2] = 0;
    mib[3] = AF_LINK;
    mib[4] = NET_RT_IFLIST;
    if ((mib[5] = if_nametoindex("en0")) == 0) {
        perror("if_nametoindex error");
        exit(2);
    }
    
    if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
        perror("sysctl 1 error");
        exit(3);
    }
    
    if ((buf = malloc(len)) == NULL) {
        perror("malloc error");
        exit(4);
    }
    
    if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
        perror("sysctl 2 error");
        exit(5);
    }
    
    ifm = (struct if_msghdr *)buf;
    sdl = (struct sockaddr_dl *)(ifm + 1);
    ptr = (unsigned char *)LLADDR(sdl);
    printf("%02x:%02x:%02x:%02x:%02x:%02x\n", *ptr, *(ptr+1), *(ptr+2),
           *(ptr+3), *(ptr+4), *(ptr+5));
    

    Fails. Reason: Returns 02:00:00:00:00:00 for any network adapter.

    Trying to connect to lockdownd:

    void *libHandle = dlopen("/usr/lib/liblockdown.dylib", RTLD_LAZY);
    if (libHandle)
    {
        lockdown_connect = dlsym(libHandle, "lockdown_connect");
        lockdown_copy_value = dlsym(libHandle, "lockdown_copy_value");
    
        id connection = lockdown_connect();
        NSString *kLockdownDeviceColorKey
        NSString *color = lockdown_copy_value(connection, nil, kLockdownDeviceColorKey);
        NSLog(@"color = %@", color);
        lockdown_disconnect(connection);
    
        dlclose(libHandle);
    }
    else {
        printf("[%s] Unable to open liblockdown.dylib: %s\n",
               __FILE__, dlerror());
    }
    

    Fails. Reason: lockdown_connect() fails, returning null.

    Trying to use libMobileGestalt:

    void *libHandle = dlopen("/usr/lib/libMobileGestalt.dylib", RTLD_LAZY);
    if (libHandle)
    {
        MGCopyAnswer = dlsym(libHandle, "MGCopyAnswer");
    
        NSString* value = MGCopyAnswer(CFSTR("SerialNumber"));
        NSLog(@"Value: %@", value);
        CFRelease(value);
    }
    

    Fails. Reason: requests for unique identifiers return null. Any other request works fine.

    My suggestion is to use some privilege escalation technique to get superuser access, then run any of the methods listed here to get the property.

    Also, extend the study on liblockdown. If it's accessible at user-level (with something other than lockdown_connect), it might be possible to read these things.

    0 讨论(0)
提交回复
热议问题