After spending time auditing open source kernels and finding some interesting bugs, I wondered whether I could find a bug in a Microsoft Windows driver. There are lots of third-party drivers available for Windows, so choosing just a few to explore wasn’t easy. I finally chose some antivirus products, since they’re usually promising targets for bug hunting.[57] I visited VirusTotal[58] and chose the first antivirus product that I recognized on its list: avast! from ALWIL Software.[59] That turned out to be a serendipitous decision.
On June 1, 2010, ALWIL Software was renamed AVAST Software.
I used the following steps to find the vulnerability:
The vulnerability described in this chapter affects all Microsoft Windows platforms supported by avast! Professional 4.7. The platform that I used throughout this chapter was the default installation of Windows XP SP3 32-bit.
First, I set up a Windows XP VMware[60] guest system that I configured for remote kernel debugging with WinDbg.[61] The necessary steps are described in Section B.3.
After downloading and installing the latest version of avast! Professional[62] in the VMware guest system, I used DriverView[63] to generate a list of the drivers that avast! loaded.
One of the benefits of DriverView is that it makes identification of third-party drivers easy. As illustrated in Figure 6-1, avast! loaded four drivers. I chose the first one on the list, called Aavmker4.sys, and used IDA Pro[64] to generate a list of the device objects of that driver.
A driver can create device objects to represent devices, or an interface to the driver, at any time by calling IoCreateDevice
or IoCreateDeviceSecure
.[65]
After IDA disassembled the driver, I started reading the assembly of the driver’s initialization routine, called DriverEntry()
.[66]
[..] .text:000105D2 ; const WCHAR aDeviceAavmker4.text:000105D2 aDeviceAavmker4: ; DATA XREF: DriverEntry+12
.text:000105D2 unicode 0, <\Device\AavmKer4>,0
[..] .text:00010620 ; NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, → PUNICODE_STRING RegistryPath) .text:00010620 public DriverEntry .text:00010620 DriverEntry proc near .text:00010620 .text:00010620 SymbolicLinkName= UNICODE_STRING ptr −14h .text:00010620 DestinationString= UNICODE_STRING ptr −0Ch .text:00010620 DeviceObject = dword ptr −4 .text:00010620 DriverObject = dword ptr 8 .text:00010620 RegistryPath = dword ptr 0Ch .text:00010620 .text:00010620 push ebp .text:00010621 mov ebp, esp .text:00010623 sub esp, 14h .text:00010626 push ebx .text:00010627 push esi .text:00010628 mov esi, ds:RtlInitUnicodeString .text:0001062E push edi .text:0001062F lea eax, [ebp+DestinationString].text:00010632 push offset aDeviceAavmker4 ; SourceString
.text:00010637 push eax ; DestinationString .text:00010638 call esi ; RtlInitUnicodeString .text:0001063A mov edi, [ebp+DriverObject] .text:0001063D lea eax, [ebp+DeviceObject] .text:00010640 xor ebx, ebx .text:00010642 push eax ; DeviceObject .text:00010643 push ebx ; Exclusive .text:00010644 push ebx ; DeviceCharacteristics .text:00010645 lea eax, [ebp+DestinationString] .text:00010648 push 22h ; DeviceType .text:0001064A push eax ; DeviceName .text:0001064B push ebx ; DeviceExtensionSize .text:0001064C push edi ; DriverObject.text:0001064D call ds:IoCreateDevice
.text:00010653 cmp eax, ebx .text:00010655 jl loc_1075E [..]
In the DriverEntry()
function, a device called \Device\AavmKer4
(see .text:00010632
and .text:000105D2
) is created using the IoCreateDevice()
function at address .text:0001064D
. The illustrated assembly snippet of DriverEntry()
can be translated into the following C code:
[..] RtlInitUnicodeString (&DestinationString, &L"\\Device\\AavmKer4"); retval = IoCreateDevice (DriverObject, 0, &DestinationString, 0x22, 0, 0, &DeviceObject); [..]
I then checked the security settings of the AavmKer4
device using WinObj (see Figure 6-2).[67]
To view the security settings of the device in WinObj, I right-clicked the device name, chose Properties from the option list, and then chose the Security tab. The device object allows every system user (Everyone group) to read from or to write to the device (see Figure 6-3). This means that every user of the system is allowed to send data to the IOCTLs implemented by the driver, which is great—this makes this driver a valuable target!
A Windows user space application must call DeviceIoControl()
in order to send an IOCTL request to a kernel driver. Such calls to DeviceIoControl()
cause the I/O manager of Windows to create an IRP_MJ_DEVICE_CONTROL
request, which is sent to the topmost driver. The driver implements a special dispatch routine to handle IRP_MJ_DEVICE_CONTROL
requests, and that dispatch routine is referenced through an array called MajorFunction[]
. This array is an element of the DRIVER_OBJECT
data structure, which can be found in ntddk.h of the Windows Driver Kit.[68] To save space, I removed the comments from the following code.
[..]
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
UNICODE_STRING DriverName;
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;
[..]
Below, the elements of the MajorFunction[]
array are defined (also from ntddk.h):
[..]
#define IRP_MJ_CREATE 0x00
#define IRP_MJ_CREATE_NAMED_PIPE 0x01
#define IRP_MJ_CLOSE 0x02
#define IRP_MJ_READ 0x03
#define IRP_MJ_WRITE 0x04
#define IRP_MJ_QUERY_INFORMATION 0x05
#define IRP_MJ_SET_INFORMATION 0x06
#define IRP_MJ_QUERY_EA 0x07
#define IRP_MJ_SET_EA 0x08
#define IRP_MJ_FLUSH_BUFFERS 0x09
#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
#define IRP_MJ_SET_VOLUME_INFORMATION 0x0b
#define IRP_MJ_DIRECTORY_CONTROL 0x0c
#define IRP_MJ_FILE_SYSTEM_CONTROL 0x0d
#define IRP_MJ_DEVICE_CONTROL 0x0e
#define IRP_MJ_INTERNAL_DEVICE_CONTROL 0x0f
#define IRP_MJ_SHUTDOWN 0x10
#define IRP_MJ_LOCK_CONTROL 0x11
#define IRP_MJ_CLEANUP 0x12
#define IRP_MJ_CREATE_MAILSLOT 0x13
#define IRP_MJ_QUERY_SECURITY 0x14
#define IRP_MJ_SET_SECURITY 0x15
#define IRP_MJ_POWER 0x16
#define IRP_MJ_SYSTEM_CONTROL 0x17
#define IRP_MJ_DEVICE_CHANGE 0x18
#define IRP_MJ_QUERY_QUOTA 0x19
#define IRP_MJ_SET_QUOTA 0x1a
#define IRP_MJ_PNP 0x1b
#define IRP_MJ_PNP_POWER IRP_MJ_PNP // Obsolete....
#define IRP_MJ_MAXIMUM_FUNCTION 0x1b
[..]
To list the IOCTLs implemented by a driver, I had to find the driver’s IOCTL dispatch routine. If I’d had access to the C code of the driver, this would have been easy, since I know that the assignment of the dispatch routine usually looks like this:
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IOCTL_dispatch_routine;
Unfortunately, I didn’t have access to the source code of the avast! Aavmker4.sys driver. How could I find the dispatch assignment using only the disassembly provided by IDA Pro?
To answer this question, I needed more information about the DRIVER_OBJECT
data structure. I attached WinDbg to the VMware guest system and used the dt
command (see Section B.2 for a detailed description of the following debugger commands) to display the available information about the structure:
kd>.sympath SRV*c:\WinDBGSymbols*http://msdl.microsoft.com/download/symbols
kd>.reload
[..] kd>dt -v _DRIVER_OBJECT .
nt!_DRIVER_OBJECT struct _DRIVER_OBJECT, 15 elements, 0xa8 bytes +0x000 Type : Int2B +0x002 Size : Int2B +0x004 DeviceObject : +0x008 Flags : Uint4B +0x00c DriverStart : +0x010 DriverSize : Uint4B +0x014 DriverSection : +0x018 DriverExtension : +0x01c DriverName : struct _UNICODE_STRING, 3 elements, 0x8 bytes +0x000 Length : Uint2B +0x002 MaximumLength : Uint2B +0x004 Buffer : Ptr32 to Uint2B +0x024 HardwareDatabase : +0x028 FastIoDispatch : +0x02c DriverInit : +0x030 DriverStartIo : +0x034 DriverUnload :+0x038 MajorFunction : [28]
The debugger output shows that the MajorFunction[]
array starts at structure offset 0x38
. After looking at the ntddk.h header file of the Windows Driver Kit, I knew that IRP_MJ_DEVICE_CONTROL
was located at offset 0x0e
in MajorFunction[]
and that the element size of the array was a pointer (4 bytes on 32-bit platforms).
So the assignment can be expressed as the following:
In C: DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IOCTL_dispatch_routine; Offsets : DriverObject + 0x38 + 0x0e * 4 = IOCTL_dispatch_routine; Simplified form : DriverObject + 0x70 = IOCTL_dispatch_routine;
There are countless ways to express this assignment in Intel assembly, but what I found in the driver code of avast! was these instructions:
[..] .text:00010748 mov eax, [ebp+DriverObject] [..] .text:00010750 mov dword ptr [eax+70h], offset sub_1098C [..]
At address .text:00010748
, a pointer to a DRIVER_OBJECT
is stored in EAX
. Then at address .text:00010750
, the function pointer of the IOCTL dispatch routine gets assigned to MajorFunction[IRP_MJ_DEVICE_CONTROL]
.
Assignment in C: DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = sub_1098c; Offsets : DriverObject + 0x70 = sub_1098c;
I had finally found the IOCTL dispatch routine of the driver: sub_1098C
! The IOCTL dispatch routine could also be found with the help of the debugger:
kd>!drvobj AavmKer4 7
Driver object (86444f38) is for: *** ERROR: Symbol file could not be found. Defaulted to export symbols for Aavmker4.SYS - \Driver\Aavmker4 Driver Extension List: (id , addr) Device Object list: 863a9150 DriverEntry: f792d620 Aavmker4 DriverStartIo: 00000000 DriverUnload: 00000000 AddDevice: 00000000 Dispatch routines: [00] IRP_MJ_CREATE f792d766 Aavmker4+0x766 [01] IRP_MJ_CREATE_NAMED_PIPE f792d766 Aavmker4+0x766 [02] IRP_MJ_CLOSE f792d766 Aavmker4+0x766 [03] IRP_MJ_READ f792d766 Aavmker4+0x766 [04] IRP_MJ_WRITE f792d766 Aavmker4+0x766 [05] IRP_MJ_QUERY_INFORMATION f792d766 Aavmker4+0x766 [06] IRP_MJ_SET_INFORMATION f792d766 Aavmker4+0x766 [07] IRP_MJ_QUERY_EA f792d766 Aavmker4+0x766 [08] IRP_MJ_SET_EA f792d766 Aavmker4+0x766 [09] IRP_MJ_FLUSH_BUFFERS f792d766 Aavmker4+0x766 [0a] IRP_MJ_QUERY_VOLUME_INFORMATION f792d766 Aavmker4+0x766 [0b] IRP_MJ_SET_VOLUME_INFORMATION f792d766 Aavmker4+0x766 [0c] IRP_MJ_DIRECTORY_CONTROL f792d766 Aavmker4+0x766 [0d] IRP_MJ_FILE_SYSTEM_CONTROL f792d766 Aavmker4+0x766[0e] IRP_MJ_DEVICE_CONTROL f792d98c Aavmker4+0x98c
[..]
The output of WinDbg shows that the IRP_MJ_DEVICE_CONTROL
dispatch routine can be found at address Aavmker4+0x98c
.
After I found the dispatch routine, I searched this function for the implemented IOCTLs. The IOCTL dispatch routine has the following prototype:[69]
NTSTATUS DispatchDeviceControl( __in struct _DEVICE_OBJECT *DeviceObject, __in struct _IRP *Irp ) { ... }
The second function parameter is a pointer to an I/O request packet (IRP) structure. An IRP is the basic structure that the Windows I/O manager uses to communicate with drivers and allow drivers to communicate with each other. This structure transports the user-supplied IOCTL data as well as the requested IOCTL code.[70]
I then had a look at the disassembly of the dispatch routine in order to generate a list of the IOCTLs:
[..] .text:0001098C ; int __stdcall sub_1098C(int, PIRP Irp) .text:0001098C sub_1098C proc near ; DATA XREF: DriverEntry+130 [..].text:000109B2 mov ebx, [ebp+Irp] ; ebx = address of IRP
.text:000109B5 mov eax, [ebx+60h]
[..]
A pointer to the IRP structure is stored in EBX
at address .text:000109B2
of the IOCTL dispatch routine. Then a value, located at offset 0x60
of the IRP structure, is referenced (see .text:000109B5
).
kd>dt -v -r 3 _IRP
nt!_IRP struct _IRP, 21 elements, 0x70 bytes +0x000 Type : ?? +0x002 Size : ?? +0x004 MdlAddress : ???? +0x008 Flags : ?? [..]+0x040 Tail : union __unnamed, 3 elements, 0x30 bytes
+0x000 Overlay : struct __unnamed, 8 elements, 0x28 bytes +0x000 DeviceQueueEntry : struct _KDEVICE_QUEUE_ENTRY, 3 elements, 0x10 bytes +0x000 DriverContext : [4] ???? +0x010 Thread : ???? +0x014 AuxiliaryBuffer : ???? +0x018 ListEntry : struct _LIST_ENTRY, 2 elements, 0x8 bytes+0x020 CurrentStackLocation : ????
[..]
The output of WinDbg shows that the IRP structure member CurrentStackLocation
is located at offset 0x60
. This structure is defined in ntddk.h of the Windows Driver Kit:
[..] // // I/O Request Packet (IRP) definition // typedef struct _IRP { [..]//
// Current stack location - contains a pointer to the current
// IO_STACK_LOCATION structure in the IRP stack. This field
// should never be directly accessed by drivers. They should
// use the standard functions.
//
struct _IO_STACK_LOCATION *CurrentStackLocation;
[..]
The layout of the _IO_STACK_LOCATION
structure is shown below (see ntddk.h of the Windows Driver Kit):
[..]typedef struct _IO_STACK_LOCATION {
UCHAR MajorFunction; UCHAR MinorFunction; UCHAR Flags; UCHAR Control; [..] // // System service parameters for: NtDeviceIoControlFile // // Note that the user's output buffer is stored in the // UserBuffer field // and the user's input buffer is stored in the SystemBuffer // field. // struct {ULONG OutputBufferLength;
ULONG POINTER_ALIGNMENT InputBufferLength;
ULONG POINTER_ALIGNMENT IoControlCode;
PVOID Type3InputBuffer; } DeviceIoControl; [..]
In addition to the IoControlCode
of the requested IOCTL, this structure contains information about the size of the input and output buffer. Now that I had more information about the _IO_STACK_LOCATION
structure, I took a second look at the disassembly:
[..] .text:0001098C ; int __stdcall sub_1098C(int, PIRP Irp) .text:0001098C sub_1098C proc near ; DATA XREF: DriverEntry+130 [..] .text:000109B2 mov ebx, [ebp+Irp] ; ebx = address of IRP.text:000109B5 mov eax, [ebx+60h] ; eax = address of CurrentStackLocation
.text:000109B8 mov esi, [eax+8] ; ULONG InputBufferLength
.text:000109BB mov [ebp+var_1C], esi ; save InputBufferLength in var_1C.text:000109BE mov edx, [eax+4] ; ULONG OutputBufferLength
.text:000109C1 mov [ebp+var_3C], edx ; save OutputBufferLength in var_3C.text:000109C4 mov eax, [eax+0Ch] ; ULONG IoControlCode
.text:000109C7 mov ecx, 0B2D6002Ch ; ecx = 0xB2D6002C
.text:000109CC cmp eax, ecx ; compare 0xB2D6002C with IoControlCode
.text:000109CE ja loc_10D15 [..]
As I mentioned before, a pointer to _IO_STACK_LOCATION
is stored in EAX
at address .text:000109B5
, and then at address .text:000109B8
the InputBufferLength
is stored in ESI
. At .text:000109BE
the OutputBufferLength
is stored in EDX
, and at .text:000109C4
the IoControlCode
is stored in EAX
. Later, the requested IOCTL code stored in EAX
is compared with the value 0xB2D6002C
(see address .text:000109C7
and .text:000109CC
). Hey, I found the first valid IOCTL code of the driver! I searched the function for all values that are compared with the requested IOCTL code in EAX
and got a list of the supported IOCTLs of Aavmker4.sys.
After I generated the list of all the supported IOCTLs, I tried to locate the buffer containing the user-supplied IOCTL input data. All IRP_MJ_DEVICE_CONTROL
requests supply both an input buffer and an output buffer. The way the system describes these buffers depends on the data transfer type. The transfer type is stored in the IOCTL code itself. Under Microsoft Windows, the IOCTL code values are normally created using the CTL_CODE
macro.[71] Here’s another excerpt from ntddk.h:
[..] // // Macro definition for defining IOCTL and FSCTL function control codes. Note // that function codes 0-2047 are reserved for Microsoft Corporation, and // 2048-4095 are reserved for customers. //#define CTL_CODE( DeviceType, Function, Method, Access ) ( \
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
)
[..] // // Define the method codes for how buffers are passed for I/O and FS controls // #define METHOD_BUFFERED 0 #define METHOD_IN_DIRECT 1 #define METHOD_OUT_DIRECT 2 #define METHOD_NEITHER 3 [..]
The transfer type is specified using the Method
parameter of the CTL_CODE
macro. I wrote a little tool to reveal which data transfer type is used by the IOCTLs of Aavmker4.sys:
Example 6-1. A little tool that I wrote (IOCTL_method.c) to show which data transfer type is used by the IOCTLs of Aavmker4.sys
01 #include <windows.h> 02 #include <stdio.h> 03 04 int 05 main (int argc, char *argv[]) 06 { 07 unsigned int method = 0; 08 unsigned int code = 0; 09 10 if (argc != 2) { 11 fprintf (stderr, "Usage: %s <IOCTL code>\n", argv[0]); 12 return 1; 13 } 14 15 code = strtoul (argv[1], (char **) NULL, 16); 16 method = code & 3; 17 18 switch (method) { 19 case 0: 20 printf ("METHOD_BUFFERED\n"); 21 break; 22 case 1: 23 printf ("METHOD_IN_DIRECT\n"); 24 break; 25 case 2: 26 printf ("METHOD_OUT_DIRECT\n"); 27 break; 28 case 3: 29 printf ("METHOD_NEITHER\n"); 30 break; 31 default: 32 fprintf (stderr, "ERROR: invalid IOCTL data transfer method\n"); 33 break; 34 } 35 36 return 0; 37 }
I then compiled the tool with the command-line C compiler of Visual Studio (cl
):
C:\BHD>cl /nologo IOCTL_method.c
IOCTL_method.c
The following output shows the tool from Example 6-1 in action:
C:\BHD>IOCTL_method.exe B2D6002C
METHOD_BUFFERED
So the driver uses the METHOD_BUFFERED
transfer type to describe the input and output buffers of an IOCTL request. According to the buffer descriptions in the Windows Driver Kit, the input buffer of IOCTLs, which use the METHOD_BUFFERED
transfer type, can be found at Irp->AssociatedIrp.SystemBuffer
.
Below is an example of a reference to the input buffer in the disassembly of Aavmker4.sys:
[..] .text:00010CF1 mov eax, [ebx+0Ch] ; ebx = address of IRP .text:00010CF4 mov eax, [eax] [..]
In this example, EBX
holds a pointer to the IRP structure. At address .text:00010CF1
, the IRP structure member at offset 0x0c
is referenced.
kd>dt -v -r 2 _IRP
nt!_IRP struct _IRP, 21 elements, 0x70 bytes +0x000 Type : ?? +0x002 Size : ?? +0x004 MdlAddress : ???? +0x008 Flags : ??+0x00c AssociatedIrp : union __unnamed, 3 elements, 0x4 bytes
+0x000 MasterIrp : ???? +0x000 IrpCount : ??+0x000 SystemBuffer : ????
[..]
The output of WinDbg shows that AssociatedIrp
is located at this offset (IRP->AssociatedIrp
). At address .text:00010CF4
, the input buffer of the IOCTL call is referenced and stored in EAX
(Irp->AssociatedIrp.SystemBuffer
). Now that I had found the supported IOCTLs, as well as the IOCTL input data, I started searching for bugs.
To find a possible security defect, I audited the handler code of one IOCTL at a time while tracing the supplied input data. When I came across the IOCTL code 0xB2D60030
, I found a subtle bug.
If the IOCTL code 0xB2D60030
is requested by a user space application, the following code is executed:
[..]
.text:0001098C ; int __stdcall sub_1098C(int, PIRP Irp)
.text:0001098C sub_1098C proc near ; DATA XREF: DriverEntry+130
[..]
.text:00010D28 cmp eax, 0B2D60030h ; IOCTL-Code == 0xB2D60030 ?
.text:00010D2D jz short loc_10DAB ; if so -> loc_10DAB
[..]
If the requested IOCTL code matches 0xB2D60030
(see .text:00010D28
), the assembler code at address .text:00010DAB
(loc_10DAB
) is executed:
[..] .text:000109B8 mov esi, [eax+8] ; ULONG InputBufferLength.text:000109BB mov [ebp+var_1C], esi
[..] .text:00010DAB loc_10DAB: ; CODE XREF: sub_1098C+3A1.text:00010DAB xor edi, edi ; EDI = 0
.text:00010DAD cmp byte_1240C, 0 .text:00010DB4 jz short loc_10DC9 [..] .text:00010DC9 loc_10DC9: ; CODE XREF: sub_1098C+428.text:00010DC9 mov esi, [ebx+0Ch] ; Irp->AssociatedIrp.SystemBuffer
.text:00010DCC cmp [ebp+var_1C], 878h ; input data length == 0x878 ?
.text:00010DD3 jz short loc_10DDF ; if so -> loc_10DDF [..]
At address .text:00010DAB EDI
is set to 0. The EBX
register holds a pointer to the IRP structure, and at address .text:00010DC9
a pointer to the input buffer data is stored in ESI
(Irp->AssociatedIrp.SystemBuffer
).
At the beginning of the dispatch routine, the InputBufferLength
of the request is stored in the stack variable var_1c
(see .text:000109BB
). The length of the input data at address .text:00010DCC
is then compared to the value 0x878
(see Figure 6-4).
If the data length equals 0x878
, the user-controlled input data, pointed to by ESI
, is further processed:
[..] .text:00010DDF loc_10DDF: ; CODE XREF: sub_1098C+447 .text:00010DDF mov [ebp+var_4], edi.text:00010DE2 cmp [esi], edi ; ESI == input data
.text:00010DE4 jz short loc_10E34 ; if input data == NULL -> loc_10E34 [..].text:00010DE6 mov eax, [esi+870h] ; ESI and EAX
are pointing to the →
input data
.text:00010DEC mov [ebp+var_48], eax ; a pointer to user controlled data →
is stored in var_48
.text:00010DEF cmp dword ptr [eax], 0D0DEAD07h ; validation of input data
.text:00010DF5 jnz short loc_10E00 [..].text:00010DF7 cmp dword ptr [eax+4], 10BAD0BAh ; validation of input data
.text:00010DFE jz short loc_10E06 [..]
The code at address .text:00010DE2
checks whether the input data equals NULL. If the input data is not NULL, a pointer from this data is extracted at [user_data+0x870]
and stored in EAX
(see .text:00010DE6
). This pointer value is stored in the stack variable var_48
(see .text:00010DEC
). A check is then performed to see if the data, pointed to by EAX
, starts with the values 0xD0DEAD07
and 0x10BAD0BA
(see .text:00010DEF
and .text:00010DF7
). If so, the parsing of the input data continues:
[..] .text:00010E06 loc_10E06: ; CODE XREF: sub_1098C+472 .text:00010E06 xor edx, edx .text:00010E08 mov eax, [ebp+var_48] .text:00010E0B mov [eax], edx .text:00010E0D mov [eax+4], edx.text:00010E10 add esi, 4 ; source address
.text:00010E13 mov ecx, 21Ah ; length
.text:00010E18 mov edi, [eax+18h] ; destination address
.text:00010E1B rep movsd ; memcpy()
[..]
The rep movsd
instruction at address .text:00010E1B
represents a memcpy()
function. So ESI
holds the source address, EDI
holds the destination address, and ECX
holds the length for the copy operation. ECX
gets assigned the value 0x21a
(see .text:00010E13
). ESI
points to the user-controlled IOCTL data (see .text:00010E10
), and EDI
is also derived from user-controlled data pointed to by EAX
(see .text:00010E18
and Figure 6-5).
Here’s some pseudo C code of that memcpy()
call:
memcpy ([EAX+0x18], ESI + 4, 0x21a * 4);
Or, in more abstract terms:
memcpy (user_controlled_address, user_controlled_data, 0x868);
It is therefore possible to write 0x868
bytes (0x21a * 4
bytes, as the rep movsd
instruction copies DWORDs from one location to another) of user-controllable data to an arbitrary user-controlled address in either user or kernel space. Nice!
The anatomy of the bug, diagrammed in Figure 6-6, is as follows:
An IOCTL request (0xB2D60030
) is sent to the kernel driver Aavmker4.sys using the AavmKer4
device.
The driver code checks whether the IOCTL input data length equals the value 0x878
. If so, proceed to step 3.
The driver checks whether the user-controlled IOCTL input data contains the values 0xD0DEAD07
and 0x10BAD0BA
. If so, proceed to step 4.
The erroneous memcpy()
call is executed.
The memory is corrupted.