web-dev-qa-db-ja.com

iOSのプライベートな一意のデバイス識別子

私たちは同僚と共同で、非公開の非公式のコードをたくさん使用するプロジェクトに取り組んでいます。これは、AppStoreでの使用を意図したではありません

私たちが持っている最初で唯一の要件は、脱獄をしないことです。

まず第一に、UDIDまたはOpenUDIDまたは他のソリューションはここでは機能しませんため、これらは期待されていません。

[〜#〜] imei [〜#〜]ICCID、IMSI とシリアルを取得しようとすることから始めて、多くの背景研究とテストを行いましたプログラムで番号を付けます。上記の方法はいずれも、iOS 7以降脱獄なしでは機能しません。

また、有名な IOKitBrowser を使用してIOKitフレームワークを操作し、iOS internalsの内容全体をダンプするために、か月も費やしました。残念ながら、 私たちは発見しましたiOS 8.3動作しなくなりました。

ここでは、UDIDやその他の「主流」のものを取得することについて話しているのではありませんが、一般的に言えば取得する方法が必要です

デバイスをワイプしても、異なるiOSバージョン間で存続するデバイスを識別するのに十分な一意の永続的なハードウェア識別子

この質問は他の質問と重複せず( ここでは解決策は見つかりません など)、プライベートAPIのみを対象としています。

任意の助けをいただければ幸いです。

33

少し調べたところ、すべてのプライベート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;
}

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. 2ページのアプリ内でWebサーバーを起動します。1つは特別に細工されたMobileConfigurationプロファイルを返し、もう1つはUDIDを収集する必要があります。詳細 ここここ および ここ
  2. アプリ内からMobile Safariの最初のページを開くと、設定プロファイルのインストールを要求するSettings.appにリダイレクトされます。プロファイルをインストールすると、UDIDが2番目の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) // 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に署名する も必要です。

14
bzz

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呼び出しを使用してネットワークアダプターのハードウェア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以外の方法で)アクセスできる場合は、これらを読み取ることができる場合があります。

15
gbuzogany

libMobileGestalt.dylibを介してlockdowndAPIに直接アクセスしてみてください。

ヘッダー ここ 。 UDIDにアクセスするための基本的なコードは次のとおりです(dylibをロードする必要があります)。

CFStringRef udid = (CFStringRef)MGCopyAnswer(kMGUniqueDeviceID);

ここ から取得(わずかに変更).

libMobileGestaltの詳細については、 ここ を参照してください。

これが失敗した場合でも、SSLソケットを介してlockdowndとの通信を試みることができます( ここ を参照)。

ただし、お気づきかもしれませんが、これはすべて古いものです。まだ試してみる価値があると思います。

3
Malte

完全な意図はわかりませんが、インストール時にアプリ内で独自のIDを生成して保存するだけで十分でしょうか?次に、アプリでそのIDをサーバーに送信し、取得したIPを保存します。おそらく、アプリの電話を頻繁に使用するようにロジックを設定して、IPが変更された場合に追加のIPを保存できるようにすることもできます。明らかにこれは完全なものではありませんが、回避策の始まりかもしれません。

2
Rel

商用ソリューションがあなたに興味があるかどうかはわかりませんが、チェックしてください http://www.appsflyer.com

私は彼らとは関係ありませんが、以前の雇用主で彼らのSDKを使用しました。彼らは機能するデバイス指紋技術を持っています。

注:ユーザーがIDFAをリセットすると、AppsFlyerはこれを新しいデバイスとして認識します。ただし、しばらくの間、思い出せません。SDKを使用して、AdSupport.frameworkを使用しないでください。そうすると、IDFAを使用できなくなります。だから私は彼らのデバイスのフィンガープリントが機能するかもしれないと推測しています。

彼らはまた、競合他社を抱えており、デバイスのフィンガープリントを検索します。 Yozioとbranch.ioをチェックしてください。どちらもそうしていると主張しています。私は彼らの製品を使用していません、彼らのウェブサイトを見ただけです。

2
Paul Cezanne

これについて: 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に基づいて計算されます。バンドルIDは逆DNS形式であると想定されています。

2
Rel

ユーザーにMSISDNを要求することはできますか? (国際電話番号)SMSコードがユーザーmsisdnに送信されたコードで確認することにより、最初にログインしたときのwhatsAppの動作と同様です。ユーザーのMSISDNを知っている場合は、それらを保持できます。サーバーDBで、ホワイトリストに登録されたmsisdnにのみサービスの登録と使用を許可します。より安全にしたい場合は、SMSをより頻繁に送信できますが、SIMカードの変更を理解する方法があります(これはt-モバイルとヨーロッパでは、推測内で)を使用して、ユーザーがSMSを別のMSISDNに割り当ててから本当のMSISDN simカードに変更することによって、あなたを騙すことができないようにします)。

MSISDNは世界中で一意であり、通信事業者によって保護されているため、このソリューションは厳密に保護されていると思います。あなたは何を言っていますか?幸運を

更新:実際にもう一度質問を注意深く読んだ後、ユーザーに情報をログインさせたくないと思いますか?それが間違った答えで申し訳ありません場合:/

注:なぜ使用できないのですか

[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]

広告IDはデバイスに固有であり、永続的です。あなたが私的にそれを使うことができるかどうか私は知りません。

1
aytunch

実際、この解決策が役立つかどうかはわかりません。しかし、UDIDのサポートを削除した後。以下の方法で一意のデバイスIDを管理します。 Vendor IDの助けを借りて。ここで私たちがしたこと。

アプリケーションの実行中に最初に、特定のアプリの天気vendor IDstored in key chain or notであることを確認します。保存されていない場合は、vendor IDkey chainに保存します。そのため、私のアプリがキーチェーンにない、保存されている特定のアプリの天気予報ベンダーIDを再度確認するのは2回目です。保存されている場合は、キーチェーンから持ってきて、要件に従って同じアクションを実行します。 so here alway vendor ID is unique for device.

vendor IDの助けを借りて、デバイスの一意性を保つための手順は次のとおりです。

ステップ1:プロジェクトに Lockbox を統合します。 stored/retrivedベンダーID in/fromキーチェーンに役立ちます。

ステップ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;
    }

上記の手順の助けを借りて、あなたは常にデバイスの一意性を取得します。キーチェーンが削除されることはないからです。それは常に更新されました。

これがあなたを助けることを願っています...

1
Jatin Patel