Analyzing a Trio of Remote Code Execution Bugs in Intel Wireless Adapters

May 05, 2020 | Vincent Lee

Earlier this month, we published three memory corruption bugs (ZDI-20-494, ZDI-20-495, and ZDI-20-496 - collectively referred to as CVE-2020-0558) affecting two Windows Wi-Fi drivers for various Intel dual-band wireless adapters. According to the vendor, these drivers are intended for the AC 7265 Rev D, AC 3168, AC 8265, and AC8260 wireless adapters. Both ZDI-20-494 and ZDI-20-496 are out-of-bounds write vulnerabilities that share a common root cause and reside in both drivers, namely Netwtw04.sys and Netwtw06.sys. ZDI-20-495 is a stack buffer overflow vulnerability that affects the Netwtw06.sys driver only. These bugs were discovered by Haikuo Xie and Ying Wang of Baidu Security Lab and were originally reported to the ZDI at the end of November 2019.

You may find a copy of the PoCs for all three vulnerabilities at our GitHub page and poke at them yourself. The PoCs have been tested against the AC 3168 and AC 8260 adapters using the Qualcomm Atheros AR9271-based USB network adapter.

All three vulnerabilities require the victim to enable the “Mobile Hotspot” feature. An attacker could exploit these vulnerabilities by simply connecting to the wireless network with malicious 802.11 MAC frames without knowing the password for the mobile hotspot.

ZDI-20-495

This stack-buffer overflow vulnerability exists in the Netwtw06.sys driver. The PoC sends four 802.11 MAC frames to the victim mobile hotspot, triggering a BSOD on the victim machine. The cause of the vulnerability is straightforward. An overly long SSID is passed to a vulnerable function through the Tag-length-value encoded information element tagged-parameter located within an 802.11 association request frame body. Below is the dissection of the malicious frame, as generated with the Scapy utility:

Figure 1 - Packet dissection of the malicious 802.11 Frame

In the above diagram, we can see the information element with the ID 0x00, which corresponds to the SSID, has a length of 55 bytes. The long SSID string follows.

The vulnerable function, prvhPanClientSaveAssocResp(), copies the SSID into a fixed-length stack buffer through memcpy_s() with an incorrect DstSize parameter. Instead of the destination buffer size, it supplies the attacker-provided SSID length. Below is a disassembly code snippet of the prvhPanClientSaveAssocResp() function taken from version 20.70.13.2 of the driver.

Figure 2 - Disassembly of the vulnerable function prvhPanClientSaveAssocResp()

At 0x1403A7F5C, r8 points to the start of the SSID information element. At 0x1403A7F66, the attacker provided SSID length (55) is passed to DstSize, and this value is later passed into MaxCount as well. Passing the SSID length in this manner defeats the security of memcpy_s() and is the essence of this vulnerability. If we look further up the disassembly, we can see the stack buffer, var_4C, is 36 bytes long only:

Figure 3 - Stack buffer var_4C

When memcpy_s() proceeds to copy the attacker-controlled buffer into the undersized stack buffer variable, a buffer overflow condition occurs.

ZDI-20-494 and ZDI-20-496

Since these two vulnerabilities share the same root cause, we will discuss ZDI-20-494 only. An out-of-bounds write vulnerability exists within the processing of association request frames. In order to reach the vulnerable code path, the attacker must first send an authentication request[1] before sending the malicious association request. The attacker sends an association request frame containing an information element with the ID of 59 (0x3B), which corresponds to Supported Operational Classes. The value of the information element consists of 221 null bytes. A frame dissection of the request follows:

Figure 4 - Packet dissection of the malicious association request sent by the PoC

The driver calls two functions to handle the information element: prvPanCnctProcessAssocSupportedChannelList() and utilRegulatoryClassToChannelList(). In the handling of the malicious request, prvPanCnctProcessAssocSupportedChannelList() attempts to call the function utilRegulatoryClassToChannelList() 221 times, corresponding to the information element length. Below is a disassembly code snippet of the prvPanCnctProcessAssocSupportedChannelList() function taken from version 19.51.23.1 of the Netwtw04.sys driver:

Figure 5 - Disassembly snippet of prvPanCnctProcessAssocSupportedChannelList ()

At 0x140388500, the ebx loop index is initialized to 0. The loop exit condition at 0x1403885AF compares the loop index ebx with the information element length stored in the eax register from four instructions prior[2]. The utilRegulatoryClassToChannelList() function is called within the loop body at 0x140388559. The third argument to the function is a memory buffer address passed through the r8 register, which is the address of the buffer affected by this out-of-bounds write vulnerability. Also note that at 0x14088502, the first DWORD of the buffer is initialized to zero.

The utilRegulatoryClassToChannelList() function reads the first DWORD of the buffer from the vulnerable buffer as an index and uses it as an offset to write 0xFF bytes of data to itself. This occurs every time the function is called. Due to a lack of bounds checking, it is possible for the index to point to memory regions beyond the end of the buffer when this function is called repeatedly.

Figure 6 - Disassembly of utilRegulatoryClassToChannelList()

At 0x1400D06A8, the vulnerable buffer from the third argument is transferred to the rbx register. At 0x140D068F, the loop index edi is initialized to 0 prior to entering the loop body. This will iterate for 0xFF times. In the basic block starting from 0x140D0718, the first DWORD from the buffer is read and stored in the eax register. This value is immediately used as an offset to the vulnerable buffer and a byte is written to it. At 0x1004D0729, the first DWORD of the vulnerable buffer is incremented. An out-of-bounds write condition occurs when the utilRegulatoryClassToChannelList() function is called more than two times.

Conclusion

Although the triggering conditions for these bugs are quite rare, it is still very interesting to see bugs in the data link layer to come through our program. While there have been a few talks on fuzzing the information element, we are not seeing many analyses and discoveries of Wi-Fi-based bugs. The IEEE 802.11 family of wireless technology standards offer a vast attack surface, and the vulnerability researcher community has barely begun to scrutinize the protocol. A good driver bug from this attack vector gives direct access to the kernel. When compared to web browser-based attacks, which require multiple bugs and sandbox escapes, the Wi-Fi attack vector could be an interesting alternate vector for attackers to consider. For those interested in learning more about the IEEE 802.11 family of standards, 802.11 Wireless Networks: The Definitive Guide written by Matthew S. Gast is a great resource to start your learning.

You can find me on Twitter @TrendyTofu, and follow the team for the latest in exploit techniques and security patches.

Footnotes

[1] An authentication request is not to be confused with the Robust Security Network, also known as RSN or WPA2, standard defined in IEEE 802.11i-2004. An authentication request is a low-level “authentication” that provides no meaningful network security. It is better to think of this as a station identifying itself to the network.

[2] Since the index starts from zero, eax is decremented once prior to comparison to avoid an off-by-one mistake. This looks like a for-loop was converted into a do-while loop at compile time. Such conversions are often done to reduce the number of jump instructions and to reduce the loop overhead. For those interested in learning more about loop optimization, consider reading section 12 of Optimizing subroutines in assembly language: An optimization guide for x86 platforms by Agner Fog.