第五章 监控Native API调用
翻译:Kendiv( fcczj@263.net )
更新:Tuesday, February 22, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
拦截系统调用在任何时候都是程序员们的最爱。这种大众化爱好的动机也是多种多样的:代码性能测试(Code Profiling)和优化,逆向工程,用户活动记录等等。所有这些都有一个共同的目的:将控制传递给一块特殊的代码,这样无论一个应用程序何时调用系统服务,都可以发现哪个服务被调用了,接收了什么参数,返回的结果是什么以及执行它花费了多少时间。根据最初由Mark Russinovich和
Bryce Cogswell提出的技巧,本章将介绍一个可以hook任意Native API函数的通用框架。这里使用的方法完全是数据驱动(data-driven)的,因此,它可以很容易被扩展,并能适应其他Windows NT/2000版本。所有进程的API调用产生的数据都被写入一个环状缓冲区中,客户端程序可以通过设备I/O控制来读取该缓冲区。采用的数据协议(protocol data)的格式是以行为导向的ANSI文本流,它符合严格的格式化规则,应用程序可以很容易的再次处理它们(postprocessing)。为了示范此种客户端程序的基本框架,本章还提供了一个示例性的数据协议察看器,该程序运行于控制台窗口中。
译注:
profiling 一般是指对程序做性能方面的测试, 主要是速度上的。 在翻译时,可译为:剖析,最好是根据上文环境进行翻译。
修改服务描述符表
对比“原始”的操作系统,如DOS或Windows 3.x,它们对程序员在API中加入hook的限制很少,而Win32系统,如Windows 2000/NT和Windows 9.x则很难驾驭,因为它们使用了巧妙的保护机制把不相关的代码分离出来。在Win32 API上设置一个系统范围的hook绝不是一个小任务。幸运的是,我们有像Matt Pietrek和Jeffery Richter这样的Win32向导,他们做了大量的工作来向我们展示如何完成这一任务,尽管事实上,并没有简单和优雅的解决方案。在1997年,Russinovich和Cogswell介绍了一种可在Windows NT上实现系统范围hook的完全不同的方法,可在更低一层上拦截系统调用(Russinovich和Cogswell 1997)。他们提议向Native API Dispatcher中注入日志机制,这仅比用户模式和内核模式之间的边界低一些,在这个位置上Windows NT暴露出一个“瓶颈”:所有用户模式的线程必须通过此处,才能使用操作系统内核提供的服务。
服务和参数表
就像在第二章讨论过的,发生在用户模式下的Native API调用必须通过INT 2eh接口,该接口提供一个i386的中断门来改变特权级别。你可能还记得所有INT 2eh调用都是由内部函数KiSystemService()在内核模式下处理的,该函数使用系统服务描述符表(SDT)来查找Native API处理例程的入口地址。图5-1给出了这种分派机制的基本组件之间的相互关系。列表5-1再次给出了SERVICE_DESCRIPTOR_TABLE结构及其子结构的正式定义,在第二章中,已经给出过它们的定义(列表2-1)。
调用KiSystemService()时,需提供两个参数,由INT 2eh的调用者通过EAX和EDX寄存器传入。EAX包含从0开始的索引,该索引可用于一个API函数指针的数组,EDX指向调用者的参数堆栈。KiSystemService()通过读取ServiceTable的一个成员的值(该成员是ntoskrnl.exe的一个公开数据结构:KeServiceDescriptorTable,图5-1的左面列出了该结构)来获取函数数组的基地址。实际上,KeServiceDescriptorTable指向一个包含四个服务表的结构,但默认情况下,仅有第一个服务表是有效的。KiSystemService()通过EAX中的索引值进入内部结构KiServiceTable,以查找可处理此API调用的函数的入口地址。在调用目标函数之前,KiSystemServie()以相同的方式查询KiArgumentTable结构,以找出调用者在参数堆栈中传入了多少字节,然后使用这个值将参数复制到当前内核堆栈中。此后,就只需要一个简单的汇编指令:CALL来执行API处理例程了。这里涉及的所有函数都符合__stdcall标准。
图5-1. KeServiceDescriptorTable的结构图
typedef NTSTATUS (NTAPI*NTPROC)();
typedef NTPROC* PNTPROC;
#define NTPROC_ sizeof(NTPROC)
typedef struct _SYSTEM_SERVICE_TABLE
{
PNTPROC ServiceTable; // array of entry points
PDOWRD CounterTable; // array of usage counters
DWORD ServiceLimit; // number of table entries
PBYTE ArgumentTable; // array of byte counts
}
SYSTEM_SERVICE_TABLE,
*PSYSTEM_SERVICE_TABLE,
**PPSYSTEM_SERVICE_TABLE;
//-----------------------------------------------------------------------------------------------------------
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
SYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe ( native api )
SYSTEM_SERVICE_TABLE win32k; // win32k.sys (gdi/user support)
SYSTEM_SERVICE_TABLE Table3; // not used
SYSTEM_SERVICE_TABLE Table4; // not used
}
SYSTEM_DESCRIPTOR_TABLE,
*PSYSTEM_DESCRIPTOR_TABLE,
**PPSYSTEM_DESCRIPTOR_TABLE;
列表5-1. SERVICE_DESCRIPTOR_TABLE结构的定义
Windows 2000还提供了另一个服务描述符表参数块----KeServiceDescriptorTableShadow。不过,KeServiceDescriptorTable已经由ntoskrnl.exe公开的导出了,因此,内核模式的驱动程序可以很容易的访问它,而KeServiceDescriptorTableShadow则不行。在Windows 2000下,KeServiceDescriptorTableShadow紧随KeServiceDescriptorTable之后,但是你不能在Windows NT中以这样的方法找到它,因为,这一规则并不使用于Windows NT。可能在Windows 2000的后续版本的中,这种方法也不行。这两个参数块的不同之处在于:KeServiceDescriptorTableShadow中的第二个项被系统使用了,用来指向内部的W32pServiceTable和w32pArgumentTable结构,Win32的内核模式组件Win32K.sys使用这两个结构来分派自己的API调用,如图5-2所示。KiSystemService()通过检查EAX中索引值的第12和13位来确认是不是应该由Win32K.sys处理API调用。如果这两个位都是0,则是由ntoskrnl.exe处理的Native API调用,因此KiSystemService()使用第一个SDT。如果第12位为1并且第13位为0,KiSystemService()使用第二个SDT,这个SDT并没有被当前系统使用。这意味着Native API调用的索引值的潜在范围是:0x0000 --- 0x0FFFF,Win32K.sys调用使用的索引范围是:0x1000 --- 0x1FFF。因此,0x2000 --- 0x2FFF和0x3000 --- 0x3FFF保留给剩下的两个SDT。在Windows 2000中,Native API服务表包含248个项,Win32K.sys表包含639个项。
图5-2. KeServiceDescriptorTableShadow的结构图
Russinovich和Cogswell的独具特色的方法是:通过简单的向KiServiceTable数组中放入一个不同的处理例程来hook所有API调用。这个处理例程最终会调用位于ntoskrnl.exe中的原始处理例程,但它有机会察看一下被调用函数的输入/输出参数。这个方法非常强大却又如此简单。因为所有用户模式的线程必须经过这个“针眼”才能获得Native API的服务,安装一个全局hook来简单的替换一个函数指针的方法,在启动一个新的进程和线程的情况下,也能很稳定的工作。这并不需要一种通讯机制来通知新加入或将要移除的进程/线程。
不幸的是,系统服务表在不同Windows NT版本上不相同。表5-1比较了Windows NT/2000的KiServiceTable。很显然,不仅是处理例程的号码从211增加到了248,而且新的处理例程并不是直接添加到列表的末尾,而是被插入到了各个地方!因此,一个服务函数索引,如0x20在Windows 2000中指向NtCreateFile(),而在Windows NT中却指向NtCreateProfile()。所以,通过操作服务函数表进行hook的API调用监控器必须小心的检查它所在的Windows NT的版本。这可以通过如下几个方式来完成:
l 一种可能性是,检查由ntoskrnl.exe导出的公开变量:NtBuildNumber,就像Russinovich和Cogswell在他们的原文中所作的那样。Windows NT 4.0为所有Service Pack提供的Build Number是:1381。Windows 2000的当前Build Number是:2195。看来有希望,这个版本号在Windows NT的早期版本中很稳定。
l 另一个可能性是,检查SharedUserData结构中的NtMajorVersion和NtMinorVersion成员,该结构定义与Windows 2000头文件ntddk.h中。Windows NT 4.0的所有Service Pack都将SharedUserData->NtMajorVersion设为4,将SharedUserData->NtMinorVersion设为0。Windows 2000的当前版本为Windows NT Version 5.0。
l 本章给出的代码采用了另一中替代方法:它测试SDT的ServiceLimit成员是否和它的预期值相匹配,该预期值是211(0xD3)针对Windows NT 4.0和248(0xF8)针对Windows 2000。
表5-1. Windows 2000/NT 服务表对比
Windows 2000
索引
Windows NT 4.0
NtAcceptConnectPort
0x00
NtAcceptConnectPort
NtAccessCheck
0x01
NtAccessCheck
NtAccessCheckAndAuditAlarm
0x02
NtAccessCheckAndAuditAlarm
NtAccessCheckByType
0x03
NtAddAtom
NtAccessCheckByTypeAndAuditAlarm
0x04
NtAdjustGroupsToken
NtAccessCheckByTypeResultList
0x05
NtAdjustPrivilegesToken
NtAccessCheckByTypeResultListAndAuditAlarm
0x06
NtAlertResumeThread
NtAccessCheckByTypeResultListAndAuditAlarmByHandle
0x07
NtAlertThread
NtAddAtom
0x08
NtAllocateLocallyUniqueld
NtAdjustGroupsToken
0x09
NtAllocateUuids
NtAdjustPrivilegesToken
0x0A
NtAllocateVirtualMemory
NtAlertResumeThread
0x0B
NtCallbackReturn
NtAlertThread
0x0C
NtCancelloFile
NtAllocateLocallyUniqueld
0x0D
NtCancelTimer
NtAllocateUserPhysicalPages
0x0E
NtClearEvent
NtAllocateUuids
0x0F
NtClose
NtAllocateVirtualMemory
0x10
NtCloseObjectAuditAlarm
NtAreMappedFilesTheSame
0x11
NtCompleteConnectPort
NtAssignProcessToJobObject
0x12
NtConnectPort
NtCallbackReturn
0x13
NtContinue
NtCancelloFile
0x14
NtCreateDirectoryObject
NtCancelTi mer
0x15
NtCreateEvent
NtCancelDeviceWakeupRequest
0x16
NtCreateEventPair
NtClearEvent
0x17
NtCreateFile
NtClose
0x18
NtCreateloCompletion
NtCloseObjectAuditAlarm
0x19
NtCreateKey
NtCompleteConnectPort
0x1A
NtCreateMailslotFile
NtConnectPort
0x1B
NtCreateMutant
NtContinue
0x1C
NtCreateNamedPipeFile
NtCreateDirectoryObject
0x1D
NtCreatePagingFile
NtCreateEvent
0x1E
NtCreatePort
NtCreateEventPair
0x1F
NtCreateProcess
NtCreateFile
0x20
NtCreateProfile
NtCreateloCompletion
0x21
NtCreateSection
NtCreateJobObject
0x22
NtCreateSemaphore
NtCreateKey
0x23
NtCreateSymbolicLinkObject
NtCreateMailslotFile
0x24
NtCreateThread
NtCreateMutant
0x25
NtCreateTimer
NtCreateNamedPipeFile
0x26
NtCreateToken
NtCreatePagingFile
0x27
NtDelayExecution
NtCreatePort
0x28
NtDeleteAtom
NtCreateProcess
0x29
NtDeleteFile
NtCreateProfile
0x2A
NtDeleteKey
NtCreateSection
0x2B
NtDeleteObjectAuditAlarm
NtCreateSemaphore
0x2C
NtDeleteValueKey
NtCreateSymbolicLinkObject
0x2D
NtDeviceloControlFile
NtCreateThread
0x2E
NtDisplayString
NtCreateTimer
0x2F
NtDuplicateObject
NtCreateToken
0x30
NtDuplicateToken
NtCreateWaitablePort
0x31
NtEnumerateKey
NtDelayExecution
0x32
NtEnumerateValueKey
NtDeleteAtom
0x33
NtExtendSection
NtDeleteFile
0x34
NtFindAtom
NtDeleteKey
0x35
NtFlushBuffersFile
NtDeleteObj ectAuditAlarm
0x36
NtFlushlnstructionCache
NtDeleteValueKey
0x37
NtFlushKey
NtDeviceloControlFile
0x38
NtFlushVirtualMemory
NtDisplayString
0x39
NtFlushWriteBuffer
NtDuplicateObject
0x3A
NtFreeVirtualMemory
NtDuplicateToken
0x3B
NtFsControlFile
NtEnumerateKey
0x3C
NtGetContextThread
NtEnumerateValueKey
0x3D
NtGetPlugPlayEvent
NtExtendSection
0x3E
NtGetTickCount
NtFilterToken
0x3F
NtlmpersonateClientOfPort
NtFindAtom
0x40
NtlmpersonateThread
NtFlushBuffersFile
0x41
NtlnitializeRegistry
NtFlushlnstructionCache
0x42
NtListenPort
NtFlushKey
0x43
NtLoadDriver
NtFlushVirtualMemory
0x44
NtLoadKey
NtFlushWriteBuffer
0x45
NtLoadKey2
NtFreeUserPhysicalPages
0x46
NtLockFile
NtFreeVirtualMemory
0x47
NtLockVirtualMemory
NtFsControlFile
0x48
NtMakeTemporaryObject
NtGetContextThread
0x49
NtMapViewOfSection
NtGetDevicePowerState
0x4A
NtNotifyChangeDirectoryFile
NtGetPlugPlayEvent
0x4B
NtNotifyChangeKey
NtGetTickCount
0x4C
NtOpenDirectoryObject
NtGetWriteWatch
0x4D
NtOpenEvent
NtlmpersonateAnonymousToken
0x4E
NtOpenEventPair
NtlmpersonateClientOfPort
0x4F
NtOpenFile
NtlmpersonateThread
0x50
NtOpenloCompletion
NtlnitializeRegistry
0x51
NtOpenKey
NtlnitiatePowerAction
0x52
NtOpenMutant
NtlsSystemResumeAutomatic
0x53
NtOpenObjectAuditAlarm
NtListenPort
0x54
NtOpenProcess
NtLoadDriver
0x55
NtOpenProcessToken
NtLoadKey
0x56
NtOpenSection
NtLoadKey2
0x57
NtOpenSemaphore
NtLockFile
0x58
NtOpenSymbolicLinkObject
NtLockVirtualMemory
0x59
NtOpenThread
NtMakeTemporaryObject
0x5A
NtOpenThreadToken
NtMapUserPhysicalPages
0x5B
NtOpenTimer
NtMapUserPhysicalPagesScatter
0x5C
NtPlugPlayControl
NtMapViewOfSection
0x5D
NtPrivilegeCheck
NtNotifyChangeDirectoryFile
0x5E
NtPrivilegedServiceAuditAlarm
NtNotifyChangeKey
0x5F
NtPrivilegeObjectAuditAlarm
NtNotifyChangeMultipleKeys
0x60
NtProtectVirtualMemory
NtOpenDirectoryObject
0x61
NtPulseEvent
NtOpenEvent
0x62
NtQuerylnformationAtom
NtOpenEventPair
0x63
NtQueryAttributesFile
NtOpenFile
0x64
NtQueryDefaultLocale
NtOpenloCompletion
0x65
NtQueryDirectoryFile
NtOpenJobObject
0x66
NtQueryDirectoryObject
NtOpenKey
0x67
NtQueryEaFile
NtOpenMutant
0x68
NtQueryEvent
NtOpenObjectAuditAlarm
0x69
NtQueryFullAttributesFile
NtOpenProcess
0x6A
NtQuerylnformationFile
NtOpenProcessToken
0x6B
NtQueryloCompletion
NtOpenSection
0x6C
NtQuerylnformationPort
NtOpenSemaphore
0x6D
NtQuerylnformationProcess
NtOpenSymbolicLinkObject
0x6E
NtQuerylnformationThread
NtOpenThread
0x6F
NtQuerylnformationToken
NtOpenThreadToken
0x70
NtQuerylntervalProfile
NtOpenTimer
0x71
NtQueryKey
NtPlugPlayControl
0x72
NtQueryMultipleValueKey
NtPowerlnformation
0x73
NtQueryMutant
NtPrivilegeCheck
0x74
NtQueryObject
NtPrivilegedServiceAuditAlarm
0x75
NtQueryOleDirectoryFile
NtPrivilegeObjectAuditAlarm
0x76
NtQueryPerformanceCounter
NtProtectVirtualMemory
0x77
NtQuerySection
NtPulseEvent
0x78
NtQuerySecurityObject
NtQuerylnformationAtom
0x79
NtQuery Semaphore
NtQueryAttributesFile
0x7A
NtQuerySymbolicLinkObject
NtQueryDefaultLocale
0x7B
NtQuerySystemEnvironmentValue
NtQueryDefaultUILanguage
0x7C
NtQuerySystemlnformation
NtQueryDirectoryFile
0x7D
NtQuerySystemTime
NtQueryDirectoryObject
0x7E
NtQuery Timer
NtQueryEaFile
0x7F
NtQueryTimerResolution
NtQueryEvent
0x80
NtQuery ValueKey
NtQueryFullAttributesFile
0x81
NtQuery VirtualMemory
NtQuerylnformationFile
0x82
NtQuery VolumelnformationFile
NtQuerylnformationJobObject
0x83
NtQueueApcThread
NtQueryloCompletion
0x84
NtRaiseException
NtQuerylnformationPort
0x85
NtRaiseHardError
NtQuerylnformationProcess
0x86
NtReadFile
NtQuerylnformationThread
0x87
NtReadFileScatter
NtQuerylnformationToken
0x88
NtReadRequestData
NtQuerylnstallUILanguage
0x89
NtReadVirtualMemory
NtQuerylntervalProfile
0x8A
NtRegisterThreadTerminatePort
NtQueryKey
0x8B
NtReleaseMutant
NtQueryMultiple ValueKey
0x8C
NtReleaseSemaphore
NtQueryMutant
0x8D
NtRemoveloCompletion
NtQueryObject
0x8E
NtReplaceKey
NtQueryOpenSubKeys
0x8F
NtReplyPort
NtQueryPerformanceCounter
0x90
NtReplyWaitReceivePort
NtQueryQuotalnformationFile
0x91
NtReplyWaitReplyPort
NtQuerySection
0x92
NtRequestPort
NtQuerySecurityObject
0x93
NtRequestWaitReplyPort
NtQuerySemaphore
0x94
NtResetEvent
NtQuerySymbolicLinkObject
0x95
NtRestoreKey
NtQuerySystemEnvironmentValue
0x96
NtResumeThread
NtQuerySystemlnformation
0x97
NtSaveKey
NtQuerySystemTime
0x98
NtSetloCompletion
NtQueryTimer
0x99
NtSetContextThread
NtQueryTimerResolution
0x9A
NtSetDefaultHardErrorPort
NtQueryValueKey
0x9B
NtSetDefaultLocale
NtQueryVirtualMemory
0x9C
NtSetEaFile
NtQueryVolumelnformationFile
0x9D
NtSetEvent
NtQueueApcThread
0x9E
NtSetHighEventPair
NtRaiseException
0x9F
NtSetHighWaitLowEventPair
NtRaiseHardError
0xA0
NtSetHighWaitLowThread
NtReadFile
0xA1
NtSetlnformationFile
NtReadFileScatter
0xA2
NtSetlnformationKey
NtReadRequestData
0xA3
NtSetlnformationObject
NtReadVirtualMemory
0xA4
NtSetlnformationProcess
NtRegisterThreadTerminatePort
0xA5
NtSetlnformationThread
NtReleaseMutant
0xA6
NtSetlnformationToken
NtReleaseSemaphore
0xA7
NtSetlntervalProfile
NtRemoveloCompletion
0xA8
NtSetLdtEntries
NtReplaceKey
0xA9
NtSetLowEventPair
NtReplyPort
0xAA
NtSetLowWaitHighEventPair
NtReplyWaitReceivePort
0xAB
NtSetLowWaitHighThread
NtReplyWaitReceivePortEx
0xAC
NtSetSecurity Object
NtReplyWaitRepiyPort
0xAD
NtSetSystemEnvironmentValue
NtRequestDeviceWakeup
0xAE
NtSetSystemlnformation
NtRequestPort
0xAF
NtSetSystemPowerState
NtRequestWaitReplyPort
0xB0
NtSetSystemTime
NtRequestWakeupLatency
0xB1
NtSetTimer
NtResetEvent
0xB2
NtSetTimerResolution
NtResetWriteWatch
0xB3
NtSetValueKey
NtRestoreKey
0xB4
NtSetVolumelnformationFile
NtResumeThread
0xB5
NtShutdownSystem
NtSaveKey
0xB6
NtSignalAndWaitForSingleObject
NtSaveMergedKeys
0xB7
NtStartProfile
NtSecureConnectPort
0xB8
NtStopProfile
NtSetloCompletion
0xB9
NtSuspendThread
NtSetContextThread
0xBA
NtSystemDebugControl
NtSetDefaultHardErrorPort
0xBB
NtTerminateProcess
NtSetDefaultLocale
0xBC
NtTerminateThread
NtSetDefaultUILanguage
0xBD
NtTestAlert
NtSetEaFile
0xBE
NtUnloadDriver
NtSetEvent
0xBF
NtUnloadKey
NtSetHighEventPair
0xC0
NtUnlockFile
NtSetHighWaitLowEventPair
0xC1
NtUnlockVirtualMemory
NtSetlnformationFile
0xC2
NtUnmapViewOfSection
NtSetlnformationJobObject
0xC3
NtVdmControl
NtSetlnformationKey
0xC4
NtWaitForMultipleObjects
NtSetlnformationObject
0xC5
NtWaitForSingleObject
NtSetlnformationProcess
0xC6
NtWaitHighEventPair
NtSetlnformationThread
0xC7
NtWaitLowEventPair
NtSetlnformationToken
0xC8
NtWriteFile
NtSetlntervalProfile
0xC9
NtWriteFileGather
NtSetLdtEntries
0xCA
NtWriteRequestData
NtSetLowEventPair
0xCB
NtWriteVirtualMemory
NtSetLowWaitHighEventPair
0xCC
NtCreateChannel
NtSetQuotalnformationFile
0xCD
NtListenChannel
NtSetSecurity O b j ect
0xCE
NtOpenChannel
NtSetSystemEnvironment Value
0xCF
NtReplyWaitSendChannel
NtSetSystemlnformation
0xD0
NtSendWaitReplyChannel
NtSetSystemPowerSrate
0xD1
NtSetContextChannel
NtSetSystemTime
0xD2
NtYieldExecution
NtSetThreadExecutionState
0xD3
N/A
NtSetTimer
0xD4
N/A
NtSetTimerResolution
0xD5
N/A
NtSetUuidSeed
0xD6
N/A
NtSetValueKey
0xD7
N/A
NtSetVolumelnformationFile
0xD8
N/A
NtShutdownSystem
0xD9
N/A
NtSignalAndWaitForSingleObject
0xDA
N/A
NtStartProfile
0xDB
N/A
NtStopProfile
0xDC
N/A
NtSuspendThread
0xDD
N/A
NtSystemDebugControl
0xDE
N/A
NtTerminateJobObject
0xDF
N/A
NtTerminateProcess
0xE0
N/A
NtTerminateThread
0xE1
N/A
NtTestAlert
0xE2
N/A
NtUnloadDriver
0xE3
N/A
NtUnloadKey
0xE4
N/A
NtUnlockFile
0xE5
N/A
NtUnlockVirtualMemory
0xE6
N/A
NtUnmapViewOfSection
0xE7
N/A
NtVdmControl
0xE8
N/A
NtWaitForMultipleObjects
0xE9
N/A
NtWaitForSingleObject
0xEA
N/A
NtWaitHighEventPair
0xEB
N/A
NtWaitLowEventPair
0xEC
N/A
NtWriteFile
0xED
N/A
NtWriteFileGather
0xEE
N/A
NtWriteRequestData
0xEF
N/A
NtWriteVirtualMemory
0xF0
N/A
NtCreateChannel
0xF1
N/A
NtListenChannel
0xF2
N/A
NtOpenChannel
0xF3
N/A
NtReplyWaitSendChannel
0xF4
N/A
NtSendWaitReplyChannel
0xF5
N/A
NtSetContextChannel
0xF6
N/A
NtYieldExecution
0xF7
N/A
Russinoich和Cogewell采用的最重要的一步是:编写一个内核模式的设备驱动程序来安装和维护Native API Hook。因为,用户模式下的模块没有修改系统服务描述符表的权限。就像第四章中的Spy驱动程序,这是一种多少有些特殊的驱动程序,因为它不处理通常的I/O请求。它只是导出一个简单的设备I/O控制(IOCTL)接口,以让用户模式下的代码访问它收集到的数据。该驱动程序的主要任务是修改KiServiceTable、拦截并记录所选的Windows 2000 Native API调用。尽管这种方法很简单而且优雅,它还是有些让人担心。它的简单使我想起了在DOS时代,hook一个系统服务只需要简单的修改处理器的中断向量表中的指针。任何知道如何编写基本的Windows 2000内核驱动程序的人都可以hook任意的NT系统服务而不需要而外的努力。
Russinovich和Cogswell使用他们的技术开发了一个非常有用的Windows NT注册表监视器。当使用他们的技术来完成其他“间谍”任务时,我很快就变得烦躁起来,这是因为我需要为我要监控的每个API函数都编写一个独立的hook API函数。为了避免编写大量的代码,我打算找出一种方法来强迫所有我感兴趣的API函数进入同一个hook函数中。这个任务花费了我大量的时间,并给我展示了多种多样的蓝屏。不过,最终的结果是我得到了一个通用的解决方案,只需花费很少的努力,我就能hook不同的API函数集合。
……………..待续……………