《Windows核心编程》之“API Hooking”(二)

来源:互联网 发布:常见的编程语言 编辑:程序博客网 时间:2024/06/11 09:04

    前一篇主要讲“API Hooking”的原理——修改IAT,这一篇主要讲代码实现和调试。

一、修改IAT

    我们要修改IAT,首先要了解导入段的数据结构和操作API。

1,IMAGE_IMPORT_DESCRIPTOR

    在winnt.h文件中,定义了这么一个数据结构来描述导入段中的信息单元

typedef struct _IMAGE_IMPORT_DESCRIPTOR {    union {        DWORD   Characteristics;            // 0 for terminating null import descriptor        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)    } DUMMYUNIONNAME;    DWORD   TimeDateStamp;                  // 0 if not bound,                                            // -1 if bound, and real date\time stamp                                            //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)                                            // O.W. date/time stamp of DLL bound to (Old BIND)    DWORD   ForwarderChain;                 // -1 if no forwarders    DWORD   Name;    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)} IMAGE_IMPORT_DESCRIPTOR;typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

    其中,“Name”表示的是DLL名,如:kernel32.dll。“FirstThunk”指向IAT的第一个项。我们只需要了解这两个成员即可。



    如上图,INT是指“Import Name Table”,IAT是指“Import Address Table”。新系统中,一般用IAT即可,老的用INT。


2,ImageDirectoryEntryToDataEx或ImageDirectoryEntryToData

    Microsoft提供了这么一个API来获取Image文件的导入段的信息单位。

Locates a directory entry within the image header and returns the address of the data for the directory entry. This function returns the section header for the data located, if one exists.

Syntax

C++
PVOID WINAPI ImageDirectoryEntryToDataEx(  _In_      PVOID                 Base,  _In_      BOOLEAN               MappedAsImage,  _In_      USHORT                DirectoryEntry,  _Out_     PULONG                Size,  _Out_opt_ PIMAGE_SECTION_HEADER *FoundHeader);

    注意第三个参数,要获取导入段的信息,需要传入IMAGE_DIRECTORY_ENTRY_IMPORT。


3,ReplaceIATEntryInOneMod

    Jeffrey提供了一个函数,封装了修改一个模块的某个导出函数的功能。

void CAPIHook::ReplaceIATEntryInOneMod(PCSTR pszCalleeModName,    PROC pfnCurrent, PROC pfnNew, HMODULE hmodCaller) {   // Get the address of the module's import section   ULONG ulSize;   // An exception was triggered by Explorer (when browsing the content of    // a folder) into imagehlp.dll. It looks like one module was unloaded...   // Maybe some threading problem: the list of modules from Toolhelp might    // not be accurate if FreeLibrary is called during the enumeration.   PIMAGE_IMPORT_DESCRIPTOR pImportDesc = NULL;   __try {      pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) ImageDirectoryEntryToData(         hmodCaller, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);   }    __except (InvalidReadExceptionFilter(GetExceptionInformation())) {      // Nothing to do in here, thread continues to run normally      // with NULL for pImportDesc    }      if (pImportDesc == NULL)      return;  // This module has no import section or is no longer loaded   // Find the import descriptor containing references to callee's functions   for (; pImportDesc->Name; pImportDesc++) {      PSTR pszModName = (PSTR) ((PBYTE) hmodCaller + pImportDesc->Name);      if (lstrcmpiA(pszModName, pszCalleeModName) == 0) {         // Get caller's import address table (IAT) for the callee's functions         PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)             ((PBYTE) hmodCaller + pImportDesc->FirstThunk);         // Replace current function address with new function address         for (; pThunk->u1.Function; pThunk++) {            // Get the address of the function address            PROC* ppfn = (PROC*) &pThunk->u1.Function;            // Is this the function we're looking for?            BOOL bFound = (*ppfn == pfnCurrent);            if (bFound) {               if (!WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,                     sizeof(pfnNew), NULL) && (ERROR_NOACCESS == GetLastError())) {                  DWORD dwOldProtect;                  if (VirtualProtect(ppfn, sizeof(pfnNew), PAGE_WRITECOPY,                      &dwOldProtect)) {                     WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,                         sizeof(pfnNew), NULL);                     VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect,                         &dwOldProtect);                  }               }               return;  // We did it, get out            }         }      }  // Each import section is parsed until the right entry is found and patched   }}

1)它首先获得导入段的头指针,再通过“pImportDesc++”指针移动来遍历导入段。由此可知,导入段中保存的各模块信息是顺序排列(数组),并用IMAGE_IMPORT_DESCRIPTOR结构体封装好的。

2)找到对应的DLL名后,再匹配函数地址。

3)通过WriteProcessMemory修改IAT。如果修改失败,可以试着调整“页保护”属性。

