Uninformed: Informative Information for the Uninformed

Vol 4» 2006.Jun


Hiding Threads from User-mode

KAV's errors with hooking do not end with NtOpenProcess, however. One of the system services KAV hooks is NtQuerySystemInformation. This routine's behavior is modified to sometimes truncate a thread listing from certain processes when the SystemProcessesAndThreads information class is requested. This is the underlying mechanism for user mode to receive a process and thread listing of all programs running in the system, and in effect, provides a means for KAV to hide threads from user mode. The very fact that this code exists at all in KAV is curious; hiding running code from user mode is typically something that is associated with rootkits and not anti-virus software.

Aside from the potentially abusive behavior of hiding running code, this hook contains several security flaws:

  1. It uses the user mode output buffer from NtQuerySystemInformation after it has been filled by the actual kernel implementation, but it does not guard against a malicious user mode program modifying this buffer or even freeing it. There is no SEH frame wrapping this function, so a user mode program could cause KAV to touch freed memory.

  2. There is no validation of offsets within the returned output buffer to ensure that offsets do not refer to memory outside of the output buffer. This is problematic, because the returned data structure is actually a list of sub-structures that must be walked by adding an offset supplied as part of a particular substructure to the address of that substructure in order to reach the next substructure. Such an offset could be modified by user mode to actually point into kernel memory. Because the hook then sometimes writes data into what it believes is the user mode output buffer, this is an interesting avenue to explore for gaining kernel privileges from an unprivileged user mode function.

.text:F8224430 ; NTSTATUS __stdcall KavNtQuerySystemInformation(
	SYSTEM_INFORMATION_CLASS SystemInformationClass,
	PVOID SystemInformation,
	ULONG SystemInformationLength,
	PULONG ReturnLength)
