寻找IOS内核符号

 

上周,Google的Project Zero项目组成员Ian Beer在Twitter上发文称他发现了通过task_for_pid_0或tfp0获取内核内存读写权限的方法。这一发现可以帮助到那些引导IOS 11内核安全研究的人们。

一、内核符号的缺失

虽然本次漏洞的发布令人更加兴奋的是意味着可以构造出新的越狱方法,但我还是想借此机会再次研究关于移动安全的问题。我过去的大部分经历主要是做关于app安全等级测试的。但这一次我想深入了解操作系统的实质。

Jonathan Levin‏在介绍他即将推出的工具集的时候说过:这是给那些已经在进程中拥有kernel_task port(tfp0)权限,但不知道下一步应该怎么做的人们使用的。这看起来是一次很好的深入研究的机会。

Beer的漏洞报告和POC代码已经在昨天发布了,非常精彩。尤其是文档书写整洁,易于阅读和理解(包括一个ASCII art stack框架示例)。正当我准备好开始我的测试的时候,忽然发现了一个问题。尽管Beer足够友好地给出了几种设备的内核符号,但是没有我正准备使用的设备的内核符号。幸运的是,他在文档中详细地给出了如何查找内核符号。所以,我已经准备好开始这项工作了。

 

二、寻找内核符号过程

这篇文章剩下的部分主要介绍了我寻找内核符号的整个过程。希望应用到你的设备的时候,方法同样适用。除了已经导出的内核符号外,我没有找到其他阐述如何做这些的文档,所以我想写下这篇文章来给其他需要的人看(也包括未来的自己)。由于有着Jonathan Levin强大的工具包在,所以这项工作可能并不是必要的,但是在我看来,这是一次有趣的学习、练习的过程。

寻找内核符号的第一步就是得到对应IOS版本的kernelcache。我在https://ipsw.me/给我的iPad Mini2,以及一个在symbols.c中有定义好了的内核符号的设备下载了11.1.2版本的固件。拥有一个已经定义好内核符号的固件可以很好地帮助我确认我所寻找的内核符号是否正确。因为我们已经知道了这个设备正确的内核符号。

在解压出.ipsw文件后,我开始使用Levin的工具Joker。在尝试了一会工具的各个参数选项后,我选择对kernelcache文件使用-j,-m参数来转储所有可用的内核符号。输出包括以下我需要的内核符号的地址:

 KSYMBOL_OSARRAY_GET_META_CLASS

 KSYMBOL_IOUSERCLIENT_GET_META_CLASS

 KSYMBOL_IOUSERCLIENT_GET_TARGET_AND_TRAP_FOR_INDEX

 KSYMBOL_CSBLOB_GET_CD_HASH

 KSYMBOL_KALLOC_EXTERNAL

 KSYMBOL_KFREE

 KSYMBOL_OSSERIALIZER_SERIALIZE

 KSYMBOL_KPRINTF

 KSYMBOL_UUID_COPY

找到了9个内核符号地址,然而剩余符号的地址已经无法使用工具导出了(或者符号从来没被标记过,像JOP gadget)。所以我们现在必须在IDA pro中加载kernelcache文件进行分析查找。当然,你也可以使用其他的反汇编工具像Binary Ninja,Hopper或者radare2。

在我们开始之前,我们需要先解码kernelcache文件。我在joswr1ght的这份指导中找到了方法。

* open kernelcache in a hex editor and look for 0xFFCFFAEDFE, note the offset (435)
* wget -q http://nah6.com/%7Eitsme/cvs-xdadevtools/iphone/tools/lzssdec.cpp
* g++ -o lzssdec lzssdec.cpp
* ./lzssdec -o 435 < kernelcache >kernelcache.dec # 435 is offset byte count to 0xFFCFFAEDFE header

首先,我用IDA加载装有已知内核符号固件的kernelcache,这样我就可以知道我要寻找的内核符号的汇编形式长什么样子了。

下一个最容易找到的内核符号是KSYMBOL_RET,找到已知内核符号的地址,我看到RET指令是从_kalloc_external函数跳转回来。所以很容易在IDA中加载我的设备的kernelcache,然后在同一个函数中找到RET。对于寻找剩下的内核符号,我们可以借鉴其他提示。

接下来是KSYMBOL_CPU_DATA_ENTRIES,提示中说是在数据段中地址为0x600处。所以在IDA中选择“Jump to Segment”(ctrl+s),然后选择来到(.data)数据段开始地址处,记下起始地址值,并加上0x600得到我需要的内核符号地址。

下两个我发现的内核符号实际上是列表中的最后两个,KSYMBOL_EL1_HW_BP_INFINITE_LOOP和KSYMBOL_SLEH_SYNC_EPILOG。在IDA中打开Strings Windows(Shift+F12),并搜索字符串。双击后,我们就可以来到字符串所在处。

对于第一个,我向下寻找直到找到跳转case 49的地方,并记下此时地址。

iPod Touch 6G中的KSYMBOL_EL1_HW_BP_INFINITE_LOOP

对于第二个,在已知内核符号文件中的地址是在XREF‘d字符串下边几个LDP指令中的第一个的地址处。

最后一个比较简单的是KSYMBOL_X21_JOP_GADGET。在看到所需的指令是MOV X21, X0后,我直接在IDA中搜索文本就可以找到我设备中的此内核符号。

最后5个是最难找到的。我这一次的搜索并没有直接找到提示的内容。所以我开始研究我所发现的这些符号地址并试着寻找他们之间的关联。在用这些地址做一些基本的减法后,我发现他们之前隔着相同倍数的距离。这至少帮我减小了搜索的范围。我还比较了我已知的这些符号中最近的两个相距多少。例如:VALID_LINK_REGISTER和X21_JOP_GADGET之间只差0x28个字节。

距离最近的两个符号可以帮助我缩小搜索范围

于是我在纸上记录下已知地址处的汇编指令,加载我设备的kernelcache,并跳转到KNOWN_ADDRESS + my offset guess(已知地址+我猜测的地址偏移量)处。然后在附近寻找相同的汇编指令。

有时候笔和纸的效果最好

在代码中将找到的内核符号写入一个数组,之后只需要添加一个if条件就能应用于我的设备当中。

最后将我的iPad连接到电脑,打开Xcode项目并运行。得到如下结果:

我所做的iPad Mini 2 WiFi补丁可以在这里找到。

 

三、结语

整个过程对我来说完全是陌生的(但是非常有趣!),所以欢迎大家指正出文章中任何有错误或者理解偏差的地方。如果你知道有寻找这些地址更加简单的方法,也请你反馈给我。我喜欢学习使用新的工具和技术。所以如果能看到这些,我会非常高兴,并且继续更新这篇文章。

(完)