第五章 监控Native API调用
翻译:Kendiv( [email protected] )
更新:Tuesday, April 19, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
在用户模式下控制API Hooks
运行在用户模式的Spy客户端可通过一组IOCTL函数来控制API Hook机制及其生成的协议。这一组函数的名字都以SPY_IO_HOOK_开始,在第四章已介绍过它们,在第四章还讨论了w2k_spy.sys的内存Spy函数(参见列表4-7和表4-2)。
下面的表5-3再次给出了与表4-2相关的部分。列表5-10是列表4-7的一个摘录,示范了Hook管理函数是如何被分派(dispatch)的。这些管理函数在后面将会被反复提及。
表5-3. Spy Device支持的IOCTL Hook管理函数
函数名称 |
ID |
IOCTL代码 |
描 述 |
SPY_IO_HOOK_INFO |
11 |
0x 8000602C |
返回Native API Hook的相关信息 |
SPY_IO_HOOK_INSTALL |
12 |
0x8000E030 |
安装Native API Hook |
SPY_IO_HOOK_REMOVE |
13 |
0x8000 E034 |
移除Native API Hook |
SPY_IO_HOOK_PAUSE |
14 |
0x8000 E038 |
暂停/恢复 Hook协议 |
SPY_IO_HOOK_FILTER |
15 |
0x8000 E 03C |
允许/禁止 Hook协议过滤器 |
SPY_IO_HOOK_RESET |
16 |
0x8000 E040 |
清除Hook协议 |
SPY_IO_HOOK_READ |
17 |
0x80006044 |
从Hook协议中读取数据 |
SPY_IO_HOOK_WRITE |
18 |
0x8000E048 |
向Hook协议中写入数据 |
NTSTATUS SpyDispatcher (PDEVICE_CONTEXT pDeviceContext,
DWORD dCode,
PVOID pInput,
DWORD dInput,
PVOID pOutput,
DWORD dOutput,
PDWORD pdInfo)
{
SPY_MEMORY_BLOCK smb;
SPY_PAGE_ENTRY spe;
SPY_CALL_INPUT sci;
PHYSICAL_ADDRESS pa;
DWORD dValue, dCount;
BOOL fReset, fPause, fFilter, fLine;
PVOID pAddress;
PBYTE pbName;
HANDLE hObject;
NTSTATUS ns = STATUS_INVALID_PARAMETER;
MUTEX_WAIT (pDeviceContext->kmDispatch);
*pdInfo = 0;
switch (dCode)
{
case SPY_IO_VERSION_INFO:
{
ns = SpyOutputVersionInfo (pOutput, dOutput, pdInfo);
break;
}
case SPY_IO_OS_INFO:
{
ns = SpyOutputOsInfo (pOutput, dOutput, pdInfo);
break;
}
case SPY_IO_SEGMENT:
{
if ((ns = SpyInputDword (&dValue,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputSegment (dValue,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_INTERRUPT:
{
if ((ns = SpyInputDword (&dValue,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputInterrupt (dValue,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_PHYSICAL:
{
if ((ns = SpyInputPointer (&pAddress,
pInput, dInput))
== STATUS_SUCCESS)
{
pa = MmGetPhysicalAddress (pAddress);
ns = SpyOutputBinary (&pa, PHYSICAL_ADDRESS_,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_CPU_INFO:
{
ns = SpyOutputCpuInfo (pOutput, dOutput, pdInfo);
break;
}
case SPY_IO_PDE_ARRAY:
{
ns = SpyOutputBinary (X86_PDE_ARRAY, SPY_PDE_ARRAY_,
pOutput, dOutput, pdInfo);
break;
}
case SPY_IO_PAGE_ENTRY:
{
if ((ns = SpyInputPointer (&pAddress,
pInput, dInput))
== STATUS_SUCCESS)
{
SpyMemoryPageEntry (pAddress, &spe);
ns = SpyOutputBinary (&spe, SPY_PAGE_ENTRY_,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_MEMORY_DATA:
{
if ((ns = SpyInputMemory (&smb,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputMemory (&smb,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_MEMORY_BLOCK:
{
if ((ns = SpyInputMemory (&smb,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputBlock (&smb,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HANDLE_INFO:
{
if ((ns = SpyInputHandle (&hObject,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputHandleInfo (hObject,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HOOK_INFO:
{
ns = SpyOutputHookInfo (pOutput, dOutput, pdInfo);
break;
}
case SPY_IO_HOOK_INSTALL:
{
if (((ns = SpyInputBool (&fReset,
pInput, dInput))
== STATUS_SUCCESS)
&&
((ns = SpyHookInstall (fReset, &dCount))
== STATUS_SUCCESS))
{
ns = SpyOutputDword (dCount,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HOOK_REMOVE:
{
if (((ns = SpyInputBool (&fReset,
pInput, dInput))
== STATUS_SUCCESS)
&&
((ns = SpyHookRemove (fReset, &dCount))
== STATUS_SUCCESS))
{
ns = SpyOutputDword (dCount,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HOOK_PAUSE:
{
if ((ns = SpyInputBool (&fPause,
pInput, dInput))
== STATUS_SUCCESS)
{
fPause = SpyHookPause (fPause);
ns = SpyOutputBool (fPause,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HOOK_FILTER:
{
if ((ns = SpyInputBool (&fFilter,
pInput, dInput))
== STATUS_SUCCESS)
{
fFilter = SpyHookFilter (fFilter);
ns = SpyOutputBool (fFilter,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HOOK_RESET:
{
SpyHookReset ();
ns = STATUS_SUCCESS;
break;
}
case SPY_IO_HOOK_READ:
{
if ((ns = SpyInputBool (&fLine,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputHookRead (fLine,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HOOK_WRITE:
{
SpyHookWrite (pInput, dInput);
ns = STATUS_SUCCESS;
break;
}
case SPY_IO_MODULE_INFO:
{
if ((ns = SpyInputPointer (&pbName,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputModuleInfo (pbName,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_PE_HEADER:
{
if ((ns = SpyInputPointer (&pAddress,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputPeHeader (pAddress,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_PE_EXPORT:
{
if ((ns = SpyInputPointer (&pAddress,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputPeExport (pAddress,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_PE_SYMBOL:
{
if ((ns = SpyInputPointer (&pbName,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputPeSymbol (pbName,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_CALL:
{
if ((ns = SpyInputBinary (&sci, SPY_CALL_INPUT_,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputCall (&sci,
pOutput, dOutput, pdInfo);
}
break;
}
}
MUTEX_RELEASE (pDeviceContext->kmDispatch);
return ns;
}
列表5-10. Spy Driver的Hook命令分派器
IOCTL函数 SPY_IO_HOOK_INFO
IOCTL函数SPY_IO_HOOK_INFO采用Hook机制的当前信息来填充一个SPY_HOOK_INFO结构,这和系统的SDT类似。SPY_HOOK_INFO结构(列表5-11)中引用了多个前面介绍过的结构:
l SERVICE_DESCRIPTOR_TABLE结构,定义于列表5-1
l SPY_CALL和SPY_HOOK_ENTRY结构,定义于列表5-2
l SPY_HEADER和SPY_PROTOCOL结构,定义于列表5-9
typedef struct _SPY_HOOK_INFO
{
SPY_HEADER sh;
PSPY_CALL psc;
PSPY_PROTOCOL psp;
PSERVICE_DESCRIPTOR_TABLE psdt;
SERVICE_DESCRIPTOR_TABLE sdt;
DWORD ServiceLimit;
NTPROC ServiceTable [SDT_SYMBOLS_MAX];
BYTE ArgumentTable [SDT_SYMBOLS_MAX];
SPY_HOOK_ENTRY SpyHooks [SDT_SYMBOLS_MAX];
}
SPY_HOOK_INFO, *PSPY_HOOK_INFO, **PPSPY_HOOK_INFO;
#define SPY_HOOK_INFO_ sizeof (SPY_HOOK_INFO)
列表5-11. SPY_HOOK_INFO结构定义
在计算该结构体成员的值时一定要小心。某些成员中的指针指向内核模式下的内存块,这些内存地址在用户模式下是无法访问的。不过,你可以使用Spy设备的SPY_IO_MEMORY_DATA函数来检查这些内存块中的数据。
IOCTL函数 SPY_IO_HOOK_INSTALL
SPY_IO_HOOK_INSTALL函数使用存储在全局aSpyHooks[]数组中的Hook进入点(Hook Entry Point)来修改(patch)ntoskrnl.exe在系统SDT中的服务表(Service Table)。该全局数组在驱动程序初始化时由SpyHookInitialize()(列表5-5)和SpyHookInitializeEx()(列表5-3)函数构建。aSpyHooks[]数组中的每个有效的元素均包含一个hook进入点以及相应的格式字符串地址。SpyDispatcher()调用辅助函数SpyHookInstall()(见列表5-12)来安装Hook。SpyHookInstall()使用的SpyHookExchange()函数也包含在列表5-12中。
DWORD SpyHookExchange (void)
{
PNTPROC ServiceTable;
BOOL fPause;
DWORD i;
DWORD n = 0;
fPause = SpyHookPause (TRUE);
ServiceTable = KeServiceDescriptorTable->ntoskrnl.ServiceTable;
for (i = 0; i < SDT_SYMBOLS_MAX; i++)
{
if (aSpyHooks [i].pbFormat != NULL)
{
aSpyHooks [i].Handler = (NTPROC)
InterlockedExchange ((PLONG) ServiceTable+i,
( LONG) aSpyHooks [i].Handler);
n++;
}
}
gfSpyHookState = !gfSpyHookState;
SpyHookPause (fPause);
return n;
}
// -----------------------------------------------------------------
NTSTATUS SpyHookInstall (BOOL fReset,
PDWORD pdCount)
{
DWORD n = 0;
NTSTATUS ns = STATUS_INVALID_DEVICE_STATE;
if (!gfSpyHookState)
{
ghSpyHookThread = PsGetCurrentThreadId ();
n = SpyHookExchange ();
if (fReset) SpyHookReset ();
ns = STATUS_SUCCESS;
}
*pdCount = n;
return ns;
}
列表5-12. Patch the System’s API Service Table
在安装和移除Hooks时都需要用到SpyHookExchange()函数,该函数只是简单的交换系统的API服务表和aSpyHooks[]数组中的内容。因此,若调用两次SpyHookExchange(),则可恢复服务表和数组的初始状态。SpyHookExchange()遍历aSpyHooks[]数组来寻找包含格式字符串指针的数组元素。这里的格式字符串是为了指定要监控的API函数。在这种情况下,将使用ntoskrnl.exe中的InterlockedExchange()函数来交换服务表中的API函数指针和aSpyHooks[]元素的Handler成员,使用InterlockedExchange()函数是为了保证没有其他线程干扰这一操作。协议机制在这一过程中将被临时停止,直到对服务表的修改结束。SpyHookInstall()仅是SpyHookExchange()的一个外包函数,这样做是为了执行一些附加的操作:
l 如果全局标志gfSpyHookState表示Hooks仍在安装,则禁止访问服务表(Service Table)。
l 调用者的线程ID将被写入全局变量ghSpyHookThread。SpyHookInitializeEx()函数中的Hook分派器(Dispatcher)将屏蔽该变量所指线程发出的API调用。以避免Hook协议受到干扰。
l 根据客户端的请求,协议会被重置。这意味着所有的缓冲区都将被清空,并且句柄目录也将被重新初始化。
SPY_IO_HOOK_INSTALL接收调用者传入的一个逻辑型参数。如果为TRUE,协议将在Hooks安装完成后被重置。这是常用的一个选项。如果为FALSE,协议将从前一次Hook会话结束的位置继续。该函数的返回值表示服务表中被修改的项数。如果是Windows 2000,SPY_IO_HOOK_INSTALL返回的是44,这一数值正是列表5-6给出的格式字符串数组apdSdtFormats[]的大小。在Windows NT 4.0下,则仅有42个Hook被安装,因为API函数NtNotifyChangeMultipleKeys()和NtQueryOpenSubKeys()并不被NT 4.0支持。
…………待续…………..