概述
我们的iOS内核安全研究人员在offensive_con进行授课培训时,给学员们展示了Apple在iOS 10中引入到内核的一些代码。这段代码为kernel.backtrace系统调用实现了一个新的系统调用处理程序(Sysctl Handler)。该系统调用用于检索当前线程的用户级backtrace。我们提出这个练习,是想看看学员们是否已经走向了正确的方向,他们是否能够发现iOS内核中的0 day信息泄露漏洞。
Kernel.backtrace
kernel.backtrace是iOS内核中一个相对较新的系统调用,用于让当前进程检索自己的用户级backtrace。尽管确定用户级别的backtrace逻辑都隐藏在内核源代码的Mach部分中,但系统调用处理程序自身是在文件/bsd/kern/kern_backtrace.c中实现的。处理程序的代码如下所示。
static int backtrace_sysctl SYSCTL_HANDLER_ARGS { #pragma unused(oidp, arg2) uintptr_t *bt; uint32_t bt_len, bt_filled; uintptr_t type = (uintptr_t)arg1; bool user_64; int err = 0; if (type != BACKTRACE_USER) { return EINVAL; } if (req->oldptr == USER_ADDR_NULL || req->oldlen == 0) { return EFAULT; } bt_len = req->oldlen > MAX_BACKTRACE ? MAX_BACKTRACE : req->oldlen; bt = kalloc(sizeof(uintptr_t) * bt_len); if (!bt) { return ENOBUFS; } err = backtrace_user(bt, bt_len, &bt_filled, &user_64); if (err) { goto out; } err = copyout(bt, req->oldptr, bt_filled * sizeof(uint64_t)); if (err) { goto out; } req->oldidx = bt_filled; out: kfree(bt, sizeof(uintptr_t) * bt_len); return err; }
在上面的代码中,首先验证传入的参数,并限制可以检索的backtrace的深度(第11-19行)。然后,它将分配一个堆缓冲区,来存储第20行中用户选择的深度的backtrace(第25行)。随后将实际检索到的backtrace复制到用户区域(第30行),并释放堆缓冲区(第37行)。
漏洞分析
在继续阅读之前,我建议各位读者再次阅读上面的代码,并尝试在没有帮助的情况下自行发现漏洞。我们给出一个提示:该漏洞只能在较旧版本的iOS/watchOS/tvOS设备中利用。
在自行发现漏洞之前,请不要继续阅读本文。
我是认真的,我强烈建议大家尝试自行发现漏洞。
接下来,要么就是各位读者忽略了上面的三遍警告,要么就是大家已经通过阅读代码发现了漏洞,或者还有一种可能就是在反复阅读代码后并没有发现其中存在的问题。下面,我们一起来找出问题的所在。首先,我们再看一下将backtrace复制到用户区域的相应代码。
err = copyout(bt, req->oldptr, bt_filled * sizeof(uint64_t)); if (err) { goto out; }
如我们所见,复制到用户区域的字节数是bt_filled * sizeof(uint64_t)。这是填充的backtrace条目数量再乘以8个字节。接下来,我们看看正在处理的堆缓冲区有多大。
bt = kalloc(sizeof(uintptr_t) * bt_len); if (!bt) { return ENOBUFS; }
我们在这里看到,堆缓冲区的大小是由公式sizeof(uintptr_t) \* bt_len来确定的,也就是最大检索的backtrace条目数量乘以指针的大小。这是我们之前的提示:在最近的设备上,指针的大小只有8。较旧的iOS设备(iPhone 5c及更早版本)和较旧的Apple Watch(第3代)使用了32位的设备,因此只有4个字节的指针。这意味着,在这些旧设备上,对copyout()的调用将允许从堆中复制两倍大小的字节,而并不是缓冲区的大小。这是一个经典的堆缓冲区越界读取漏洞。
影响
正如我们所指出的,这是一个0day的内核信息泄露漏洞,该漏洞之前并没有人报告给苹果,因此在内核中仍然没有被修复。然而,针对该漏洞,存在很多的缓解因素:
1、该漏洞仅影响32位iOS设备,苹果目前仍然支持的32位版本设备只剩下Apple Watch 第3代及更低版本。
2、该漏洞只能在应用程序沙箱之外触发,因此它只能作为漏洞链的一部分,而不能直接从应用程序中利用。
iOS 12中copyin/copyout缓解
从iOS 12开始,苹果向内核中添加了缓解措施,如果内核的堆缓冲区具有继续操作所需的大小,则每次执行copyin()或copyout()时都会进行检查。如果攻击者试图在内核区域堆元素的边界上进行读取或写入,那么内核将会发生错误(Kernel Panic)。
但是,这种缓解措施并不能阻止攻击者利用此漏洞,因为苹果没有将此保护添加到32位内核中。我们不知道,他们只是忘了保护他们剩余的32位设备,还是原本就打算不再关注这批设备。
关于苹果安全赏金计划
我们将这个漏洞提交给苹果安全赏金计划,但得到了0美元的奖励。具体来说,有三个原因:
1、苹果只奖励影响其最新设备的漏洞。他们是否因此提供了旧设备的更新并不重要,他们只在漏洞影响最近版本的设备时才会发放奖金。
2、苹果不会奖励影响macOS/tvOS/watchOS的漏洞。只有当iOS设备受到漏洞影响时,他们才会发放奖励。
3、尽管许多缓解措施都依赖于内核内存保密,但苹果不会针对信息泄露漏洞给予奖励。
总结
该漏洞是一个难以解释的漏洞。漏洞存在于相对较新的代码,因此我们认为内核开发人员在编写新代码时应该引起足够的重视。另外,关于为什么在分配和复制数据时使用了两种不同的数据类型,这一点目前还是相当神秘的。此外,我们很难解释为什么每次添加新代码时应该进行的新内核代码安全审查中都没有发现这一点。在分配和复制过程中,使用了两种不同的数据类型这一点应该看起来非常明显。实际中,我们也发现,在onensive_con学习内核安全研究的学员们都很快地发现了这一漏洞。
还没有评论,来说两句吧...