Chapter 6. One Kernel to Rule Them All

Note

Saturday, March 8, 2008

Dear Diary,

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.

Note

On June 1, 2010, ALWIL Software was renamed AVAST Software.

I used the following steps to find the vulnerability:

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.

Note

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);
[..]

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:

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:

  1. An IOCTL request (0xB2D60030) is sent to the kernel driver Aavmker4.sys using the AavmKer4 device.

  2. The driver code checks whether the IOCTL input data length equals the value 0x878. If so, proceed to step 3.

  3. The driver checks whether the user-controlled IOCTL input data contains the values 0xD0DEAD07 and 0x10BAD0BA. If so, proceed to step 4.

  4. The erroneous memcpy() call is executed.

  5. The memory is corrupted.