Lucene search

K
securityvulnsSecurityvulnsSECURITYVULNS:DOC:13196
HistoryJun 15, 2006 - 12:00 a.m.

Improper Validation of Kernel Object Types

2006-06-1500:00:00
vulners.com
11

Improper Validation of Kernel Object Types

Windows exposes many kernel features through a series of ``kernel objects''. These objects may be acted upon by user mode through the user of handles. Handles are integral values that are translated by the kernel into pointers to a particular object upon which something (typically a system service) interacts with on behalf of a caller. All objects share the same handle namespace.

Because of this handle namespace sharing between objects of different types, one of the jobs of a system service inspecting a handle is to verify that the object that it refers to is of the expected type. This is accomplished by an object manager routine ObReferenceObjectByHandle, which performs the translation of handles to object pointers and does an optional built-in type check by comparing a type field in the standard object header to a passed in type.

Since KAV hooks system services, it inevitably must deal with kernel handles. Unfortunately, it does not do so correctly. In some cases, it does not ensure that a handle refers to an object of a particular type before using the object pointer. This will result in corruption or a system crash if a handle of the wrong type is passed to a system service.

One such case is the KAV NtResumeThread hook, which attempts to track the state of running threads in the system. In this particular case, it does not seem possible for user mode to crash the system by passing an object of the wrong type as the returned object pointer because it is simply used as a key in a lookup table that is prepopulated with thread object pointers. KAV also hooks NtSuspendThread for similar purposes, and this hook has the same problem with the validation of object handle types.

.text:F82245E0 ; NTSTATUS __stdcall KavNtResumeThread(
HANDLE ThreadHandle,
PULONG PreviousSuspendCount)
.text:F82245E0 KavNtResumeThread proc near ; DATA XREF: sub_F82249D0+FBo
.text:F82245E0
.text:F82245E0 ThreadHandle = dword ptr 8
.text:F82245E0 PreviousSuspendCount= dword ptr 0Ch
.text:F82245E0
.text:F82245E0 push esi
.text:F82245E1 mov esi, [esp+ThreadHandle]
.text:F82245E5 test esi, esi
.text:F82245E7 jz short loc_F8224620
.text:F82245E9 lea eax, [esp+ThreadHandle] ;
.text:F82245E9 ; This should pass an object type here!
.text:F82245ED push 0 ; HandleInformation
.text:F82245EF push eax ; Object
.text:F82245F0 push 0 ; AccessMode
.text:F82245F2 push 0 ; ObjectType
.text:F82245F4 push 0F0000h ; DesiredAccess
.text:F82245F9 push esi ; Handle
.text:F82245FA mov [esp+18h+ThreadHandle], 0
.text:F8224602 call ds:ObReferenceObjectByHandle
.text:F8224608 test eax, eax
.text:F822460A jl short loc_F8224620
.text:F822460C mov ecx, [esp+ThreadHandle]
.text:F8224610 push ecx
.text:F8224611 call KavUpdateThreadRunningState
.text:F8224616 mov ecx, [esp+ThreadHandle] ; Object
.text:F822461A call ds:ObfDereferenceObject
.text:F8224620
.text:F8224620 loc_F8224620: ; CODE XREF: KavNtResumeThread+7j
.text:F8224620 ; KavNtResumeThread+2Aj
.text:F8224620 mov edx, [esp+PreviousSuspendCount]
.text:F8224624 push edx
.text:F8224625 push esi
.text:F8224626 call OrigNtResumeThread
.text:F822462C pop esi
.text:F822462D retn 8
.text:F822462D KavNtResumeThread endp
.text:F822462D

