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 first and only requirement we have is to not use jailbreak.
First of all, UDID
or OpenUDID
or any other solutions don't work here and they're not expected to.
We've done a lot of background research and tests, starting with trying to get the IMEI, ICCID, IMSI and the Serial Number programatically. None of the above methods work with iOS 7 and above without jailbreak.
We've also spent a couple of months to play with IOKit
framework using the famous IOKitBrowser and dumping the whole contents of iOS
internals. Unfortunately, we discovered that with iOS 8.3
it stopped working.
We're talking here not about getting the UDID
or any other "mainstream" thing, but generally speaking we need a way to get
any permanent hardware identifier unique enough to identify a device that would persist in spite of device wipes and amongst different iOS versions
This question is no duplicate to others (no solutions are found here, for example) and is targeting solely private APIs.
Any help would be highly appreciated.
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:
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.