在iOS 7中获取UDID的3种可能方法

在iOS 5时代,UDID曾是无数公司用来统计App装机量的标准;在iOS 6时代,获取UDID的函数已被弃用,虽然仍可以调用,但已无法通过Apple Store的审核;在iOS 7时代,此函数从文档中彻底消失,普通的App Store开发者根本无从调用,UDID看似正式退下历史舞台。真的是这样吗?

如果你是一个纯粹的App Store开发者,那么完全可以跟书上说的一样,两耳不闻窗外事,一心只上App Store。但是即使是纯粹的App Store开发者,也会对一些苹果不让干,但实际可以干的事情感兴趣,而在iOS 7上获取UDID就是这些事其中的一件。

对于我们iOS程序员们来说,函数/方法的概念大家应该很熟悉了,很多人也一定清楚,函数都是一层套一层的,高层函数一般是对底层函数的封装,当我们想要实现一个功能从而调用高层函数时,高层函数其实是代替我们调用了底层函数,把我们的指令一层一层地传递下去,再把执行的结果一层一层地回传上来,最终回到我们手里。这种层次关系有点类似下图:


女神住在金碧辉煌的别墅里,家里装了3道王力防盗门(黑色粗线表示),如果有钥匙可以轻松打开;如果没钥匙,以我们的实力还不能破门而入。但是,每个围墙上都有大大小小的缺口,我们可以通过这些缺口绕过防盗门,可每个缺口处都有女神请的保镖在此守护。

这个场景平行转换到iOS中,就变成了:UDID的信息记录在内核中,而用户态的函数就是三道门,如果苹果给了我们钥匙,我们就可以轻松通过;如果不给我们钥匙,我们则可以偷偷从墙上的缺口绕一下,但是保镖(App Store审核机制)会把我们kick out,我们就只能闷声备大胎了。这种无奈的场景在原版iOS中很常见,但是!在越狱iOS中,红雪、绿毒、evaders等强力帮手已经帮我们引开了保镖,而且把中国著名品牌王力牌防盗门换成了山寨品牌李云牌防盗门,我们自己配把钥匙(私有函数)也能过了,终于有机会对女神一亲芳泽:kissing_heart:

通过以上的分析和比喻,你可能已经意识到,在原版iOS上很多看似不可行的东西,在越狱iOS中变成了可行的,且很可能比你想象得要简单的多。获取UDID就是其中一例,接下来我们就以3个可能获取UDID的方法来证明我们的分析,希望能够带给大家灵感。
预备、在Cydia中看看我的UDID:


可以看到,我的UDID是 48b01d6059df1e43273cefab2dd2a22512e7ca9f,留着备用。

###消失的- (NSString *)uniqueIdentifier?
在《iOS应用逆向工程》中我们曾提到过,iOS的公开函数相较私有函数,只是冰山一角。在你对私有函数有足够了解之前,最好不要妄下“没有这个函数”的结论。何况,遍历一下UIKit导出的所有头文件,即可轻松发现一个可疑函数,如图:


那么我们拿Cycript测一测好了,如图:
image
这就说明,看似消失的第一道防盗门,其实是被苹果刷了层白漆,冒充成了墙壁。我们用class-dump这面照妖镜一照,门的原型就显现了出来,加上我们的钥匙,很容易就开了3道门,迎娶白富美~

绕过第一道门,从第二道门开始深入敌后

对于UDID这辆车,在iOS 6中,虽然苹果弃用了- (NSString *)uniqueIdentifier的车身,但它的发动机还完全可以正常工作,只是车身太破旧,苹果不让我们开这辆车了。在iOS 7里苹果干脆把车身给卸掉了,没车身这车就没法开了。但它会不会在iOS 7里保留车的发动机呢?如果发动机还在,我们自己给做个车身不就可以继续开车了么?好吧,我们看看iOS 6中发动机的样子,如图:


看上去很简单啊,翻译成高层语言就是[UIDevice currentDevice] deviceInfoForKey:@“UniqueDeviceID”]。我们看看这个发动机(deviceInfoForKey:)在不在iOS 7中,如图所示:

我们也别麻烦IDA了,直接上Cycript试试:
image
即使我们没有发现被伪装成墙壁的第一道门,也可以从没有保镖把守的缺口绕开第一道门,直接用钥匙打开第二道门,是不是很轻松?

第二道门我也懒得开,钥匙留给第三道门吧

第三道门是什么(是哪些函数)?我们在IDA里双击_deviceInfoForKey:,跳转到它的实现,如图所示:


