Copy Link
Add to Bookmark
Report
uninformed 04 08
What Were They Thinking?
Anti-Virus Software Gone Wrong
Skywing
skywing@valhallalegends.com
0. Foreword
Abstract: Anti-virus software is becoming more and more prevalent on end-user
computers today. Many major computer vendors (such as Dell) bundle anti-virus
software and other personal security suites in the default configuration of
newly-sold computer systems. As a result, it is becoming increasingly important
that anti-virus software be well-designed, secure by default, and interoperable
with third-party applications. Software that is installed and running by
default constitutes a prime target for attack and, as such, it is especially
important that said software be designed with security and interoperability in
mind. In particular, this article provides examples of issues found in
well-known anti-virus products. These issues range from not properly validating
input from an untrusted source (especially within the context of a kernel
driver) to failing to conform to API contracts when hooking or implementing an
intermediary between applications and the underlying APIs upon which they rely.
For popular software, or software that is installed by default, errors of this
sort can become a serious problem to both system stability and security. Beyond
that, it can impact the ability of independent software vendors to deploy
functioning software on end-user systems.
1. Introduction
In today's computing environment, computer security is becoming a more and more
important role. The Internet poses unique dangers to networked computers, as
threats such as viruses, worms, and other malicious software become more and
more common.
As a result, there has been a shift towards including personal security
software on most new computers sold today, such as firewall software and
anti-virus software. Many new computers are operated and administered by
individuals who are not experienced or savvy with the administration of a
secure system, and as such rely solely on the protection provided by a firewall
or anti-virus security suite.
Given this, one would expect that firewall, anti-virus, and other personal
security software would be high quality - after all, for many individuals,
firewall and anti-virus software are the first (and all-too-often only) line
of defense.
Unfortunately, though, most common anti-virus and personal firewall software is
full of defects that can at best make it very difficult to interoperate with
(which turns out to be a serious problem for most software vendors, given how
common anti-virus and firewall software is), and at worst compromise the very
system security they advertise to protect.
This article discusses two personal security software packages that suffer from
problems that make it difficult to interoperate with the software, or even
compromise system security, all due to shortcuts and unsafe assumptions made by
the original developers.
- Kaspersky Internet Security Suite 5.0
- McAfee Internet Security Suite 2006
Both of these software packages include several personal security programs,
including firewall and anti-virus software.
2. The problem: Kaspersky Internet Security Suite 5.0
Kaspersky ships a personal security software suite known as Kaspersky Internet
Security Suite 5.0. This package includes various personal security software
programs, including a firewall and anti-virus software.
Kaspersky's anti-virus software is the primarily focus of this article. Like
many other anti-virus software, Kaspersky Anti-Virus provides both manual and
real-time scanning capabilities.
Kaspersky's anti-virus system (KAV) employs various unsafe techniques in its
kernel mode components, which may lead to a compromise of system security.
2.1. Patching system services at runtime.
Although KAV appears to use a filesystem filter, the standard Windows mechanism
for intercepting accesses to files (specifically designed for applications like
anti-virus software), the implementors also used a series of API-level function
hooks to intercept various file accesses. Performing function hooking in
kernel mode is a dangerous proposition; one must be very careful to fully
validate all parameters if a function could be called from user mode (otherwise
system security could be compromised by a malicious unprivileged program).
Additionally, it is generally not safe to remove code hooks in kernel mode as
it is difficult to prove that no threads will be running a particular code
region in order to unhook without risking bringing down the system. KAV also
hooks several other system services in a misguided attempt to "protect" its
processes from debuggers and process termination.
Unfortunately, the KAV programmers did not properly validate parameters passed
to hooked system calls, opening holes that, at the very least, allow unprivileged
user mode programs to bring down the system, and may even allow local privilege
escalation (though the author has not spent the time necessary to prove whether
such is possible).
KAV hooks the following system services (easily discoverable in WinDbg by
comparing nt!KeServiceDescriptorTableShadow on a system with KAV loaded with a
clean system:
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
Additionally, KAV attempts to create several entirely new system services as a
shortcut for calling kernel mode by patching the service descriptor table.
This is certainly not the preferred mechanism to allow a user mode program to
communicate with a driver; the programmers should have used the conventional
IOCTL interface, which avoids the pitfalls of patching kernel structures at
runtime and having to deal with other inconveniences such as system service
ordinals changing from OS release to OS release.
2.2. Improper validation of user mode pointers, assuming the size of the kernel
address space.
Many of the hooks that KAV installs (and even the custom system services)
suffer from flaws that are detrimental to the operation of the system.
For instance, KAV's modified NtOpenProcess attempts to determine if a user
address is valid by comparing it to the hardcoded value 0x7FFF0000. On most
x86 Windows systems, this address is below the highest user address (typically
0x7FFEFFFF). However, hardcoding the size of the kernel address space is not a
very good idea; there is a boot parameter `/3GB' that can be set in boot.ini in
order to change the default address space split of 2GB kernel and 2GB user to
1GB kernel and 3GB user. If a system with KAV is configured with /3GB, it is
expected that anything that calls NtOpenProcess (such as the win32 OpenProcess)
may randomly fail if parameter addresses are located above the first 2GB of the
user address space:
.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
The proper way to perform this validation would have been to use the documented
ProbeForRead function with a SEH frame, which will automatically raise an
access violation if the address is not a valid user address.
Additionally, many of KAV's custom system services do not properly validate
user mode pointer arguments, which could be used to bring down the system:
.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] ; Unvalidated user mode pointer access
.text:F8222BF0 mov [esp+8+arg_8], eax
.text:F8222BF4 push eax ; OutputBufferLength
.text:F8222BF5 mov eax, [esp+0Ch+arg_0]
.text:F8222BF9 push edx ; OutputBuffer
.text:F8222BFA push eax ; int
.text:F8222BFB call sub_F821F9A0 ; This routine internally assumes that all pointer parameters given are valid.
.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] ; Unvalidated user mode pointer access
.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 ; This routine internally assumes that all pointer parameters given are valid.
.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
2.3. Improper validation of user mode structures and pointers, 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, which 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.
Besides 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
2.4. Improper validation of kernel object types.
Windows exposes many kernel features through a series of "kernel objects",
which 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, in 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, which 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).
.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[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
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 is to 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, a documented kernel function that
allows drivers to register a callback that is called on process creation and
process exit.
2.5. Patching non-exported, non-system-service kernel functions.
KAV's kernel patching is not limited to just system services, however. One of
the most dangerous hooks that KAV installs is one in the middle of the
nt!SwapContext function, which is neither exported nor a system service (and
thus has reliable mechanism to be detected by driver code, other than code
fingerprinting). nt!SwapContext is called by the kernel on every context
switch in order to perform some internal bookkeeping tasks.
Patching such a critical, non-exported kernel function with a mechanism as
unreliable as blind code fingerprinting is, in the author's opinion, not a
particularly good idea. To make matters worse, KAV actually modifies code in
the middle of nt!SwapContext instead of patching the start of the function, and
as such makes assumptions about the internal register and stack usage of this
kernel function.
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
This is an extremely dangerous patching operation to make, for several reasons:
1. nt!SwapContext is a *very* hot code path, as it is called on every single
context switch. Therefore, patching it at runtime without running a non-trivial
risk of bringing down the system is very difficult, especially on
multiprocessor systems. KAV attempts to solve the synchronization problems
relating to patching this function on uniprocessor systems by disabling
interrupts entirely, but this approach will not work reliably on
multiprocessor systems. KAV makes no attempt to address this problem on
multiprocessor systems and puts them at the risk of randomly failing on boot
during KAV's patching.
2. Reliably locating this function and making assumptions about the register
and stack usage (and instruction layout) across all released and future
Windows versions is a practical impossibility, and yet KAV attempts to do
just this. This puts KAV customers at the mercy of the next Windows update,
which may cause their systems to crash on boot because KAV's hooking code
makes an assumption that has been invalidated about the context-switching
process.
Additionally, in order to perform code patching on the kernel, KAV adjusts the
page protections of kernel code to be writable by altering PTE attributes
directly instead of using documented functions (which would have proper locking
semantics for accessing internal memory management structures).
KAV nt!SwapContext patching:
.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] ; Make nt!SwapContext writable by directly accessing
.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 ; This is a function pointer filled in at runtime,
.text:F82203F6 ; differing based on whether the system has PAE
.text:F82203F6 ; enabled or not.
.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
2.6. Allowing user mode code to access kernel memory directly from user mode,
improper validation of user mode structures.
One of the most important principles of the kernel/user division that modern
operating systems enforce is that user mode is not allowed to directly access
kernel mode memory. This is necessary to enforce system stability, otherwise
a buggy user mode program could corrupt the kernel and bring down the whole
system.
Unfortunately, the KAV programmers appear to think that this distinction is not
really so important after all.
One of the strangest of the unsafe practicies implemented by KAV is to allow
user mode to directly call some portions of their kernel driver (within kernel
address space!) instead of just loading a user mode DLL (or otherwise loading
user mode code in the target process).
This mechanism appears to be used to inspect DLLs as they are loaded - a task
which would be much better accomplished with PsSetLoadImageNotifyRoutine.
KAV patches kernel32.dll as a new process is created, such that the export
table points all of the DLL-loading routines (e.g. LoadLibraryA) to a thunk
that calls portions of KAV's driver in kernel mode. Additionally, KAV modifes
protections on parts of its code and data sections to allow user mode read
access.
KAV sets a PsLoadImageNotifyRoutine hook to detect kernel32.dll being loaded in
order to know when to patch kernel32's export table. The author wonders why
KAV did not just do their work from within PsSetLoadImageNotifyRoutine directly
instead of going through all the trouble to allow user mode to call kernel mode
for a LoadLibrary hook.
The CheckInjectCodeForNewProcess function is called when a new process loads an
image, and checks for kernel32 being loaded. If this is the case, it will
queue an APC to the process that will perform patching.
.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
The APC routine itself patches kernel32's export table (and generates the
thunks to call kernel mode) and adjusts PTE attributes on KAV's driver image
to allow user mode access.
.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
The actual image patching reprotects the export table of kernel32, changes the
export address table entries for the LoadLibrary* family of functions to point
to a thunk that is written into spare space within the kernel32 image, and
writes the actual thunk code out:
.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 writes a new RVA pointing to its hook code here.
.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's export table reprotecting code assumes that the user mode PE header is
well-formed and does not contain offsets pointing to kernel mode addresses:
.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
The mechanism by which KAV uses to reprotect user mode code is rather much of
a hack as well. KAV dynamically determines the system call ordinal of the
NtProtectVirtualMemory system service and uses its own int 2e thunk to call the
service.
.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's export lookup code does not correctly validate offsets garnered from the
PE header before using them:
.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
User mode calling KAV kernel code directly without a ring 0 transition:
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 crashing the system when stepping through its kernel mode code when called
from user mode (apparently not that reliable after all!):
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]
2.7. The solution.
KAV's anti-virus software relies upon many unsafe kernel-mode hacks that put
system stability in jeopardy. Removing unsafe kernel mode hacks like
patching non-exported kernel functions or hooking various system services
without parameter validation is the first step towards fixing the problem.
Many of the operations KAV uses hooking or other unsafe means for are doable
using documented and safe APIs and conventions that are well-described in the
Windows Device Driver Kit (DDK) and Installable File System Kit (IFS Kit). It
would behoove the KAV programmers to take the time to read and understand the
documented way to do things in the Windows kernel instead of taking a quite
literally hack-and-slash approach that leaves the system at risk of crashes
and potentially even privilege escalation.
Many of the unsafe practices relied upon by KAV are blocked by PatchGuard on
x64 and will make it significantly harder to release a 64-bit version of KAV's
anti-virus software (which will become increasingly important as computers are
sold with x64 support and run x64 Windows by default). Because 32-bit kernel
drivers cannot be loaded on 64-bit Windows, KAV will need to port their driver
to x64 and deal with PatchGuard. Additionally, assumptions that end user
computers will be uniprocessor are fast becoming obsolete, as most new systems
sold today support HyperThreading or multiple cores.
3. The problem: McAfee Internet Security Suite 2006
McAfee's Internet Security Suite 2006 package includes a number of programs,
including anti-virus, firewall, and anti-spam software. In particular,
however, this article discusses one particular facet of Internet Security Suite
2006: The McAfee Privacy Service.
This component is designed to intercept outbound traffic and sanitize it of any
predefined sensitive information before it hits the wire.
>From the very start, if one is familiar with network programming, such a goal
would appear to be very difficult to practically achieve. For instance, many
programs send data in a compressed or encrypted form, and there is no common
way to process such data without writing specialized software for each target
application. This immediately limits the effectiveness of the Privacy Service
software's generalized information sanitization process to programs that have
a) had specialized handler code written for them, or b) send information to
the Internet in plaintext. Furthermore, the very act of modifying an outbound
data stream could potentially cause an application to fail (consider the case
where an application network protocol includes its own checksums of data sent
and received, where arbitrary modifications of network traffic might cause it
to be rejected).
The problem with McAfee Internet Security Suite goes deeper, however. The
mechanism by which Internet Security Suite intercepts (and potentially alters)
outbound network traffic is through a Windows-specific mechanism known as an
LSP (or Layered Service Provider).
LSPs are user mode DLLs that "plug-in" to Winsock (the Windows sockets API) and
are called for every sockets API call that a user mode program makes. This
allows easy access to view (and modify) network traffic without going through
the complexities of writing a conventional kernel driver. An LSP is loaded and
called in the context of the program making the original socket API call.
This means that for most programs using user mode socket calls, all API calls
will be redirected through the Internet Security Suite's LSP, for potential
modification.
If one has been paying attention so far, this approach should already be
setting off alarms. One serious problem with this approach is that since the
LSP DLL itself resides in the same address space (and thus has the same
privileges) as the calling program, there is nothing technically stopping a
malicious program from modifying the LSP DLL's code to exempt itself from
alteration, or even bypassing the LSP directly.
Unfortunately, the flaws in the McAfee Privacy Service do not simply end here,
although already the technical limitations of an LSP for securely intercepting
and modifying network traffic make this approach (in the author's opinion)
wholly unsuitable for a program designed to protect a user from having his or
her private data stolen by malicious software.
Specifically, there are implementation flaws in how the LSP itself handles
certain socket API calls that may cause otherwise perfectly working software
to fail when run under McAfee Internet Security Suite 2006. This poses a
serious problem to software vendors, who are often forced to interoperate with
pervasive personal security software (such as Internet Security Suite).
The Windows Sockets environment is fully multithreaded and thread-safe, and
allows programs to call into the sockets API from multiple threads concurrently
without risk of data corruption or other instability. Unfortunately, the LSP
provided by McAfee for its Privacy Service software breaks this particular
portion of the Windows Sockets API contract. In particular, McAfee's LSP does
not correctly synchronize access to internal data structures when sockets are
created or destroyed, often leading to situations where a newly created socket
handed back to an application program is already mistakenly closed by the
flawed LSP before the application even sees it.
In addition, the author has also observed a similar synchronization problem
regarding the implementation of the `select' function in the Privacy Service
LSP. The select function is used to poll a set of sockets for a series of
events, such as data being available to read, or buffer space being available
to send data. The McAfee LSP appears to fail when calls to select are made
from multiple threads concurrently, however, often appearing to switch a
ocket handle specified by the original application program with an entirely
different handle. (In Windows, the same handle space is shared by
socket handles and all other types of kernel objects, such as files or
processes and threads). This subsequently results in calls to select failing
in strange ways, or worse, returning that data is available for a particular
socket when it was in fact available on a different socket entirely.
Both of these flaws result in intermittant failures of correctly written third
party applications when used in conjunction with McAfee Internet Security Suite
2006.
3.2. Solution for Software Vendors
If one is stuck in the unfortunate position of being forced to support software
running under McAfee Internet Security Suite 2006, one potential solution to
this problem is to manually serialize all calls to select (and other functions
that create or destroy sockets, such as socket and the WSASocket family of
functions). This approach has worked in practice, and is perhaps the least
invasive solution to the flawed LSP.
An alternative solution is to bypass the LSP entirely and instead call directly
to the kernel sockets driver (AFD.sys). However, this entails determining the
actual handle associated with a socket (the handle returned by the McAfee LSP
is in fact not the underlying socket handle), as well as relying on the as of
yet officially undocumented AFD IOCTL interface.
3.3. Solution for McAfee
>From McAfee's perspective, the solution is fairly simple: correctly serialize
access to internal data structures from function calls that are made from
multiple threads concurrently.
4. Conclusion
As the Internet becomes an increasingly hostile place and the need for in-depth
personal security software (as a supplement or even replacement for proper
system administrator) grows for end-users, it will become increasingly
important for the vendors and providers of personal security software to ensure
that their programs do not impair the normal operation of systems upon which
their software is installed. The author realizes that such is a very difficult
task given what is expected of most personal security software suites, and
hopes that by shedding light on the flaws in existing software, new programs
can be made to avoid similar mistakes.