.text:F8224590 ; NTSTATUS __stdcall KavNtSuspendThread(
HANDLE ThreadHandle,
PULONG PreviousSuspendCount)
.text:F8224590 sub_F8224590 proc near ; DATA XREF: sub_F82249D0+113o
.text:F8224590
.text:F8224590 ThreadHandle = dword ptr 8
.text:F8224590 PreviousSuspendCount= dword ptr 0Ch
.text:F8224590
.text:F8224590 push esi
.text:F8224591 mov esi, [esp+ThreadHandle]
.text:F8224595 test esi, esi
.text:F8224597 jz short loc_F82245D0
.text:F8224599 lea eax, [esp+ThreadHandle] ;
.text:F8224599 ; This should pass an object type here!
.text:F822459D push 0 ; HandleInformation
.text:F822459F push eax ; Object
.text:F82245A0 push 0 ; AccessMode
.text:F82245A2 push 0 ; ObjectType
.text:F82245A4 push 0F0000h ; DesiredAccess
.text:F82245A9 push esi ; Handle
.text:F82245AA mov [esp+18h+ThreadHandle], 0
.text:F82245B2 call ds:ObReferenceObjectByHandle
.text:F82245B8 test eax, eax
.text:F82245BA jl short loc_F82245D0
.text:F82245BC mov ecx, [esp+ThreadHandle]
.text:F82245C0 push ecx
.text:F82245C1 call KavUpdateThreadSuspendedState
.text:F82245C6 mov ecx, [esp+ThreadHandle] ; Object
.text:F82245CA call ds:ObfDereferenceObject
.text:F82245D0
.text:F82245D0 loc_F82245D0: ; CODE XREF: sub_F8224590+7j
.text:F82245D0 ; sub_F8224590+2Aj
.text:F82245D0 mov edx, [esp+PreviousSuspendCount]
.text:F82245D4 push edx
.text:F82245D5 push esi
.text:F82245D6 call OrigNtSuspendThread
.text:F82245DC pop esi
.text:F82245DD retn 8
.text:F82245DD sub_F8224590 endp
.text:F82245DD

Not all of KAV's hooks are so fortunate, however. The NtTerminateProcess hook that KAV installs looks into the body of the object referred to by the process handle parameter of the function in order to determine the name of the process being terminated. However, KAV fails to validate that the object handle given by user mode really refers to a process object.

This is unsafe for several reasons, which may be well known to the reader if one is experienced with Windows kernel programming.

  1. The kernel process structure definition (EPROCESS) changes frequently from OS release to OS release, and even between service packs. As a result, it is not generally safe to access this structure directly.

  2. Because KAV does not perform proper type checking, it is possible to pass an object handle to a different kernel object - say, a mutex - which may cause KAV to bring down the system because the internal object structures of a mutex (or any other kernel object) are not compatible with that of a process object.

KAV attempts to work around the first problem by attempting to discover the offset of the member in the EPROCESS structure that contains the process name at runtime. The algorithm used is to scan forward one byte at a time from the start of the process object pointer until a sequence of bytes identifying the name of the initial system process is discovered. (This routine is called in the context of the initial system process). This routine appears to be very common amongst anti-virus and other low-level products that attempt to make use of the image file name associated with a process.

.text:F82209E0 KavFindEprocessNameOffset proc near ; CODE XREF: sub_F8217A60+FCp
.text:F82209E0 push ebx
.text:F82209E1 push esi
.text:F82209E2 push edi
.text:F82209E3 call ds:IoGetCurrentProcess
.text:F82209E9 mov edi, ds:strncmp
.text:F82209EF mov ebx, eax
.text:F82209F1 xor esi, esi
.text:F82209F3
.text:F82209F3 loc_F82209F3: ; CODE XREF: KavFindEprocessNameOffset+2Ej
.text:F82209F3 lea eax, [esi+ebx]
.text:F82209F6 push 6 ; size_t
.text:F82209F8 push eax ; char *
.text:F82209F9 push offset aSystem ; "System"
.text:F82209FE call edi ; strncmp
.text:F8220A00 add esp, 0Ch
.text:F8220A03 test eax, eax
.text:F8220A05 jz short loc_F8220A16
.text:F8220A07 inc esi
.text:F8220A08 cmp esi, 3000h
.text:F8220A0E jl short loc_F82209F3
.text:F8220A10 pop edi
.text:F8220A11 pop esi
.text:F8220A12 xor eax, eax
.text:F8220A14 pop ebx
.text:F8220A15 retn
.text:F8220A16 ; ---------------------------------------------------------------------------
.text:F8220A16
.text:F8220A16 loc_F8220A16: ; CODE XREF: KavFindEprocessNameOffset+25j
.text:F8220A16 mov eax, esi
.text:F8220A18 pop edi
.text:F8220A19 pop esi
.text:F8220A1A pop ebx
.text:F8220A1B retn
.text:F8220A1B KavFindEprocessNameOffset endp

