第六章 在用户模式下调用内核API函数 翻译:Kendiv( fcczj@263.net ) 更新:Friday, May 06, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
通往用户模式的桥梁 现在,内核调用接口的演化已经缓慢的到达了终点----至少已经涉及内核模式(kernel-mode)。让我们总结一下到目前为止,我们已经获得了什么: l 名为SpyCallEx()的函数(见列表6-3)将收到一个SPY_CALL_INPUT控制块,该控制块中包含目标地址和一些函数所需的参数。SpyCallEx()调用指定的地址,并且通过一个SPY_CALL_OUTPUT控制块将结果返回。
l 一种按名字查找导出的系统函数和变量的方法,该方法由SpyModuleSymbolEx()函数实现(见列表6-11)。
现在,最后一个问题是:“我们如何让用户模式下的应用程序访问这些资源?”回答当然是:“通过设备I/O控制(Device I/O Control)”。到现在为止,Spy device提供了一组IOCTL函数,表6-1列出了这些函数。该表是第四章的表4-2的摘要,表4-2包含w2k_spy.sys提供的所有IOCTL函数。列表6-12给出了与SpyDispatcher()函数相关的部分,第四章的列表4-7给出了SpyDispatcher()函数的具体细节。
表6-1最后一行中,名为SPY_IO_CALL的函数将作为通向用户模式的桥梁。我相信一旦Spy device可以访问这些极具价值的信息,它将很容易使用户模式的应用程序获取这些数据。就像在第四章和第五章中一样,下面我们将对新引入的IOCTL函数作一个简短的介绍。
表6-1. 与内核调用接口相关的IOCTL函数 函数名 | ID | IOCTL编码 | 描 述 | SPY_IO_MODULE_INFO | 19 | 0x8000604C | 返回有关已加载的系统模块的信息 | SPY_IO_PE_HEADER | 20 | 0x80006050 | 返回IMAGE_NT_HEADERS数据 | SPY_IO_PE_EXPORT | 21 | 0x80006054 | 返回IMAGE_EXPORT_DIRECTORY数据 | SPY_IO_PE_SYMBOL | 22 | 0x80006058 | 返回一个导出符号的地址 | SPY_IO_CALL | 23 | 0x8000E05C | 调用一个位于模块(已加载)内部的函数 |
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) { // unrelated IOCTL functions ommitted (cf. Listing 4-7) 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; } 列表6-12. Spy driver的Hook Command Dispatcher(摘录)
IOCTL函数SPY_IO_MODULE_INFO IOCTL函数SPY_IO_MODULE_INFO接收一个模块基地址,并返回一个SPY_MODULE_INFO结构(如果该地址指向了一个有效的PE Image)。列表6-13给出了该结构的定义以及与其相关的SpyOutputModuleInfo()帮助函数(列表6-12中的SpyDispatcher()将调用该函数)。SpyOutputModuleInfo()基于SpyModuleFind()函数(参见列表6-9),SpyModuleFind()函数返回它从ZwQuerySystemInformation()获取的MODULE_INFO数据。MODULE_INFO数据将被转换为SPY_MODULE_INFO格式后发送给调用者。
typedef struct _SPY_MODULE_INFO { PVOID pBase; DWORD dSize; DWORD dFlags; DWORD dIndex; DWORD dLoadCount; DWORD dNameOffset; BYTE abPath [MAXIMUM_FILENAME_LENGTH]; } SPY_MODULE_INFO, *PSPY_MODULE_INFO, **PPSPY_MODULE_INFO;
#define SPY_MODULE_INFO_ sizeof (SPY_MODULE_INFO)
// ----------------------------------------------------------------- NTSTATUS SpyOutputModuleInfo (PBYTE pbModule, PVOID pOutput, DWORD dOutput, PDWORD pdInfo) { SPY_MODULE_INFO smi; PMODULE_LIST pml; PMODULE_INFO pmi; DWORD dIndex; NTSTATUS ns = STATUS_INVALID_PARAMETER;
if ((pbModule != NULL) && SpyMemoryTestAddress (pbModule) && ((pml = SpyModuleFind (pbModule, &dIndex, &ns)) != NULL)) { pmi = pml->aModules + dIndex;
smi.pBase = pmi->pBase; smi.dSize = pmi->dSize; smi.dFlags = pmi->dFlags; smi.dIndex = pmi->wIndex; smi.dLoadCount = pmi->wLoadCount; smi.dNameOffset = pmi->wNameOffset;
strcpyn (smi.abPath, pmi->abPath, MAXIMUM_FILENAME_LENGTH);
ns = SpyOutputBinary (&smi, SPY_MODULE_INFO_, pOutput, dOutput, pdInfo);
SpyMemoryDestroy (pml); } return ns; } 列表6-13. SPY_IO_MODULE_INFO的实现方式
IOCTL函数SPY_IO_PE_HEADER IOCTL函数SPY_IO_PE_HEADER只是一个简单的IOCTL外包函数,其核心部分是ntoskrnl.exe导出的RtlImageNtHeader()函数,如列表6-14所示。和SPY_IO_MODULE_INFO类似,SPY_IO_PE_HEADER也需要一个模块基地址。返回的数据是模块的IMAGE_NT_HEADER结构。
NTSTATUS SpyOutputPeHeader (PVOID pBase, PVOID pOutput, DWORD dOutput, PDWORD pdInfo) { PIMAGE_NT_HEADERS pinh; NTSTATUS ns = STATUS_INVALID_PARAMETER;
if ((pBase != NULL) && SpyMemoryTestAddress (pBase) && ((pinh = RtlImageNtHeader (pBase)) != NULL)) { ns = SpyOutputBinary (pinh, IMAGE_NT_HEADERS_, pOutput, dOutput, pdInfo); } return ns; } 列表 |