iOS中的私有唯一设备标识符

时间:2015-04-20 23:53:03

标签: ios iphone cocoa-touch iphone-privateapi iokit

我们正与同事一起开展一个项目,涉及使用大量私有和非官方代码。这是,供AppStore使用。

我们的首要要求是使用越狱。

首先,UDIDOpenUDID或任何其他解决方案在此处不起作用,并且不应该这样做。

我们已经做了很多背景研究和测试,从尝试以编程方式获取IMEIICCID, IMSI和序列号开始。上述方法均不适用于iOS 7及以上而不使用越狱。

我们还花费了几个来使用着名的IOKitBrowserIOKit框架一起玩,并转储iOS内部的全部内容。不幸的是,we discovered iOS 8.3它已停止工作。

我们在这里谈的不是关于获取UDID或任何其他“主流”的事情,但一般来说我们需要一种方法来获取

  

任何永久性硬件标识符,其唯一性足以识别在设备擦除和不同iOS版本之间仍然存在的设备

此问题与其他人(例如no solutions are found here)无关,并且仅针对私有API

任何帮助都将受到高度赞赏。

7 个答案:

答案 0 :(得分:15)

我很遗憾地说,显然来自iOS 8.3,要获得任何唯一标识符,您需要比普通用户更高的访问级别。

在没有利用任何东西的情况下,只需使用私有框架,库和内核请求,对唯一标识符的任何请求都将返回null。

龙虎斗:

尝试使用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);
}

失败。原因:IOPlatformSerialNumber无法访问。许多其他请求工作正常。

尝试使用Mach调用来获取网络适配器HW ID:

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));

失败。原因:为任何网络适配器返回02:00:00:00:00:00

尝试连接到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());
}

失败。原因:lockdown_connect()失败,返回null

尝试使用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);
}

失败。原因:对唯一标识符的请求返回null。任何其他请求工作正常。

我的建议是使用一些权限提升技术来获取超级用户访问权限,然后运行此处列出的任何方法来获取该属性。

另外,扩展关于liblockdown的研究。如果它可以在用户级访问(使用lockdown_connect之外的其他内容),则可以阅读这些内容。

答案 1 :(得分:14)

经过一番挖掘,我发现所有私有API都使用libMobileGestalt获取任何硬件标识符,而后者又使用IOKitMobileGestalt检查当前pid的沙箱规则并查找com.apple.private.MobileGestalt.AllowedProtectedKeys权利。

请参阅以下代码:

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;
}

如上所述here,UDID计算如下:

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

MobileGestalt通过IOKit获取这些值,如下所示:

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);

如果您尝试自己动手,它会失败,因为iOS 8.3中的新沙盒规则非常严格,并且拒绝访问所有类似的硬件标识符:

deny iokit-get-properties IOPlatformSerialNumber

可能的解决方案

看起来你可以获得UDID的唯一方法如下:

  1. 在应用程序内部启动一个Web服务器,其中包含两个页面:一个应该返回特制的MobileConfiguration配置文件,另一个应该收集UDID。更多信息hereherehere
  2. 您从应用程序内部打开Mobile Safari中的第一页,它会将您重定向到Settings.app,要求安装配置文件。安装配置文件后,UDID将发送到第二个网页,您可以从应用程序内部访问它。 (Settings.app具有所有必要的权利和不同的沙盒规则)。
  3. 确认的工作解决方案

    以下是基于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)
        }
    }
    

    以下是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>
    

    配置文件安装将失败(我没有打算实现预期的响应,请参阅documentation),但应用程序将获得正确的UDID。你还应该sign the mobileconfig

答案 2 :(得分:3)

您可以尝试通过lockdownd直接访问API libMobileGestalt.dylib

标题here。 访问UDID的基本代码应该是:(您仍然需要加载dylib)

CFStringRef udid = (CFStringRef)MGCopyAnswer(kMGUniqueDeviceID);

