原文链接:https://www.synacktiv.com/posts/exploit/ios12-kernelcache-laundering.html
iOS 12发布已经过去好几周了。新的iOS版本通常意味着新的Kernelcache(内核缓存)和dyld_shared_cache文件格式,iOS 12当然也不会例外,除此之外还带来了一个惊喜:新的A12芯片的指针认证码(PAC)。这篇文章将会告诉你如何通过加强IDA来解决上述的两个问题。IDA 7.2测试版可能会增加对PAC和iOS12 kernelcache的支持,但要几周 之后才会发布,我们认为自己去实现这个功能会是很有趣的一件事。
本文概述:通过两个IDAPython插件支持PAC指令和iOS12 kernelcache 重定位。
这里不会提供更多关于PAC的信息,想要了解更多信息,可以去阅读Mark Rutland的演示文稿或直接阅读文档。尽管有这么多的吹嘘1 2 3以及PAC显然是不完美的,但依然算是朝着好的方向迈出了重要的一步。它使一些漏洞变得不可利用(特别实在受限的环境中),其他的一些漏洞也变得更加难以利用,可能还需要额外的漏洞。例如,基于堆栈的缓冲区溢出,导致在USB上暴露的服务(可用于开发类似GrayKey的设备)中对PC的直接控制可能变得不可利用,但JavaScriptCore中允许任意RW访问的漏洞很可能被利用.但是不管怎么说,苹果真的可以为这一功能感到自豪,因为它再次领先了竞争对手。
我们也不会展示IDA,因为IDA还不支持PAC指令(但是下一版本中应该会支持)。@xerub已经提供了一个插件来支持,但是还是有几个缺点:
最后一点显然是最重要的一点。按照原理来说,xerub将PAC转换成非PAC的等价指令,RETAA被转换成RET,例如,这个插件之后通过 insn insnpref字段中放入一个标识符来确保它们正确显示出来。由于这一点,Hex-Rays Decompiler只能看到经典的ARM指令,并能够反编译使用RET,BLR,BR,ERET和LDR已验证版本的函数。
然而, PACXXX指令(如PACIA)没有任何非PAC等价的指令。xerub将这些指令转换为HINT(它们位于不支持PAC的ARMv8处理器上)。问题是Hex-Rays Decompiler不知道如何将HINT指令转换为微码并放置 __asm {...} 块。当Hex-Rays Decompiler放置__asm块时,会通过尝试检测__asm块使用和修改的内容来粘合C代码。 Hex-Rays Decompiler接下来创建具有类似寄存器名称的新变量,使代码易于理解:
// the code we would like to have
void sub_XXX()
{
__asm { PACIBSP }
_X8 = qword_XXXX;
_X9 = 0x1234;
__asm
{
AUTIZA X8
PACIA X8, X9
}
qword_YYY = _X8;
return 1;
}
要知道读取哪些寄存器,Hex-Rays使用指令特征(feature)位,这些位是根据每个指令的定义来定义的,特定于processor_t对象,不能通过插件编辑(请参阅instruc_t, feature bits 和processor_t文档。
除此之外,这些位定义了指令使用或更改的操作数。你可以通过指定一个大于或等于CUSTOM_CMD_ITYPE 的insn itype将用户定义的指令添加到处理器中,但是该指令的特征位应该设置为0(参见has_insn_feature code)。问题是HINT特征位只有CF_USE1位。
这就意味着Hex-Rays Decompiler将认为PAC*指令仅使用它们的第一个操作数,并将根据此优化代码,删除重要的语句并做出错误的假设:
// the code we get
void sub_XXX()
{
__asm { PACIBSP }
_X8 = qword_XXXX;
__asm
{
AUTIZA X8
PACIA X8, X9
}
qword_YYY = qword_XXXX;
return 1;
}
解决方案可能是修改这个ARM处理器插件来修改HINT特征位,但这样做不够优雅。另一个更优雅的解决方案是开发一个Hex-Rays Decompiler插件来添加PAC内联函数,就像Dougall J为VMX做的那样,但微码API没有暴露给IDAPython(但是?)并且我们不想强迫人们编译我们的工具。
相反的,我们决定使用IDA的ARM64 processor_t 中已经存在的指令,这些指令具有以下属性:
一条简短的Python代码给我们提供了满足三个条件的指令:
print "\n".join(n for n,v in idaapi.ph_get_instruc() if v == idaapi.CF_CHG1 | idaapi.CF_USE1 | idaapi.CF_USE2)
问题是没有指令匹配...,不过不奇怪,因为大多数ARM指令使用三个操作数,第一个是目标地址,另外两个是来源。我们决定搜索所有符合以下行为的指令:
print "\n".join(n for n,v in idaapi.ph_get_instruc() if v == idaapi.CF_CHG1 | idaapi.CF_USE2 | idaapi.CF_USE3)
令人吃惊的是,HLT指令匹配(以及ERET,HVC等)。这个指令甚至没有三个操作数,只有一个直接的,没有任何修改操作(很明显),但是被使用。这可能是一个复制/粘贴错误,或者这个错误没有意义,只是开发人员很懒。HLT不会被Hex-Rays Decompiler反编译,所以是一个完美的候选者!我们只需要为我们的指令添加一个隐藏的第三个操作数,让它等于第一个,就可以实现我们的目标。
我们写了一个IDAPython脚本,放在了Github。令人惊讶的是,尽管用Python编写代码,但这个插件并没有明显地减慢分析速度。
作为奖励,我们还添加了一个ev_emu_insn回调来强制IDA将BRK视为中断指令(如果您想了解更多相关信息,可以阅读这篇文章)。
下面是kernelcache
重定位功能的截图,带有PAC指令,由于我们的插件,Hex-Rays Decompiler可以正确反编译:
由于开启了ASLR,需要重新定位kernelcache中的指针,并且由于PAC,有一些需要进行身份验证。为了做到这一点,iOS12 kernelcache使用了一种新的重新定位机制,非常类似于userland dyld_shared_cache机制。Brandon Azad已经有了这种直觉,并编写了一些东西来清理Kernelcache,但是他的代码是不完整的,很多指针都没有被正确的清理。此外,通过清理指针,就会失去重定位和PAC的信息,因为它们被存储在指针未使用的高比特中。
通过在kernelcache中搜索PACIA指令,您可以快速找到kernelcache和dyld_shared_cache重定位处理代码。我们在IDAPython插件中逆向并重新实现了kernelcache的这个代码,我们还为每个指针添加一个自动注释的功能,以告知它是否以及如何签名,并将其转换为偏移量。当在IDA中加载iOS 12 kernelcache 时,这是直观且自动完成的。
同样,代码可以在Github存储库中找到。这个机制并不是很有趣,而且代码是不需加以说明的,所以我们不会通过详细介绍它们来打扰我们的读者。 dyld_shared_cache代码留给读者练习,但它与kernelcache 非常相似,很容易实现。
下面是一个带有受保护指针的虚函数表的截图,这些指针由插件自动解码和注释:
引用了Rolf Rolles的一句话,我们的这篇博客文章很好地说明了这一点:
I think Hex-Rays doesn't get enough credit. Their products may be
proprietary, expensive, and idiosyncratic, but they're robust, powerful,
work well by default, and are endlessly configurable/customizable. At
the end of the day, I've got stuff to do and my tools need to work now.(我认为Hex-Rays没有得到足够的信任。他们的产品可能是专有的,昂贵的,并且也是特殊的,但是很健全,功能强大,默认情况下运行稳定,并且可以无限配置/可定制。在一天结束后,最后,我有事情要做,我的工具现在需要工作。)