注:上述函数中,在两个for循环内部,都要加一个“hmodCaller”,这个是EXE文件的基地址。

关于PE文件和导入段操作可以参考网文:

https://msdn.microsoft.com/en-us/library/ms809762.aspx

http://stackoverflow.com/questions/15960437/how-to-read-import-directory-table-in-c


二、LastMessageBox示例的代码亮点

1,获取DLL句柄

    在“Windows Hook”博文中,我们知道了利用DllMain函数的参数来获取DLL的句柄。而在LastMessageBoxLib中,作者又向我们展示了一个新的方法:

// Returns the HMODULE that contains the specified memory addressstatic HMODULE ModuleFromAddress(PVOID pv) {   MEMORY_BASIC_INFORMATION mbi;   return((VirtualQuery(pv, &mbi, sizeof(mbi)) != 0)       ? (HMODULE) mbi.AllocationBase : NULL);}

2,进程间通信

   // Send the string to the main dialog box   COPYDATASTRUCT cds = { 0, ((DWORD)wcslen(sz) + 1) * sizeof(wchar_t), sz };   FORWARD_WM_COPYDATA(FindWindow(NULL, TEXT("Last MessageBox Info")),       NULL, &cds, SendMessage);

    作者利用SendMessage发送WM_COPYDATA类型的消息,实现进程间通信。

参考:http://blog.csdn.net/morewindows/article/details/6804157


三、调试问题

    一开始,我按照《Windows核心编程》的方法,先用VS管理员模式运行LastMessageBox,出现等待对话框,然后再双机运行(普通模式)"DelayLoadApp.exe",并没有受到任何消息。我通过在LastMessageBoxLib的相关函数中添加Log打印的方式,发现Windows Hook和API Hooking都设置成功,但是SendMessage时失败了。(我把宏FORWARD_WM_COPYDATA展开了),通过GetLastError获得错误码为5。查看MSDN关于SendMessage的文档,有如下一句话:

When a message is blocked by UIPI the last error, retrieved with GetLastError, is set to 5 (access denied).

然后,我再搜UIPI,获知:https://blogs.msdn.microsoft.com/vishalsi/2006/11/30/what-is-user-interface-privilege-isolation-uipi-on-vista/

    UIPI - User Interface Priviledge Isolation,UI特权隔离。它是指Windows两个进程之间通过SendMessage发信息,系统会拦截低等级的进程向高等级的进程发的消息。

    综上,我将LastMessageBox.exe和DelayLoadApp.exe都改为普通模式运行,实验成功。


四、Detours库

    Microsoft提供了一个detours库,封装了API Hooking的相关功能,用户可以直接用,省去不少代码编写。

原库地址:

https://www.microsoft.com/en-us/research/project/detours/

https://www.microsoft.com/en-us/download/details.aspx?id=52586

相关介绍博文:

http://blog.csdn.net/evi10r/article/details/6659354

http://blog.csdn.net/oneVs1/article/details/4704767

http://www.cnblogs.com/dayw/p/3289443.html





0 0
原创粉丝点击