第五章 监控Native API调用
翻译:Kendiv( fcczj@263.net )
更新:Thursday, April 28, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
IOCTL函数SPY_IO_HOOK_REMOVE函数和SPY_IO_HOOK_INSTALL很相似,只不过它执行与SPY_IO_HOOK_INSTALL相反的操作。这两个函数的输入、输出参数都是相同的。下面是列表5-12和列表5-13的对比:
l 如果全局标志gfSpyHookState表示当前并未安装Hooks,那么调用将被忽略。
l 在将Service Table恢复到修改前的状态后,在全局变量ghSpyHookThread中保存的安装Hook的线程ID将被置零。
l 最重要的扩展特性是位于列表5-13中部的do/while循环,SpyHookRemove()通过测试全局结构DEVICE_CONTEXT中的所有SPY_CALL子结构的fInUse成员来判断Hook Dispatcher是否还在为其他线程提供服务。这种测试是必须的,因为客户可能会在卸载Hook后就立即卸载Spy驱动程序。如果在Hook Dispatcher中还存在其他程序的API调用的情况下,就卸载Spy驱动程序,系统将抛出一个异常,随后将引发蓝屏。对fInUse的测试每隔100msec就进行一次,以使其他线程有机会推出Spy。
NTSTATUS SpyHookRemove (BOOL fReset,
PDWORD pdCount)
{
LARGE_INTEGER liDelay;
BOOL fInUse;
DWORD i;
DWORD n = 0;
NTSTATUS ns = STATUS_INVALID_DEVICE_STATE;
if (gfSpyHookState)
{
n = SpyHookExchange ();
if (fReset) SpyHookReset ();
do {
for (i = 0; i < SPY_CALLS; i++)
{
if (fInUse = gpDeviceContext->SpyCalls [i].fInUse)
break;
}
liDelay.QuadPart = -1000000;
KeDelayExecutionThread (KernelMode, FALSE, &liDelay);
}
while (fInUse);
ghSpyHookThread = 0;
ns = STATUS_SUCCESS;
}
*pdCount = n;
return ns;
}
列表5-13. 恢复系统的API Service Table
注意,即使所有的fInUse标志都被清除,最后的100msec延迟也是必须的。这种预防措施是由于在Hook Dispatcher中存在一个很小的安全漏洞,这一漏洞存在于重置当前SPY_CALL中的fInUse标志的指令和Dispatcher将控制返回给调用着的RET指令(参考列表5-2中,SpyHook8和SpyHook9之间的ASM代码)之间。如果所有的fInUse标志都是FALSE,则存在一个很小的可能性,使得某些线程在要执行RET指令之前被暂停(Suspend)。继续延迟100msec后再移除Hook,则可以使得这些线程有机会离开这一临界区。
IOCTL函数 SPY_IO_HOOK_PAUSE
列表5-14给出了SPY_IO_HOOK_PAUSE函数,该函数允许一个客户临时禁止(或再次允许)Hook协议函数。实质上,该函数让客户设置全局逻辑变量gfSpyHookPause并将其原始值返回给客户来实现这一功能的,这里用到了ntoskrnl.exe中的InterlockExchange()函数。默认情况下,协议是被允许的;这就意味着,gfSpyHookPause的初值为FALSE。
BOOL SpyHookPause (BOOL fPause)
{
BOOL fPause1 = (BOOL)
InterlockedExchange ((PLONG) &gfSpyHookPause,
( LONG) fPause);
if (!fPause) SpyHookReset ();
return fPause1;
}
列表5-14. 打开/关闭协议
这里要特别注意的是,SPY_IO_HOOK_PAUSE的工作完全依赖于SPY_IO_HOOK_INSTALL和SPY_IO_HOOK_REMOVE。如果在安装Hook时,禁止了协议,Hook仍然会起作用,但是此时Hook Dispatcher将对所有捕获到的API调用不进行任何干扰。如果你不希望SPY_IO_HOOK_INSTALL修改Service Table后,协议就自动开始的话,你可以在安装Hook之前禁止Hook协议。注意,当协议被再次恢复时,协议将被自动重置。
IOCTL函数 SPY_IO_HOOK_FILTER
IOCTL函数SPY_IO_HOOK_FILTER维护一个全局标志,如列表5-15所示。这里,全局标志gfSpyHookFilter被设置为一个由客户提供的值,其先前的值将被返回给客户。该标志的默认值为FALSE;这意味着,默认情况下,过滤器是被禁止的。
BOOL SpyHookFilter (BOOL fFilter)
{
return (BOOL) InterlockedExchange ((PLONG) &gfSpyHookFilter,
( LONG) fFilter);
}
列表5-15. 打开/关闭协议过滤器
在讨论列表5-8中的SpyWriteFilter()函数时,就已经涉及到gfSpyHookFilter,你应该对该变量不再陌生。如果gfSpyHookFilter为TRUE,辅助函数SpyHookProtocol()(参见列表5-7)将忽略所有的API调用,这包括含有先前并未在Spy device中注册的句柄的API调用。
IOCTL函数 SPY_IO_HOOK_RESET
IOCTL函数SPY_IO_HOOK_RESET用于将协议机制恢复到原始状态,这包括清除数据缓冲区和“丢掉”所有已注册的句柄。由SpyDispatcher()调用的SpyHookReset()函数只是SpyWriteReset()的一个外包函数而已。这两个函数都包含在列表5-16中。SpyHookReset()的附加特性只是调用了使用mutex的SpyHookWait()和SpyHookRelease()函数(参见列表5-7)来进行同步。
void SpyWriteReset (PSPY_PROTOCOL psp)
{
KeQuerySystemTime (&psp->sh.liStart);
psp->sh.dRead = 0;
psp->sh.dWrite = 0;
psp->sh.dCalls = 0;
psp->sh.dHandles = 0;
psp->sh.dName = 0;
}
// -----------------------------------------------------------------
void SpyHookReset (void)
{
SpyHookWait ();
SpyWriteReset (&gpDeviceContext->SpyProtocol);
SpyHookRelease ();
return;
}
// -----------------------------------------------------------------
NTSTATUS SpyHookWait (void)
{
return MUTEX_WAIT (gpDeviceContext->kmProtocol);
}
// -----------------------------------------------------------------
LONG SpyHookRelease (void)
{
return MUTEX_RELEASE (gpDeviceContext->kmProtocol);
}
列表5-16. 重置协议
IOCTL函数 SPY_IO_HOOK_READ
API Hook记录器(logger)将协议数据写入abData[]缓冲区中,该缓冲区位于全局结构SPY_PROTOCOL中,该结构体已在列表5-9中给出。这种类型的缓冲区被设计为一个环形缓冲区。这意味着,其特点是使用一对指针来分别进行读/写访问。只要其中的一个指针移动到了缓冲区的尾端,它就会被重置并指向缓冲区的头部。读指针一直试图追上写指针,如果这两个指针指向相同地址,那意味着缓冲区为空。
SPY_IO_HOOK_READ是到目前为止,Spy Device提供的最重要的管理Hook的函数之一。它可以从协议数据缓冲区中读取任意大小的数据,并适当的调整读指针。在协议被允许时,该函数应该被频繁的调用,以避免缓冲区的溢出。列表5-17给出了处理此种IOCTL请求的一组函数。其中最基本的是SpyReadData()和SpyReadLine()。二者的区别在于如果可能,前者将返回所请求的数据,而后者仅返回一整行数据。当客户端要对读取到的数据进行过滤时,行模式就显得很方便了。SPY_IO_HOOK_READ的调用者通过传入一个逻辑型变量来确定是读取方式是行模式还是块模式。
DWORD SpyReadData (PSPY_PROTOCOL psp,
PBYTE pbData,
DWORD dData)
{
DWORD i = psp->sh.dRead;
DWORD n = 0;
while ((n < dData) && (i != psp->sh.dWrite))
{
pbData [n++] = psp->abData [i++];
if (i == SPY_DATA_BUFFER) i = 0;
}
psp->sh.dRead = i;
return n;
}
// -----------------------------------------------------------------
DWORD SpyReadLine (PSPY_PROTOCOL psp,
PBYTE pbData,
DWORD dData)
{
BYTE b = 0;
DWORD i = psp->sh.dRead;
DWORD n = 0;
while ((b != '/n') && (i != psp->sh.dWrite))
{
b = psp->abData [i++];
if (i == SPY_DATA_BUFFER) i = 0;
if (n < dData) pbData [n++] = b;
}
if (b == '/n')
{
// remove current line from buffer
psp->sh.dRead = i;
}
else
{
// don't return any data until full line available
n = 0;
}
if (n)
{
pbData [n-1] = 0;
}
else
{
if (dData) pbData [0] = 0;
}
return n;
}
// -----------------------------------------------------------------
DWORD SpyHookRead (PBYTE pbData,
DWORD dData,
BOOL fLine)
{
DWORD n = 0;
SpyHookWait ();
n = (fLine ? SpyReadLine : SpyReadData)
(&gpDeviceContext->SpyProtocol, pbData, dData);
SpyHookRelease ();
return n;
}
// -----------------------------------------------------------------
NTSTATUS SpyOutputHookRead (BOOL fLine,
PVOID pOutput,
DWORD dOutput,
PDWORD pdInfo)
{
*pdInfo = SpyHookRead (pOutput, dOutput, fLine);
return STATUS_SUCCESS;
}
列表5-17. 从协议缓冲区中读取数据
SpyOutputHookRead()和SpyHookRead()函数没有太大价值。SpyHookRead()使用了Mutex来进行同步,而且可以选择SpyReadLine()或SpyReadData(),SpyOutputHookRead()则是根据IOCTL框架的要求来提交它的结果。
IOCTL函数 SPY_IO_HOOK_WRITE
该函数允许客户向协议缓冲区中写入数据。应用程序可使用这一特性来向协议中增加独立的或附加的状态信息。列表5-18给出了该函数的实现部分。SpyHookWrite()是另一个外包函数,它使用了Mutex进行同步。它调用的SpyWriteData()函数是Spy Device中最基本的协议生成器。所有的SpyWrite*()辅助函数(如,SpyWriteFormat()、SpyWriteNumber()、SpyWriteChar()和SpyWriteLarge()函数由SpyHookProtocol()使用,见列表5-7)都是基于SpyWriteData()的。
DWORD SpyWriteData (PSPY_PROTOCOL psp,
PBYTE pbData,
DWORD dData)
{
BYTE b;
DWORD i = psp->sh.dRead;
DWORD j = psp->sh.dWrite;
DWORD n = 0;
while (n < dData)
{
psp->abData [j++] = pbData [n++];
if (j == SPY_DATA_BUFFER) j = 0;
if (j == i)
{
// remove first line from buffer
do {
b = psp->abData [i++];
if (i == SPY_DATA_BUFFER) i = 0;
}
while ((b != '/n') && (i != j));
// remove half line only if single line
if ((i == j) &&
((i += (SPY_DATA_BUFFER / 2)) >= SPY_DATA_BUFFER))
{
i -= SPY_DATA_BUFFER;
}
}
}
psp->sh.dRead = i;
psp->sh.dWrite = j;
return n;
}
// -----------------------------------------------------------------
DWORD SpyHookWrite (PBYTE pbData,
DWORD dData)
{
DWORD n = 0;
SpyHookWait ();
n = SpyWriteData
(&gpDeviceContext->SpyProtocol, pbData, dData);
SpyHookRelease ();
return n;
}
列表5-18. 向协议缓冲区中写入数据
这里要注意SpyWriteData()是如何管理缓冲区溢出情况的。如果读指针前进的太慢,写指针将会覆盖它。此时,两个指针都是有效的:
1. 写访问将被禁止,直到读指针领先于写指针。
2. 已缓冲的数据将被丢弃,以腾出空间。
Spy Device采用上述第二种方式。如果溢出发生了,从当前读指针的位置开始的一行协议数据将被丢弃,并将读指针移动到下一行开始处。如果缓冲区中仅有一行数据(这种情况极少出现),则仅丢弃该行的前一半数据。列表5-18中处理这些情况的代码都有相应的注释。
Next:
下一节将介绍一个简单的读取协议数据的程序