iOSデバイスのチップセット情報をプログラムから取得する


自分のiPhone6s or 6s+のA9チップがサムスン製かTSMC製かを調べる(バッテリーの持ちが違うらしい)のがタイムラインで流行っていて、その方法として下記記事がシェアされてました。

シリアル番号とかから判断するのかなと思いつつ記事を読んでみると、AppStoreに出ているアプリを使って判定するとのこと。なるほど、ストアに出てるということは合法的に(Private API を使うことなく)プログラムからチップ情報を取得可能 ということなので、どうやってるんだろうと思いつつ、同日に見つけた GitHub の Trending で見かけた「CPU-Identifier」というOSSを思い出しました。

中身を見てみると、次のような実装になってました。

CPU-Identifier」の実装内容

dlfcn.h をインクルード

#include <dlfcn.h>

(CPU-Identifier では sysctl.h, resource.h, vm.h もインクルードしているが、本記事でピックアップする範囲では不要)

libMobileGestalt.dylib をロード

まず、dlopen() (ダイナミックライブラリをロードしてそのハンドラを返す関数)を使って /usr/lib/libMobileGestalt.dylib をロードします。

void *gestalt = dlopen("/usr/lib/libMobileGestalt.dylib", RTLD_GLOBAL | RTLD_LAZY);

libMobileGestalt.dylib は、UDID、ディスク使用量、デバイスバージョン等のシステム情報を取得するのに使われるライブラリ、とのことです。

libMobileGestalt is a library that can be used to get various system values such as the UDID, disk usage, device version and much more.

"MGCopyAnswer"がロードされたメモリアドレスを取得

次に、dlsym() を使って、"MGCopyAnswer" というシンボルがロードされたメモリアドレスを取得します。

$MGCopyAnswer = dlsym(gestalt, "MGCopyAnswer");

ボードIDを取得

上の手順で取得したメモリアドレスを格納した MGCopyAnswer は、MobileGestalt.h にて次のように定義されています。

CFPropertyListRef MGCopyAnswer(CFStringRef property);

で、この CFPropertyListRef から、"HardwarePlatform" 情報を次のように取り出します。

CFStringRef boardID = (CFStringRef)$MGCopyAnswer(CFSTR("HardwarePlatform"));

CFStringRef は Toll-Free Bridged なので、(__bridge NSString *)boardID とするだけで NSString にキャストできます。

この「ボードID」が 6s / 6s Plus だと "s8000" か "s8003" になるので、それぞれ Samsung 製、TSMC 製と判定できます。

if ([(__bridge NSString *)boardID isEqualToString:@"s8000"]) {
    manufactory.text = @"Samsung";
    isA9 = YES;
    imageName = @"A9";
}
if ([(__bridge NSString *)boardID isEqualToString:@"s8003"]) {
    manufactory.text = @"TSMC";
    isA9 = YES;
    imageName = @"A9";
}

ちなみに僕の iPhone 6s は見事に「ハズレ」とされる Samsung 製でした。。

A9以外のチップの判定

さらに「CPU-Identifier」では下記コードのようにA9チップ以外も判定できるように実装されています。

NSString* str2Cmp = [(__bridge NSString *)boardID lowercaseString];
if ([str2Cmp hasPrefix:@"s5l8960"] || [str2Cmp hasPrefix:@"s5l8965"]){
    imageName = @"A7";
}else if ([str2Cmp hasPrefix:@"t7000"]){
    imageName = @"A8";
}else if ([str2Cmp hasPrefix:@"t7001"]){
    imageName = @"A8X";
}else if ([str2Cmp hasPrefix:@"s5l8950"]){
    imageName = @"A6";
}else if ([str2Cmp hasPrefix:@"S5L8955"]){
    imageName = @"A6X";
}else if ([str2Cmp hasPrefix:@"s5l8940"] || [str2Cmp hasPrefix:@"s5l8942"] ){
    imageName = @"A5";
}else if ([str2Cmp hasPrefix:@"s5l8945"]){
    imageName = @"A5X";
}else if ([str2Cmp hasPrefix:@"s5l8930"]){
    imageName = @"A4";
}

別の判定方法

ここまでやってみてやっと気付いたのですが、冒頭の参考記事で紹介されているアプリとは判定方法が違うようです。記事内のアプリでは、下記のように判定を行っていました。

  • iPhone 6s
    • N71AP:Samsung製
    • N71mAP:TSMC製
  • iPhone 6s Plus
    • N66AP:Samsung製
    • N66mAP:TSMC製

ハードについて全然詳しくないので、s8000が何を指していてN71APが何を指しているかとか正直よくわからないのですが、とりあえずこの "N71AP" をどうやったら取得できるのか、MGCopyAnswer から色々取得して見てみました。

CFStringRef modelNumber = (CFStringRef)$MGCopyAnswer(CFSTR("ModelNumber"));
NSLog(@"modelNumber:%@", modelNumber);

CFStringRef chipId = (CFStringRef)$MGCopyAnswer(CFSTR("ChipID"));
NSLog(@"chipId:%@", chipId);

CFStringRef hwModel = (CFStringRef)$MGCopyAnswer(CFSTR("HWModelStr"));
NSLog(@"hwModel:%@", hwModel);

iPhone6sでの実行結果:

modelNumber:MKQT2
chipId:32768
hwModel:N71AP

出ました、冒頭記事(内のアプリ)は "HWModelStr" を用いた判定方法のようです。

ちなみにこの N71AP というのは、ググってみると Samsung 製 A9 プロセッサを使用している iPhone 6s そのもののことを指しているようです。

This is the iPhone 6s which uses the Samsung A9 processor.

これって合法?(このコード入りで審査通る?)

冒頭で、ストアにも出せる、と書きましたが、実際のところ怪しいかもしれません。下記記事ではこの「CPU-Identifier」作者の方が配布している野良アプリを使用する方法を紹介していて、

ここでは、

AppStoreで配布出来るものではないため

という記述もあります。

まぁ作者の方は単に面倒なのでこういう配布方法を取ったということは十分に考えられますが、万が一ストアに出すアプリでこの方法でハードウェア情報にアクセスしたい場合は、Appleのガイドライン等をよく読み自己責任でお願いいたします。

(たとえばシリアル番号とかUDIDも取れてしまうので、こういうのをサーバーに送ったりするアプリはアウトかと)

CFStringRef SNumber = MGCopyAnswer(CFSTR("SerialNumber"));
NSLog (@"Serial Number : %@", SNumber);

CFStringRef UDNumber = MGCopyAnswer(CFSTR("UniqueDeviceID"));
NSLog (@"UniqueDeviceID : %@", UDNumber);