.text:F8217B5C call KavFindEprocessNameOffset
.text:F8217B61 mov g_EprocessNameOffset, eax

Given a handle to an object of the wrong type, KAV will read from the returned object body pointer in an attempt to determine the name of the process being destroyed. This will typically run off the end of the structure for an object that is not a process object (the Process object is very large compared to some objects, such as a Mutex object, and the offset of the process name within this structure is typically several hundred bytes or more). It is expected that this will cause the system to crash if a bad handle is passed to NtTerminateProcess.

.text:F82241C0 ; NTSTATUS __stdcall KavNtTerminateProcess(HANDLE ThreadHandle,NTSTATUS ExitStatus)
.text:F82241C0 KavNtTerminateProcess proc near ; DATA XREF: sub_F82249D0+ABo
.text:F82241C0
.text:F82241C0 var_58 = dword ptr -58h
.text:F82241C0 ProcessObject = dword ptr -54h
.text:F82241C0 ProcessData = KAV_TERMINATE_PROCESS_DATA ptr -50h
.text:F82241C0 var_4 = dword ptr -4
.text:F82241C0 ProcessHandle = dword ptr 4
.text:F82241C0 ExitStatus = dword ptr 8
.text:F82241C0
.text:F82241C0 sub esp, 54h
.text:F82241C3 push ebx
.text:F82241C4 xor ebx, ebx
.text:F82241C6 push esi
.text:F82241C7 mov [esp+5Ch+ProcessObject], ebx
.text:F82241CB call KeGetCurrentIrql
.text:F82241D0 mov esi, [esp+5Ch+ProcessHandle]
.text:F82241D4 cmp al, 2 ;
.text:F82241D4 ; IRQL >= DISPATCH_LEVEL? Abort
.text:F82241D4 ; ( This is impossible for a system service )
.text:F82241D6 jnb Ret_KavNtTerminateProcess
.text:F82241DC cmp esi, ebx ;
.text:F82241DC ; Null process handle? Abort
.text:F82241DE jz Ret_KavNtTerminateProcess
.text:F82241E4 call PsGetCurrentProcessId
.text:F82241E9 mov [esp+5Ch+ProcessData.CurrentProcessId], eax
.text:F82241ED xor eax, eax
.text:F82241EF cmp esi, 0FFFFFFFFh
.text:F82241F2 push esi ; ProcessHandle
.text:F82241F3 setnz al
.text:F82241F6 dec eax
.text:F82241F7 mov [esp+60h+ProcessData.TargetIsCurrentProcess], eax
.text:F82241FB call KavGetProcessIdFromProcessHandle
.text:F8224200 lea ecx, [esp+5Ch+ProcessObject] ; Object
.text:F8224204 push ebx ; HandleInformation
.text:F8224205 push ecx ; Object
.text:F8224206 push ebx ; AccessMode
.text:F8224207 push ebx ; ObjectType
.text:F8224208 push 0F0000h ; DesiredAccess
.text:F822420D push esi ; Handle
.text:F822420E mov [esp+74h+ProcessData.TargetProcessId], eax
.text:F8224212 mov [esp+74h+var_4], ebx
.text:F8224216 call ds:ObReferenceObjectByHandle
.text:F822421C test eax, eax
.text:F822421E jl short loc_F8224246
.text:F8224220 mov edx, [esp+5Ch+ProcessObject]
.text:F8224224 mov eax, g_EprocessNameOffset
.text:F8224229 add eax, edx
.text:F822422B push 40h ; size_t
.text:F822422D lea ecx, [esp+60h+ProcessData.ProcessName]
.text:F8224231 push eax ; char *
.text:F8224232 push ecx ; char *
.text:F8224233 call ds:strncpy
.text:F8224239 mov ecx, [esp+68h+ProcessObject]
.text:F822423D add esp, 0Ch
.text:F8224240 call ds:ObfDereferenceObject
.text:F8224246
.text:F8224246 loc_F8224246: ; CODE XREF: KavNtTerminateProcess+5Ej
.text:F8224246 cmp esi, 0FFFFFFFFh
.text:F8224249 jnz short loc_F8224255
.text:F822424B mov edx, [esp+5Ch+ProcessData.TargetProcessId]
.text:F822424F push edx
.text:F8224250 call sub_F8226710
.text:F8224255
.text:F8224255 loc_F8224255: ; CODE XREF: KavNtTerminateProcess+89j
.text:F8224255 lea eax, [esp+5Ch+ProcessData]
.text:F8224259 push ebx ; int
.text:F822425A push eax ; ProcessData
.text:F822425B call KavCheckTerminateProcess
.text:F8224260 cmp eax, 7
.text:F8224263 jz short loc_F822427D
.text:F8224265 cmp eax, 1
.text:F8224268 jz short loc_F822427D
.text:F822426A cmp eax, ebx
.text:F822426C jz short loc_F822427D
.text:F822426E mov esi, STATUS_ACCESS_DENIED
.text:F8224273 mov eax, esi
.text:F8224275 pop esi
.text:F8224276 pop ebx
.text:F8224277 add esp, 54h
.text:F822427A retn 8
.text:F822427D ; ---------------------------------------------------------------------------
.text:F822427D
.text:F822427D loc_F822427D: ; CODE XREF: KavNtTerminateProcess+A3j
.text:F822427D ; KavNtTerminateProcess+A8j …
.text:F822427D mov eax, [esp+5Ch+ProcessData.TargetProcessId]
.text:F8224281 cmp eax, 1000h
.text:F8224286 jnb short loc_F8224296
.text:F8224288 mov dword_F8228460[eax8], ebx
.text:F822428F mov byte_F8228464[eax
8], bl
.text:F8224296
.text:F8224296 loc_F8224296: ; CODE XREF: KavNtTerminateProcess+C6j
.text:F8224296 push eax
.text:F8224297 call sub_F82134D0
.text:F822429C mov ecx, [esp+5Ch+ProcessData.TargetProcessId]
.text:F82242A0 push ecx
.text:F82242A1 call sub_F8221F70
.text:F82242A6 mov edx, [esp+5Ch+ExitStatus]
.text:F82242AA push edx
.text:F82242AB push esi
.text:F82242AC call OrigNtTerminateProcess
.text:F82242B2 mov esi, eax
.text:F82242B4 lea eax, [esp+5Ch+ProcessData]
.text:F82242B8 push 1 ; int
.text:F82242BA push eax ; ProcessData
.text:F82242BB mov [esp+64h+var_4], esi
.text:F82242BF call KavCheckTerminateProcess
.text:F82242C4 mov eax, esi
.text:F82242C6 pop esi
.text:F82242C7 pop ebx
.text:F82242C8 add esp, 54h
.text:F82242CB retn 8
.text:F82242CE ; ---------------------------------------------------------------------------
.text:F82242CE
.text:F82242CE Ret_KavNtTerminateProcess: ; CODE XREF: KavNtTerminateProcess+16j
.text:F82242CE ; KavNtTerminateProcess+1Ej
.text:F82242CE mov ecx, [esp+5Ch+ExitStatus]
.text:F82242D2 push ecx
.text:F82242D3 push esi
.text:F82242D4 call OrigNtTerminateProcess
.text:F82242DA pop esi
.text:F82242DB pop ebx
.text:F82242DC add esp, 54h
.text:F82242DF retn 8
.text:F82242DF KavNtTerminateProcess endp

The whole purpose of this particular system service hook is ``shady'' as well. The hook prevents certain KAV processes from being terminated, even by a legitimate computer administrator - something that is once again typically associated with malicious software, such as rootkits, rather than commercial software applications. One possible explanation for this is that it is an attempt to prevent viruses from terminating the virus scanner processes itself, although one wonders how much of a concern this would be if KAV's real-time scanning mechanisms really do work as advertised.

Additionally, KAV appears to do some state tracking just before the process is terminated with this system service hook. The proper way to do this would have been through PsSetCreateProcessNotifyRoutine which is a documented kernel function that allows drivers to register a callback that is called on process creation and process exit.