国外安全技术高手谈卡巴斯基存在隐患

互联网 | 编辑: 杨剑锋 2007-06-18 09:30:00转载 返回原文

补丁更新服务问题

【编者按:这是国外高手所写的一篇认为卡巴斯基在其杀毒软件里使用了许多不安全技术的文章,我们将分期将其一一展现给大家。(原文发表于2006年6月)】

卡巴斯基发布了称为卡巴斯基互联网安全套件5.0的个人安全软件包。该软件包里含有很多个人安全软件程序,包括防火墙和杀毒软件。

我们这篇文章的核心就是要谈谈卡巴斯基的杀毒软件。跟很多其他的杀毒软件一样,卡巴斯基的杀毒软件也是既能手动操作扫描病毒也可以实时扫描病毒。

卡巴斯基的杀毒软件系统(KAV)在其内核层组件中使用了很多不安全的技术。这些技术中的一部分甚至有可能会导致系统安全遭到威胁。

问题所在

实时的补丁更新服务

尽管KAV看起来好像是使用文件过滤系统——这是一种标准的拦截文件存取操作的Windows机制(尤其像是为杀毒软件而设的),KAV还使用了一系列API层的函数钩子来拦截对文件的存取。如果某个函数在用户层下可以调用的话,那么执行那些内核层的钩子函数会非常危险,人们必须十分谨慎地验证所有的参数的可靠性(否则系统可能会受到未授权的程序的威胁)。另外,通常删除跟内核层相关联的代码也将面临很大危险,因为你很难保证没有线程在某个特定代码区域运行,也很难做到不对系统造成任何威胁就能解开钩子。KAV还跟很多其他的系统服务相关联,这是它所谓的保护进程不被反编译或者终止而进行的措施。

不幸的是,KAV程序员们并没有正确的验证那些与系统调用相关联的参数,因此导致在此处出现了很多的漏洞,最终这些漏洞使非授权的用户层程序能够造成系统的崩溃。这些漏洞中的一部分甚至允许人们扩大本地权限(尽管笔者并没有花时间来验证这点是否可行)。

KAV与下列系统服务相关联(在WinDbg中,把一个运行着KAV的系统里的 nt!KeServiceDescriptorTableShadow 跟一个干净系统里的相比较,你很容易就能发现这些关联)。

kd> dps poi ( nt!KeServiceDescriptorTableShadow ) l dwo ( nt!KeServiceDescriptorTableShadow + 8 )
8191c9c8  805862de nt!NtAcceptConnectPort
8191c9cc  8056fded nt!NtAccessCheck
...
8191ca2c  f823fd00 klif!KavNtClose
...
8191ca84  f823fa20 klif!KavNtCreateProcess
8191ca88  f823fb90 klif!KavNtCreateProcessEx
8191ca8c  80647b59 nt!NtCreateProfile
8191ca90  f823fe40 klif!KavNtCreateSection
8191ca94  805747cf nt!NtCreateSemaphore
8191ca98  8059d4db nt!NtCreateSymbolicLinkObject
8191ca9c  f8240630 klif!KavNtCreateThread
8191caa0  8059a849 nt!NtCreateTimer
...
8191cbb0  f823f7b0 klif!KavNtOpenProcess
...
8191cc24  f82402f0 klif!KavNtQueryInformationFile
...
8191cc7c  f8240430 klif!KavNtQuerySystemInformation
...
8191cd00  f82405e0 klif!KavNtResumeThread
...
8191cd58  f82421f0 klif!KavNtSetInformationProcess
...
8191cdc0  f8240590 klif!KavNtSuspendThread
...
8191cdcc  f82401c0 klif!KavNtTerminateProcess

另外,KAV还试图建立很多全新的系统服务,它通过更新服务描述表的方法,把这些服务变成调用系统内核模块的捷径。这显然不是允许用户模块程序与驱动程序交互的最佳机制;程序人员应当使用传统的IOCTL接口,该接口不用实时地更新内核结构,并且可以避免很多不便,比如从一个系统升级成另一个系统的正常的系统服务更新。

用户层指针验证不足

用户层指针验证不足

KAV安装的很多关联(甚至还包括很多定制的系统服务)都不断出现对系统来说很致命的漏洞。例如,KAV修改过的NTOpenProcess试图通过与固定值0x7fff0000相对照来判定用户地址是否合法。在大多数x86系统上,这个地址是低于最高用户地址的(即0x7FFEFFFF)。但是,把用户地址空间固定这种做法并不明智。例如,有个可以在boot.ini文件中进行设置的boot参数"/3GB",能改变默认的地址空间分配方式,即将2GB内核、2GB用户空间修改成1GB内核、3GB用户空间。如果运行KAV的系统被设置了/3GB,那么只要参数地址是位于用户地址空间的前2GB里,任何对NtOpenProcess的调用(比如Win32的OpenProcess)都会随机失败。

.text:F82237B0 ; NTSTATUS __stdcall KavNtOpenProcess(PHANDLE ProcessHandle,
	ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes,
	PCLIENT_ID ClientId)
.text:F82237B0 KavNtOpenProcess proc near              ; DATA XREF: sub_F82249D0+BFo
.
.
.
.text:F8223800    cmp     eax, 7FFF0000h  ; eax = ClientId
.text:F8223805    jbe     short loc_F822380D
.text:F8223807
.text:F8223807 loc_F8223807:                           ; CODE XREF: KavNtOpenProcess+4Ej
.text:F8223807    call    ds:ExRaiseAccessViolation

比较合适的、进行此项验证的方法应该是:使用带有SEH框架的ProbeForRead存档函数。这种功能在地址是非合法用户的地址的时候,会自动阻止其访问。

另外,很多KAV的定制系统服务都没有好好地验证用户层的指针自变量,而黑客们正可以利用这些自变量来使系统崩溃。

.text:F8222BE0 ; int __stdcall KAVService10(int,PVOID OutputBuffer,int)
.text:F8222BE0 KAVService10    proc near               ; DATA XREF: .data:F8227D14o
.text:F8222BE0
.text:F8222BE0 arg_0           = dword ptr  4
.text:F8222BE0 OutputBuffer    = dword ptr  8
.text:F8222BE0 arg_8           = dword ptr  0Ch
.text:F8222BE0
.text:F8222BE0    mov     edx, [esp+OutputBuffer]
.text:F8222BE4    push    esi
.text:F8222BE5    mov     esi, [esp+4+arg_8]
.text:F8222BE9    lea     ecx, [esp+4+arg_8]
.text:F8222BED    push    ecx             ; int
.text:F8222BEE    mov     eax, [esi]      ; 未验证的用户层指针访问
.text:F8222BF0    mov     [esp+8+arg_8], eax
.text:F8222BF4    push    eax             ; 输出缓存长度
.text:F8222BF5    mov     eax, [esp+0Ch+arg_0]
.text:F8222BF9    push    edx             ; 输出缓存
.text:F8222BFA    push    eax             ; int
.text:F8222BFB    call    sub_F821F9A0    ; 
                                          ; 该例行程序从内部假设所有的指针参数都是合法的。
.text:F8222C00    mov     edx, [esi]
.text:F8222C02    mov     ecx, [esp+4+arg_8]
.text:F8222C06    cmp     ecx, edx
.text:F8222C08    jbe     short loc_F8222C13
.text:F8222C0A    mov     eax, 0C0000173h
.text:F8222C0F    pop     esi
.text:F8222C10    retn    0Ch
.text:F8222C13 ; -----------------------------------------------------------------
.text:F8222C13
.text:F8222C13 loc_F8222C13:              ; CODE XREF: KAVService10+28j
.text:F8222C13    mov     [esi], ecx
.text:F8222C15    pop     esi
.text:F8222C16    retn    0Ch
.text:F8222C16 KAVService10    endp

.text:F8222C20 KAVService11    proc near               ; DATA XREF: .data:F8227D18o
.text:F8222C20
.text:F8222C20 arg_0           = dword ptr  4
.text:F8222C20 arg_4           = dword ptr  8
.text:F8222C20 arg_8           = dword ptr  0Ch
.text:F8222C20
.text:F8222C20    mov     edx, [esp+arg_4]
.text:F8222C24    push    esi
.text:F8222C25    mov     esi, [esp+4+arg_8]
.text:F8222C29    lea     ecx, [esp+4+arg_8]
.text:F8222C2D    push    ecx
.text:F8222C2E    mov     eax, [esi]      ; 非合法用户层指针的访问
.text:F8222C30    mov     [esp+8+arg_8], eax
.text:F8222C34    push    eax
.text:F8222C35    mov     eax, [esp+0Ch+arg_0]
.text:F8222C39    push    edx
.text:F8222C3A    push    eax
.text:F8222C3B    call    sub_F8214CE0    ; 这段例行程序在内部假设所有的指针参数都是合法的
                                          ; 所有的指针参数都是合法的
