第五章 监控Native API调用
翻译:Kendiv( fcczj@263.net )
更新:Tuesday, April 12, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
管理句柄
要特别注意的是:SpyHookProtocol()函数仅在它内部包含SpyWriteFilter()函数的条件语句返回TRUE时,才会记录API函数调用。这种行为是为了减少Hook协议中的无用信息。例如,移动鼠标将触发一系列NtReadFile()函数调用。其他产生无用信息的源头和物理实验中的测量有些相似:比如,你在实验环境下测量某种物理效果时,测量方法本身也会对要测量的物理效果产生影响,这将导致测量结果的“失真”。这同样发生在记录API时。注意:NtDeviceIoControlFile()函数也包含在列表5-6中的格式化字符串数组中。不过,Spy设备的客户端采用Device I/O Control调用来读取API Hook协议。这意味着客户端将在协议数据中发现它自己的NtDeviceIoControlFile()调用。根据IOCTL事务的频率,你所期望的数据可能会淹没在它自己的函数调用记录中(译注:此处是指,如果客户端频繁的使用IOCTL,那么记录下的IOCTL函数调用中,仅有少量是我们所期望关注的,而大部分都是客户端自己产生的),Spy设备通过记录安装API Hook的那个线程的ID,并忽略来自该线程的API调用来解决这一问题。
对于所有产生句柄,并且产生的句柄未被记录的API调用,SpyWriteFilter()都将忽略,以消除无用信息。如果Spy设备发现一个句柄已经关闭或已返回给系统,那么任何使用该句柄的函数序列都将被忽略。更有效的是,如果API调用包含由系统或其他在启动API Hook协议之前就存在的进程创建的句柄(这种句柄的生命期都很长),那么该API调用将会被禁止。当然,客户端可以通过IOCTL禁止或允许这种过滤。你可以使用本章稍后将介绍的一个简单的客户端程序来来测试这种过滤机制。你将会很惊讶这个简单的“噪音过滤”功能的强大。
在列表5-6中,产生句柄的函数有:NtCreateFile()、NtCreateKey()、NtOpenFile()、NtOpenKey()、NtOpenProcess()和NtOpenThread()。所有这些函数的格式字符串中都包含一个%+控制符,该控制符用于表示表5-2中的“句柄(注册)”。对于关闭或释放句柄的函数:NtClose()和NtDeleteKey(),在二者的格式字符串中均包含一个%-控制符,对应表5-2中的“句柄(撤销注册)”。其他函数只是简单的使用句柄(这些函数并不会创建获释放这些句柄),在它们的格式字符串中包含%!。基本上,句柄是在进程的上下文中,用于唯一标识一个对象的数字。当一个API函数产生一个新的句柄时,客户端通常必须传入一个OBJECT_ATTRIBUTES结构来存放该句柄以及其他的一些东西,对于对象的名字来说,它应该是可以访问的。稍后,将不再需要该名称,因为系统可以使用对象句柄在句柄表中查寻其对应的对象的属性。这对于API Spy的用户来说却不是好消息,因为它必需在大量的协议项中用毫无疑义的数字代替符号名称。因此,我的Spy设备将注册所有的对象名称及其对应的句柄和该句柄所有者进程的ID,每当一个新的句柄出现,就会更新这一列表。当一个注册的句柄/进程对在稍后出现时,API记录者将会从列表中检索出其对应的原始符号名称,并将其加入的协议中。
已注册的句柄在被某个API函数显示关闭前,都是有效的。如果在某个可产生新的句柄的API调用中,再次出现已注册句柄,那么该句柄将不再处于已注册状态。对于Windows 2000,我频繁的观测系统有时会返回相同的句柄值,尽管在此之前,协议里没有包含任何关闭该句柄的调用。我不记得在Windows NT 4.0中是否也存在类似的情况。一个已注册的句柄,若再次代表不同的对象属性出现时,会被显示的由某种方式关闭掉,因此必须要撤销此种句柄的注册。否则,Spy设备的句柄目录将最终进入溢出状态。
SpyHookPrtocol()调用的SpyWriteFilter()函数(在列表5-7中给出)是句柄跟踪机制的一个基本组成部分。任何被hook的API函数在被调用时,都会经过该函数。列表5-8给出了该函数的实现方式。
BOOL SpyWriteFilter (PSPY_PROTOCOL psp,
PBYTE pbFormat,
PVOID pParameters,
DWORD dParameters)
{
PHANDLE phObject = NULL;
HANDLE hObject = NULL;
POBJECT_ATTRIBUTES poa = NULL;
PDWORD pdNext;
DWORD i, j;
pdNext = pParameters;
i = j = 0;
while (pbFormat [i])
{
while (pbFormat [i] && (pbFormat [i] != '%')) i++;
if (pbFormat [i] && pbFormat [++i])
{
j++;
switch (pbFormat [i++])
{
case 'b':
case 'a':
case 'w':
case 'u':
case 'n':
case 'l':
case 's':
case 'i':
case 'c':
case 'd':
case 'p':
{
break;
}
case 'o':
{
if (poa == NULL)
{
poa = (POBJECT_ATTRIBUTES) *pdNext;
}
break;
}
case '+':
{
if (phObject == NULL)
{
phObject = (PHANDLE) *pdNext;
}
break;
}
case '!':
case '-':
{
if (hObject == NULL)
{
hObject = (HANDLE) *pdNext;
}
break;
}
default:
{
j--;
break;
}
}
pdNext++;
}
}
return // number of arguments ok
(j == dParameters)
&&
// no handles involved
(((phObject == NULL) && (hObject == NULL))
||
// new handle, successfully registered
((phObject != NULL) &&
SpyHandleRegister (psp, PsGetCurrentProcessId (),
*phObject, OBJECT_NAME (poa)))
||
// registered handle
SpyHandleSlot (psp, PsGetCurrentProcessId (), hObject)
||
// filter disabled
(!gfSpyHookFilter));
}
列表5-8. SpyWriteFilter()将从协议中去除未注册的API调用
SpyWriteFilter()主要通过扫描协议格式字符串来查找%o(对象属性)、%+(新的句柄)、%!(打开句柄)和%-(关闭句柄),并对其中某几个控制ID的组合采取特殊的动作,如下所示:
l 如果没有涉及句柄,所有的API调用都将被记录。这涉及所有格式字符串中不包含格式控制ID%+、%!和%-的API函数。
l 如果格式字符串中包含%+,这表示该API函数将分配一个新的句柄,将使用帮助函数SpyHandleRegister()来注册该句柄,并且将该句柄与格式字符串中第一个%o项的名字关联起来。如果这样的名字不存在,则该句柄将以一个空字符串注册。如果注册成功,此API调用将被记录。
l 如果格式字符串中包含%!或%-,则被调用的函数将使用或关闭一个句柄。这种情况下,SpyWriteFilter()将使用SpyHandleSlot()函数来查询该句柄的索引,以测试该句柄是否已注册。如果测试成功,该API调用将被记录。
l 对于所有其他的情况,仅当过滤机制关闭时,函数调用才被记录。过滤机制是否开启由全局逻辑变量gfSpyHookFilter表示。
句柄目录是SPY_PROTOCOL结构的一部分,包含在Spy设备(w2k_spy.sys)的全局DEVICE_CONTEXT结构中,列表5-9给出了它的定义以及SPY_HEADER子结构的定义。在这些定义之后的是四个句柄管理函数(SpyHandleSlot()、SpyHandleName()、SpyHandleUnregister()和SpyHandleRegister())的源代码。通过将句柄的值加入到ahObjets[]数组的末尾来注册该句柄。同时,拥有该句柄的进程ID将被保存到ahprocesses[]数组中,对象名将被复制到awNames[]缓冲区中,名字的起始偏移地址将被保存到adNames[]数组中。当注销一个句柄时,过程与上述类似,这可能需要移动数组中的其它元素以保证数组中不存在“空洞”。列表5-9顶部定义的常量定义了句柄目录的大小:它最多可存放4,096个句柄,名称的大小被限制为1,048,576个Unicode字符(2MB),协议缓冲区的大小为1MB。
#define SPY_HANDLES 0x00001000 // max number of handles
#define SPY_NAME_BUFFER 0x00100000 // object name buffer size
#define SPY_DATA_BUFFER 0x00100000 // protocol data buffer size
// -----------------------------------------------------------------
typedef struct _SPY_HEADER
{
LARGE_INTEGER liStart; // start time
DWORD dRead; // read data index
DWORD dWrite; // write data index
DWORD dCalls; // api usage count
DWORD dHandles; // handle count
DWORD dName; // object name index
}
SPY_HEADER, *PSPY_HEADER, **PPSPY_HEADER;
#define SPY_HEADER_ sizeof (SPY_HEADER)
// -----------------------------------------------------------------
typedef struct _SPY_PROTOCOL
{
SPY_HEADER sh; // protocol header
HANDLE ahProcesses [SPY_HANDLES]; // process id array
HANDLE ahObjects [SPY_HANDLES]; // handle array
DWORD adNames [SPY_HANDLES]; // name offsets
WORD awNames [SPY_NAME_BUFFER]; // name strings
BYTE abData [SPY_DATA_BUFFER]; // protocol data
}
SPY_PROTOCOL, *PSPY_PROTOCOL, **PPSPY_PROTOCOL;
#define SPY_PROTOCOL_ sizeof (SPY_PROTOCOL)
// =================================================================
// HANDLE MANAGEMENT
// =================================================================
DWORD SpyHandleSlot (PSPY_PROTOCOL psp,
HANDLE hProcess,
HANDLE hObject)
{
DWORD dSlot = 0;
if (hObject != NULL)
{
while ((dSlot < psp->sh.dHandles)
&&
((psp->ahProcesses [dSlot] != hProcess) ||
(psp->ahObjects [dSlot] != hObject ))) dSlot++;
dSlot = (dSlot < psp->sh.dHandles ? dSlot+1 : 0);
}
return dSlot;
}
// -----------------------------------------------------------------
DWORD SpyHandleName (PSPY_PROTOCOL psp,
HANDLE hProcess,
HANDLE hObject,
PWORD pwName,
DWORD dName)
{
WORD w;
DWORD i;
DWORD dSlot = SpyHandleSlot (psp, hProcess, hObject);
if ((pwName != NULL) && dName)
{
i = 0;
if (dSlot)
{
while ((i+1 < dName) &&
(w = psp->awNames [psp->adNames [dSlot-1] + i]))
{
pwName [i++] = w;
}
}
pwName [i] = 0;
}
return dSlot;
}
// -----------------------------------------------------------------
DWORD SpyHandleUnregister (PSPY_PROTOCOL psp,
HANDLE hProcess,
HANDLE hObject,
PWORD pwName,
DWORD dName)
{
DWORD i, j;
DWORD dSlot = SpyHandleName (psp, hProcess, hObject,
pwName, dName);
if (dSlot)
{
if (dSlot == psp->sh.dHandles)
{
// remove last name entry
psp->sh.dName = psp->adNames [dSlot-1];
}
else
{
i = psp->adNames [dSlot-1];
j = psp->adNames [dSlot ];
// shift left all remaining name entries
while (j < psp->sh.dName)
{
psp->awNames [i++] = psp->awNames [j++];
}
j -= (psp->sh.dName = i);
// shift left all remaining handles and name offsets
for (i = dSlot; i < psp->sh.dHandles; i++)
{
psp->ahProcesses [i-1] = psp->ahProcesses [i];
psp->ahObjects [i-1] = psp->ahObjects [i];
psp->adNames [i-1] = psp->adNames [i] - j;
}
}
psp->sh.dHandles--;
}
return dSlot;
}
// -----------------------------------------------------------------
DWORD SpyHandleRegister (PSPY_PROTOCOL psp,
HANDLE hProcess,
HANDLE hObject,
PUNICODE_STRING puName)
{
PWORD pwName;
DWORD dName;
DWORD i;
DWORD dSlot = 0;
if (hObject != NULL)
{
// unregister old handle with same value
SpyHandleUnregister (psp, hProcess, hObject, NULL, 0);
if (psp->sh.dHandles == SPY_HANDLES)
{
// unregister oldest handle if overflow
SpyHandleUnregister (psp, psp->ahProcesses [0],
psp->ahObjects [0], NULL, 0);
}
pwName = ((puName != NULL) && SpyMemoryTestAddress (puName)
? puName->Buffer
: NULL);
dName = ((pwName != NULL) && SpyMemoryTestAddress (pwName)
? puName->Length / WORD_
: 0);
if (dName + 1 <= SPY_NAME_BUFFER - psp->sh.dName)
{
// append object to end of list
psp->ahProcesses [psp->sh.dHandles] = hProcess;
psp->ahObjects [psp->sh.dHandles] = hObject;
psp->adNames [psp->sh.dHandles] = psp->sh.dName;
for (i = 0; i < dName; i++)
{
psp->awNames [psp->sh.dName++] = pwName [i];
}
psp->awNames [psp->sh.dName++] = 0;
psp->sh.dHandles++;
dSlot = psp->sh.dHandles;
}
}
return dSlot;
}
列表5-9. 句柄管理相关的结构和函数
…….………..待续………………