所以,其实_deviceInfoForKey:这第二道门背后的第三道门就是MGCopyAnswer。我们来找到它的钥匙,看看它出自哪位配钥匙的师傅:
image
二话不说,我们去找名叫libMobileGestalt.dylib的师傅配钥匙去~
image
可以看到,R1直接被赋0了!看看sub_36ee8924里发生了什么:
image
R0被放进了R11里。根据书上的“金句”,这个函数只有一个参数,就是R0,对不对?那它有没有返回值呢?我们往上数第4张图,如果MGCopyAnswer没有返回值,那“CBZ R0, loc_2FC4930E”是在干嘛?而且,如果你对汇编稍微熟悉那么一点点,就会知道,如果MGCopyAnswer没有返回值,那_deviceInfoForKey:返回的要么是nil,要么就是它自己的参数。这显然是不合理的,结合ObjC函数命名规则,我们可以确定,MGCopyAnswer的原型为NSString *MGCopyAnswer(NSString *),或者CFStringRef MGCopyAnswer(CFStringRef)。因为NSString *和CFStringRef是无缝桥接的,所以我们以更为熟悉的NSString *MGCopyAnswer(NSString *)为标准,验证我们的想法(main函数没有返回int值,不规范,请大家注意):


结语

如果你对MGCopyAnswer感兴趣,可以继续跟进它的实现,作为练习。刚才,我们以3种可能的方式在iOS 7中获取了宝贵的UDID信息,而这被非常多的App Store开发者认为是不可能完成的任务。当然,这一切的功劳都要归功于越狱和iOS逆向工程的强大威力,在我看来,iOS逆向工程的牛X之处就在于,一旦我们发现感兴趣的一个点,就可以通过动静结合的分析方式,由这个点发现一条线,直到打开一个面,最终获得那些纯正向的开发者所不可能获取到的广泛知识。希望朋友们能从这个例子中获取一些灵感,体会到逆向工程之美,如有问题,敬请指出,不足之处,还请海涵,谢谢支持!

2 Likes

坛主 能给个例子么?

代码已经在图里了啊,要什么例子?

嗯 看到了 我在我的工程里面试一试~

mgcopyanswer是从哪个framework里面导出的啊?

帖子里写得很详细了,是从一个dylib里导出的,仔细再看一遍

其实系统获取UDID有两条路径 我暂且称为路劲
一条就是UIDevice 基本都是给app用的不管是/Applications还是 /var/mobile/Applications 楼主讲的都是这条路径
当然UIDevice还包括一支 liblockdown.dylib的 kLockdownUniqueDeviceIDKey 获取

而另一条路径 是给后台demon 用的 ISDevice的guid

嗯,我也觉得获取UDID不止我提到的几种方法,但我发帖的本意是,通过一个App Store开发者(逆向工程初学者)能想到的点入手,来深挖其底层实现,进而找到获取UDID的方法,用这种过程来带动初学者的思考,培养大家逆向工程的意识。
lockdownd肯定也是可以获知UDID的,这个我在iphonedevwiki上曾看到过相关的资料和代码;但是ISDevice这个倒真没听说过,愿闻其详

ISDevice 在private api里 他有个属性guid hook它就行了 你试试

看了一下你说的ISDevice,确实可以获取到UDID,但是底层调用的还是MGCopyAnswer。具体的调用细节是:

[ISDevice guid]

调用

[SSLockdown copyDeviceGUID]

调用

MGCopyAnswer

你说的对 你是怎么看细节的 有什么好的工具吗

细节就是用IDA看,非常清晰;具体的用法和实例书上都讲得很清楚了

uniqueIdentifier在iOS7中获得的是修改过的uuid。不是存uuid

1 Like

楼主好帖啊,看来这本书不买都不行了啊

貌似这种方法到7.1.1中取不到了,每次取到的都是Null, 楼主有新的解决办法不?

前2种UIKit的方法确实不行了,最后1种MGCopyAnswer的方法还可以用

我用的是结语上面的那个方法试的,我又换了越狱的7.1.1上面试,直接用clang在终端上编译的scp到设备上可以取到,但是用xcode打包在这个越狱设备上面直接跑就取到的是Null了, 代码方面给你提供的一致, 工程配置上面在Other Linker Flags->-l MobileGestalt (这个测试工程在越狱的6.1.6的设备上是正常的,待会在6.1.6未越狱设备上再试试)

Xcode打包拷过去你指的是以App的形式吧?是在/var/mobile/Applications/下吧?这种mobile的App因为沙盒的原因,貌似不管是iOS几,都是获取不到UDID的

对, 是App沙盒形式的,这种在IOS7以前的设备中可以取到,不管是越狱的还是没有越狱的都可以(6.1.6未越狱亲测可以成功获取到)!我感觉是运行时由于系统的限制,没法使动态库libMobileGestalt.dylib动态连接造成的,要是有把动态库进行静态编译到App中的话或许就可以解决了?楼主有没有什么高招?

你的意思是,对libMobileGestalt.dylib的访问都受到了沙盒的限制?如果是这样的话,你可以试试dlopen这个dylib,看看能不能成功得到handle。我的猜测是libMobileGestalt底层对UDID的访问被沙盒拦下来了,比你说的要更底层