对Windows最新严重内核驱动win32kfull.sys漏洞的分析

  • A+
所属分类:逆向工程

对Windows最新严重内核驱动win32kfull.sys漏洞的分析

在2019年11月发布的软件更新中,对Windows内核驱动程序win32kfull.sys的微小代码更改引入了一个重大漏洞。代码更改本来是无害的。从表面上看,更改只是插入了一个断言类型的函数调用,以防止参数中的某些无效数据。在本文中,我将剖析相关的函数,然后看看是哪里出了问题。该漏洞由anch0vy @ theori和kkokkokye @ theori报告给了ZDI,并于2020年2月被Microsoft修复,漏洞编号为CVE-2020-0792

对Windows最新严重内核驱动win32kfull.sys漏洞的分析
0x01 函数分析

在检查导致漏洞的代码更改之前,我将首先讨论相关函数的操作,该函数本身将具有指导意义。

漏洞函数是win32kfull.sys!NtUserResolveDesktopForWOW。前缀Nt表示此函数是有时称为“ Windows Native API”的成员,这意味着它是内核函数,可通过syscall指令从用户模式调用。就我的目的而言,无需了解NtUserResolveDesktopForWOWAPI 的确切目的。但是,我必须知道的是NtUserResolveDesktopForWOW从用户模式调用的,并且实际的实现位于名为的较低级别的函数win32kfull!xxxResolveDesktopForWOW中。该函数NtUserResolveDesktopForWOW本身仅执行很少的操作,它的主要任务是在用户模式和内核模式之间安全地交换参数和结果数据。

该函数的签名如下:

NTSTATUS NtUserResolveDesktopForWOW(_UNICODE_STRING *pStr)

类型的单个参数是输入_UNICODE_STRING*输出参数。调用者将指针传递给_UNICODE_STRING用户存储器中的结构,该结构最初填充有用作函数输入数据的数据。返回之前,NtUserResolveDesktopForWOW用_UNICODE_STRING表示结果的新字符串数据覆盖此用户模式结构。

_UNICODE_STRING的结构定义如下:

对Windows最新严重内核驱动win32kfull.sys漏洞的分析

MaximumLength指示分配的Buffer字节大小,而Length指示缓冲区中存在的实际字符串数据的字节大小(不包括空终止符)。

如上所述,NtUserResolveDesktopForWOW的主要目的是在调用时安全地交换xxxResolveDesktopForWOW数据。该NtUserResolveDesktopForWOW函数执行以下步骤,所有这些步骤对于安全性至关重要:

1:_UNICODE_STRING*从用户模式接受type参数,并验证它是指向用户模式地址(而不是内核模式地址)的指针。如果它指向内核模式地址,则抛出异常。

2:它将的所有字段复制_UNICODE_STRING到无法从用户模式访问的局部变量。

3:从这些局部变量读取,它可以验证_UNICODE_STRING的完整性。具体来说,它可以验证该Length值不大于MaximumLength,并且该值Buffer完全存在于用户模式内存中。如果这些测试中的任何一个失败,它将引发异常。

4:同样,使用局部变量中的值,它创建一个_UNICODE_STRING完全驻留在内核模式内存中的新变量,并指向原始缓冲区的新内核模式副本,我将这个新结构命名为kernelModeString。

5:传递kernelModeString给基础函数xxxResolveDesktopForWOW。成功完成后,xxxResolveDesktopForWOW将其结果放入kernelModeString。

6:最后,如果xxxResolveDesktopForWOW成功完成,它将字符串结果复制xxxResolveDesktopForWOW到新的用户模式缓冲区中,并覆盖原始_UNICODE_STRING结构以指向新缓冲区。

为什么需要这种复杂的操作?首先,它必须防范的危险是用户模式进程可能通过Buffer字段或作为pStr参数本身传递指向内核内存的指针。无论哪种情况,xxxResolveDesktopForWOW都会对从内核内存读取的数据起作用。在那种情况下,通过观察结果,用户模式代码可以收集有关在指定内核模式地址处存在的内容的线索。这将是从高特权内核模式到低特权用户模式的信息泄漏。此外,如果pStr本身是内核模式地址,则将的结果xxxResolveDesktopForWOW写回到所指向的内存时,可能会损坏内核内存pStr。

为了适当地防止这种情况,仅插入指令来验证_UNICODE_STRING用户模式是不够的。请考虑以下情形:

-用户模式根据需要传递_UNICODE_STRING指向用户模式缓冲区的指针。-内核代码验证Buffer指向用户内存的内容,并得出结论认为这样做是安全的。-目前,在另一个线程上运行的用户模式代码会修改该Buffer字段,使其现在指向内核内存。-当内核模式代码在原始线程上继续执行时,下次读取该Buffer字段时,它将使用不安全的值。

这是一种“Time-Of-Check Time-Of-Use ”(TOCTOU)漏洞,在这种情况下,两个以不同特权级别运行的代码段访问一个共享的内存区域,这被称为“double fetch”。这是指在上述情况下内核代码执行的两次访存。第一次提取将检索有效数据,但是到第二次提取发生时,数据已中毒。

https://cwe.mitre.org/data/definitions/367.html

