在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等强力帮手已经帮我们引开了保镖,而且把中国著名品牌王力牌防盗门换成了山寨品牌李云牌防盗门,我们自己配把钥匙(私有函数)也能过了,终于有机会对女神一亲芳泽
通过以上的分析和比喻,你可能已经意识到,在原版iOS上很多看似不可行的东西,在越狱iOS中变成了可行的,且很可能比你想象得要简单的多。获取UDID就是其中一例,接下来我们就以3个可能获取UDID的方法来证明我们的分析,希望能够带给大家灵感。
预备、在Cydia中看看我的UDID:
可以看到,我的UDID是 48b01d6059df1e43273cefab2dd2a22512e7ca9f,留着备用。
###消失的- (NSString *)uniqueIdentifier?
在《iOS应用逆向工程》中我们曾提到过,iOS的公开函数相较私有函数,只是冰山一角。在你对私有函数有足够了解之前,最好不要妄下“没有这个函数”的结论。何况,遍历一下UIKit导出的所有头文件,即可轻松发现一个可疑函数,如图:
那么我们拿Cycript测一测好了,如图:
这就说明,看似消失的第一道防盗门,其实是被苹果刷了层白漆,冒充成了墙壁。我们用class-dump这面照妖镜一照,门的原型就显现了出来,加上我们的钥匙,很容易就开了3道门,迎娶白富美~
绕过第一道门,从第二道门开始深入敌后
对于UDID这辆车,在iOS 6中,虽然苹果弃用了- (NSString *)uniqueIdentifier
的车身,但它的发动机还完全可以正常工作,只是车身太破旧,苹果不让我们开这辆车了。在iOS 7里苹果干脆把车身给卸掉了,没车身这车就没法开了。但它会不会在iOS 7里保留车的发动机呢?如果发动机还在,我们自己给做个车身不就可以继续开车了么?好吧,我们看看iOS 6中发动机的样子,如图:
看上去很简单啊,翻译成高层语言就是[UIDevice currentDevice] deviceInfoForKey:@“UniqueDeviceID”]。我们看看这个发动机(deviceInfoForKey:)在不在iOS 7中,如图所示:
我们也别麻烦IDA了,直接上Cycript试试:
即使我们没有发现被伪装成墙壁的第一道门,也可以从没有保镖把守的缺口绕开第一道门,直接用钥匙打开第二道门,是不是很轻松?
第二道门我也懒得开,钥匙留给第三道门吧
第三道门是什么(是哪些函数)?我们在IDA里双击_deviceInfoForKey:,跳转到它的实现,如图所示:
所以,其实_deviceInfoForKey:这第二道门背后的第三道门就是MGCopyAnswer。我们来找到它的钥匙,看看它出自哪位配钥匙的师傅:
二话不说,我们去找名叫libMobileGestalt.dylib的师傅配钥匙去~
可以看到,R1直接被赋0了!看看sub_36ee8924里发生了什么:
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之处就在于,一旦我们发现感兴趣的一个点,就可以通过动静结合的分析方式,由这个点发现一条线,直到打开一个面,最终获得那些纯正向的开发者所不可能获取到的广泛知识。希望朋友们能从这个例子中获取一些灵感,体会到逆向工程之美,如有问题,敬请指出,不足之处,还请海涵,谢谢支持!