跨进程API Hook(初稿)
detrox
什么是“跨进程 API Hook”?
众所周知Windows应用程序的各种系统功能是通过调用API函数来实现。API Hook就是给系统的API附加上一段小程序,它能监视甚至控制应用程序对API函数的调用。所谓跨进程也就是让自己的程序来控制别人程序的API调用了。
API Hook 理论
通过对Win32 PE文件的分析(如果你还不熟悉PE文件格式,可以看看Iczelion的PE教程或者LUEVELSMEYER的<<The PE File Format>>)。我们知道在PE文件中的IMPORT TABLE内存储着API函数的很多信息。其中包括API的函数名,调用地址等等。而操作系统在执行PE文件时会先将其映射到内存中。在映射的同时还会把当前版本操作系统中API函数的入口地址写入IMPORT TABLE中一组与API调用相关的结构体内,用于该应用程序的API调用。 当应用程序调用API时,他会在自己内存映像里寻找API的入口地址,然后执行CALL指令。如此一来,我们通过修改应用程序内存映像的IMPORT TABLE中API函数的入口地址,就可以达到重定向API的目的。将API地址改为我们自己函数的地址,这样我们的函数就可以完成对API的监视和控制了。
API Hook 的实现
/* 1 */HANDLE hCurrent = GetModuleHandle(NULL); /* 2 */IMAGE_DOS_HEADER *pidh; /* 3 */IMAGE_NT_HEADERS *pinh; /* 4 */IMAGE_DATA_DIRECTORY *pSymbolTable; /* 5 */IMAGE_IMPORT_DESCRIPTOR *piid; /* 6 */pidh = (IMAGE_DOS_HEADER *)hCurrent; /* 7 */pinh = (IMAGE_NT_HEADERS *)((DWORD)hCurrent + pidh->e_lfanew); /* 8 */pSymbolTable = &pinh->OptionalHeader.DataDirectory[1]; /* 9 */piid =(IMAGE_IMPORT_DESCRIPTOR *)((DWORD)hCurrent + pSymbolTable->VirtualAddress); /*10 */do { /*11 */ IMAGE_THUNK_DATA *pitd,*pitd2; /*12 */ pitd = (IMAGE_THUNK_DATA *)((DWORD)hCurrent + piid->OriginalFirstThunk); /*13 */ pitd2 = (IMAGE_THUNK_DATA *)((DWORD)hCurrent + piid->FirstThunk); /*14 */ do { /*15 */ IMAGE_IMPORT_BY_NAME *piibn; /*16 */ piibn = (IMAGE_IMPORT_BY_NAME *)((DWORD)hCurrent + *((DWORD *)pitd)); /*17 */ PROC *ppfn = (PROC *)(pitd2->u1.Function); /*18 */ if (!strcmp("MessageBoxW",(char *)piibn->Name)) { /*19 */ oldMsg = (MsgBoxType)(ppfn); /*20 */ DWORD addr = (DWORD)MyMessage; /*21 */ DWORD written = 0; /* 改变内存读写状态 */ /*22 */ DWORD oldAccess; /*23 */ VirtualProtect(&pitd2->u1.Function,sizeof(DWORD),PAGE_WRITECOPY,&oldAccess); /*24 */ APIAddress = (DWORD)&pitd2->u1.Function; /* 向内存映像写入数据 */ /*25 */ WriteProcessMemory(GetCurrentProcess(),&pitd2->u1.Function, &addr,sizeof(DWORD), &written); /*26 */ } /*27 */ pitd++;pitd2++; /*28 */ } while (pitd->u1.Function); /*29 */ piid++; /*30 */} while (piid->FirstThunk + piid->Characteristics + piid->ForwarderChain + piid->Name + piid->TimeDateStamp);
分析:
寻觅IMPORT TALBE
在/*1*/中我们使用GetModuleHandle(NULL)来返回当前进程在内存中映像的基地址。但这个值在文档中仅仅被描述为"a module handle for the specified module",虽然他确实是进程内存映像的基地址。如果你不太放心的话也可以使用,GetModuleInformation函数来获得基地址,只不过你要额外包含psapi.h和psapi.lib了(这个库在VC6里没有,所以我就没有用这个函数了)。在/* 6 */里我们先找到IMAGE_DOS_HEADER结构,他的起始地址就是映像的基地址。/*7*/通过IMAGE_DOS_HEADER给出的PE文件头的偏移量,找到IMAGE_NT_HEADERS结构。顺藤摸瓜,IMAGE_NT_HEADERS里的OptionalHeader中的DataDirectory数组里的第二个元素正是指向我们想要的IMPORT TABLE的地址。在/*9*/中我们将其转化为一个IMAGE_IMPORT_DESCRIPTOR的结构指针存入piid中。
替换的API函数入口地址
在/*12*/和/*13*/中我们分别取得OriginalFirstThunk和FirstThunk结构,用于以后得到API函数的名称和入口地址。/*10*/的do循环让我们遍历每一个IMAGE_IMPORT_DESCRIPTOR结构也就是应用程序引用的每个DLL。在/*14*/的循环中我们遍历DLL中的IMAGE_THUNK_DATA结构来一一查询API的信息。/*16*/中我们将OriginalFirstThunk转换为IMAGE_IMPORT_BY_NAME结构用于获得API函数的名称进行比对。在/*18*/我们找到MessageBoxW函数之后,在/*19*/保存其原始入口地址便于以后恢复时使用。在/*23*/我们需要用VirtualProtect改变一下内存区域的读写性,因为一般应用程序的映像都是只读的,直接写入会造成一个非法访问的异常出现。在/*25*/我们写入自己函数的地址。
这样就基本完成一个API函数的重定向。
其他
恢复函数的API入口地址相对比较简单。只要把保存的值再写回去就可以了。上面的程序中/*24*/我用APIAddress保存了存有MessageBoxW入口地址的地方的地址,便于以后调用WriteProcessMemory恢复时使用。
跨进程理论
我们要用自己的函数来替代别人程序里的API函数,但我们的函数与别人的程序处于不同的进程空间内啊。不同的进程空间是不能相互调用函数的。因此我们要想办法把自己的函数放入别人的进程空间去。这时我们就需要使用DLL injection技术了。如果你对她还不是十分熟悉的话,建议看看Jeffrey Richter大师的<<Programming Applications for Microsoft Windows>>,也可以参考陈宽达先生的<<C++ Builder深度历险>>。
简而言之,DLL injection就是想办法让对方的进程加载我们的一个DLL程序,把需要替换的函数放在我们这个DLL里。如此一来,我们的函数就进入了别人的进程空间了。DLL injection方法很多,Richter大师在书中对各方法利弊有详细解释,陈宽大先生的书中也有深入的分析。我在这里使用SetWindowsHookEx函数来达到目的。主要有这几个原因: 1, 不用重新启动系统,调试方便。2, 可以利用消息循环机制进行两个进程之间的通信,可以较好的掌握Hook的状态。便于安装与卸载。
SetWindowsHookEx之所以能完成DLL injection是因为它要给一个应用程序某个环节加上一个Hook,而Hook就要有Hook Procedure也就是Hook函数。如果这个Hook函数在一个DLL中,那么系统就会把这个DLL加载到SetWindowsHookEx的目标进程上。从而也就达到了我们DLL injection的目的了。当然这里我们会用WH_GETMESSAGE的Hook进行injection,因为这个Hook可以用来监视目标进程的消息循环方便我们的进程与目标进程通信。
跨进程的实现和几点注意
/* DllPart.Dll */ #include <windows.h> #include <stdio.h> #include <stdlib.h> #include <string.h> typedef (WINAPI *MsgBoxType)(HWND,LPCWSTR,LPCWSTR,UINT); MsgBoxType oldMsg; /*API原入口地址*/ DWORD APIAddress; /*存储API入口地址的地方的地址*/ int WINAPI MyMessage(HWND hWnd ,LPCWSTR M1,LPCWSTR M2, UINT M3) { /* 这是用来替换的函数 */ return oldMsg(hWnd,buf,M2,MB_OK); } const char szApp[] = "DllPart.dll"; HHOOK hHook; /*Hook的句柄*/ HMODULE hInst; /*DLL 模块句柄,用于SetWindowsHookEx函数*/ HWND hTarget; /*目标窗口句柄*/ /