Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2022-42864 — 针对 CVE-2022-42864 IOHIDFamily 竞态条件的概念验证 | Kitploit
工具/GitHubGitHub/muirey03/cve-2022-42864
漏洞分析漏洞利用二进制利用
GitHubmuirey03/cve-2022-42864

CVE-2022-42864

针对 CVE-2022-42864 IOHIDFamily 竞态条件的概念验证

查看仓库
68793年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2022-42864:邪恶的 cookies

这个仓库是什么?

这是我为 CVE-2022-42864 编写的(不完整的)概念验证漏洞利用代码,这是 IOHIDFamily 中的一个时间检查-时间使用(TOCTOU)漏洞,已在 iOS 16.2 / macOS Ventura 13.1 中修复。

概念验证的状态如何?

该漏洞利用目前实现了与 multicast_bytecopy 漏洞利用中相同的“任意 kfree”原语。然而,multicast_bytecopy 的后续漏洞利用流程已被大幅缓解,因此这不是一个完整的漏洞利用,仅仅证明了该问题的严重性。

我该运行它吗?

如果你还需要问,那就不应该运行。它不会做任何有用的事情,只会导致内核崩溃。对于此代码可能造成的任何数据丢失或不稳定,我不承担任何责任。

这个 Bug

Apple 的注释 来自修复该问题时的源代码,很好地总结了这一点:

// Find the number of cookies in the data. The data from elementData is shared with user space and may change at any time.

让我们来看一下补丁之前的函数(我已尝试标注相关行):

IOReturn IOHIDDevice::postElementTransaction(const void* elementData, UInt32 dataSize, UInt32 completionTimeout, IOHIDCompletion * completion)
{
    IOReturn ret = kIOReturnError;
    uint32_t   cookies_[kMaxLocalCookieArrayLength];
    uint32_t   *cookies = cookies_;
    uint32_t   cookieCount = 0;
    uint32_t   cookieSize = 0;
    uint32_t   dataOffset = 0;
    uint8_t    *data = (uint8_t*)elementData;
    IOMemoryDescriptor *elementDesc = getMemoryWithCurrentElementValues();
    require(_elementArray && elementDesc, fail);

    WORKLOOP_LOCK;

    // Find the number of cookies in the data. Check that all cookies are valid elements.                   [1]
    while (dataOffset < dataSize) {
        const IOHIDElementValueHeader *headerPtr = (const IOHIDElementValueHeader *)(data + dataOffset);
        IOHIDElementPrivate *element = GetElement(headerPtr->cookie);
        if (!element) {
            HIDDeviceLogError("Could not find element for cookie: %d", headerPtr->cookie);
            ret = kIOReturnAborted;
            goto fail;
        }
        cookieCount++;

        require_noerr_action(os_add3_overflow(dataOffset, headerPtr->length, sizeof(IOHIDElementValueHeader), &dataOffset), fail, HIDDeviceLogError("Overflow iterating cookie data buffer %u %u", dataOffset, headerPtr->length));
    }
    // Data isn't as large as expected, don't overrun, just abort
    if (dataOffset != dataSize) {   //                                                                      [2]
        HIDDeviceLogError("Cookie data buffer is smaller than expected. %u vs. %u",
                        (unsigned int)dataSize, (unsigned int)dataOffset);
        ret = kIOReturnAborted;
        goto fail;
    }
    dataOffset = 0;

    require_noerr_action(os_mul_overflow(cookieCount, sizeof(uint32_t), &cookieSize),
                        fail,
                        HIDDeviceLogError("Overflow calculating cookieSize"));

    cookies = (cookieCount <= kMaxLocalCookieArrayLength) ? cookies : (uint32_t*)IOMallocData(cookieSize); // [3]

    if (cookies == NULL) {
        ret = kIOReturnNoMemory;
        goto fail;
    }

    // Update the elements, this replaced the shared kernel-user shared memory.
    for (size_t index = 0; dataOffset < dataSize; ++index) {    //                                          [4]
        const IOHIDElementValueHeader *headerPtr;
        IOHIDElementPrivate *element;
        OSData *elementVal;

        headerPtr = (const IOHIDElementValueHeader *)(data + dataOffset);
        element = GetElement(headerPtr->cookie);
        dataOffset += headerPtr->length + sizeof(IOHIDElementValueHeader);

        elementVal = OSData::withBytesNoCopy((void*)headerPtr->value,
                                            headerPtr->length); //                                          [5]
        require_action(elementVal, fail, ret = kIOReturnNoMemory);
        element->setDataBits(elementVal);
        elementVal->release();

        cookies[index] = headerPtr->cookie; //                                                              [6]
    }

    // Actually post elements
    ret = postElementValues((IOHIDElementCookie *)cookies, (UInt32)cookieCount, 0, completionTimeout, completion);

fail:
    WORKLOOP_UNLOCK;
    if (cookies != &cookies_[0]) {
        IOFreeData(cookies, cookieSize);
    }

    return ret;
}
  • [1] 处的循环计算缓冲区中 IOHIDElementValue 的数量,并将该计数存储在 cookieCount 中。
  • [2] 处的检查(结合 while 循环的条件)将确保每个 header 的 length 字段不会超出 elementData 缓冲区的边界(也不能少于缓冲区的末尾,尽管这不太相关)。
  • 一旦该循环对所有元素完成计数和合理性检查,就会在 [3] 处向堆分配一个 cookies 缓冲区,大小为 cookieCount * 4(如果 cookieCount 足够小,则使用栈缓冲区)。
  • 然后 [4] 处的第二个循环再次遍历缓冲区,重新解析 IOHIDElementValue。
  • 在 [5] 处创建 OSData 对象来保存每个元素的值,使用第一个循环中已验证的 length 字段。
  • 在 [6] 处,每个元素的 cookie 被写入在 [3] 处分配的 cookies 数组中。

