aleksandr.k, voidsec
文章原文:https://www.crowdfense.com/windows-wi-fi-driver-rce-vulnerability-cve-2024-30078/
在6月的“补丁星期二”期间,微软发布了针对CVE-2024-30078的修复程序。这个漏洞的严重性被标记为“重要”,影响被设置为“远程代码执行 (RCE)”。
阅读了微软的公告后,我们对这个漏洞产生了兴趣。未经身份验证的攻击者似乎可以通过向相邻系统发送恶意数据包来实现远程代码执行。尽管攻击者必须在目标系统附近才能发送和接收无线电传输以利用这个漏洞,但无线RCE听起来太吸引人了,不容忽视。
我们的分析是在Windows 11版本23H2下进行的。
这个漏洞位于nwifi.sys Wi-Fi驱动程序中。我们使用BinDiff比较了这个驱动程序的两个版本:
补丁被应用在函数Dot11Translate80211ToEthernetNdisPacket()
中:
在修复版本中,添加了以下额外的检查:
在函数Dot11Translate80211ToEthernetNdisPacket()
的调用图中,我们可以看到如何从接入点(AP)和站点(STA)模式访问存在漏洞的函数。
请注意:这里,IDA的图表是不准确的,因为ExtSTAReceivePacket()
函数没有被直接调用,只是被引用。
接入点模式(AP):也称为热点模式。在此模式下,Wi-Fi模块充当接入点,类似于路由器的功能。它创建一个无线网络,允许其他设备连接到它。
站点模式(STA):STA模式允许设备连接到现有的无线网络。它将设备变成现有无线网络的客户端,从而能够访问互联网或与其他设备通信。在STA模式下,设备成为网络上的一个节点,便于与其他设备进行数据交换和通信。
我们决定专注于STA路径,因为这对我们的用例来说更有趣。
nwifi.sys驱动程序是一个NDIS筛选器驱动程序。它位于驱动程序堆栈中的协议驱动程序和微型端口驱动程序之间。其预期用途是监视和修改(如果需要)来自或发往Wi-Fi的数据包,并将它们传递给堆栈中的下一个驱动程序。
驱动程序必须向NdisFRegisterFilterDriver()
例程注册回调。最有趣的回调是AttachHandler
和ReceiveNetBufferListsHandler
。此驱动程序中的回调ReceiveNetBufferListsHandler
存在于Pt6Receive()
函数中;它从底层驱动程序接收数据包。
即使出于某种原因,IDA没有在上面的函数图中显示这一点,我们也可以在WinDbg中动态验证它。实际的调用堆栈如下所示:
Pt6Receive()
接收指向由我们的数据包组成的结构(NET_BUFFER_LIST
)的指针。然后它调用ExtSTARecvInitializeMSDUFromNBL()
函数将NBL
结构转换为名为MSDU
的结构。
MSDU
结构类似于以下片段:
struct MSDU_struct { uint64_t info6; char flags; uint32_t llc_offset; uint32_t data_length; uint32_t field_14; uint64_t data_begin; NETWORK_BUFFER_LIST *nbl; _MDL *first_mdl; _MDL *last_mdl; uint32_t offset; uint32_t nbl_data_length; };
随后,该结构通过MSDU
传递给函数Dot11Translate80211ToEthernetNdisPacket()
、ExtSTAReceivePacket()
、ExtSTAReceiveDataPacket()
。
函数名称明确表明它将IEEE 802.11数据包转换为以太网数据包。该函数将MSDU
结构作为其第二个参数。此结构有一个指向_MDL
传入的第一个结构的指针NET_BUFFER_LIST
,其中包含要转换的数据包。转换是通过在下一层之前格式化以太网报头来完成的。转换发生在放置原始数据包的同一内存区域内。
经过一些逆向工程,补丁变得更加容易理解。函数Dot11Translate80211ToEthernetNdisPacket()
读取LLC数据包的标头,以了解数据包中的下一层。在读取LLC标头之前,它会检查数据包的缓冲区是否有足够的数据。LLC标头大小为8个字节:
代码中的检查如下:
如果下一层是VLAN,则应该有额外的4个字节的IEEE 802.1Q标头,并且这4个字节也必须存在于数据包中:
在存在漏洞的版本中,没有检查IEEE 802.1Q标头的这4个字节是否存在。然而,在修补版本中已添加该检查,如下图所示:
在存在漏洞的版本中,如果LLC->Type == 0x8100
且LLC的8个字节之后没有数据,则会发生越界读取(案例#4)。覆盖发生在构建以太网头的代码部分:
变量v17
是必须构建以太网报头的偏移量。以太网报头(v17
)的偏移量是根据LCC
和IEEE 802.1Q标头中的值计算的。以太网报头可以放置在四种不同的情况下:
情况1:LCC->type != 0x81
&& LCC->type < 0x600
在这种情况下,v17
将等于LCC offset - 0xE
,并且以太网报头将在LCC
标头之前构建:
情况2:LCC->type != 0x81
&& LCC->type >= 0x600
在这种情况下,v17
等于LCC offset - 0xE + 8
,并且以太网报头将重写LCC
标头:
情况3:LCC->type == 0x81
&& vlanid < 0x600
在这种情况下,v17 = LCC offset - 0xE + 4
,以太网报头将重写一半的LCC
标头:
情况4:LCC->type == 0x81
&& vlanid >= 0x600
这就是我们所需要的!在这种情况下,v17 = LCC offset - 0xE + 4 + 8
以太网报头的其余部分将被写入外部,在以下描述的内存中_MDL
:
不幸的是,放置数据包的缓冲区有额外的限制,我们无法控制。
tpid
检查IEEE 802.1Q标头的字段,该vlanid
字段必须正确。函数Dot11Translate80211ToEthernetNdisPacket()
检查IEEE 802.1Q标头,我们无法控制此字段。此外,无法保证这些字节有用。
为了确认我们的分析,我们动态检查了所有内容,并通过Wi-Fi向存在漏洞的机器发送数据包。
正如微软公告所指出的,攻击者不需要在目标系统上进行身份验证。但是,为了传送数据包,我们必须与目标位于同一个Wi-Fi网络上;否则,底层驱动程序将阻止数据包。
关于如何实施攻击,有多种选择:
由于我们首先需要能够通过Wi-Fi发送原始数据包,因此为了进行测试,我们使用Python和scary库构建了一个假AP。它的速度足够快,可用于开发和原型设计,使我们能够通过Wi-Fi发送原始数据包。
我们的设置包括两台虚拟机:一台Windows 11 23H2和一台Kali Linux机器。两台虚拟机均已连接AC1200 Wi-Fi 5 USB适配器。
我们的AP必须做以下最少的事情:
关联后,站点使用DHCP、ARP和其他协议请求网络信息。这些数据包以数据包的形式发送。我们的有效载荷也必须以数据包的形式发送,才能达到我们的目标功能:
可以使用以下代码片段生成有效载荷(数据)数据包:
def send_payload_v1(self, destination, with_vlan): radiotap = RadioTap() dot11 = Dot11(type=2, subtype=0, addr1=destination, addr2=self.ap.mac, addr3=self.ap.mac, SC=self.ap.next_sc(), FCfield='from-DS') llc = LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03) / SNAP(OUI=0x000000, code=0x8100) packet = radiotap / dot11 / llc # 如果需要,这里可以添加任何额外的字节 sendp(packet, iface=self.ap.interface, verbose=False)
现在我们已经做好了一切准备,可以动态尝试攻击了,我们必须在易受攻击的函数上设置断点(Dot11Translate80211ToEthernetNdisPacket()
),启动假AP并连接到它。STA成功通过所有连接步骤后,断点被触发:
在上图中我们可以看到我们的数据包;它的长度为0x20字节。IEEE 802.11标头位于偏移处0x00,而LLC标头位于偏移处0x18。
我们还可以看到LCC->type
,0x81,所以我们应该通过检查IEEE 802.1Q头的代码,也就是tpid
field。即便如此,tpid
值也没有通过检查,所以这个数据包不会被转换。
tpid
要转换的数据包,的低12位必须为0。
在此代码中,覆盖已经发生(上图中的第一条指令)。
我们可以用适当的值和正确的大小喷射数据包,然后发送触发漏洞的数据包。这听起来不错,但在调试器中很难捕获真正的攻击数据包。因此,我们模拟了在攻击数据包之后正确设置字节的情况。
从下图可以看出,这使我们能够绕过检查tpid
。这也有助于在检查时找到正确的路径,vlanid
以将以太网偏移量增加到8:
下图显示了易受越界写入攻击的代码。在这里,你可以看到寄存器rcx
被设置为必须构建以太网头的指针。最后三条指令构建以太网头。理论上,溢出将发生在最后两条指令处:
在漏洞触发之前,内存布局如下所示:
这里我们可以看到以太网头将构建在哪个地址。红色下划线表示溢出将覆盖的字节。
执行这三条指令后,内存布局如下所示: 这里我们可以看到前两个字节已经被源MAC地址的结尾覆盖,而源MAC地址处于攻击者的控制之下。后面两个字节保持不变,因为寄存器
si
的值相同,并且是从同一个位置读取的。
首先,要获得覆盖状态,我们需要非常幸运,并且在数据包缓冲区后面有适当的字节,这些字节将通过tpid
并vlanid
进行检查。我们无法控制数据包内的这些字节,这会导致覆盖。我们可能可以在发送触发漏洞的数据包之前使用喷射技术,但还有其他限制:
为了利用此漏洞,我们需要某种形式的信息泄露漏洞,而目前还缺乏这种漏洞。
被覆盖的内存也很重要。它应该是一些有用的东西,比如一些指针。但这个缓冲区不包含任何有用的东西。如果我们用命令检查这个缓冲区!address
,它会给出以下结果:
此内存区域看起来很奇怪(例如区域大小)。使用RamMap检查此内存区域会显示内存的标签:
而如果我们尝试用命令检查这个内存!poolused 4
,它会显示标签是未知的。
剩下唯一要做的事情就是了解这个内存区域是什么,并在其分配上设置一个断点。
这让我们了解了内存是如何分配的;当将适配器连接到系统时分配内存,断开连接时释放内存。分配的缓冲区大小为0x7800。
经过分析,此漏洞的影响似乎远没有微软预期的那么严重。我们只能覆盖紧跟在我们数据包后面的另一个数据包(字节数有限),但这种情况不太可能发生,而且毫无用处,因为那里没有有趣的数据可以用来控制执行流程。无论如何,我们很希望被证明是错的,因为这似乎是一个非常酷的利用途径。
11 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!