在上个月微软的安全补丁中,包含一个在野利用的 win32k 提权漏洞。该漏洞似乎不能在 win11 的系统版本上触发,仅存在于早期系统。
这类漏洞的利用来源已久,此次,我们希望分析这类在当前新缓解措施不断改善的背景下,攻击组织是可能如何继续利用这个漏洞。
我们在 server2016 下完成整个分析过程。
0day 漏洞,即零日漏洞,指未被披露和修补的漏洞,时间概念上类比于 Web3 更为人熟知的概念,是 T+0 交易。0day 漏洞被发现后可以在未被察觉的情况恶意利用,这类攻击往往具备极大的破坏性。
Numen Cyber 本次发现的 0day 漏洞是微软 Windows 系统层面的漏洞,通过该漏洞,黑客可获取 Windows 的完全控制权。
被黑客控制所有权的后果,包括不限于个人信息窃取、系统崩溃数据丢失、财务损失、恶意软件植入等,小范围说,你的私钥可以被窃取,以及数字资产被转移。大范围说,这个漏洞能掀掉基于 Web2 基础设施运行的 Web3 牌局。
分析补丁,我们似乎并不能太直观看出是什么问题。这里仅仅似乎是一个对象的引用计数被多处理了一次:
但因为win32k是比较古老的代码,我们能找到一些早期的源码注释:
这样就非常好理解了,这里说明以前的代码只是锁定了窗口对象,没有锁定窗口对象中的菜单对象,这里菜单对象可能被错误引用。
https://github.com/numencyber/Vulnerability_PoC/blob/main/CVE-2023-29336/poc.cpp
如何错误的引用这个窗口中的菜单对象呢?
分析漏洞函数上下文,我们发现一个问题,传入到 xxxEnableMenuItem()的菜单,通常已经在上一层函数被锁定,那这里到底是要保护哪一个菜单对象呢?
继续分析 xxxEnableMenuItem 中,对菜单对象的可能处理过程。我们终于发现 xxxEnableMenuItem 中的 MenuItemState 函数返回的菜单有两种可能,一种就是窗口中的主菜单,但也有可能是菜单中的子菜单,甚至子子菜单。
poc 中,我们构造一个特殊的菜单(这里是三层,四个菜单):
上面相邻的菜单都是父子关系,如 菜单D 是 菜单C 的子菜单。
并且这些菜单有以下一些特点(这些特征都是为了通过 xxxEnableMenuItem 函数中的检测和判断,因为这和该漏洞产生原理有关):
在 xxxRedrawTitle 返回用户层的时候,删除 菜单C 和 菜单B 的引用关系,然后成功释放该 菜单C。
最后,回到内核中的 xxxEnableMenuItem 函数的 xxxRedrawTitle 函数返回点时,后面即将引用的 菜单C 对象已经无效。
A. 整体思路
在确定使用哪种利用思路之前,我们通常希望做一些理论上的前期判断,以避免一些不能绕过关键问题的方案会浪费大量尝试时间。这也是在分析其他漏洞 poc 或者 exp 的一个常规过程。
本次漏洞 exp 构造前,我们主要有以下两种考量方向:
执行 shellcode 代码:
这个思路参考早期的 CVE-2017-0263 和 CVE-2016-0167。这种方式我们并没有尝试,因为在该漏洞的这个方案下,执行 shellcode 的入口点以及一些例如 SMEP 的安全机制问题在高版本 windows 中,可能并没有一些方便且已经公开的解决方式。
利用读写原语修改 token 地址:
即使在最近的两年,依然已经有公开的 exp 可以参考。其中对于桌面堆内存布局以及桌面堆中的读写原语具有长久的通用性。我们现在唯一需要花更多时间完成的只是分析出 UAF 内存被重用时,如何第一次控制 cbwndextra 为一个特别大的值。
所以,这里我们将整个 exp 利用拆分为两个问题。一个是如何利用 UAF 漏洞控制 cbwndextra 的值,另一个则是控制 cbwndextra 值后,稳定的读写原语方式。
B. 如何写入第一次数据
当我们最开始触发漏洞时,系统并不总会 crash。因为我们的漏洞触发方式里,我们已经去掉了被重用漏洞在系统中所有的其他关联。
系统可能错误使用这个被我们控制内存的窗口对象数据基本只有 xxxEnableMenuItem 函数中的 MNGetPopupFromMenu() 和xxxMNUpdateShownMenu()。
我们使用窗口类 WNDClass 中的窗口名称对象来占用漏洞触发中我们释放的菜单对象。
我们能够实现第一次的数据写入时机也在这其中。
我们需要做的只有一件事,就是找到一个可以由我们构建的地址结构中,能够被任意写入数据的地方。哪怕仅仅一个字节(我们可以将这个字节写入到 cbwndextra 的高位)。
此过程如在迷宫中寻找一条出路,不再赘述。
最终我们有 xxxRedrawWindow 函数中的两个预备的方案。
如果使用 GreCreateRectRgnIndirect,交换相邻内存数据的方式有两个困难。
一是 cbwndextra 的前一个8位是一个非常不易控制的参数,并且似乎只能在有限的条件下短暂为1.另外使用这种方式时,cbwndextra 的其他相对偏移会定位到前一个对象(无论任何对象,因为这里只和窗口对象 cbwndextra 偏移本身大小有关)的最后8位,这是一个堆链表尾的安全字节,其不易受控。
所以我们使用了第二个地址写入点,即依靠一个标志位的 AND 2 操作。但同样,由于上述的堆链表尾的安全字节不易受控的原因,我们变换以下思路:
我们并不写入窗口对象的 cb-extra,而是写入 HWNDClass 的 cb-extra。这是因为后者的 cb-extra 偏移相对于前者的 cb-extra 偏移更小,我们可以通过布局内存,控制前一个对象的内存数据来当作通过 xxxRedrawWindow 函数中,对对象标志判断的参数。
C. 稳定的内存布局
我们设计内存至少是连续三个 0x250 字节的 HWND 对象。释放掉中间那一个,构造一个 0x250 字节的 HWNDClass 对象去占用释放掉的 HWND 对象。
前一个 HWND 对象其尾部数据作为通过 xxxRedrawWindow 中标志检验的参数。后一个 HWND 对象其菜单对象和其 HWNDClass 对象作为最终读写原语的媒介。
我们尽量控制窗口对象和我们的的 HWNDClass 对象尽量大小一致,窗口对象的扩展数据大小也要足够大,以通过前面提到的修改第一个窗口 class 对象的额外数据大小参数。
我们通过堆内存中的泄露的内核句柄地址来精确判断(计算按顺序排列的相邻对象的间距)我们申请的窗口对象是否按照我们预期的顺序排列。
D. 读写原语的一些修改
任意读原语我们仍然使用 GetMenuBarInfo();
任意写原语我们则使用 SetClassLongPtr();
除了替换 TOKEN 的写入操作是依赖第二个窗口的 class 对象,其他写入都是利用第一个窗口对象的 class 对象使用偏移来写入。
EXP 链接
https://github.com/numencyber/Vulnerability_PoC/tree/main/CVE-2023-29336
A. win32k 现状
win32k 漏洞历史众所周知。但在最新的 windows11 预览版中,微软已经在尝试使用 Rust 重构该部分内核代码。未来该类型的漏洞在新系统可能被杜绝。
B. 漏洞利用过程基本不太困难
唯有如何使用释放内存重新占用的数据去控制第一次写入的方法需要比较细心的尝试外,基本不要需要使用到任何新的利用技术,该类漏洞严重依赖桌面堆句柄地址的泄露。
虽然其较以往有改动,但如果不彻底解决这个问题,对于老旧系统始终是一个不安全的隐患。
C. 漏洞的发现
分析该漏洞,我们擅自猜测该漏洞的发现可能依赖于更加完善的代码覆盖率检测。一旦系统 API 在目标函数的执行路径能够到达最深处的漏洞点,并且目前窗口对象本身也是一个多重嵌套引用状态,这个漏洞就可能被 fuzz 发现。
D. 其他发现途径
对于漏洞利用检测来说,除了对于漏洞触发函数的关键点的检测,对于这类不常见的对内存布局以及对窗口或者窗口类额外数据的异常偏移读写的针对检测将是发现此类型同种漏洞的可能途径之一
相关教程
2023-11-18
2023-08-15
2023-12-06
2023-08-12
2023-06-23
2024-05-05
2023-08-13
2023-08-18
2024-11-17
2024-11-16
2024-11-16
2024-11-15
2024-11-15
2024-11-15