第六章 在用户模式下调用内核API函数
翻译:Kendiv( fcczj@263.net )
更新:Sunday, May 15, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
访问未导出的符号
为什么我们要允许应用程序执行本该内核驱动程序完成的操作?我们能将应用程序的能力增强到内核驱动程序也无法达到的程度吗?我们可以调用既没有文档化也没有导出的内部函数吗?这听起来有些危险,不过,就像我接下来要展示的那样,如果小心的进行控制,它将不会像看起来那样“可怕”。
查找内部符号
前面所讲的内核调用接口将查找导出符号的地址的工作委托给了Spy device,该Spy device可全面访问位于高一半的线形地址空间(upper half of the linear address space)中的内核模块的PE映像。不过,如果要调用的函数或者要访问的全局变量并没有导出,那么Spy device将没有机会找到它们的地址。在编写本章以及检查内核调试器输出的某些反汇编代码时,我总是想:“他们不导出这个漂亮的函数真是可惜!”这是因为内核调试器向我展示了确切的函数名,这让我非常恼火,但我的应用程序将完全忽略它们。当然,我可以使用我的内核调用接口跳到这些函数的无格式二进制入口点(the plain binary entry point of the function),但这不是一种很好的编程风格。以后发布的Service Pack很可能将这些入口点移动到其他位置。
我相信如果调试器可以做到这一点,我的程序也应该可以做到。在第一章中给出的一个示例DLL将我引上了正确的道路。只要操作系统的的符号文件正确安装了,w2k_img.dll就可以查找由Windows 2000内核模块定义的任意符号的地址。因此,我进一步扩展了w2k_call.dll,增加了一个新的API函数,该函数可先查找一个内部符号对应的线性地址,然后调用w2kCall()执行它。当然,针对全局变量也提供了类似的函数。
列表6-32给出了所有扩展后的调用接口函数。并且,为了方便,还为每种主要函数类型提供了单独的函数,这些单独的函数对应列表6-20到6-22中的函数。w2kXCall()将完成主要的工作,它调用w2k_img.dll中的API函数imgTableResolve()以获取指定符号的线性地址,如果成功,该地址将被列入随后的w2kCall()调用列表中。因为w2kCall()被设计为调用一个地址而不是一个符号,NULL指针将作为w2kCall()的pbSymbol参数传入。pEntryPoint参数将被设置为符号的地址pie->pAddress,该地址从对应的符号文件中获取。就像在第一章解释的那样,w2k_img.dll可以确定大多数内部函数所需的调用约定,因此,通过测试IMG_CONVENTION_FASTCALL结构中pie->dconvention的值可自动设置fFastcall参数。参数的字节数和指向参数的指针将向前传递,就像接收自调用者一样。可以从符号信息中获取参数的个数,不过这仅对__stdcall和__fastcall函数有效。__cdecl symbols don’t encode the argument stack size in their decoration.
BOOL WINAPI w2kXCall (PULARGE_INTEGER puliResult,
PBYTE pbSymbol,
DWORD dArgumentBytes,
PVOID pArguments)
{
PIMG_TABLE pit;
PIMG_ENTRY pie;
BOOL fOk = FALSE;
if (((pit = w2kSymbolsGlobal (NULL)) != NULL) &&
((pie = imgTableResolve (pit, pbSymbol)) != NULL) &&
(pie->pAddress != NULL))
{
fOk = w2kCall (puliResult, NULL, pie->pAddress,
pie->dConvention == IMG_CONVENTION_FASTCALL,
dArgumentBytes, pArguments);
}
else
{
if (puliResult != NULL) puliResult->QuadPart = 0;
}
return fOk;
}
// -----------------------------------------------------------------
BOOL WINAPI w2kXCallV (PULARGE_INTEGER puliResult,
PBYTE pbSymbol,
DWORD dArgumentBytes,
...)
{
return w2kXCall (puliResult, pbSymbol,
dArgumentBytes, &dArgumentBytes + 1);
}
// -----------------------------------------------------------------
NTSTATUS WINAPI w2kXCallNT (PBYTE pbSymbol,
DWORD dArgumentBytes,
...)
{
ULARGE_INTEGER uliResult;
return (w2kXCall (&uliResult, pbSymbol,
dArgumentBytes, &dArgumentBytes + 1)
? uliResult.LowPart
: STATUS_IO_DEVICE_ERROR);
}
// -----------------------------------------------------------------
BYTE WINAPI w2kXCall08 (BYTE bDefault,
PBYTE pbSymbol,
DWORD dArgumentBytes,
...)
{
ULARGE_INTEGER uliResult;
return (w2kXCall (&uliResult, pbSymbol,
dArgumentBytes, &dArgumentBytes + 1)
? (BYTE) uliResult.LowPart
: bDefault);
}
// -----------------------------------------------------------------
WORD WINAPI w2kXCall16 (WORD wDefault,
PBYTE pbSymbol,
DWORD dArgumentBytes,
...)
{
ULARGE_INTEGER uliResult;
return (w2kXCall (&uliResult, pbSymbol,
dArgumentBytes, &dArgumentBytes + 1)
? (WORD) uliResult.LowPart
: wDefault);
}
// -----------------------------------------------------------------
DWORD WINAPI w2kXCall32 (DWORD dDefault,
PBYTE pbSymbol,
DWORD dArgumentBytes,
...)
{
ULARGE_INTEGER uliResult;
return (w2kXCall (&uliResult, pbSymbol,
dArgumentBytes, &dArgumentBytes + 1)
? uliResult.LowPart
: dDefault);
}
// -----------------------------------------------------------------
QWORD WINAPI w2kXCall64 (QWORD qDefault,
PBYTE pbSymbol,
DWORD dArgumentBytes,
...)
{
ULARGE_INTEGER uliResult;
return (w2kXCall (&uliResult, pbSymbol,
dArgumentBytes, &dArgumentBytes + 1)
? uliResult.QuadPart
: qDefault);
}
// -----------------------------------------------------------------
PVOID WINAPI w2kXCallP (PVOID pDefault,
PBYTE pbSymbol,
DWORD dArgumentBytes,
...)
{
ULARGE_INTEGER uliResult;
return (w2kXCall (&uliResult, pbSymbol,
dArgumentBytes, &dArgumentBytes + 1)
? (PVOID) uliResult.LowPart
: pDefault);
}
列表6-32. 扩展的调用接口
注意在列表6-32中的w2kXCall()在开始工作之前会首先调用w2kSymbolsGlobal()。列表6-33给出了w2kSymbolsGlobal()函数,以及其他一些帮助函数,w2kSymbolsGlobal()的目的是在第一次调用w2kXCall()之前加载ntoskrnl.exe的符号文件。符号表存储在名为gpit的全局变量中,该变量的类型为PIMG_TABLE,在随后调用的函数中可以使用该表。在辅助函数的帮助下,w2kSymbolsLoad()可通过*pdstatus参数返回列表6-2中的某一个状态代码。为了避免由于不匹配的符号信息而跳到无效的地址处,w2kSymbolsLoad()函数将使用w2kPeCheck()函数小心的将符号文件的时间戳、校验和(checksum)与目标模块的内存映像的相应域进行比较,如果它们不匹配,则丢弃相应的符号表。
PIMG_TABLE WINAPI w2kSymbolsLoad (PBYTE pbModule,
PDWORD pdStatus)
{
PVOID pBase;
DWORD dStatus = W2K_SYMBOLS_UNDEFINED;
PIMG_TABLE pit = NULL;
if ((pBase = imgModuleBaseA (pbModule)) == NULL)
{
dStatus = W2K_SYMBOLS_MODULE_ERROR;
}
else
{
if ((pit = imgTableLoadA (pbModule, pBase)) == NULL)
{
dStatus = W2K_SYMBOLS_LOAD_ERROR;
}
else
{
if (!w2kPeCheck (pbModule, pit->dTimeStamp,
pit->dCheckSum))
{
dStatus = W2K_SYMBOLS_VERSION_ERROR;
pit = imgMemoryDestroy (pit);
}
else
{
dStatus = W2K_SYMBOLS_OK;
}
}
}
if (pdStatus != NULL) *pdStatus = dStatus;
return pit;
}
// -----------------------------------------------------------------
PIMG_TABLE WINAPI w2kSymbolsGlobal (PDWORD pdStatus)
{
DWORD dStatus = W2K_SYMBOLS_UNDEFINED;
PIMG_TABLE pit = NULL;
w2kSpyLock ();
if ((gdStatus == W2K_SYMBOLS_OK) && (gpit == NULL))
{
gpit = w2kSymbolsLoad (NULL, &gdStatus);
}
dStatus = gdStatus;
pit = gpit;
w2kSpyUnlock ();
if (pdStatus != NULL) *pdStatus = dStatus;
return pit;
}
// -----------------------------------------------------------------
DWORD WINAPI w2kSymbolsStatus (VOID)
{
DWORD dStatus = W2K_SYMBOLS_UNDEFINED;
w2kSymbolsGlobal (&dStatus);
return dStatus;
}
// -----------------------------------------------------------------
VOID WINAPI w2kSymbolsReset (VOID)
{
w2kSpyLock ();
gpit = imgMemoryDestroy (gpit);
gdStatus = W2K_SYMBOLS_OK;
w2kSpyUnlock ();
return;
}