.text:F8222C40    test    eax, eax
.text:F8222C42    jnz     short loc_F8222C59
.text:F8222C44    mov     ecx, [esp+4+arg_8]
.text:F8222C48    mov     edx, [esi]
.text:F8222C4A    cmp     ecx, edx
.text:F8222C4C    jbe     short loc_F8222C57
.text:F8222C4E    mov     eax, STATUS_INVALID_BLOCK_LENGTH
.text:F8222C53    pop     esi
.text:F8222C54    retn    0Ch
.text:F8222C57 ; -------------------------------------------------------------------
.text:F8222C57
.text:F8222C57 loc_F8222C57:              ; CODE XREF: KAVService11+2Cj
.text:F8222C57    mov     [esi], ecx
.text:F8222C59
.text:F8222C59 loc_F8222C59:              ; CODE XREF: KAVService11+22j
.text:F8222C59    pop     esi
.text:F8222C5A    retn    0Ch
.text:F8222C5A KAVService11    endp

隐藏用户的线程

隐藏用户层的线程

然而,KAV的关联错误还不仅仅止于NtOpenProcess。KAV关联的另一个系统服务是NtQuerySystemInformation。这个例行程序被修改成当SystemProcessesAndThreads信息类被调用的时候就从某些进程中截断线程列表。这是一种潜在的机制,用户层可以接受进程以及所有运行在系统中的程序的线程列表,这就提供给KAV一种隐藏用户层线程的手段。KAV中存在这样一段代码——这件事本身就是不可思议的;在用户层隐藏正在运行着的代码这种行为是跟rootkits相联系的,这不是杀毒软件应该有的功能。

撇去隐藏运行代码这个潜在的威胁不谈,它还存在很多安全漏洞:

它使用用户层的NtQuerySystemInformation输出缓存——实际上内核层已经占用了这部分,但它不能防御恶意用户层的程序对该缓存的修改或释放。该项函数没有SEH框架,因此用户层的应用有可能导致KAV对空的内存进行操作。

这其中没有对返回的输出缓存的偏移地址进行验证,也就不能确定偏移地址是不是指向输出缓存之外的内存。显然这会造成问题,因为返回的数据结构实际上是子结构的一个列表,加一个偏移地址相当于给那个结构的地址加上了一个特殊构架,以便访问下一个结构。用户层可以修改这个偏移地址,让它实际上指向内核内存。由于钩子函数有时会把数据写入到它认为的用户输出缓存,那么黑客就有了一个有趣的、可以用来从非授权的用户层获得内核权限的方法。

.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 (隐藏进程线程)
.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
 
内核对象类型不恰当验证

内核对象类型不恰当验证

Windows一系列的“内核对象”暴露了内核的很多特性。这些对象有可能是由用户层通过句柄用户来执行的。句柄是整数值,内核会判断作为调用者进行交互的是哪个(通常是系统服务),然后将句柄解析成某个特定对象的指针。所有的对象都共享相同的句柄命名空间。

由于这些不同类型的对象共享着相同的命名空间,系统服务检查句柄的一项任务就是验证这些对象指向的是期望类型。这项工作是由对象管理例行程序bReferenceObjectByHandle完成的,这个例行程序会将句柄解析成对象指针并进行可选的嵌入类型检查,它是通过把标准对象页头中的类型域与需要验证的进行对照来完成检查的。

由于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没有验证用户层提供的对象句柄是否是真的指向一个进程对象。

由于种种原因,这样做很不安全,如果读者曾经进行过内核编程你就会深刻体会为什么这样不安全。

内核进程结构定义(EPROCESS)随着OS更换甚至是Servicepack的更换而频繁地改变。这样造成的结果就是,直接访问这样的结构通常都很不安全。

由于KAV不适当地执行类型核实操作,因此很有可能将一个对象句柄传递给不同的内核对象——比如,mutex——于是因为mutex的内部结构(或者任何其它的内核对象)与进程对象的内部结构不兼容,KAV将导致系统崩溃。

对于上一个问题,KAV想用如下方法应对:它试图实时地找出包含进程名称的EPROCESS结构成员的偏移地址。它所使用的算法是,每次从进程对象指针的开始提前扫描一字节,直到它发现用来识别初始系统进程名字的那个字节串。(这个例行程序在初始系统进程环境中被调用)。与杀毒软件、还有其他利用与进程关联的像文件名称的那些低端产品比起来,这种例行程序似乎很普通。