double fetch 漏洞的补救措施是确保内核从用户模式收集的所有数据都只被获取一次,并复制到内核模式状态中,而该状态不能被用户模式篡改。这就是的操作中第2步和第4步将NtUserResolveDesktopForWOW复制_UNICODE_STRING到内核空间的原因。请注意,Buffer指针的验证将推迟到步骤2完成之后进行,以便仅在将数据复制到防篡改存储中之后才能对数据进行验证。

NtUserResolveDesktopForWOW甚至将字符串缓冲区本身复制到内核内存,这是消除与可能的double fetch 相关的所有可能问题的唯一真正安全的方法。分配内核模式缓冲区以容纳字符串数据时,它分配的缓冲区大小与用户模式缓冲区的大小相同,如所示MaximumLength。然后,它将复制字符串的实际字节。为了使此操作安全,需要确保该Length值不大于MaximumLength。此验证也包含在上面的步骤3中。

鉴于上述所有情况,该函数的签名是:

NTSTATUS NtUserResolveDesktopForWOW(volatile _UNICODE_STRING *pStr)

该volatile关键字警告编译器外部代码可以修改_UNICODE_STRING结构。没有volatile,C / C ++编译器本身可能会引入源代码中不存在的double fetch 。

对Windows最新严重内核驱动win32kfull.sys漏洞的分析
0x02 漏洞分析

对Windows最新严重内核驱动win32kfull.sys漏洞的分析

MmUserProbeAddress是一个全局变量,它包含一个将用户空间与内核空间区分开来的地址。与该值的比较用于确定地址是指向用户空间还是内核空间。

该代码*(_BYTE *)MmUserProbeAddress = 0用于引发异常,因为此地址永远不可写。

上面显示的代码可以正常运行。但是,在2019年11月的更新中,进行了微小的更改:

对Windows最新严重内核驱动win32kfull.sys漏洞的分析

请注意,length_ecx只是我为将Length字段复制到的局部变量指定的名称。该局部变量的存储恰好是ecx寄存器,因此也就是名称。

如你所见,该代码现在在其他检查之前进行了另一项验证检查:确保该代码length_ecx & 1为0,也就是说,确保指定Length的数字为偶数。如果为Length奇数,将是无效的。这是因为Length指定了该字符串占用的字节数,由于该字符串中的每个Unicode字符均由2字节序列表示,因此该字节数应始终为偶数。因此,在进行其余检查之前,它确保该检查Length是偶数,并且如果此检查失败,则正常处理将停止,并转而使用断言。

这就是问题所在。事实证明,该函数MicrosoftTelemetryAssertTriggeredNoArgsKM根本不是断言!与将引发异常的断言相反,断言MicrosoftTelemetryAssertTriggeredNoArgsKM仅生成一些数据以发送回Microsoft,然后返回给调用方。不幸的是,函数名称中出现了“ Assert”一词,实际上,该函数名称似乎欺骗了Microsoft的内核开发人员,并在check on上进行了添加length_ecx。开发人员给人的印象是,调用MicrosoftTelemetryAssertTriggeredNoArgsKM将终止当前函数的执行,以便可以将其余检查放到一个else子句中。实际上,如果Length为奇数会发生以下情况:MicrosoftTelemetryAssertTriggeredNoArgsKM调用,然后控制返回到当前函数。其余检查被跳过,因为它们在else子句中。这意味着,通过为指定一个奇数值Length,我可以跳过所有剩余的验证。

这有多严重?事实证明,这非常糟糕。回想一下,为了确保最大的安全性,NtUserResolveDesktopForWOW请将字符串数据本身复制到内核缓冲区中。它将内核缓冲区分配为与原始用户缓冲区相同的大小MaximumLength。然后根据中指定的数字复制字符串的字节Length。因此,为避免缓冲区溢出,有必要添加一个验证以确保该Length值不大于MaximumLength。如果我可以跳过该验证,则会在内核内存中直接看到缓冲区溢出。

因此,在这种具有讽刺意味的情况下,安全检查的一种稍微有缺陷的组合所产生的结果可能比代码最初需要防范的结果更加可怕。只需通过为Length字段指定一个奇数值,攻击者就可以在内核池分配结束之后写入任意字节序列。

如果你想自己尝试,那么PoC代码只需以下内容即可:

对Windows最新严重内核驱动win32kfull.sys漏洞的分析

这将分配大小为2的内核池缓冲区,并尝试0xffff从用户内存中将字节复制到该缓冲区中。可能要在启用“特殊池”的情况下运行此程序,win32kfull.sys以确保可预测的崩溃。

对Windows最新严重内核驱动win32kfull.sys漏洞的分析
0x03  分析总结

Microsoft在2020年2月迅速修补了此漏洞。修补程序是现在代码在调用之后显式引发MicrosoftTelemetryAssertTriggeredNoArgsKM异常。这是通过写入来完*MmUserProbeAddress成的。即使Microsoft将此列为对“ Windows图形组件”的更改,但仍引用了win32kfull.sys内核驱动程序,该内核驱动程序在渲染图形中起着关键作用。

参考及来源:https://www.zerodayinitiative.com/blog/2020/5/7/how-a-deceptive-assert-caused-a-critical-windows-kernel-vulnerability

对Windows最新严重内核驱动win32kfull.sys漏洞的分析

对Windows最新严重内核驱动win32kfull.sys漏洞的分析


发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: