大鱼:利用 OpenBSD 中一个 37 年的内核漏洞
大鱼。小计数器。根 shell。Calif 经常发布我们的进攻性研究和工作,但我们不会在我们为客户执行的防御性工作上引起太多注意——协助开源项目进行修复、补丁生成、加密改进和内部工程项目。今年早些时候,我们报告了一些漏洞,通常还附带了对 OpenBSD 的补丁建议。其中一个可以追溯到大约 37 年前的原始 BSD 源代码,在这里,定义了一个作为有符号 16 位短整数的共享内存附加计数器。重复地为具有共享内存附加记录的进程分叉可能会将该计数器推到其最大值 32,767 之上,导致其回绕到负值。这种整数溢出可能导致通过竞争条件的使用后释放;结合内核中的另一个越界写入,它允许本地特权提升到根权限。相关问题已在 errata 026 和 018 中解决。攻击方法我们成功地在启用了 KARL / SMAP / SMEP 的 OpenBSD 中获取了本地根权限,而无需利用信息泄露漏洞,路径如下:映射 127 个 SysV 共享内存段,通过 munmap() 取消映射它们,并分叉 259 个进程以将共享内存附加计数包裹到 -32,643。执行堆整理和布局,使用来自 sysctl( KERN_MALLOCSTATS) 的指标(如 kb_total 和 kb_totalfree)来跟踪分配并确定何时分配新页面。在一个进程中使用 IPC_RMID 赢得竞争条件,同时在辅助进程中附加一个新的大小混淆的共享内存段。对 IP socket 分配进行对齐,并将 IP_MULTICAST_TTL 设置为非默认值 2,以分配一个持久的 16 字节 struct ip_moptions。它的最后一个单词打包了两个 16 位计数器:imo + 12 = imo_num_memberships | imo_max_memberships << 16。触发条件 -1 越界写入以覆盖会员数量和最大会员字段。关闭 socket 以遍历组成员,触发原子递减,修改 PTE 自引用映射两次,将其从用户映射切换为监督访问映射,通过覆盖可预测的内核指针实现内核代码执行。清理共享内存会计并恢复被覆盖的内核指针以获得根 shell。这是问题的简要概述,更长的版本更加有趣,包括漏洞的历史,因为一个短 shm_nattch 可以追溯到约 37 年前,在原始 BSD 源代码中。退一步说,此漏洞最终通过写入什么位置原语获得代码执行,其中写入是由 TAILQ_REMOVE 引起的,写的是我们的用户空间内存映射,使其以超级模式可访问,以绕过 SMAP / SMEP,而位置则是可预测的内核指针。漏洞缓解虽然 OpenBSD 的 KARL 重新链接内核二进制文件,以随机化函数和数据地址,但内核的内存分配器仍然是确定性的。引导时分配器和最佳适合分配器都缺乏基础地址随机化。因此,早期引导分配在相同硬件的引导过程中始终位于可预测的位置。当这些可预测的结构分配包含函数指针时,它们就成为理想的攻击目标。例如,该攻击可靠地瞄准 lo0 和 enc0 ioctl 处理程序,通过覆盖一个来获取控制流,并使用另一个来恢复被覆盖的指针。除了上述 errata 修复之外,还有几种其他方法可以用来消除或减少再次使用这些途径的可能性。首先,我们可以实现安全卸链技术,该技术在执行卸链内存写入之前检查双向链表不变性。如果不变性被违反,则会根据处于用户模式还是内核模式,调用合适的 abort() 或 panic(),以防止内存损坏的发生。示例实现可以在这里找到。FreeBSD 和 NetBSD 在其 sys/queue.h 实现中实现了安全卸链。其次,我们可以随机化自引用 PTE 映射分配地址,以使目标原子递减位置不可用。这是通过使用随机字节( .openbsd.randomdata )和在引导时选择一个 PML4 插槽( 320 + (random_byte & 127) )来完成的,并在该位置安装递归条目。PTE_BASE 定义成为运行时表达式,生成从 0xffffa00000000000 到 0xffffdf8000000000 的基础,间隔为 512 GiB。示例实现可以在这里找到。如果随机化不适合,其他选项可能是足够的,例如将 PTE 标记为只读,并在修改期间更改 CR0.WP。第三,内核分配器可以通过两种方法变得非确定性;通过在引导时随机化内存分配发生的基址,以及在运行时随机化分配顺序。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