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

Private unique device identifier in iOS

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

我们的第一个也是唯一的要求是使用越狱。

首先,UDIDOpenUDID 或任何其他解决方案 在这里不起作用 并且它们不应该起作用。

我们做了很多背景研究和测试,首先尝试以编程方式获取 IMEI, ICCID, IMSI 和序列号。 None 以上方法适用于 iOS 7 及更高版本 无需 越狱。

我们还花了几个 个月 来玩 IOKit 框架,使用著名的 IOKitBrowser and dumping the whole contents of iOS internals. Unfortunately, we discoverediOS 8.3 它停止了正在工作。

我们在这里讨论的不是获取 UDID 或任何其他 "mainstream" 的东西,而是 一般来说 我们需要一种方法来获取

any permanent hardware identifier unique enough to identify a device that would persist in spite of device wipes and amongst different iOS versions

这个问题与其他问题(例如no solutions are found here)没有重复,并且仅针对私有API

非常感谢任何帮助。

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

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

An alphanumeric string that uniquely identifies a device to the app’s vendor. (read-only)

The value of this property is the same for apps that come from the same vendor running on the same device. A different value is returned for apps on the same device that come from different vendors, and for apps on different devices regardless of vendor.

从 iOS 6 开始,当前在 iOS 8

这符合您的要求:

any permanent hardware identifier unique enough to identify a device that would persist in spite of device wipes and amongst different iOS versions

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

Normally, the vendor is determined by data provided by the App Store. If the app was not installed from the app store (such as enterprise apps and apps still in development), then a vendor identifier is calculated based on the app’s bundle ID. The bundle ID is assumed to be in reverse-DNS format.

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

我不隶属于他们,但我们在我以前的雇主那里使用过他们的 SDK。他们的设备指纹技术确实有效。

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

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

您可以尝试通过 libMobileGestalt.dylib.

直接访问 lockdownd API

Headerhere。 访问UDID的基本代码应该是:(还需要加载dylib)

CFStringRef udid = (CFStringRef)MGCopyAnswer(kMGUniqueDeviceID);

here.

中提取(稍作修改)

有关 libMobileGestalt 的更多信息,请查看 here

如果失败,您仍然可以尝试通过 SSL 套接字与 lockdownd 通信(请参阅 here),虽然不知道它是如何工作的,但您可能会弄明白。

您可能已经注意到,上面的所有内容都已有多年历史。我想还是值得一试。

很抱歉,显然从 iOS 8.3 开始,要获得任何唯一标识符,您需要比普通用户更高的访问级别。

不利用任何东西,只用私有框架、库和内核请求,任何对唯一标识符的请求returns 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 调用获取网络适配器硬件 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));

失败。原因:Returns 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() 失败,returning 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);
}

失败。原因:请求唯一标识符 return null。任何其他请求都可以正常工作。

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

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

经过一些挖掘,我发现所有私有 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. 使用两个页面在应用程序内部启动网络服务器:一个应该 return 特制的 MobileConfiguration 配置文件,另一个应该收集 UDID。更多信息 here, here and here.
  2. 您从应用程序内部打开 Mobile Safari 中的第一个页面,它会将您重定向到 Settings.app 要求安装配置文件。安装配置文件后,UDID 会发送到第二个网页,您可以从应用程序内部访问它。 (Settings.app 拥有所有必要的权利和不同的沙盒规则)。

确认有效的解决方案

这是一个基于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: "/") {
            .setHeader("Content-Type", value: "application/x-apple-aspen-config")
            .respondWithData(NSData(contentsOfFile: NSBundle.mainBundle().pathForResource("udid", ofType: "mobileconfig")!)!)
        }
        handleMethod("POST", withPath: "/") {
            let raw = NSString(data:[=14=].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!

            .statusCode = 200
            .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), but the app will get a correct UDID. And you should also sign the mobileconfig

实际上我不知道这个解决方案是否有用。但是在删除了对 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;
    }

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

希望这对你有帮助...

您可以向用户询问他们的 MSISDN 吗? (国际 phone 号码)就像当您通过发送给用户 msisdn 的短信代码确认首次登录时 whatsApp 所做的一样。如果您知道用户的 MSISDN,则可以将它们保存在服务器数据库中,并且只允许列入白名单的 msisdn 注册和使用您的服务。如果你想更安全,你可以更频繁地发送短信,但是有一种方法可以从应用程序中了解 SIM 卡的变化(我猜这是为 t-mobile 和欧洲使用),这样用户就无法欺骗你为不同的 MSISDN 输入短信,然后更改为 his/her 真正的 MSISDN SIM 卡。

MSISDN 在世界范围内是独一无二的,它们受到电信运营商的保护,所以我想这个解决方案是绝对安全的。你怎么说?祝你好运

更新:实际上,再次仔细阅读您的问题后,我认为您不希望用户登录任何信息,对吧?如果是这样,抱歉回答错误:/

注意:为什么不能使用

[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]

广告标识符是设备唯一的,我猜是永久的。不过不知道能不能私下用..