[英]Private unique device identifier in iOS
我们正与同事一起开展一个项目,涉及使用大量私有和非官方代码。 这不适用于AppStore。
我们的第一个也是唯一的要求是不使用越狱。
首先, UDID
或OpenUDID
或任何其他解决方案在这里不起作用 ,并且不期望它们。
我们已经做了很多背景研究和测试,从尝试以编程方式获取IMEI , ICCID,IMSI和序列号开始。 如果没有越狱,上述方法都不适用于iOS 7及更高版本。
我们还花了几个月的时间使用着名的IOKitBrowser来使用IOKit
框架并转储iOS
内部的全部内容。 不幸的是, 我们发现在iOS 8.3
它停止了工作。
我们在这里谈的不是获取UDID
或任何其他“主流”的东西,但一般来说我们需要一种方法来获取
任何永久性硬件标识符,其唯一性足以识别在设备擦除和不同iOS版本之间仍然存在的设备
这个问题与其他人没有重复(例如, 这里没有找到解决方案 ),并且仅针对私有API 。
任何帮助将受到高度赞赏。
我很遗憾地说,显然来自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之外的其他内容),则可以阅读这些内容。
经过一番挖掘后,我发现所有私有API都使用libMobileGestalt
获取任何硬件标识符,而后者又使用IOKit
。 MobileGestalt
检查当前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;
}
如上所述这里 ,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的唯一方法如下:
确认的工作解决方案
以下是基于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>
配置文件安装将失败(我没有打算实现预期的响应,请参阅文档 ),但应用程序将获得正确的UDID。 你还应该签署mobileconfig 。
您可以尝试通过libMobileGestalt.dylib
直接访问lockdownd
API
。
标题在这里 。 访问UDID的基本代码应该是:(您仍然需要加载dylib)
CFStringRef udid = (CFStringRef)MGCopyAnswer(kMGUniqueDeviceID);
从这里采取(并稍加修改)。
有关libMobileGestalt
更多信息,请查看此处 。
如果失败了你仍然可以通过SSL Socket尝试与lockdownd
通信(见这里 ),不知道它是如何工作的,但是你可能会想出来。
正如你可能已经注意到的那样,所有的东西都已经存在了多年。 我猜还是值得一试。
我不确定您的全部意图,但是在安装时让应用程序生成并在应用程序中存储您自己的唯一ID就足够了吗? 然后让应用程序将该ID发送到服务器并存储它来自的IP。 也许还有一些逻辑可以让应用程序电话每隔一段时间回家,这样如果它们发生变化,你就可以存储额外的IP。 显然这不是万无一失的,但它可能是一种解决方法的开始。
怎么样: 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格式。
我不知道你是否对商业解决方案感兴趣,但请查看http://www.appsflyer.com
我不隶属于他们,但我们在我以前的雇主那里使用了他们的SDK。 他们拥有可以正常工作的设备指纹技术。
注意:如果用户重置IDFA,则AppsFlyer会将此视为新设备。 但是,它已经有一段时间了,所以我不记得了,我认为你可以使用他们的SDK,而不是使用AdSupport.framework,然后他们将无法使用IDFA。 所以我猜他们的设备指纹可能有效。
他们也有竞争对手,搜索设备指纹。 看看Yozio和branch.io,他们都声称这样做。 我没有使用他们的产品,只是看过他们的网站。
其实我不知道这个解决方案是否有用。 但删除了对UDID的支持。 我按照以下方式管理唯一设备标识。 在Vendor ID
帮助下。 在这里我们做了什么。
作为初始应用程序运行时,我将检查特定应用程序的天气vendor ID
是否stored in key chain or not
。 如果它没有存储,那么我将该vendor ID
存储在key chain
。 所以我的应用程序第二次再次检查它存储的特定应用程序的天气供应商ID,而不是在密钥链中。 如果存储,则从钥匙链带来,并根据要求对其进行操作。 so here alway vendor ID is unique for device.
以下是我们通过vendor ID
保持设备唯一性的步骤。
第1步 :在项目中集成Lockbox 。 这将帮助您in/from
钥匙链in/from
钥匙链中stored/retrived
供应商ID。
步骤2 :以下是执行checking vendor ID
和从密钥链retrieving 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? (国际电话号码)就像whatsApp首次登录时所做的那样,通过发送给用户msisdn的短信确认。 如果您知道用户的MSISDN,可以将它们保存在服务器数据库中,并且只允许列入白名单的msisdn注册并使用您的服务。 如果你想要更安全,你可以更频繁地发送短信但是有一种方法可以从APP内部了解SIM卡的变化(这适用于t-mobile和欧洲使用),这样用户就无法欺骗你为不同的MSISDN加入短信,然后转到他/她的真实MSISDN SIM卡。
MSISDN在全球范围内是独一无二的,它们受到电信运营商的保护,因此我认为这种解决方案是严格安全的。 你说什么? 祝好运
更新:实际上再次仔细阅读你的问题,我想你不希望用户登录任何信息吗? 如果那是对不起的答案:/
注意:为什么你不能使用
[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]
广告标识符对于设备是唯一的,并且我猜是永久的。 我不知道你是否可以私下使用它..
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.