前言
在iOS7之前我们可以通过- (NSString *)uniqueIdentifier
这个方法获取iPhone的唯一标识符,也叫作UDID。不过自从iOS7苹果就把这个方法给禁了,此时我们想要获取iPhone的唯一标识符就很困难。
不过苹果提供一个叫做IDFA的标识符,这个IDFA是广告标识符用来追踪广告投放的,不过用户可以在设置中手动重置IDFA,可靠性很低,目前常见的两种标记iPhone的方式为
- openUDID
- IDFA或UUID+keychain
这两种模式都有个弊端,用户重置手机或者刷机唯一标识符会发生变化,不过对于大多数情况是够用了。看来苹果是把路给封死了,有没有办法拿到之前的UDID呢?我们注意到iPhone的设置通用关于里面有手机的硬件信息,其中有一个serialNumber,这个serialnumber就是我们查询手机是否过保的依据,那么它肯定是唯一的,所以下文是围绕这个进行的探索。最终是可以拿到这个serialNumber的, 不过由于苹果的沙盒限制,所以只能在越狱机中拿到,如果想在非越狱机中拿到必须添加entitlements文件来获取权限,可想而知这个应用是无法上架的。下文仅作为逆向工程的一种思路和探索
转载请注明出处:来自LeonLei的博客http://www.gaoshilei.com
正文
一、SSH连接手机(USB模式)
1.映射端口
1 | LeonLei-MBP:~ gaoshilei$ /Users/gaoshilei/Desktop/reverse/USBSSH/tcprelay.py -t 22:6666 |
2.连接手机,并且用grep命令快速筛选当前我们要调试的应用Preferences,附加debugserver开始1234端口等待lldb调试
1 | LeonLei-MBP:~ gaoshilei$ ssh root@localhost -p 6666 |
3.完成以上两步接下来就可以进行lldb调试了,首先要把远端(手机)的1234端口映射到本地,跟前面提到的SSH端口映射一样
1 | LeonLei-MBP:~ gaoshilei$ /Users/gaoshilei/Desktop/reverse/USBSSH/tcprelay.py -t 1234:1234 |
二、通过LLDB、IDA寻找线索
lldb的调试端口已经打开,此时我们可以进入调试
1 | LeonLei-MBP:~ gaoshilei$ lldb |
此时我们已经成功进入Preferences的调试阶段,先c一下,让程序继续运行
1 | (lldb) c |
这么做的原因是我们待会要打印image的基地址偏移,有可能在我们打印的image list中没有我们想要的image。
此时我们已经找到到Preference.framework的基地址偏移,见下图
1 | (lldb) im li -o -f |
我们要找的image的序号在这里是44,它的基地址偏移为0x2e50000,我们把从iPhone中导出的PrivateFrameworks中的Preferences.framework丢到IDA中进行分析,这个二进制文件比较小,很快就分析完成,在前面我们已经知道iPhone的唯一序列号serial number是通过PSListController生成的,并且我们知道这是一个cell,我们要去调试[PSListController tableView:cellForRowAtIndexPath:]
这个方法,从中找到cell值的来源,从而找到获取序列号的方法。
1 | __text:00000001908040C8 ; -[PSListController tableView:cellForRowAtIndexPath:] |
我们在Preference.framework中基地址为0x190804114的位置打个断点,具体的做法是:
1 | (lldb) br s -a 0x190804114+0x2e50000 |
这里断点这样打是因为系统加载可执行文件和各种framework的时候会有一个地址偏移,我们在打断点的时候要把这个偏移量加上,这样我们打的断点才是准确的。
可以看到我们已经成功打了一个断点,断点的address = 0x193654114。此时我们打印变量x0和x27的值
1 | (lldb) po $x0 |
我们执行ni让程序继续(这里的ni
命令相当于Xcode的那个下箭头命令,也就是下一行)
1 | (lldb) ni |
我们ni的两次,程序已经走到0x19080411C的位置,然后我们继续打印变量x0和x27的值
1 | (lldb) po $x0 |
打印出来的x0和x27都是随机数,还是没有什么收获,我们继续
1 | (lldb) ni |
我们让程序执行下一步,发现此时x0已经有值了,可以明显的看出,x0的值是在0x190804114~0x19080411C这段代码生成的,下面我们的工作重点就是寻找这段代码干了什么,胜利就在眼前!下面我们验证一下这里面到底有没有我们要的序列号:
1 | (lldb) po [[$x0 objectAtIndex:13] class] |
我们打印数组中存放cell数据的object属于哪个类,发现是PSSpecifier
,我们找到之前导出的类的头文件,发现这个类有一个叫做properties
的实例方法,我们调用一下发现我们要的序列号就在里面value = DNPMVG0EFF9V
,这跟iPhone设置中看到的序列号是一致的。猜测这个数组里面存放着系统设置中PSUIAboutController
中所有cel的数据,这个数组下一个肯定要传递到cell生成的方法中,这个就不做验证了,大事重要,我们继续找序列号的生成方法。
这个PSSpecifier
中有一个AboutDataSource
对象,这个非常可疑,从名称上可以判断,这个类是专门用于数据处理的,不过在这之前我们还是先验证一下,在0x190804114~0x19080411C这段地址中,执行了_PSListController._specifiers
,我们从PSListController
的头文件(下文有讲怎么获取)中可以看到有一个specifiers属性,我们在IDA分析的文件中找到[PSListController specifiers]
,我们先定位到方法在二进制文件中的位置:
1 | __text:00000001907FE4A8 ; -[PSListController specifiers] |
然后在这里面下个断点看看会发生什么
1 | (lldb) br s -a 0x1907FE4D0+0x198e58640 |
我们从设置中进入通用>关于,发现一开始就走到了这个断点,我们猜测,一进入关于页面,系统会首先把所有cell的数据都准备好,然后加载UI
1 | Process 1192 stopped |
我们打印变量x8和x9的值,看一下系统做了什么
1 | (lldb) po $x8 |
并没有数据之类的东西值得我们关注,让断点继续往下走,走到0x19364e4dc的位置,我们再次打印变量x8和x9的值
1 | (lldb) n |
此时的变量x9已经变成了AboutDataSource
,这里验证了我们上一步的猜想,所以我们重点来研究它,我们先找到这个类在哪个framework中,这里使用的是grep命令
1 | LeonLei-MBP:~ gaoshilei$ grep AboutDataSource -r /Users/gaoshilei/Desktop/reverse/iOS-Runtime-Headers-9.1 |
这里要说明一下iOS-Runtime-Headers-9.1这个文件夹是iOS9.1系统的所有头文件(共有+私有),这个你可以自己导(iOS9之后只能用runtime导,class-dump已经不行了),你也可以拿现成的用,github上面已经有雷锋把所有系统的头文件都导出来了,直接下载就可以了。我们发现AboutDataSource
这个类在PrivateFrameworks/PreferencesUI.framework
中,先看一下这个类里面有什么方法和属性,有一个方法- (void)_loadValues;
我们对它进行分析。这里又要借助IDA分析,把PreferencesUI这个二进制文件丢到IDA里面,在0x19091EBB8这个位置打个断点
1 | (lldb) br s -a 0x19091EBB8+0x2e50000 |
接下来我们进入关于来触发断点
1 | (lldb) po (char *) $x28 |
在这里打印变量x28的值,发现它是一个方法名,从名称来看是给specifier
赋值的,看来我们要寻找的真相已经很近了,让代码走到下面的位置0x19376ebd8
1 | Process 2107 stopped |
此时我们打印的x0是一个NSCFConstantString
,本质就是一个NSString
,继续ni
让程序运行到0x19376ebdc
1 | Process 2107 stopped |
在这里我们打印了变量x0的值为DNPMVG0EFF9V,这就是我们苦苦寻找的序列号。不难看出,序列号就是在0x19376ebd8这行拿到的,范围越来越小,敌人无路可逃!下面我们就要对这行进行分析,我们按照之前的步骤,再次走到0x19376ebd8这个位置,这不过这次我们不要step-over
,我们用si
跳入看看
1 | (lldb) si |
这个函数最外层只有两行代码,将立即数0赋给x1,然后跳进了子程序sub_196008648,跳进去之后进行了一些很复杂的运算,这里就不做介绍了,里面的实现大概是这样的:
x0是作为一个参数传入的,并且这里x0的值为SerialNumber
,在地址为0x196008678的地方,这个函数中x1变成了一串随机数,有点像MD5加密之后的东西,应该是“钥匙”
1 | (lldb) po (char*) $x1 |
在0x196008690这里,我们setp-into
这个函数,在函数的末尾返回值的地方0x196007474打个断点,打印返回值x0
1 | (lldb) po $x0 |
这里的x0由SerialNumber
变成了真正的序列号,并且就是在0x196008690对应的子程序sub_19600738C里面拿到的,所以我们就这样一个猜测,在MGCopyAnswer
函数中,x0作为一个参数传入,并且在内部进行了一系列复杂的运算,拿到了获取序列号的“钥匙”x1,然后在sub_19600738C中拿到了最终的序列号。这里笔者也没有对序列号的拿到在进行进一步的深究,这里苹果做了很大的限制,再继续研究恐怕也是收获不大,而且我们在这里已经能拿到序列号了。
### 三、验证结果
接下来就是验证的过程了,我们写一个tweak来验证,当然也可以用其他方式来验证:
tweak的创建这里就不赘述了,我把我的tweak和makefile文件内容贴一下:
tweak文件:
1 | tweak.xm: |
这里注入系统的SpringBoard,在SB启动的时候hook住applicationDidFinishLaunching:函数,并且在这个函数里面添加获取序列号的代码,并且以弹框的形式展现出来。
makefile文件:
1 | THEOS_DEVICE_IP = 192.168.0.115 |
其中有一行SerialNumber_LDFLAGS = -lMobileGestalt
千万要注意,使用的时候要加载这个静态库,因为SpringBoard加载的时候我也不确定是否有加载这个库,然后我们验证一下吧!