经过一番搜索,我发现所有私有API都使用libMobileGestalt
来获取任何硬件标识符,而libMobileGestalt
又使用IOKit
。 MobileGestalt
会检查当前pid的沙盒规则,并寻找com.apple.private.MobileGestalt.AllowedProtectedKeys
授权。
请看下面的代码:
signed int __fastcall sub_2EB8803C(int a1, int a2, int a3, int a4)
{
int v4;
int v5;
int v6;
int v7;
int v8;
int v9;
int v10;
int v11;
int v12;
signed int v13;
int v14;
char *v15;
int v16;
int v17;
int v18;
int v19;
signed int v20;
int v21;
__CFString *v22;
int v23;
__CFString *v24;
int v26;
char v27;
int v28;
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;
int v4;
int v5;
int v6;
int v7;
signed int result;
char v9;
int v10;
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的唯一方法是:
- 在应用程序中启动一个包含两个页面的Web服务器:其中一个页面应返回特殊制作的MobileConfiguration配置文件,另一个页面应收集UDID。更多信息请参见此处,此处和此处。
- 您从应用程序内部在移动Safari中打开第一个页面,并重定向到Settings.app请求安装配置文件。安装配置文件后,UDID将发送到第二个Web页面,您可以从应用程序内部访问它。(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: "/") {
$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)
$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文件。