- 網上搜的各種私有API, 陸陸續(xù)續(xù)都被蘋果限制了, 文后有挨個說明
- Settings.app擁有所有的
entitlements
和跨沙盒的權限, 于是有人就想出了在Settings.app里拿數(shù)據的方案:
利用Settings.app發(fā)起請求
- 做兩個網頁, 一個是提供
MobileConfiguration
配置文件下載的, 里面有你想查詢的數(shù)據項, 以及安裝文件時Settings.app會自動請求的一個網址(這就是第二個網頁) - Settings.app在訪問第二個網頁時, 會根據
.mobileconfig
文件里的配置, 把相應的數(shù)據封裝在plist
文件里, 服務端你解出來就好了, 可以存到數(shù)據庫或緩存庫里, 映射一個唯一ID給前端, 前端再請求這個唯一ID, 就能拿到數(shù)據了 - 實在要這么做的話, 可以在app里內建webserver, 自行伺服這兩個網絡請求
- 但是一旦你存起來的ID被清除了, 似乎又得走一次裝證書 > 自動發(fā)起第二個請求 > 響應并解析plist的流程, 這全是需要干預用戶的操作, 比較麻煩, 不對, 是很麻煩, 一般用戶應該會反對這種流程
自行伺服兩個網址的例子, 通過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)
}
}
這里伺服了兩個請求, 端口是55555
, 都是根路徑, 一個GET, 一個POST, GET請求返回一個.mobileconfig
文件, POST請求返回一個字符串, 這個字符串里包含了UDID
字段
下面是udid.mobileconfig
文件內容, 我們請求了5個數(shù)據段, 并且約定了回調路徑是http://localhost:55555
, 也就是上面代碼里的POST請求路徑:
<?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>
這個描述文件你不是必須要簽名(簽名后會有讓人放心的綠色對勾), 因為我們需要的只是在Settings.app里觸發(fā)那一下回調, 并不是真的要這個文件. 每次裝完證書后右上角的小菊花在旋轉就是在回調, 至少這個文件的安裝是這樣的.
各種全軍覆沒的私有API
I'm sorry to say that apparently from iOS 8.3, to get any unique identifier you need a higher access level than normal user.
Without exploiting anything, just with private frameworks, libraries and kernel requests, any request to unique identifiers 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);
}
Fails. Reason:
IOPlatformSerialNumber
,IOPlatformUUID
is not accessible. Many other requests work fine.
use Mach calls to get network adapters HW IDs:
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));
Fails. Reason: Returns 02:00:00:00:00:00 for any network adapter. 這個是我唯一沒有試過的
connect to 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());
}
Fails. Reason: lockdown_connect() fails, 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);
}
Fails. Reason: requests for unique identifiers return null. Any other request works fine.
這一小節(jié)的答案的作者的建議是提權, 當然, 第一節(jié)的方案其實就是變相提權, 因為Settings.app擁有所有權限.
以上主要來自于這個SO里的幾個回答