here获取(并稍加修改)。

有关libMobileGestalt的详细信息,请查看here

如果此操作失败,您仍然可以尝试通过SSL套接字与lockdownd进行通信(请参阅here),不知道它是如何工作的,但您可能会想出来。

正如你可能已经注意到的那样,所有的东西都已经存在了多年。我猜还是值得一试。

答案 3 :(得分:2)

我不确定您的全部意图,但是在安装时让应用生成并在应用中存储您自己的唯一ID就足够了吗?然后让应用程序将该ID发送到服务器并存储它来自的IP。也许还有一些逻辑可以让应用程序电话每隔一段时间回家,这样如果它们发生变化,你就可以存储额外的IP。显然这不是万无一失的,但它可能是一种解决方法的开始。

答案 4 :(得分:2)

这个怎么样:https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIDevice_Class/#//apple_ref/occ/instp/UIDevice/identifierForVendor

  

一个字母数字字符串,用于唯一标识应用供应商的设备。 (只读)

     

此属性的值对于来自的应用程序是相同的   在同一设备上运行的同一供应商。返回不同的值   对于来自不同供应商的同一设备上的应用程序,以及   不论供应商如何,都可以在不同的设备上使用。

iOS 6以及iOS 8中的当前版本

符合您的要求:

  

任何永久性硬件标识符,其唯一性足以识别在设备擦除和不同iOS版本之间仍然存在的设备

这被记录为每个设备都是唯一的,无论是从应用商店还是企业交付都是持久的。

  

通常,供应商由App Store提供的数据确定。如果未从应用商店安装应用(例如仍处于开发阶段的企业应用和应用),则会根据应用的捆绑ID计算供应商标识符。假设捆绑ID是反向DNS格式。

答案 5 :(得分:2)

我不知道您是否对商业解决方案感兴趣,但请查看http://www.appsflyer.com

我与他们没有关系,但我们在我以前的雇主那里使用了他们的SDK。他们有一种设备指纹技术可以工作。

注意:如果用户重置IDFA,则AppsFlyer会将此视为新设备。然而,它已经有一段时间了,所以我不记得了,我认为你可以使用他们的SDK,而不是使用AdSupport.framework,然后他们就不会有IDFA可用。所以我猜测他们的设备指纹可能有效。

他们也有竞争对手,搜索设备指纹识别。看看Yozio和branch.io,他们都声称这样做。我刚看过他们的网站,我就没有使用他们的产品。

答案 6 :(得分:1)

其实我不知道这个解决方案是否有用。但删除了对UDID的支持。我按照以下方式管理唯一设备标识。在Vendor ID的帮助下。在这里我们做了什么。

初始应用程序运行时,我会检查特定应用的天气vendor IDstored in key chain or not。如果没有存储,那么我会将vendor ID存储在key chain中。所以我的应用程序第二次再次检查它存储的特定应用程序的天气供应商ID,而不是在密钥链中。如果存储,则从钥匙链带来,并根据要求对其进行操作。 so here alway vendor ID is unique for device.

以下是我们在vendor ID的帮助下保持设备唯一性的步骤。

第1步:在项目中集成Lockbox。这将帮助您stored/retrived供应商ID in/from密钥链。

第2步:以下是从密钥链执行checking vendor IDretrieving vendor ID操作的代码。

-(NSString*)getidentifierForVendor{
    NSString *aStrExisting = [Lockbox stringForKey:[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleIdentifierKey]];

    if (aStrExisting == Nil) {
        NSString *aVendorID = [[[UIDevice currentDevice]identifierForVendor]UUIDString];
        aStrExisting=aVendorID;
        [Lockbox setString:aVendorID forKey:[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleIdentifierKey]];
        return aVendorID;
    }else{
        return aStrExisting;
    }

借助上述步骤,您始终可以获得设备的唯一性。因为永远不会删除密钥链。它总是更新。

希望这能帮到你......