.text:F8224430 KavNtQuerySystemInformation proc near   ; DATA XREF: sub_F82249D0+17Bo
.text:F8224430
.text:F8224430 var_10          = dword ptr -10h
.text:F8224430 var_C           = dword ptr -0Ch
.text:F8224430 var_8           = dword ptr -8
.text:F8224430 SystemInformationClass= dword ptr  4
.text:F8224430 SystemInformation= dword ptr  8
.text:F8224430 SystemInformationLength= dword ptr  0Ch
.text:F8224430 ReturnLength    = dword ptr  10h
.text:F8224430 arg_24          = dword ptr  28h
.text:F8224430
.text:F8224430    mov     eax, [esp+ReturnLength]
.text:F8224434    mov     ecx, [esp+SystemInformationLength]
.text:F8224438    mov     edx, [esp+SystemInformation]
.text:F822443C    push    ebx
.text:F822443D    push    ebp
.text:F822443E    push    esi
.text:F822443F    mov     esi, [esp+0Ch+SystemInformationClass]
.text:F8224443    push    edi
.text:F8224444    push    eax
.text:F8224445    push    ecx
.text:F8224446    push    edx
.text:F8224447    push    esi
.text:F8224448    call    OrigNtQuerySystemInformation
.text:F822444E    mov     edi, eax
.text:F8224450    cmp     esi, SystemProcessesAndThreadsInformation ;
.text:F8224450               ; Not the process / thread list API?
.text:F8224450               ; Return to caller
.text:F8224453    mov     [esp+10h+ReturnLength], edi
.text:F8224457    jnz     ret_KavNtQuerySystemInformation
.text:F822445D    xor     ebx, ebx
.text:F822445F    cmp     edi, ebx        ;
.text:F822445F               ; Nothing returned?
.text:F822445F               ; Return to caller
.text:F8224461    jl      ret_KavNtQuerySystemInformation
.text:F8224467    push    ebx
.text:F8224468    push    9
.text:F822446A    push    8
.text:F822446C    call    sub_F8216730
.text:F8224471    test    al, al
.text:F8224473    jz      ret_KavNtQuerySystemInformation
.text:F8224479    mov     ebp, g_KavDriverData
.text:F822447F    mov     ecx, [ebp+0Ch]
.text:F8224482    lea     edx, [ebp+48h]
.text:F8224485    inc     ecx
.text:F8224486    mov     [ebp+0Ch], ecx
.text:F8224489    mov     ecx, ebp
.text:F822448B    call    ds:ExInterlockedPopEntrySList
.text:F8224491    mov     esi, eax
.text:F8224493    cmp     esi, ebx
.text:F8224495    jnz     short loc_F82244B7
.text:F8224497    mov     eax, [ebp+10h]
.text:F822449A    mov     ecx, [ebp+24h]
.text:F822449D    mov     edx, [ebp+1Ch]
.text:F82244A0    inc     eax
.text:F82244A1    mov     [ebp+10h], eax
.text:F82244A4    mov     eax, [ebp+20h]
.text:F82244A7    push    eax
.text:F82244A8    push    ecx
.text:F82244A9    push    edx
.text:F82244AA    call    [ebp+arg_24]
.text:F82244AD    mov     esi, eax
.text:F82244AF    cmp     esi, ebx
.text:F82244B1    jz      ret_KavNtQuerySystemInformation
.text:F82244B7
.text:F82244B7 loc_F82244B7:              ; CODE XREF: KavNtQuerySystemInformation+65j
.text:F82244B7    mov     edi, [esp+10h+SystemInformation]
.text:F82244BB    mov     dword ptr [esi], 8
.text:F82244C1    mov     dword ptr [esi+4], 9
.text:F82244C8    mov     [esi+8], ebx
.text:F82244CB    mov     [esi+34h], ebx
.text:F82244CE    mov     dword ptr [esi+3Ch], 1
.text:F82244D5    mov     [esi+10h], bl
.text:F82244D8    mov     [esi+30h], ebx
.text:F82244DB    mov     [esi+0Ch], ebx
.text:F82244DE    mov     [esi+38h], ebx
.text:F82244E1    mov     ebp, 13h
.text:F82244E6
.text:F82244E6 LoopThreadProcesses:       ; CODE XREF: KavNtQuerySystemInformation+ECj
.text:F82244E6    mov     dword ptr [esi+40h], 4 ;
.text:F82244E6               ; Loop through the returned list of processes and threads.
.text:F82244E6               ; For each process, we shall check to see if it is a
.text:F82244E6               ; special (protected) process.  If so, then we might
.text:F82244E6               ; decide to remove its threads from the listing returned
.text:F82244E6               ; by setting the thread count to zero.
.text:F82244ED    mov     [esi+48h], ebx
.text:F82244F0    mov     [esi+44h], ebp
.text:F82244F3    mov     eax, [edi+SYSTEM_PROCESSES.ProcessId]
.text:F82244F6    push    ebx
.text:F82244F7    push    esi
.text:F82244F8    mov     [esi+4Ch], eax
.text:F82244FB    call    KavCheckProcess
.text:F8224500    cmp     eax, 7
.text:F8224503    jz      short CheckNextThreadProcess
.text:F8224505    cmp     eax, 1
.text:F8224508    jz      short CheckNextThreadProcess
.text:F822450A    cmp     eax, ebx
.text:F822450C    jz      short CheckNextThreadProcess
.text:F822450E    mov     [edi+SYSTEM_PROCESSES.ThreadCount], ebx ; 
                             ; Zero thread count out (hide process threads)
.text:F8224511
.text:F8224511 CheckNextThreadProcess:    ; CODE XREF: KavNtQuerySystemInformation+D3j
.text:F8224511               ; KavNtQuerySystemInformation+D8j ...
.text:F8224511    mov     eax, [edi+SYSTEM_PROCESSES.NextEntryDelta]
.text:F8224513    cmp     eax, ebx
.text:F8224515    setz    cl
.text:F8224518    add     edi, eax
.text:F822451A    cmp     cl, bl
.text:F822451C    jz      short LoopThreadProcesses