Date Wed 11 June 2014 By Jordan Bouyat Category Fuzzing. Tags usb fuzzing facedancer

We recently begun to search bugs in USB host stacks using one of our tool based on the Facedancer. This article first presents our fuzzing approach followed by a practical example of a bug in Windows 8.1 x64 full-updated. The goal of this article is not to redefine state-of-the-art USB fuzzing, nor to give a full description of our fuzzing architecture, but rather to narrate a scenario which starts from fuzzing and ends up with a bug report.

Fuzzing approach Our fuzzing architecture is based on a Facedancer and Umap tool to which we added some features: Traffic capture in PCAP for the emulated device;

Traffic replay from a recorded PCAP;

Packet mutation based on Radamsa .

USB basics The goal of the article is not to describe how USB works in detail, but some knowledge is still required for a better understanding. When a device is connected, the host issues standard requests to the device to retrieve information (vendor id, product id, available features, ...) about it. It does so in order to configure it and to load the appropriate driver(s) into the OS. This information is called descriptors . These requests/descriptors are exchanged on the special endpoint 0: every new standard device connected must respond to requests sent to it. Endpoints are logical links between a device interface and the USB host stack. An interface is composed with one or more endpoints, and offers class functions (HID, mass storage, ...) or specific functions.

A fuzzing instance example We emulated a USB mass storage device and dumped the traffic exchanged. Then, we decided to fuzz the configuration descriptor, and particularly the bNumEndpoints field. The mutation simply consisted in replacing this byte by a random one. After some time, we triggered a BSOD on Windows 8.1 x64. Here, our mutated descriptor is sent to the host in the packet framed in red. After replaying several times the packets sequence with the mutated descriptor, we surmised that the BSOD was triggered just after the host sent the Set Configuration request framed in orange. In Wireshark, the mutated descriptor looks like: The crashdump analysis was pretty much useless because the kernel pool memory was corrupted: every time, it crashed at a different location. We desperatly continued to inject the packet and at some point, Windows BSOD gave us the following problem location: USBSTOR.sys. The driver name is explicit: it is the mass storage driver. So we looked into it.

Reversing the mass storage driver After downloading the symbols of USBSTOR.sys, we loaded it into IDA Pro. Luckily, the symbols are easily comprehensible and we quickly found the interesting function: USBSTOR_SelectConfiguration() The first basic block shows a call to an export of usbd.sys: USBD_CreateConfigurationRequestEx() that returns a pointer to a URB_FUNCTION_SELECT_CONFIGURATION structure. According to the MSDN , this "routine allocates and formats a URB to select a configuration for a USB device". URBs are structures used by client drivers to describe the request they want to send to devices . The second basic block does a call to USBSTOR_SyncSendUsbRequest() and takes as first parameter the URB previously created. When the call to this function is made, the request is sent through the USB stack and then physically from the host controller to the device. If we break on the USBSTOR_SyncSendUsbRequest() call, we observe that it is not this call that crashes the system. If we look into the USBD_CreateConfigurationRequestEx() function, we see that it copies the bNumEndpoints field (that we set to 0 during the fuzzing) from the USB_INTERFACE_DESCRIPTOR structure to the NumberOfPipes field of the USBD_INTERFACE_INFORMATION structure. The USB_INTERFACE_DESCRIPTOR structure was initialized during the enumeration process and will not be studied in this article. Now, we go back to USBSTOR.sys, after the call to USBD_CreateConfigurationRequestEx() . RDI points to: struct _URB_SELECT_CONFIGURATION { struct URB_HEADER Hdr ; PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor ; USBD_CONFIGURATION_HANDLE ConfigurationHandle ; USBD_INTERFACE_INFORMATION Interface ; }; and R14 points to: typedef struct _USBD_INTERFACE_INFORMATION { USHORT Length ; UCHAR InterfaceNumber ; UCHAR AlternateSetting ; UCHAR Class ; UCHAR SubClass ; UCHAR Protocol ; UCHAR Reserved ; USBD_INTERFACE_HANDLE InterfaceHandle ; ULONG NumberOfPipes ; // Our bNumEndpoints = 0 is here ! USBD_PIPE_INFORMATION Pipes [ 1 ]; } USBD_INTERFACE_INFORMATION , * PUSBD_INTERFACE_INFORMATION ; Now, the _USBD_INTERFACE_INFORMATION structure is copied to RCX. Few instructions later, we get back this pointer into RAX. The pseudocode of these instructions is: ECX <- endpoint number If the number of endpoint is zero ECX <- ECX-1 ECX <- 0-1 = 0xffffffff R8 <- (RCX*3*8)+80 R8->(0xffffffff*3*8)+80 memset(@dest, 0x0, R8) memset(@dest, 0x0, 0x1800000038) Here, we have a memset with a size that equals to 0x1800000038 . This has for consequence a non exploitable kernel pool overflow. Windows 8.1 32-bit We saw what happened in 64-bit mode, but not in 32-bit. We will not detail again the instruction flow as it is exactly the same. The following code snippet corresponds to the 32-bit equivalent of the previous code snippet. In pseudocode, the size for the memset() looks like: EAX <- endpoint number If the number of endpoint is zero EAX <- EAX-1 EAX <- 0-1 = 0xffffffff EAX <- (EAX*0x14)+0x38 EAX->(0xffffffff*0x14)+0x38 = 0x24 memset(@dest, 0x0, EAX) memset(@dest, 0x0, 0x24) Here, the size calculation is different because the pointer size in the structures is different. Because EAX is only 32 bits long, the result 0x1400000024 does not fit into it, hence 0x00000024 is stored. The size of an _URB_SELECT_CONFIGURATION in 0x38 bytes, so 20 bytes of the allocated structure are not initialized. It could have been exploitable under specific conditions if the allocated space was not correctly filled with a memcpy() just after.

Bug report to Microsoft The bugs have been reported to Microsoft. They do not consider this local DoS as a security issue since it requires a physical access.

Acknowledgments Thanks to the QuarksLab team for their help on reverse engineering and their reviews!