一个“短命”的iOS内核漏洞

漏洞的生命周期一直是一个很有意思的话题。人们(特别是媒体)通常对两类安全漏洞特别感兴趣。一类是许久不被修复的安全漏洞。这类漏洞通常反映出厂商对安全的漠视,甚至可以激起厂商和安全社区的对立,因此颇具新闻价值。另外一类是存在已久但刚被发现和公开的安全漏洞。这类漏洞一经公布,有时候会跳出很多人来“认货”,高喊自己手中的存货被曝光了。这类漏洞背后隐含的问题是很难估计这些漏洞的影响范围(例如漏洞已经被多少人发现了、已经被多少攻击利用了),因此特别适合各种阴谋论。

在这篇blog中,我们要分享一个非常“短命”的iOS内核漏洞。这个漏洞在iOS 9的测试版中被引入,但是在iOS 9的正式版中被修复,是个典型的存活周期短、影响范围极其有限的漏洞。之所以分享这个漏洞,一是这个漏洞成因非常典型,但反映出苹果对代码安全非常重视从而确保漏洞的及时修复;二是讨论一下简单模糊测试(Fuzz Testing)在挖掘这类漏洞时候的低效问题。

漏洞分析

漏洞存在于名为AppleCredentialManager的内核扩展模块。该模块通过AppleCredentialManagerUserClient与用户态程序通信,AppleCredentialManagerUserClient只有一个externalmethod。这个selector 0的处理函数可以接受任意长度的struct输入。下面简要描述一下漏洞的触发流程:

  • 在处理输入时,AppleCredentialManagerUserClient会首先检查输入数据的前四个字节是否为一个特定的Magic Number 0x53435244,如果不是则不会进一步处理这个输入。

  • AppleCredentialManagerUserClient进一步根据输入数据(下文简称input)的第5个字节判断数据的类型,即根据这个opcode(即input[5])进行数据分发。

  • 在iOS 8.4.1 中,AppleCredentialManagerUserClient接受的opcode范围是从0到10,但是在iOS 9的beta版和正式中,AppleCredentialManagerUserClient接受的opcode范围扩展到从0到19。

  • 在iOS 9 beta 版中处理opcode=11的输入数据时,AppleCredentialManagerUserClient会进一步解析从input+8开始的数据。这段数据的组织结构示意图如下:

|一个以\0结尾的字符串 | 4字节(代表后继数据长度) | 二进制数据 | paramsCount | param_0 | param_1 | …. | param_n|

  • paramsCount代表后继param结构的个数,而每个param结构的大小为12字节。在处理paramsCount和随后的params的时候,AppleCredentialManagerUserClient的处理逻辑如下:
memmove(&paramsCount, input+ offset_of_paramsCount, 4u);
if(paramsCount!=0){
    parametersBuffer = (void *)IOMalloc(12 * paramsCount);

  • 到这里问题已经显现。由于缺乏对 paramsCount的检查,计算内存分配size的时候存在整数溢出:如果设置 paramsCount为超大数值(例如0x80000001),会导致实际上仅分配少量字节。分配内存成功后,AppleCredentialManagerUserClient进一步将input中的param填充到分配的内存里,从而导致堆溢出。
  • 此外,填充内存的循环以paramsCount为边界。通常这样的漏洞会最终引起内核panic,很难利用。但在AppleCredentialManagerUserClient的填充过程中,会判断输入param的有效性。如果发现输入的param无效,则会跳出循环,为漏洞利用创造了条件。

漏洞修复

在iOS 9的正式版中,我们发现该漏洞已经被修复。AppleCredentialManagerUserClient增加了多处检查。

首先,AppleCredentialManagerUserClient增加检查,确保paramsCount不能大于10。

memmove(&paramsCount, input+ offset_of_paramsCount, 4u);
if ( paramsCount >= 0xB )
{
...
}

对于过大的paramsCount,内核会产生下面的assert报警

AssertMacros: paramsCount <= 10 (value: 0x0), file: /BuildRoot/Library/Caches/com.apple.xbs/Sources/AppleCredentialManager/AppleCredentialManager-83.1.1/common/utils.c, line: 440

其次,在内存分配前,AppleCredentialManagerUserClient增加了乘法溢出的判断

if ( paramsCount != 12 * paramsCount / 12 )
{
...
}

简单Fuzzing的实效

对于这种漏洞,简单的主动式Fuzzing(即通过随机构造畸形样本对目标代码测试)会特别低效。只有当样本前四个字节是0x53435244并且第5个字节在0-19范围内,才能够对代码进行有效测试。而随机生成的样本很难满足这种约束。在PC端,大量改进代码Fuzzing的技术已经被提出并应用(例如符号执行),但在iOS内核场景下如何结合高级模糊测试技术还存在很多难题尚待解决。

发表评论

电子邮件地址不会被公开。 必填项已用*标注