那么问题出在哪里?当 elementData 是非易失性时,此函数的行为完全正确;当该方法使用共享内存调用时,问题就出现了。来看看 IOHIDInterface::SetElementValues_Impl DriverKit 方法:

kern_return_t
IMPL(IOHIDInterface, SetElementValues)
{
    IOReturn ret = kIOReturnError;
    UInt8 *values = NULL;
    IOBufferMemoryDescriptor *md = NULL;
    
    md = OSDynamicCast(IOBufferMemoryDescriptor, elementValues);
    require_action(md && count, exit, ret = kIOReturnBadArgument);

    values = (UInt8 *)md->getBytesNoCopy();
    
    // Post the data to the device
    ret = _owner->postElementTransaction(values, (UInt32)md->getLength());
    require_noerr_action(ret, exit, HIDServiceLogError("postElementValues failed: 0x%x", ret));

exit:
    return ret;
}

这里,postElementTransaction 以 md->getBytesNoCopy() 被调用,这块内存与用户空间共享,违反了 elementData 非易失性的假设。elementData 缓冲区的内容可以在 [1] 循环之后、[4] 循环之前发生变化,那么这对攻击者意味着什么?

攻击者可以通过两种方式滥用这一点:

  1. 第一种是将缓冲区末尾的一个小的 IOHIDElementValueHeader 的 length 替换为一个更大的值。这意味着当 [5] 处的 OSData 被创建时,它将远远超出 elementData 缓冲区的边界,使攻击者能够使用 IOHIDInterface::GetElementValues_Impl 读取越界数据。
  2. 第二种是将缓冲区开头的一个大的 IOHIDElementValueHeader 的 length 替换为一个较小的值。这将使 [4] 处的循环解析比 [1] 处循环最初计数的更多 header,因此当 cookies 被写入 [6] 处的 cookies 数组时,由于 index 从未与 cookieCount 进行校验,它们将溢出数组。

实际上,这允许攻击者读取任意大小的越界内核堆数据,并将任意数据(同样是任意大小)越界写入内核堆。这是两个强大的原语。

赢得竞争

对于竞态条件,我们总是寻找方法来确定竞争是否成功,这样我们就可以不断尝试直到成功,使我们的触发变得确定性。幸运的是,在这种竞态条件下,我们完全可以做到这一点。

对于越界读取变体,我在正在切换 length 的 header 之后再放置一个 IOHIDElementValueHeader,将其 value 设置为可识别的常量 0xD1AB011CAC1DF00D。然后,当回读元素的值时,如果在返回数据的开头看到熟悉的 0xD1AB011CAC1DF00D header,我就知道赢得了竞争。

对于越界写入变体,我在正在切换 length 的 header 之后、其 cookie 将被溢出的那些 header 之前放置一个 IOHIDElementValueHeader,这次的可识别 value 为 0xD15EA5ED。在我们没有赢得竞争的情况下,这个 header 将被封装在较大元素的 value 内部,因此只有在我们赢得竞争时,该 header 才会被解析并且元素的值才会被设置为 0xD15EA5ED。通过回读元素的值,我就知道是否成功。

Apple 的修复

为了修复该问题,Apple 选择在循环 [1] 和循环 [4] 之间增加第三个循环,验证每个 length 字段,然后将其缓存到一个新的 dataLengths 数组中,同时确保元素数量没有改变。最后的循环使用缓存的长度进行计算,避免再次从缓冲区读取。

利用时的问题

利用此问题时需要克服的主要障碍是,我们溢出的缓冲区属于 KHEAP_DATA_BUFFERS,因此可利用的目标有限。在这个概念验证中,我选择以 kmsg headers 为目标,因为它们是 KHEAP_DATA_BUFFERS 中极少数包含内核指针的结构之一。我通过这种方法获得的“任意 kfree”原语与 multicast_bytecopy 漏洞利用中使用的原语相同,不过 IOSurfaceClient 数组现在已被 PAC 保护,且伪造的 client 需要有一个指向创建它们的 IOSurfaceRootUserClient 的有效指针,这使得它不再是一个理想的内核读/写目标。

构建与安装

Apple 并没有让构建和安装自定义 DriverKit 扩展变得非常容易,尤其是在没有付费 Apple Developer 账号的情况下,但这仍然是可能的。

在开始之前,我建议:

  • 禁用 SIP
  • 设置启动参数 amfi_get_out_of_my_way=1 cs_enforcement_disable=1
  • 运行 systemextensionsctl developer on
  • 重新启动电脑

之后,你应该可以在 Xcode 项目设置中选择你的开发者团队,项目将构建成功。然后你可以运行 HIDDriverLoader,使用“Install Dext”安装 DriverKit 扩展,并使用“Trigger Exploit”——你猜对了——触发漏洞利用。

如果这失败了,你还可以尝试使用以下命令进行未签名构建:

xcodebuild build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO

然后使用以下命令手动签名:

codesign -fs self-sign-cert --entitlements HIDDriverLoader/HIDDriverLoader.entitlements build/Release/HIDDriverLoader.app/Contents/MacOS/HIDDriverLoader
codesign -fs self-sign-cert --entitlements HIDDriver/HIDDriver.entitlements build/Release/HIDDriverLoader.app/Contents/Library/SystemExtensions/*.dext/*.driver

将 self-sign-cert 替换为你钥匙串中自签名证书的名称。

感谢阅读 :)

下载工具