Kernel Buffer Overflow in NDProxy.sys (MS10-099)
A couple of weeks ago, Microsoft patched the vulnerability MS10-099, which was discovered by FortiGuard Labs' resercher Honggang Ren. Since the patches are likely all deployed by now, we're happy to disclose more details about it, based on Honggang's inputs.
This vulnerability is a kernel buffer overflow which exists in NDProxy.sys, a device driver interfacing mini-ports to the telephony API (aka TAPI). The vulnerable code is brought up when issuing a DeviceIoControl call from “userland” with control code 0x8ff23c8. This ioctl call is related to the TAPI provider initialization.
Various functions within that ioctl call are vulnerable, but for the sake of the example, let’s focus on the following code, belonging to function PxTapiGetDevCaps:
memory_copy_code: sub ecx, edx mov [ebp+var_608], ecx mov ecx, edx <b>;; setting “counter”</b> shr ecx, 2 <b>;; adjusting counter value to a number of dwords (not bytes)</b> mov edi, eax <b>;; setting destination</b> rep movsd
This is a typical memory copy operation, where rep movsd copies dwords starting at address esi to the allocated memory space starting at address edi. The number of dwords to copy is determined by ecx. Thus, if we can set the value in ecx to what we want, we can overwrite memory beyond the buffer at edi, and possibly gain control of the execution flow (classical buffer overflow situation).
It happens to be the case here, as edi points inside the ioctl call’s input buffer, and the value of the counter is taken from this very same buffer, as shown by the code below, that sits right above our memory copy code:
(Note: eax initially points the call’s input buffer, and ebx to a global buffer)
mov esi, [ebx+2Ch] mov edx, [esi+8] <b>;; edx is loaded with value 0x1A8</b> add eax, 2Ch mov ecx, [eax] <b>;; ecx is loaded with the value at offset 0x2c in the input buffer</b> cmp edx, ecx mov [ebp+var_624], ecx jbe memory_copy_code <b>;; jmp if edx <= ecx</b> mov edx, ecx <b>;; swapping edx and ecx if ecx is smaller</b> memory_copy_code: ...
What essentially happens here is that the value at offset 0x8 in a global buffer is compared to the value at offset 0x2c in the ioctl call’s input buffer, and the smaller one is kept in edx to be used as the counter value when reaching the memory copy code. In the latter, eax is used to set the destination of the memory copy, and we saw in the code above that it points to offset 0x2c in the input buffer.
A more high level way to say this could be that there are two structures in two buffers, of the form [size][data], at offsets 0x8 and 0x2c (see figure below), and what the memory copy code does is copying the one at offset 0x8 into the one at offset 0x2c, stopping when the size of the smaller of the two is reached.
The figure below sums up the memory layout of the two buffers:
Pre-allocated global buffer: offset esi 0x8 | | V V [ ... [size1][...data...] ... ] Call's input buffer: offset 0x2c | V [ ... [size2][...data...] ... ] <