.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就可以通过读取返回的对象body指针,得到被破坏的进程名字。如果一个对象不是进程对象的话,这种方法是无法应付到对象结构末尾的(与一些对象比起来,进程对象非常巨大,比如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进程不会被终止,即使是合法的系统管理员也无法终止KAV进程——这种功能更像是恶意软件,比如Rootkits,而不是商用软件应该有的功能。对此的合理解释可能是,它想要阻止病毒删除扫描病毒的进程,尽管如此,我们还是想知道如果KAV实时扫描机制真的如广告所说的那么有用话,那么这种情况发生的可能性究竟有多高?

另外,KAV似乎只在进程与系统服务的关联终止前才跟踪其状态。而恰当的方法应当是通过PsSetCreateProcessNotifyRoutine这个有记录的内核功能来进行,该功能允许驱动程序注册那些进程创建和进程退出时被调用的callback函数。

为非输出的、非系统服务的内核函数打补丁

为非输出的、非系统服务的内核函数打补丁

然而,KAV的内核补丁程序不仅限于系统服务。KAV安装的最危险的钩子函数就是nt!SwapContext功能中的一个,该钩子不是输出的也不是系统服务(因此没有可靠的驱动代码检测机制,更不用说验证代码指纹了)。为了执行一些内部记录任务,内核在每个相关的变更时调用nt!SwapContext。

因为如此关键,而又是非输出、差不多没有代码指纹的、不可靠机制的内核功能打补丁,就我看来,这并不是什么好主意。更糟的是,KAV实际上还修改nt!SwapContext中的代码,而不是从函数的开始处打补丁,并且对使用该内核函数的内部注册机以及堆栈也好像是这么操作的。

kd> u nt!SwapContext
nt!SwapContext:
804db924 0ac9             or      cl,cl
804db926 26c6462d02       mov     byte ptr es:[esi+0x2d],0x2
804db92b 9c               pushfd
804db92c 8b0b             mov     ecx,[ebx]
804db92e e9dd69d677       jmp     klif!KavSwapContext (f8242310)
The unmodified nt!SwapContext has code that runs along the lines of this: 


lkd> u nt!SwapContext
nt!SwapContext:
80540ab0 0ac9             or      cl,cl
80540ab2 26c6462d02       mov     byte ptr es:[esi+0x2d],0x2
80540ab7 9c               pushfd
80540ab8 8b0b             mov     ecx,[ebx]
80540aba 83bb9409000000   cmp     dword ptr [ebx+0x994],0x0
80540ac1 51               push    ecx
80540ac2 0f8535010000     jne     nt!SwapContext+0x14d (80540bfd)
80540ac8 833d0ca0558000 cmp dword ptr [nt!PPerfGlobalGroupMask (8055a00c)],0x0

这是非常危险的一种打包操作,原因有如下几点:

nt!SwapContext是非常热门的代码路径,每次环境变更时都会调用它。这样一来,很难做到实时给它打补丁,并且不冒使系统崩溃的风险。KAV在单核系统上解决此类与给此函数打补丁相关的同步问题的方法是:完全关闭中断,但是,在多核系统上,这个方法并不总是有效。KAV在多核系统上并没有去努力解决这个问题,它使这些系统面临在KAV打补丁的时候会随机地出现启动失败的风险。

准确地找到该函数的位置,并找到所有已发布的和将来出现的系统版本的注册机和堆栈用法(以及指令布局),这些都是不可能操作完成的,而KAV却恰恰试图做这样的事情。这使得KAV用户不得不担心新的系统更新,由于KAV的钩子代码所做的假设与环境变更的进程不相容,那些更新就有可能使他们的系统启动失败。

另外,为了在内核上执行代码补丁,KAV调整了内核代码的页保护设置,使其可以通过直接修改PTE属性而不是使用文档函数(该函数可以锁住访问内部内存管理结构的语义逻辑)变得可以写入。

KAV nt!SwapContext patching: 
KAV nt!SwapContext补丁:

.text:F82264EA    mov     eax, 90909090h  ; Build the code to be written to nt!SwapContext
.text:F82264EF    mov     [ebp+var_38], eax
.text:F82264F2    mov     [ebp+var_34], eax
.text:F82264F5    mov     [ebp+var_30], ax
.text:F82264F9    mov     byte ptr [ebp+var_38], 0E9h
.text:F82264FD    mov     ecx, offset KavSwapContext
.text:F8226502    sub     ecx, ebx
.text:F8226504    sub     ecx, 5
.text:F8226507    mov     [ebp+var_38+1], ecx
.text:F822650A    mov     ecx, [ebp+var_1C]
.text:F822650D    lea     edx, [ecx+ebx]
.text:F8226510    mov     dword_F8228338, edx
.text:F8226516    mov     esi, ebx
.text:F8226518    mov     edi, offset unk_F8227DBC
.text:F822651D    mov     eax, ecx
.text:F822651F    shr     ecx, 2
.text:F8226522    rep movsd
.text:F8226524    mov     ecx, eax
.text:F8226526    and     ecx, 3
.text:F8226529    rep movsb
.text:F822652B    lea     ecx, [ebp+var_48] ; 使nt!SwapContext可通过直接访问而写入
.text:F822652B               ; the PTEs.
.text:F822652E    push    ecx
.text:F822652F    push    1
.text:F8226531    push    ebx
.text:F8226532    call    ModifyPteAttributes
.text:F8226537    test    al, al
.text:F8226539    jz      short loc_F8226588
.text:F822653B    mov     ecx, offset KavInternalSpinLock
.text:F8226540    call    KavSpinLockAcquire ; Disable interrupts
.text:F8226545    mov     ecx, [ebp+var_1C] ; Write to kernel code
.text:F8226548    lea     esi, [ebp+var_38]
.text:F822654B    mov     edi, ebx
.text:F822654D    mov     edx, ecx
.text:F822654F    shr     ecx, 2
.text:F8226552    rep movsd
.text:F8226554    mov     ecx, edx
.text:F8226556    and     ecx, 3
.text:F8226559    rep movsb
.text:F822655B    mov     edx, eax
.text:F822655D    mov     ecx, offset KavInternalSpinLock
.text:F8226562    call    KavSpinLockRelease ; Reenable interrupts
.text:F8226567    lea     eax, [ebp+var_48] ; Restore the original PTE attributes.
.text:F822656A    push    eax
.text:F822656B    mov     ecx, [ebp+var_48]
.text:F822656E    push    ecx
.text:F822656F    push    ebx
.text:F8226570    call    ModifyPteAttributes
.text:F8226575    mov     al, 1
.text:F8226577    mov     ecx, [ebp+var_10]
.text:F822657A    mov     large fs:0, ecx
.text:F8226581    pop     edi
.text:F8226582    pop     esi
.text:F8226583    pop     ebx
.text:F8226584    mov     esp, ebp
.text:F8226586    pop     ebp
.text:F8226587    retn
KavSpinLockAcquire subroutine (disables interrupts): 


.text:F8221240 KavSpinLockAcquire proc near            ; CODE XREF: sub_F8225690+D7p
.text:F8221240               ; sub_F8225D50+8Cp ...
.text:F8221240    pushf
.text:F8221241    pop     eax
.text:F8221242
.text:F8221242 loc_F8221242:              ; CODE XREF: KavSpinLockAcquire+13j
.text:F8221242    cli
.text:F8221243    lock bts dword ptr [ecx], 0
.text:F8221248    jb      short loc_F822124B
.text:F822124A    retn
.text:F822124B ; ------------------------------------------------------------
.text:F822124B
.text:F822124B loc_F822124B:              ; CODE XREF: KavSpinLockAcquire+8j
.text:F822124B    push    eax
.text:F822124C    popf
.text:F822124D
.text:F822124D loc_F822124D:              ; CODE XREF: KavSpinLockAcquire+17j
.text:F822124D    test    dword ptr [ecx], 1
.text:F8221253    jz      short loc_F8221242
.text:F8221255    pause
.text:F8221257    jmp     short loc_F822124D
.text:F8221257 KavSpinLockAcquire endp
KavSpinLockRelease subroutine (reenables interrupts): 


.text:F8221260 KavSpinLockRelease proc near            ; CODE XREF: sub_F8225690+F2p
.text:F8221260               ; sub_F8225D50+BAp ...
.text:F8221260    mov     dword ptr [ecx], 0
.text:F8221266    push    edx
.text:F8221267    popf
.text:F8221268    retn
.text:F8221268 KavSpinLockRelease endp
ModifyPteAttributes subroutine: 


.text:F82203C0 ModifyPteAttributes proc near           ; CODE XREF: sub_F821A9D0+91p
.text:F82203C0                                         ; sub_F8220950+43p ...
.text:F82203C0
.text:F82203C0 var_24          = dword ptr -24h
.text:F82203C0 var_20          = byte ptr -20h
.text:F82203C0 var_1C          = dword ptr -1Ch
.text:F82203C0 var_18          = dword ptr -18h
.text:F82203C0 var_10          = dword ptr -10h
.text:F82203C0 var_4           = dword ptr -4
.text:F82203C0 arg_0           = dword ptr  8
.text:F82203C0 arg_4           = byte ptr  0Ch
.text:F82203C0 arg_8           = dword ptr  10h
.text:F82203C0
.text:F82203C0    push    ebp
.text:F82203C1    mov     ebp, esp
.text:F82203C3    push    0FFFFFFFFh
.text:F82203C5    push    offset dword_F8212180
.text:F82203CA    push    offset _except_handler3
.text:F82203CF    mov     eax, large fs:0
.text:F82203D5    push    eax
.text:F82203D6    mov     large fs:0, esp
.text:F82203DD    sub     esp, 14h
.text:F82203E0    push    ebx
.text:F82203E1    push    esi
.text:F82203E2    push    edi
.text:F82203E3    mov     [ebp+var_18], esp
.text:F82203E6    xor     ebx, ebx
.text:F82203E8    mov     [ebp+var_20], bl
.text:F82203EB    mov     esi, [ebp+arg_0]
.text:F82203EE    mov     ecx, esi
.text:F82203F0    call    KavGetEflags
.text:F82203F5    push    esi
.text:F82203F6    call    KavGetPte       ; 在这里的功能指针将被实时填充
.text:F82203F6               ; 因系统是否有PAE而不同
.text:F82203F6               ; 使能或者使不能。
.text:F82203FC    mov     edi, eax
.text:F82203FE    mov     [ebp+var_1C], edi
.text:F8220401    cmp     edi, 0FFFFFFFFh
.text:F8220404    jz      short loc_F8220458
.text:F8220406    mov     [ebp+var_4], ebx
.text:F8220409    mov     ecx, esi
.text:F822040B    call    KavGetEflags
.text:F8220410    mov     eax, [edi]
.text:F8220412    test    al, 1
.text:F8220414    jz      short loc_F8220451
.text:F8220416    mov     ecx, eax
.text:F8220418    mov     [ebp+var_24], ecx
.text:F822041B    cmp     [ebp+arg_4], bl
.text:F822041E    jz      short loc_F8220429
.text:F8220420    mov     eax, [ebp+var_1C]
.text:F8220423    lock or dword ptr [eax], 2
.text:F8220427    jmp     short loc_F8220430
.text:F8220429 ; -------------------------------------------------------------
.text:F8220429
.text:F8220429 loc_F8220429:              ; CODE XREF: ModifyPteAttributes+5Ej
.text:F8220429    mov     eax, [ebp+var_1C]
.text:F822042C    lock and dword ptr [eax], 0FFFFFFFDh
.text:F8220430
.text:F8220430 loc_F8220430:              ; CODE XREF: ModifyPteAttributes+67j
.text:F8220430    mov     eax, [ebp+arg_8]
.text:F8220433    cmp     eax, ebx
.text:F8220435    jz      short loc_F822043C
.text:F8220437    and     ecx, 2
.text:F822043A    mov     [eax], cl
.text:F822043C
.text:F822043C loc_F822043C:              ; CODE XREF: ModifyPteAttributes+75j
.text:F822043C    mov     [ebp+var_20], 1
.text:F8220440    mov     eax, [ebp+arg_0]
.text:F8220443    invlpg  byte ptr [eax]
.text:F8220446    jmp     short loc_F8220451
.text:F8220448 ; ---------------------------------------------------------------------------
.text:F8220448
.text:F8220448 loc_F8220448:              ; DATA XREF: .text:F8212184o
.text:F8220448    mov     eax, 1
.text:F822044D    retn
.text:F822044E ; ---------------------------------------------------------------------------
.text:F822044E
.text:F822044E loc_F822044E:              ; DATA XREF: .text:F8212188o
.text:F822044E    mov     esp, [ebp-18h]
.text:F8220451
.text:F8220451 loc_F8220451:              ; CODE XREF: ModifyPteAttributes+54j
.text:F8220451               ; ModifyPteAttributes+86j
.text:F8220451    mov     [ebp+var_4], 0FFFFFFFFh
.text:F8220458
.text:F8220458 loc_F8220458:              ; CODE XREF: ModifyPteAttributes+44j
.text:F8220458    mov     al, [ebp+var_20]
.text:F822045B    mov     ecx, [ebp+var_10]
.text:F822045E    mov     large fs:0, ecx
.text:F8220465    pop     edi
.text:F8220466    pop     esi
.text:F8220467    pop     ebx
.text:F8220468    mov     esp, ebp
.text:F822046A    pop     ebp
.text:F822046B    retn    0Ch
.text:F822046B ModifyPteAttributes endp
 

允许用户层代码访问内核内存

允许用户层代码访问内核内存

当前操作系统所使用的划分内核/用户的主要原则是:不允许用户层直接访问内核层的内存。这对维持系统稳定十分重要,比如它可以阻止有bug的用户层程序造成内核崩溃,甚至使整个系统崩溃。不幸的是,KAV的程序员们似乎认为这个原则根本不重要。

KAV所执行的不安全操作里最奇怪的地方就在于:它允许用户层直接调用他们内核驱动的一部分(在内核地址空间内!),而不是只加载用户层DLL(或者在目标进程中加载用户层代码)。

该机制似乎是用来在加载DLL时,调查这些DLL的——这种工作本来最好应当由PsSetLoadImageNotifyRoutine来完成。

创建新进程后,KAV给Kernel32.dll打上补丁,这样输出表就会指向所有的DLL——往调用内核层kav的驱动部分的thunk中加载例行程序(例如LoadLibraryA)。另外,KAV还修改它的代码的部分保护以及数据段,以便允许用户层进行读操作。

KAV设置了PsLoadImageNotifyRoutine关联来检测kernel32.dll的加载,以便确定什么时候给kernel32的输出表打补丁。笔者想知道为什么KAV不只在PsLoadImageNotifyRoutine中直接工作,而是费尽千辛万苦地通过允许用户层来调用内核层来进行LoadLibrary关联。

在新进程加载图片的时候,KAV会调用CheckInjectCodeForNewProcess函数,并且核查 kernel32是否被加载。如果Kernel32已经加载,那么它将安排APC队列给要执行补丁程序的进程。

.text:F82218B0 
; int __stdcall CheckInjectCodeForNewProcess(wchar_t *,PUCHAR ImageBase) .text:F82218B0 CheckInjectCodeForNewProcess proc near
; CODE XREF: KavLoadImageNotifyRoutine+B5p .text:F82218B0 ; KavDoKernel32Check+41p .text:F82218B0 .text:F82218B0 arg_0 = dword ptr 4 .text:F82218B0 ImageBase = dword ptr 8 .text:F82218B0 .text:F82218B0 mov al, byte_F82282F9 .text:F82218B5 push esi .text:F82218B6 test al, al .text:F82218B8 push edi .text:F82218B9 jz short loc_F8221936 .text:F82218BB mov eax, [esp+8+arg_0] .text:F82218BF push offset aKernel32_dll ; "kernel32.dll" .text:F82218C4 push eax ; wchar_t * .text:F82218C5 call ds:_wcsicmp .text:F82218CB add esp, 8 .text:F82218CE test eax, eax .text:F82218D0 jnz short loc_F8221936 .text:F82218D2 mov al, g_FoundKernel32Exports .text:F82218D7 mov edi, [esp+8+ImageBase] .text:F82218DB test al, al .text:F82218DD jnz short KavInitializePatchApcLabel .text:F82218DF push edi .text:F82218E0 call KavCheckFindKernel32Exports .text:F82218E5 test al, al .text:F82218E7 jz short loc_F8221936 .text:F82218E9 .text:F82218E9 KavInitializePatchApcLabel:
; CODE XREF: CheckInjectCodeForNewProcess+2Dj .text:F82218E9 push '3SeB' ; Tag .text:F82218EE push 30h ; NumberOfBytes .text:F82218F0 push 0 ; PoolType .text:F82218F2 call ds:ExAllocatePoolWithTag .text:F82218F8 mov esi, eax .text:F82218FA test esi, esi .text:F82218FC jz short loc_F8221936 .text:F82218FE push edi .text:F82218FF push 0 .text:F8221901 push offset KavPatchNewProcessApcRoutine .text:F8221906 push offset loc_F82218A0 .text:F822190B push offset loc_F8221890 .text:F8221910 push 0 .text:F8221912 call KeGetCurrentThread .text:F8221917 push eax .text:F8221918 push esi .text:F8221919 call KeInitializeApc .text:F822191E push 0 .text:F8221920 push 0 .text:F8221922 push 0 .text:F8221924 push esi .text:F8221925 call KeInsertQueueApc .text:F822192B test al, al .text:F822192D jnz short loc_F822193D .text:F822192F push esi ; P .text:F8221930 call ds:ExFreePool .text:F8221936 .text:F8221936 loc_F8221936:
; CODE XREF: CheckInjectCodeForNewProcess+9j .text:F8221936 ; CheckInjectCodeForNewProcess+20j ... .text:F8221936 pop edi .text:F8221937 xor al, al .text:F8221939 pop esi .text:F822193A retn 8 .text:F822193D ; -------------------------------------------------- .text:F822193D .text:F822193D loc_F822193D:
; CODE XREF: CheckInjectCodeForNewProcess+7Dj .text:F822193D pop edi .text:F822193E mov al, 1 .text:F8221940 pop esi .text:F8221941 retn 8

APC例行程序本身给kernel32的输出表打了补丁(并且生成thunk来调用内核层)并调整KAV的驱动镜像的PTE属性,以便允许用户层访问。

.text:F8221810 KavPatchNewProcessApcRoutine proc near 
; DATA XREF: CheckInjectCodeForNewProcess+51o .text:F8221810 .text:F8221810 var_8 = dword ptr -8 .text:F8221810 var_4 = dword ptr -4 .text:F8221810 ImageBase = dword ptr 8 .text:F8221810 .text:F8221810 push ebp .text:F8221811 mov ebp, esp .text:F8221813 sub esp, 8 .text:F8221816 mov eax, [ebp+ImageBase] .text:F8221819 push esi .text:F822181A push eax ; ImageBase .text:F822181B call KavPatchImageForNewProcess .text:F8221820 mov esi, dword_F8230518 .text:F8221826 mov eax, dword_F823051C .text:F822182B and esi, 0FFFFF000h .text:F8221831 cmp esi, eax .text:F8221833 mov [ebp+ImageBase], esi .text:F8221836 jnb short loc_F8221883 .text:F8221838 .text:F8221838 loc_F8221838: ; CODE XREF: KavPatchNewProcessApcRoutine+71j .text:F8221838 push esi .text:F8221839 call KavPageTranslation0 .text:F822183F push esi .text:F8221840 mov [ebp+var_8], eax .text:F8221843 call KavPageTranslation1 .text:F8221849 mov [ebp+var_4], eax .text:F822184C mov eax, [ebp+var_8] .text:F822184F lock or dword ptr [eax], 4 .text:F8221853 lock and dword ptr [eax], 0FFFFFEFFh .text:F822185A mov eax, [ebp+var_4] .text:F822185D invlpg byte ptr [eax] .text:F8221860 lock or dword ptr [eax], 4 .text:F8221864 lock and dword ptr [eax], 0FFFFFEFDh .text:F822186B mov eax, [ebp+ImageBase] .text:F822186E invlpg byte ptr [eax] .text:F8221871 mov eax, dword_F823051C .text:F8221876 add esi, 1000h .text:F822187C cmp esi, eax .text:F822187E mov [ebp+ImageBase], esi .text:F8221881 jb short loc_F8221838 .text:F8221883 .text:F8221883 loc_F8221883: ; CODE XREF: KavPatchNewProcessApcRoutine+26j .text:F8221883 pop esi .text:F8221884 mov esp, ebp .text:F8221886 pop ebp .text:F8221887 retn 0Ch .text:F8221887 KavPatchNewProcessApcRoutine endp .text:F8221750 ; int __stdcall KavPatchImageForNewProcess(PUCHAR ImageBase) .text:F8221750 KavPatchImageForNewProcess proc near
; CODE XREF: KavPatchNewProcessApcRoutine+Bp .text:F8221750 .text:F8221750 ImageBase = dword ptr 8 .text:F8221750 .text:F8221750 push ebx .text:F8221751 call ds:KeEnterCriticalRegion .text:F8221757 mov eax, dword_F82282F4 .text:F822175C push 1 ; Wait .text:F822175E push eax ; Resource .text:F822175F call ds:ExAcquireResourceExclusiveLite .text:F8221765 push 1 .text:F8221767 call KavSetPageAttributes1 .text:F822176C mov ecx, [esp+ImageBase] .text:F8221770 push ecx ; ImageBase .text:F8221771 call KavPatchImage .text:F8221776 push 0 .text:F8221778 mov bl, al .text:F822177A call KavSetPageAttributes1 .text:F822177F mov ecx, dword_F82282F4 ; Resource .text:F8221785 call ds:ExReleaseResourceLite .text:F822178B call ds:KeLeaveCriticalRegion .text:F8221791 mov al, bl .text:F8221793 pop ebx .text:F8221794 retn 4 .text:F8221794 KavPatchImageForNewProcess endp

真正的镜像补丁会保护 kernel32的输出表,它将输出地址表的入口改成LoadLibrary* 函数族,指向thunk,该thunk被写进Kernel32 镜像的空闲空间,并将真正的thunk代码写出:

.text:F8221680 ; int __stdcall KavPatchImage(PUCHAR ImageBase)
.text:F8221680 KavPatchImage   proc near  ; CODE XREF: KavPatchImageForNewProcess+21p
.text:F8221680
.text:F8221680 var_C           = dword ptr -0Ch
.text:F8221680 FunctionVa      = dword ptr -8
.text:F8221680 var_4           = dword ptr -4
.text:F8221680 ImageBase       = dword ptr  4
.text:F8221680
.text:F8221680    mov     eax, [esp+ImageBase]
.text:F8221684    sub     esp, 0Ch
.text:F8221687    push    ebp
.text:F8221688    push    3Ch
.text:F822168A    push    eax
.text:F822168B    call    KavReprotectExportTable
.text:F8221690    mov     ebp, eax
.text:F8221692    test    ebp, ebp
.text:F8221694    jnz     short loc_F822169F
.text:F8221696    xor     al, al
.text:F8221698    pop     ebp
.text:F8221699    add     esp, 0Ch
.text:F822169C    retn    4
.text:F822169F ; ----------------------------------
.text:F822169F
.text:F822169F loc_F822169F:              ; CODE XREF: KavPatchImage+14j
.text:F822169F    push    ebx
.text:F82216A0    push    esi
.text:F82216A1    push    edi
.text:F82216A2    xor     ebx, ebx
.text:F82216A4    mov     edi, ebp
.text:F82216A6    mov     esi, offset ExportedFunctionsToCheckTable
.text:F82216AB
.text:F82216AB CheckNextFunctionInTable:       ; CODE XREF: KavPatchImage+B4j
.text:F82216AB    mov     edx, [esi+0Ch]
.text:F82216AE    mov     eax, [esp+1Ch+ImageBase]
.text:F82216B2    lea     ecx, [esp+1Ch+var_C]
.text:F82216B6    push    ecx
.text:F82216B7    push    edx
.text:F82216B8    push    eax
.text:F82216B9    call    LookupExportedFunction
.text:F82216BE    test    eax, eax
.text:F82216C0    mov     [esp+1Ch+FunctionVa], eax
.text:F82216C4    jz      short loc_F8221725
.text:F82216C6    mov     edx, [esp+1Ch+var_C]
.text:F82216CA    lea     ecx, [esp+1Ch+var_4]
.text:F82216CE    push    ecx
.text:F82216CF    push    40h
.text:F82216D1    push    4
.text:F82216D3    push    edx
.text:F82216D4    call    KavExecuteNtProtectVirtualMemoryInt2E
.text:F82216D9    test    al, al
.text:F82216DB    jz      short loc_F8221725
.text:F82216DD    cmp     dword ptr [esi], 0
.text:F82216E0    jnz     short loc_F82216EF
.text:F82216E2    mov     eax, [esp+1Ch+FunctionVa]
.text:F82216E6    mov     ecx, [esp+1Ch+var_C]
.text:F82216EA    mov     [esi], eax
.text:F82216EC    mov     [esi+8], ecx
.text:F82216EF
.text:F82216EF loc_F82216EF:              ; CODE XREF: KavPatchImage+60j
.text:F82216EF    mov     eax, edi
.text:F82216F1    mov     edx, 90909090h
.text:F82216F6    mov     [eax], edx
.text:F82216F8    mov     [eax+4], edx
.text:F82216FB    mov     [eax+8], edx
.text:F82216FE    mov     [eax+0Ch], dx
.text:F8221702    mov     [eax+0Eh], dl
.text:F8221705    mov     byte ptr [edi], 0E9h
.text:F8221708    mov     ecx, [esi+4]
.text:F822170B    mov     edx, ebx
.text:F822170D    sub     ecx, ebx
.text:F822170F    sub     ecx, ebp
.text:F8221711    sub     ecx, 5
.text:F8221714    mov     [edi+1], ecx
.text:F8221717    mov     ecx, [esp+1Ch+ImageBase]
.text:F822171B    mov     eax, [esp+1Ch+var_C]
.text:F822171F    sub     edx, ecx
.text:F8221721    add     edx, ebp
.text:F8221723    mov     [eax], edx      ;
.text:F8221723               ; Patching Export Table here
.text:F8221723               ; e.g. write to 7c802f58
.text:F8221723               ; (kernel32 EAT entry for LoadLibraryA)
.text:F8221723               ;
.text:F8221723               ; 578  241 00001D77 LoadLibraryA = _LoadLibraryA@4
.text:F8221723               ;  579  242 00001D4F LoadLibraryExA = _LoadLibraryExA@12
.text:F8221723               ; 580  243 00001AF1 LoadLibraryExW = _LoadLibraryExW@12
.text:F8221723               ; 581  244 0000ACD3 LoadLibraryW = _LoadLibraryW@4
.text:F8221723               ;
.text:F8221723               ; KAV在这写入一个新的RVA指向它自己的钩子代码。
.text:F8221725
.text:F8221725 loc_F8221725:              ; CODE XREF: KavPatchImage+44j
.text:F8221725               ; KavPatchImage+5Bj
.text:F8221725    add     esi, 10h
.text:F8221728    add     ebx, 0Fh
.text:F822172B    add     edi, 0Fh
.text:F822172E    cmp     esi, offset byte_F82357E0
.text:F8221734    jb      CheckNextFunctionInTable
.text:F822173A    pop     edi
.text:F822173B    pop     esi
.text:F822173C    pop     ebx
.text:F822173D    mov     al, 1
.text:F822173F    pop     ebp
.text:F8221740    add     esp, 0Ch
.text:F8221743    retn    4
.text:F8221743 KavPatchImage   endp

KAV的输出表保护代码做了这样一个假设:用户层的PE页头是形式正确的并且不包含指向内核层地址的偏移地址:

.text:F8221360 KavReprotectExportTable proc near       ; CODE XREF: KavPatchImage+Bp
.text:F8221360
.text:F8221360 var_10          = dword ptr -10h
.text:F8221360 var_C           = dword ptr -0Ch
.text:F8221360 var_8           = dword ptr -8
.text:F8221360 var_4           = dword ptr -4
.text:F8221360 arg_0           = dword ptr  4
.text:F8221360 arg_4           = dword ptr  8
.text:F8221360
.text:F8221360    mov     eax, [esp+arg_0]
.text:F8221364    sub     esp, 10h
.text:F8221367    cmp     word ptr [eax], 'ZM'
.text:F822136C    push    ebx
.text:F822136D    push    ebp
.text:F822136E    push    esi
.text:F822136F    push    edi
.text:F8221370    jnz     loc_F8221442
.text:F8221376    mov     esi, [eax+3Ch]
.text:F8221379    add     esi, eax
.text:F822137B    mov     [esp+20h+var_C], esi
.text:F822137F    cmp     dword ptr [esi], 'EP'
.text:F8221385    jnz     loc_F8221442
.text:F822138B    lea     eax, [esp+20h+var_8]
.text:F822138F    xor     edx, edx
.text:F8221391    mov     dx, [esi+14h]
.text:F8221395    push    eax
.text:F8221396    xor     eax, eax
.text:F8221398    push    40h
.text:F822139A    mov     ax, [esi+6]
.text:F822139E    lea     ecx, [eax+eax*4]
.text:F82213A1    lea     eax, [edx+ecx*8+18h]
.text:F82213A5    push    eax
.text:F82213A6    push    esi
.text:F82213A7    call    KavExecuteNtProtectVirtualMemoryInt2E 
; NtProtectVirtualMemory .text:F82213AC test al, al .text:F82213AE jz loc_F8221442 .text:F82213B4 mov ecx, [esi+8] .text:F82213B7 mov [esp+20h+var_10], 0 .text:F82213BF inc ecx .text:F82213C0 mov [esi+8], ecx .text:F82213C3 xor ecx, ecx .text:F82213C5 mov cx, [esi+14h] .text:F82213C9 cmp word ptr [esi+6], 0 .text:F82213CE lea edi, [ecx+esi+18h] .text:F82213D2 jbe short loc_F8221442 .text:F82213D4 mov ebp, [esp+20h+arg_4] .text:F82213D8 .text:F82213D8 loc_F82213D8: ; CODE XREF: KavReprotectExportTable+E0j .text:F82213D8 mov ebx, [edi+10h] .text:F82213DB test ebx, 0FFFh .text:F82213E1 jz short loc_F82213EA .text:F82213E3 or ebx, 0FFFh .text:F82213E9 inc ebx .text:F82213EA .text:F82213EA loc_F82213EA: ; CODE XREF: KavReprotectExportTable+81j .text:F82213EA mov ecx, [edi+8] .text:F82213ED mov edx, ebx .text:F82213EF sub edx, ecx .text:F82213F1 cmp edx, ebp .text:F82213F3 jle short loc_F822142C .text:F82213F5 mov esi, [edi+0Ch] .text:F82213F8 mov ecx, [esp+20h+arg_0] .text:F82213FC sub esi, ebp .text:F82213FE push ebp .text:F82213FF add esi, ebx .text:F8221401 add esi, ecx .text:F8221403 push esi .text:F8221404 call KavFindSectionName .text:F8221409 test al, al .text:F822140B jz short loc_F8221428 .text:F822140D cmp dword ptr [edi+1], 'TINI' .text:F8221414 jz short loc_F8221428 .text:F8221416 lea eax, [esp+20h+var_4] .text:F822141A push eax .text:F822141B push 40h .text:F822141D push ebp .text:F822141E push esi .text:F822141F call KavExecuteNtProtectVirtualMemoryInt2E
; NtProtectVirtualMemory .text:F8221424 test al, al .text:F8221426 jnz short loc_F822144E .text:F8221428 .text:F8221428 loc_F8221428: ; CODE XREF: KavReprotectExportTable+ABj .text:F8221428 ; KavReprotectExportTable+B4j .text:F8221428 mov esi, [esp+20h+var_C] .text:F822142C .text:F822142C loc_F822142C: ; CODE XREF: KavReprotectExportTable+93j .text:F822142C mov eax, [esp+20h+var_10] .text:F8221430 xor ecx, ecx .text:F8221432 mov cx, [esi+6] .text:F8221436 add edi, 28h .text:F8221439 inc eax .text:F822143A cmp eax, ecx .text:F822143C mov [esp+20h+var_10], eax .text:F8221440 jb short loc_F82213D8 .text:F8221442 .text:F8221442 loc_F8221442: ; CODE XREF: KavReprotectExportTable+10j .text:F8221442 ; KavReprotectExportTable+25j ... .text:F8221442 pop edi .text:F8221443 pop esi .text:F8221444 pop ebp .text:F8221445 xor eax, eax .text:F8221447 pop ebx .text:F8221448 add esp, 10h .text:F822144B retn 8 .text:F822144E ; ------------------------------------------------ .text:F822144E .text:F822144E loc_F822144E: ; CODE XREF: KavReprotectExportTable+C6j .text:F822144E mov eax, [edi+8] .text:F8221451 mov [edi+10h], ebx .text:F8221454 add eax, ebp .text:F8221456 mov [edi+8], eax .text:F8221459 mov eax, esi .text:F822145B pop edi .text:F822145C pop esi .text:F822145D pop ebp .text:F822145E pop ebx .text:F822145F add esp, 10h .text:F8221462 retn 8 .text:F8221462 KavReprotectExportTable endp

KAV用来保护用户层代码的机制也是一种黑客机制。KAV动态地判定系统调用NtProtectVirtualMemory系统服务的序数,然后用它自己的int 2e thunk来调用该服务。

.text:F8221320 KavExecuteNtProtectVirtualMemoryInt2E proc near
.text:F8221320               ; CODE XREF: KavReprotectExportTable+47p
.text:F8221320               ; KavReprotectExportTable+BFp ...
.text:F8221320
.text:F8221320 arg_0           = dword ptr  4
.text:F8221320 arg_4           = dword ptr  8
.text:F8221320 arg_8           = dword ptr  0Ch
.text:F8221320 arg_C           = dword ptr  10h
.text:F8221320
.text:F8221320    mov     eax, [esp+arg_0]
.text:F8221324    mov     ecx, [esp+arg_C]
.text:F8221328    mov     edx, [esp+arg_8]
.text:F822132C    push    ebx
.text:F822132D    mov     [esp+4+arg_0], eax
.text:F8221331    push    ecx
.text:F8221332    lea     eax, [esp+8+arg_4]
.text:F8221336    push    edx
.text:F8221337    mov     edx, NtProtectVirtualMemoryOrdinal
.text:F822133D    lea     ecx, [esp+0Ch+arg_0]
.text:F8221341    push    eax
.text:F8221342    push    ecx
.text:F8221343    push    0FFFFFFFFh
.text:F8221345    push    edx
.text:F8221346    xor     bl, bl
.text:F8221348    call    KavInt2E
.text:F822134D    test    eax, eax
.text:F822134F    mov     al, 1
.text:F8221351    jge     short loc_F8221355
.text:F8221353    mov     al, bl
.text:F8221355
.text:F8221355 loc_F8221355: ; CODE XREF: KavExecuteNtProtectVirtualMemoryInt2E+31j
.text:F8221355    pop     ebx
.text:F8221356    retn    10h
.text:F8221356 KavExecuteNtProtectVirtualMemoryInt2E endp


.user:F8231090 KavInt2E        proc near  
; CODE XREF: KavExecuteNtProtectVirtualMemoryInt2E+28p .user:F8231090 .user:F8231090 arg_0 = dword ptr 8 .user:F8231090 arg_4 = dword ptr 0Ch .user:F8231090 .user:F8231090 push ebp .user:F8231091 mov ebp, esp .user:F8231093 mov eax, [ebp+arg_0] .user:F8231096 lea edx, [ebp+arg_4] .user:F823109C int 2Eh .user:F823109C .user:F823109E pop ebp .user:F823109F retn 18h .user:F823109F KavInt2E endp .user:F823109F

KAV的输出查询代码没有正确地验证就使用从PE页头储存的那些偏移地址:

.text:F8220CA0 LookupExportedFunction proc near        ; CODE XREF: sub_F8217A60+C9p
.text:F8220CA0               ; sub_F82181D0+Dp ...
.text:F8220CA0
.text:F8220CA0 var_20          = dword ptr -20h
.text:F8220CA0 var_1C          = dword ptr -1Ch
.text:F8220CA0 var_18          = dword ptr -18h
.text:F8220CA0 var_14          = dword ptr -14h
.text:F8220CA0 var_10          = dword ptr -10h
.text:F8220CA0 var_C           = dword ptr -0Ch
.text:F8220CA0 var_8           = dword ptr -8
.text:F8220CA0 var_4           = dword ptr -4
.text:F8220CA0 arg_0           = dword ptr  4
.text:F8220CA0 arg_4           = dword ptr  8
.text:F8220CA0 arg_8           = dword ptr  0Ch
.text:F8220CA0
.text:F8220CA0    mov     edx, [esp+arg_0]
.text:F8220CA4    sub     esp, 20h
.text:F8220CA7    cmp     word ptr [edx], 'ZM'
.text:F8220CAC    push    ebx
.text:F8220CAD    push    ebp
.text:F8220CAE    push    esi
.text:F8220CAF    push    edi
.text:F8220CB0    jnz     loc_F8220DE1
.text:F8220CB6    mov     eax, [edx+3Ch]
.text:F8220CB9    add     eax, edx
.text:F8220CBB    cmp     dword ptr [eax], 'EP'
.text:F8220CC1    jnz     loc_F8220DE1
.text:F8220CC7    mov     eax, [eax+78h]
.text:F8220CCA    mov     edi, [esp+30h+arg_4]
.text:F8220CCE    add     eax, edx
.text:F8220CD0    mov     [esp+30h+var_14], eax
.text:F8220CD4    mov     esi, [eax+1Ch]
.text:F8220CD7    mov     ebx, [eax+24h]
.text:F8220CDA    mov     ecx, [eax+20h]
.text:F8220CDD    add     esi, edx
.text:F8220CDF    add     ebx, edx
.text:F8220CE1    add     ecx, edx
.text:F8220CE3    cmp     edi, 1000h
.text:F8220CE9    mov     [esp+30h+var_4], esi
.text:F8220CED    mov     [esp+30h+var_C], ebx
.text:F8220CF1    mov     [esp+30h+var_18], ecx
.text:F8220CF5    jnb     short loc_F8220D27
.text:F8220CF7    mov     ecx, [eax+10h]
.text:F8220CFA    mov     eax, edi
.text:F8220CFC    sub     eax, ecx
.text:F8220CFE    mov     eax, [esi+eax*4]
.text:F8220D01    add     eax, edx
.text:F8220D03    mov     edx, [esp+30h+arg_8]
.text:F8220D07    test    edx, edx
.text:F8220D09    jz      loc_F8220DE3
.text:F8220D0F    mov     ebx, ecx
.text:F8220D11    shl     ebx, 1Eh
.text:F8220D14    sub     ebx, ecx
.text:F8220D16    add     ebx, edi
.text:F8220D18    pop     edi
.text:F8220D19    lea     ecx, [esi+ebx*4]
.text:F8220D1C    pop     esi
.text:F8220D1D    pop     ebp
.text:F8220D1E    mov     [edx], ecx
.text:F8220D20    pop     ebx
.text:F8220D21    add     esp, 20h
.text:F8220D24    retn    0Ch
.text:F8220D27 ; ----------------------------------------------
.text:F8220D27
.text:F8220D27 loc_F8220D27:              ; CODE XREF: LookupExportedFunction+55j
.text:F8220D27    mov     edi, [eax+14h]
.text:F8220D2A    mov     [esp+30h+arg_0], 0
.text:F8220D32    test    edi, edi
.text:F8220D34    mov     [esp+30h+var_8], edi
.text:F8220D38    jbe     loc_F8220DE1
.text:F8220D3E    mov     [esp+30h+var_1C], esi
.text:F8220D42
.text:F8220D42 loc_F8220D42:              ; CODE XREF: LookupExportedFunction+13Bj
.text:F8220D42    cmp     dword ptr [esi], 0
.text:F8220D45    jz      short loc_F8220DC5
.text:F8220D47    mov     ecx, [eax+18h]
.text:F8220D4A    xor     ebp, ebp
.text:F8220D4C    test    ecx, ecx
.text:F8220D4E    mov     [esp+30h+var_10], ecx
.text:F8220D52    jbe     short loc_F8220DC5
.text:F8220D54    mov     edi, [esp+30h+var_18]
.text:F8220D58    mov     [esp+30h+var_20], ebx
.text:F8220D5C
.text:F8220D5C loc_F8220D5C:              ; CODE XREF: LookupExportedFunction+11Bj
.text:F8220D5C    mov     ebx, [esp+30h+var_20]
.text:F8220D60    xor     esi, esi
.text:F8220D62    mov     si, [ebx]
.text:F8220D65    mov     ebx, [esp+30h+arg_0]
.text:F8220D69    cmp     esi, ebx
.text:F8220D6B    jnz     short loc_F8220DAA
.text:F8220D6D    mov     eax, [edi]
.text:F8220D6F    mov     esi, [esp+30h+arg_4]
.text:F8220D73    add     eax, edx
.text:F8220D75
.text:F8220D75 loc_F8220D75:              ; CODE XREF: LookupExportedFunction+F3j
.text:F8220D75    mov     bl, [eax]
.text:F8220D77    mov     cl, bl
.text:F8220D79    cmp     bl, [esi]
.text:F8220D7B    jnz     short loc_F8220D99
.text:F8220D7D    test    cl, cl
.text:F8220D7F    jz      short loc_F8220D95
.text:F8220D81    mov     bl, [eax+1]
.text:F8220D84    mov     cl, bl
.text:F8220D86    cmp     bl, [esi+1]
.text:F8220D89    jnz     short loc_F8220D99
.text:F8220D8B    add     eax, 2
.text:F8220D8E    add     esi, 2
.text:F8220D91    test    cl, cl
.text:F8220D93    jnz     short loc_F8220D75
.text:F8220D95
.text:F8220D95 loc_F8220D95:              ; CODE XREF: LookupExportedFunction+DFj
.text:F8220D95    xor     eax, eax
.text:F8220D97    jmp     short loc_F8220D9E
.text:F8220D99 ; ---------------------------------------------
.text:F8220D99
.text:F8220D99 loc_F8220D99:              ; CODE XREF: LookupExportedFunction+DBj
.text:F8220D99               ; LookupExportedFunction+E9j
.text:F8220D99    sbb     eax, eax
.text:F8220D9B    sbb     eax, 0FFFFFFFFh
.text:F8220D9E
.text:F8220D9E loc_F8220D9E:              ; CODE XREF: LookupExportedFunction+F7j
.text:F8220D9E    test    eax, eax
.text:F8220DA0    jz      short loc_F8220DED
.text:F8220DA2    mov     eax, [esp+30h+var_14]
.text:F8220DA6    mov     ecx, [esp+30h+var_10]
.text:F8220DAA
.text:F8220DAA loc_F8220DAA:              ; CODE XREF: LookupExportedFunction+CBj
.text:F8220DAA    mov     esi, [esp+30h+var_20]
.text:F8220DAE    inc     ebp
.text:F8220DAF    add     esi, 2
.text:F8220DB2    add     edi, 4
.text:F8220DB5    cmp     ebp, ecx
.text:F8220DB7    mov     [esp+30h+var_20], esi
.text:F8220DBB    jb      short loc_F8220D5C
.text:F8220DBD    mov     ebx, [esp+30h+var_C]
.text:F8220DC1    mov     edi, [esp+30h+var_8]
.text:F8220DC5
.text:F8220DC5 loc_F8220DC5:              ; CODE XREF: LookupExportedFunction+A5j
.text:F8220DC5               ; LookupExportedFunction+B2j
.text:F8220DC5    mov     ecx, [esp+30h+arg_0]
.text:F8220DC9    mov     esi, [esp+30h+var_1C]
.text:F8220DCD    inc     ecx
.text:F8220DCE    add     esi, 4
.text:F8220DD1    cmp     ecx, edi
.text:F8220DD3    mov     [esp+30h+arg_0], ecx
.text:F8220DD7    mov     [esp+30h+var_1C], esi
.text:F8220DDB    jb      loc_F8220D42
.text:F8220DE1
.text:F8220DE1 loc_F8220DE1:              ; CODE XREF: LookupExportedFunction+10j
.text:F8220DE1               ; LookupExportedFunction+21j ...
.text:F8220DE1    xor     eax, eax
.text:F8220DE3
.text:F8220DE3 loc_F8220DE3:              ; CODE XREF: LookupExportedFunction+69j
.text:F8220DE3               ; LookupExportedFunction+162j
.text:F8220DE3    pop     edi
.text:F8220DE4    pop     esi
.text:F8220DE5    pop     ebp
.text:F8220DE6    pop     ebx
.text:F8220DE7    add     esp, 20h
.text:F8220DEA    retn    0Ch
.text:F8220DED ; ---------------------------------------------
.text:F8220DED
.text:F8220DED loc_F8220DED:              ; CODE XREF: LookupExportedFunction+100j
.text:F8220DED    mov     eax, [esp+30h+var_4]
.text:F8220DF1    mov     ecx, [esp+30h+arg_0]
.text:F8220DF5    lea     ecx, [eax+ecx*4]
.text:F8220DF8    mov     eax, [ecx]
.text:F8220DFA    add     eax, edx
.text:F8220DFC    mov     edx, [esp+30h+arg_8]
.text:F8220E00    test    edx, edx
.text:F8220E02    jz      short loc_F8220DE3
.text:F8220E04    pop     edi
.text:F8220E05    pop     esi
.text:F8220E06    pop     ebp
.text:F8220E07    mov     [edx], ecx
.text:F8220E09    pop     ebx
.text:F8220E0A    add     esp, 20h
.text:F8220E0D    retn    0Ch
.text:F8220E0D LookupExportedFunction endp

未经过 ring 0 转换就在用户模式下直接调用 KAV 核心代码:

kd> bp f824d820
kd> g
Breakpoint 0 hit
klif!sub_F8231820:
001b:f824d820 83ec08      sub     esp,0x8
kd> kv
ChildEBP RetAddr  Args to Child    
          
WARNING: Stack unwind information not available. Following frames may be wrong.

0006f4ec 7432f69c 74320000 00000001 00000000 klif!sub_F8231820
0006f50c 7c9011a7 74320000 00000001 00000000 0x7432f69c
0006f52c 7c91cbab 7432f659 74320000 00000001 
ntdll!LdrpCallInitRoutine+0x14 0006f634 7c916178 00000000 c0150008 00000000
ntdll!LdrpRunInitializeRoutines+0x344 (FPO: [Non-Fpo]) 0006f8e0 7c9162da 00000000 0007ced0 0006fbd4
ntdll!LdrpLoadDll+0x3e5 (FPO: [Non-Fpo]) 0006fb88 7c801bb9 0007ced0 0006fbd4 0006fbb4
ntdll!LdrLoadDll+0x230 (FPO: [Non-Fpo]) 0006fc20 f824d749 0106c0f0 0000000e 0107348c 0x7c801bb9 0006fd14 7c918dfa 7c90d625 7c90eacf 00000000 klif!loc_F823173D+0xc 0006fe00 7c910551 000712e8 00000044 0006ff0c
ntdll!_LdrpInitialize+0x246 (FPO: [Non-Fpo]) 0006fecc 00000000 00072368 00000000 00078c48
ntdll!RtlFreeHeap+0x1e9 (FPO: [Non-Fpo]) kd> t klif!sub_F8231820+0x3: 001b:f824d823 53 push ebx kd> r eax=0006f3cc ebx=00000000 ecx=00005734 edx=0006f3ea esi=7c882fd3 edi=7432f608 eip=f824d823 esp=0006ef00 ebp=0006f4ec iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206 klif!sub_F8231820+0x3: 001b:f824d823 53 push ebx kd> dg 1b P Si Gr Pr Lo Sel Base Limit Type l ze an es ng Flags ---- -------- -------- ---------- - -- -- -- -- -------- 001B 00000000 ffffffff Code RE 3 Bg Pg P Nl 00000cfa kd> !pte eip VA f824d823 PDE at C0300F80 PTE at C03E0934 contains 01010067 contains 06B78065 pfn 1010 ---DA--UWEV pfn 6b78 ---DA--UREV

当在用户调用模式下逐步转换它的核心模式代码时KAV便在开始破坏系统了 (毕竟很明显这是不可靠的!):

Breakpoint 0 hit
klif!sub_F8231820:
001b:f824d820 83ec08      sub     esp,0x8
kd> u eip
klif!sub_F8231820:
f824d820 ebfe             jmp     klif!sub_F8231820 (f824d820)
f824d822 085355           or      [ebx+0x55],dl
f824d825 56               push    esi
f824d826 57               push    edi
f824d827 33ed             xor     ebp,ebp
f824d829 6820d824f8       push    0xf824d820
f824d82e 896c2418         mov     [esp+0x18],ebp
f824d832 896c2414         mov     [esp+0x14],ebp
kd> g
Breakpoint 0 hit
klif!sub_F8231820:
001b:f824d820 ebfe        jmp     klif!sub_F8231820 (f824d820)
kd> g
Breakpoint 0 hit
klif!sub_F8231820:
001b:f824d820 ebfe        jmp     klif!sub_F8231820 (f824d820)
kd> bd 0
kd> g
Break instruction exception - code 80000003 (first chance)
*******************************************************************************
*                                                                             *
*   You are seeing this message because you pressed either                    *
*       CTRL+C (if you run kd.exe) or,                                        *
*       CTRL+BREAK (if you run WinDBG),                                       *
*   on your debugger machine's keyboard.                                      *
*                                                                             *
*                   THIS IS NOT A BUG OR A SYSTEM CRASH                       *
*                                                                             *
* If you did not intend to break into the debugger, press the "g" key, then   *
* press the "Enter" key now.  This message might immediately reappear.  If it *
* does, press "g" and "Enter" again.                                          *
*                                                                             *
*******************************************************************************
nt!RtlpBreakWithStatusInstruction:
804e3592 cc               int     3
kd> gu

*** Fatal System Error: 0x000000d1
                       (0x00003592,0x0000001C,0x00000000,0x00003592)

Break instruction exception - code 80000003 (first chance)
*******************************************************************************
*                                                                             *
*   You are seeing this message because you pressed either                    *
*       CTRL+C (if you run kd.exe) or,                                        *
*       CTRL+BREAK (if you run WinDBG),                                       *
*   on your debugger machine's keyboard.                                      *
*                                                                             *
*                   THIS IS NOT A BUG OR A SYSTEM CRASH                       *
*                                                                             *
* If you did not intend to break into the debugger, press the "g" key, then   *
* press the "Enter" key now.  This message might immediately reappear.  If it *
* does, press "g" and "Enter" again.                                          *
*                                                                             *
*******************************************************************************
nt!RtlpBreakWithStatusInstruction:
804e3592 cc               int     3
kd> g
Break instruction exception - code 80000003 (first chance)

A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.

A fatal system error has occurred.

Connected to Windows XP 2600 x86 compatible target, ptr64 FALSE
Loading Kernel Symbols
..........................
Loading User Symbols
................................
Loading unloaded module list
............
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

Use !analyze -v to get detailed debugging information.

BugCheck D1, {3592, 1c, 0, 3592}

*** ERROR: Module load completed but symbols could not be loaded for klif.sys
Probably caused by : hardware

Followup: MachineOwner
---------
 *** Possible invalid call from 804e331f ( nt!KeUpdateSystemTime+0x160 )
 *** Expected target 804e358e ( nt!DbgBreakPointWithStatus+0x0 )

nt!RtlpBreakWithStatusInstruction:
804e3592 cc               int     3
kd> !analyze -v
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high.  This is usually
caused by drivers using improper addresses.
If kernel debugger is available get stack backtrace.
Arguments:
Arg1: 00003592, memory referenced
Arg2: 0000001c, IRQL
Arg3: 00000000, value 0 = read operation, 1 = write operation
Arg4: 00003592, address which referenced memory

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


READ_ADDRESS:  00003592 

CURRENT_IRQL:  1c

FAULTING_IP: 
+3592
00003592 ??               ???

PROCESS_NAME:  winlogon.exe

DEFAULT_BUCKET_ID:  INTEL_CPU_MICROCODE_ZERO

BUGCHECK_STR:  0xD1

LAST_CONTROL_TRANSFER:  from 804e3324 to 00003592

FAILED_INSTRUCTION_ADDRESS: 
+3592
00003592 ??               ???

POSSIBLE_INVALID_CONTROL_TRANSFER:  from 804e331f to 804e358e

TRAP_FRAME:  f7872ce0 -- (.trap fffffffff7872ce0)
ErrCode = 00000000
eax=00000001 ebx=000275fc ecx=8055122c edx=000003f8 esi=00000005 edi=ddfff298
eip=00003592 esp=f7872d54 ebp=f7872d64 iopl=0         nv up ei pl nz na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010202
00003592 ??               ???
Resetting default scope

STACK_TEXT:  
WARNING: Frame IP not in any known module. Following frames may be wrong.
f7872d50 804e3324 00000001 f7872d00 000000d1 0x3592
f7872d50 f824d820 00000001 f7872d00 000000d1 nt!KeUpdateSystemTime+0x165
0006f4ec 7432f69c 74320000 00000001 00000000 klif+0x22820
0006f50c 7c9011a7 74320000 00000001 00000000 ODBC32!_DllMainCRTStartup+0x52
0006f52c 7c91cbab 7432f659 74320000 00000001 ntdll!LdrpCallInitRoutine+0x14
0006f634 7c916178 00000000 c0150008 00000000 ntdll!LdrpRunInitializeRoutines+0x344
0006f8e0 7c9162da 00000000 0007ced0 0006fbd4 ntdll!LdrpLoadDll+0x3e5
0006fb88 7c801bb9 0007ced0 0006fbd4 0006fbb4 ntdll!LdrLoadDll+0x230
0006fbf0 7c801d6e 7ffddc00 00000000 00000000 kernel32!LoadLibraryExW+0x18e
0006fc04 7c801da4 0106c0f0 00000000 00000000 kernel32!LoadLibraryExA+0x1f
0006fc20 f824d749 0106c0f0 0000000e 0107348c kernel32!LoadLibraryA+0x94
00000000 00000000 00000000 00000000 00000000 klif+0x22749


STACK_COMMAND:  .trap 0xfffffffff7872ce0 ; kb

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME:  hardware

IMAGE_NAME:  hardware

DEBUG_FLR_IMAGE_TIMESTAMP:  0

BUCKET_ID:  CPU_CALL_ERROR

Followup: MachineOwner
---------
 *** Possible invalid call from 804e331f ( nt!KeUpdateSystemTime+0x160 )
 *** Expected target 804e358e ( nt!DbgBreakPointWithStatus+0x0 )

kd> u 804e331f 
nt!KeUpdateSystemTime+0x160:
804e331f e86a020000       call    nt!DbgBreakPointWithStatus (804e358e)
804e3324 ebb4             jmp     nt!KeUpdateSystemTime+0x11b (804e32da)
804e3326 90               nop
804e3327 fb               sti
804e3328 8d09             lea     ecx,[ecx]
nt!KeUpdateRunTime:
804e332a a11cf0dfff       mov     eax,[ffdff01c]
804e332f 53               push    ebx
804e3330 ff80c4050000     inc     dword ptr [eax+0x5c4]

解决方案

解决方案

KAV的杀毒软件依靠诸多不安全内核层的黑客程序,从而将系统稳定性置于危险中。想要解决这个问题,首先KAV需要去掉不安全的内核层黑客程序,比如给非输出函数打补丁或者不加验证地关联系统服务等等。

KAV使用钩子函数或者其他不安全措施的那些操作也可以通过有记录的并且安全的API以及例行程序来实现,这些都是在Windows 设备驱动工具组(DDK)和可安装文件系统工具组(IFS kit)中详细说明过的。KAV的程序员有必要花时间理解关于如何使用有记录的方法在系统内核进行操作,而不是用字面上的hack-and-slash的方法,致使系统有崩溃或者甚至扩大特权范围的危险。

KAV所倚仗的很多不安全操作都被x64的补丁防御功能拦截了,这使得KAV更难发布针对64位系统的杀毒软件了(由于计算机开始支持x64,有的默认的就是x64的操作系统,于是X64版的杀毒软件也变得尤为重要)。32位的内核驱动无法在64位的系统中使用,因此KAV需要将它的驱动移植到x64上并且解决补丁防御功能的问题。另外,终端用户使用的是单核计算机的假设很快就会不成立了,现在很多系统都支持超线程或者多核。

【参考资料】

Kaspersky Internet Security Suite 5.0

返回原文

本文导航

相关阅读

每日精选

点击查看更多

首页 手机 数码相机 笔记本 游戏 DIY硬件 硬件外设 办公中心 数字家电 平板电脑