CVE-2024-21338 是微软于 2024 年 2 月的周二补丁日披露的 Windows 内核权限提升高危漏洞,CVSS 3.1 评分为 7.8(高危),存在于 Windows 内置的 AppLocker 应用白名单驱动 appid.sys 中。
该漏洞的核心危害在于:攻击者可通过精心构造的 IOCTL 请求,在内核态触发任意函数调用,最终从用户模式突破至内核模式执行代码,实现完整的系统控制。
更值得关注的是,该0day 漏洞在补丁发布前已被朝鲜国家级黑客组织 Lazarus 利用长达半年的时间,用于其 FudModule Rootkit 的内核权限获取,彻底替代了此前易被检测的 BYOVD(自带漏洞驱动) 攻击模式。
该漏洞已于 2024 年 2 月 13 日 修复,建议使用 Windows 10 1709 等微软在 2024 年 2 月 13 日 之前停止支持的 Windows 系统版本进行测试。
在 Windows 系统中,需要开启 Application Identity 服务,才能利用该漏洞。在 services.msc 中,找到 Application Identity 服务并手动启动。
通过拆解0x22A018控制码可知,该控制码需要具有写入权限的句柄,才能成功触发。我们打开 WinObj 工具,找到 AppID,如图所示: 可以看出,管理员无权限对设备对象进行写操作,只有在 LOCAL SERVICE 的权限上下文中才具备对设备对象的写操作权限。
漏洞的核心缺陷位于appid.sys对 IOCTL 控制码0x22A018的处理函数中,本质是对用户态传入的指针完全缺乏合法性校验,导致任意内核函数调用能力。
在(*a2)(userBuffer, v42)中,第一个参数正是内核态从用户态拷贝过来的缓冲区基址。
而a2则是userBuffer的0x10偏移,指向一个函数指针,用于调用用户态传入的函数地址。整体调用如下:
总的结构体定义如下:
注意事项
由于appid.sys的设备仅允许 Local Service 进行写访问,攻击者需要获取该身份的模拟令牌管理员无法直接复制 svchost.exe 进程的令牌,所以先获取 SYSTEM 权限。代码如下:
通过复制 Winlogon.exe 进程中的 SYSTEM 令牌并模拟权限上下文,再遍历 svchost 进程比对对应 Local Service 的 SID 并复制 LOCAL SERVICE 令牌,最后模拟 LOCAL SERVICE 权限上下文。比对 Local Service SID 的代码如下:
我们并不能直接找碎片化的gadget作为原语,因为 KCFG 会验证回调函数的地址是否为合法的内核函数地址,如果不是合法函数地址,执行时就会导致蓝屏。
虽然 KCFG 和 SMEP 极大地限制了攻击者通过漏洞执行任意代码的难度,但实际上并不能阻止我们利用漏洞进行提权。
因为攻击者只需找到一个符合 KCFG 的合法内核写原语作为我们的回调函数,那这些安全机制自然就被绕过了。
在nero22k的这篇文章 里讲过可以使用ExpProfileDelete进行内核写操作,将自己线程里的PreviousMode字段通过ObfDereferenceObject将其递减为0,从而将当前线程的权限提升为内核态。
虽然这是一个符合 KCFG 的函数,但实际上极力不推荐使用此函数,我通过 IDA 将其反汇编如下:
在ExpProfileDelete中,我们发现该函数会检查a1+0x30处是否为0,若为非零值,就会执行该分支。a1是我们传入到内核的缓冲区基址,而我们最多也就传入了0x18或者0x20字节,而a1+0x30处的内存地址不属于我们传入的缓冲区,当对a1+0x30处的内存地址进行解映射操作时,会触发蓝屏。
我们用 WinDbg 给ExpProfileDelete函数下个断点: 在rcx+0x30处,我们发现该地址的值为0,说明该分支不会执行。会继续执行ObfDereferenceObjectWithTag函数。
但当rcx+0x30处的内存地址不为0时,情况就不一样了:
如果我们再往下,就会执行第一个分支里面的代码,从而将rcx+0x30处的内存地址解映射,导致蓝屏。
值得庆幸的是,我找到了另一个相对更安全的内核写原语,那就是ObpDirectoryTeardownCallback函数,它同样也会调用ObfDereferenceObjectWithTag函数,将其递减为0,从而将当前线程的权限提升为内核态。
在找到ObpDirectoryTeardownCallback函数后,我们就可以顺理成章地将(*a2)(userBuffer, v42)的地址指向该函数,从而触发漏洞。
你以为这样就完事了吗?那就大错特错了,你确实成功触发了漏洞,但也因此导致了蓝屏或者永远留在了死循环中。请看以下反 C 代码:
我们需要给((__int64 (__fastcall *)(__int64, __int64 *, _QWORD, PVOID))v39[1])(v40, &v32, cbInput, pbInput)找一个合适的回调函数地址,使其返回的值小于0,从而跳出这个死循环。我觉得ZwQuerySystemInformation是一个很好的的选择,这个函数指针第一个参数传的就是地址值,而ZwQuerySystemInformation的第一个参数是枚举值,远小于地址值,若第一个参数是无效的枚举值,函数便会直接返回失败,也就是小于0的值,然后直接跳出循环。
以我对 Windows 内核的了解,ObfDereferenceObjectWithTag会对目标对象体对应的OBJECT_HEADER结构体中的PointerCount字段也就是在对象体的0x30负偏移处进行递减操作。所以我们在APPID_KERNEL_EXPLOIT第一个字段填写目标PreviousMode字段的地址加上0x30即可将PreviousMode字段递减为0,从而将当前线程的权限提升为内核态。
尽管ObpDirectoryTeardownCallback利用方式相对稳健,但仍存在内存篡改风险——具体表现为破坏自身ETHREAD结构体的特定字段。该影响在初始阶段可能不明显,但为实现可靠的权限提升,建议采用双线程架构:
主线程首先创建跳板线程(线程A) 和 攻击线程(线程B)
线程A 触发漏洞修改自身PreviousMode字段完成内核态提升后,再通过NtWriteVirtualMemory将线程B 的PreviousMode字段置为0。
线程A 随即退出,隔离内存篡改带来的不稳定因素。
调度 线程B 执行后续操作,此时其所有系统调用将被内核判定 为内核模式发起,从而绕过用户态安全检查。 伪代码如下:
本文配套的 CVE-2024-21338 Exploit代码,全程由本人独立逆向分析、自主编写实现,无任何开源项目复刻、无第三方代码搬运;其中双线程稳定利用架构、内核原语优化方案等均为个人原创研究成果,完整源码已开源至个人 GitHub仓库 。
通过利用 CVE-2024-21338 漏洞,我们可以将自身进程的Protection字段修改为任何值,从而实现进程保护:
CVE-2024-21338 漏洞的核心在于 AppLocker 驱动对用户态输入的校验缺失,导致攻击者可通过构造特定 IOCTL 请求实现内核态任意函数调用。尽管存在 SMEP 和 KCFG 等安全机制的限制,攻击者仍可通过寻找合法的内核写原语(如ObpDirectoryTeardownCallback)来实现权限提升。
双线程提权方案通过隔离内存篡改风险,显著提高了利用的稳定性,展示了攻击者如何在现代 Windows 安全机制下实现权限提升。该漏洞的发现和利用也提醒我们,内核驱动的输入校验至关重要,任何微小的疏忽都可能被攻击者利用来获取系统控制权。
更新到最新版本的 Windows 操作系统,微软在最新的appid.sys 中的0x22A018处添加了ExGetPreviousMode函数进行验证,判断当前线程是否为用户模式线程,如果当前线程为用户模式线程,就会直接返回错误,而不是执行,如图所示:
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2天前
被漫雾.编辑
,原因: