不正确的校验内核对象类型
Windows通过一系列的"内核对象"来揭示很多内核特性。这些内核对象在用户模式下被包装为句柄。这些句柄本身是整数值,在内核模式下被转换为指向内核对象的指针,用来和内核函数进行通信(尤其是系统服务)。所有的内核对象都是使用共同的句柄空间。
由于这个句柄空间被不同类型的内核对象所共享,系统服务的一个任务就是校验句柄是否指向正确的类别。校验是通过对象管理器中ObReferenceObjectByHandle来完成,该函数将会把句柄转换为对象指针,并比较对象的类别是否和传入的类别一致。
鉴于KAV对系统服务进行了挂钩,所以它不可避免的要直接操作内核句柄,不幸的是,它并没有使用正确的办法。在某些时候,KAV在使用对象指针时,并没有确保该句柄所指的对象是正确的类别。如果错误类别的句柄传送给系统服务,有可能会导致系统的崩溃。
比如,KAV对NtResumeThread进行挂钩,以便跟踪系统中运行线程的状态。在这种情况下,从用户模式下的程序即使把一个错误类别的句柄作为返回对象指针也不太可能导致系统崩溃,因为仅仅是使用了一个查找句柄表的键值而已。KAV挂钩的NtSuspendThread也有同样的情况,没有校验对象句柄的类别。
.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
并不是所有的KAV挂钩都能幸运的避免使用不正确类别内核对象带来的不良后果。KAV安装的NtTerminateProcess挂钩,通过进程句柄来获取将要结束进程的名称。然而KAV并未验证用户模式传入的对象句柄是否指向正确的进程对象。
对于熟悉Windows内核编程的读者都知道,这种方式是不安全的,理由如下:
1、内核进程结构(EPROCESS)在不同版本的Windows之间变化频繁,甚至在service pack之间也有变化。所以直接获取这个结构并不安全。
2、因为KAV没有校验内核对象类型的正确性,可能会造成传入不匹配类别的对象句柄。比如,传入一个Mutex对象就有可能导致系统崩溃,因为在内核内部结构中Mutex对象和进程对象并不兼容。
KAV希望通过算法找到进程名在EPROCESS结构体中的偏移量,来解决上面提到的第一个问题。该算法主要从进程对象指针的开始,逐字节遍历,直到找到和初始系统进程的名称(System)。这种办法在反病毒和一些底层程序中使用非常普遍,用来建立进程和映像文件名称之间的联系。
.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
如果传入了一个错误类别的对象句柄,当KAV去读取将要结束进程的名称时,对于不是进程的对象就有可能越界读取(这是因为进程对象比很多对象都要大得多,比如Mutex对象,而且进程名称的偏移量都至少是几百字节)。因此当然一个错误类别的句柄传递给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[eax*8], 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
这个系统服务挂钩是"见不得人的"。这个挂钩阻止某些KAV进程被终止,甚至对管理员用户也是如此。这通常都是一些恶意程序的做法,比如Rootkits,而不是商业软件的做法。一种可能的解释就是为了阻止病毒中止病毒扫描程序的运行。可是如果KAV是像它广告中所说的那种实时扫描又怎么会有这样的担心呢?
此外,KAV在进程结束以前还通过这个挂钩来跟踪一些状态变化。然而正确的做法应该是通过官方公布的内核函数PsSetCreateProcessNotifyRoutine来进行。这个函数允许驱动程序登记一个回调函数,以便在进程创建和结束的时候被创建。
Kaspersky Flaw的都是翻译自Skywing的"What Were They Thinking? Anti-Virus Software Gone Wrong",只对其中关于Kaspersky的部分进行翻译。
本文探讨了Kaspersky Anti-Virus (KAV) 中存在的内核对象类型校验不足的问题,特别是针对NtResumeThread、NtSuspendThread 和 NtTerminateProcess 的系统服务挂钩。这些问题可能导致系统不稳定甚至崩溃。

983

被折叠的 条评论
为什么被折叠?



