跟进程打过一定交道之后你肯定会知道,进程在开始执行的时候会被加载到一个基地址,也就是GetModuleHandle(NULL)对应的值。在基地址后面的一定地址范围里存储有进程的映像文件(image),其中包含了很多信息,比如数据啊,代码啊什么的。在这些数据里,有一个比较重要的部分就是进程隐式链接的DLL的列表以及每一个链接到进程地址空间内的DLL都使用了哪些导出函数,这些函数的地址都在哪。于是,通过修改隐式链接DLL导出的API地址,能够达到一定程度上拦截API的目的。
上代码:
#define LocateAddress(type,base,offset) (type)((DWORD)base+offset)
BOOL HookApi(HINSTANCE hModule,LPCSTR pszModuleName,LPCSTR pszAPIName,DWORD pfnHookedAddr,DWORD* ppfnApiOriginal)
{
if(!pszModuleName || !pszAPIName || !pfnHookedAddr || !ppfnApiOriginal)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
if(!hModule) hModule = (HINSTANCE)GetModuleHandle(NULL);
ULONG ulSize = 0;
//获取导入库的清单
PIMAGE_IMPORT_DESCRIPTOR pDllListHeader = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToDataEx(hModule,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&ulSize,NULL);
if(!pDllListHeader) return FALSE;
while(pDllListHeader->Name)
{//枚举所有库,寻找需要Hook的那个
if(stricmp(pszModuleName,LocateAddress(char*,hModule,pDllListHeader->Name)) == 0)
break;
pDllListHeader++;
}
if(!pDllListHeader->Name) return FALSE;
//获取该动态连接库导入函数列表,列表分为两个,第一个是导出的函数名称的列表,第二个是每一个导出函数对应的地址的列表
PIMAGE_THUNK_DATA pAPINameListHeader = LocateAddress(PIMAGE_THUNK_DATA,hModule,pDllListHeader->OriginalFirstThunk);
PIMAGE_THUNK_DATA pAPIAddressListHeader = LocateAddress(PIMAGE_THUNK_DATA,hModule,pDllListHeader->FirstThunk);
while(pAPINameListHeader->u1.Function)
{
PIMAGE_IMPORT_BY_NAME pAPINameEntry = LocateAddress(PIMAGE_IMPORT_BY_NAME,hModule,pAPINameListHeader->u1.AddressOfData);
if(stricmp((char*)pAPINameEntry->Name,pszAPIName)==0)
break;
pAPINameListHeader++;
pAPIAddressListHeader++;
}
//找到了导出函数的位置,替换为钩子函数
if(!pAPIAddressListHeader->u1.Function || !pAPINameListHeader->u1.Function) return FALSE;
*ppfnApiOriginal = pAPIAddressListHeader->u1.Function;
pAPIAddressListHeader->u1.Function = pfnHookedAddr;
return TRUE;
}
ImageDirectoryEntryToDataEx
这个函数可以根据不同的选项来获取PE文件中不同的信息,在这里我就让他给我找出来隐式链接的DLL的清单在哪。
得到清单以后,我就根据清单中每一个DLL来找有没有我要拦截的API所在的那个DLL。
这里小注意一下:
pDllListHeader->Name
这个name可不是一个字符串,而是告诉你清单中这一项的DLL的名字所在的位置距离映像文件的偏移量是多少。
找到相应的DLL,我就初始化两个表头指针
PIMAGE_THUNK_DATA pAPINameListHeader = LocateAddress(PIMAGE_THUNK_DATA,hModule,pDllListHeader->OriginalFirstThunk);
PIMAGE_THUNK_DATA pAPIAddressListHeader = LocateAddress(PIMAGE_THUNK_DATA,hModule,pDllListHeader->FirstThunk);
前面说了,每一个导入到映像中的DLL都要说明你导入了哪些API,这些API的地址分别是什么。
第一个表是名称,第二个表是这个名称对应的地址。
同样,找啊找的你就找到了要拦截的API。找到它的地址之后,事情就好办了,把它的地址直接修改成Hook函数所在的地址,然后把原始的地址保留起来就行了。
下面这个程序拦截了GetCurrentProcess API并试图拦截MessageBox
#include "stdafx.h"
#include <Windows.h>
#include <Dbghelp.h>
#define LocateAddress(type,base,offset) (type)((DWORD)base+offset)
BOOL HookApi(HINSTANCE hModule,LPCSTR pszModuleName,LPCSTR pszAPIName,PROC pfnHookedAddr,PROC* ppfnApiOriginal)
{
if(!pszModuleName || !pszAPIName || !pfnHookedAddr || !ppfnApiOriginal)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
if(!hModule) hModule = (HINSTANCE)GetModuleHandle(NULL);
ULONG ulSize = 0;
//获取导入库的清单
PIMAGE_IMPORT_DESCRIPTOR pDllListHeader = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToDataEx(hModule,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&ulSize,NULL);
if(!pDllListHeader) return FALSE;
while(pDllListHeader->Name)
{//枚举所有库,寻找需要Hook的那个
if(stricmp(pszModuleName,LocateAddress(char*,hModule,pDllListHeader->Name)) == 0)
break;
pDllListHeader++;
}
if(!pDllListHeader->Name) return FALSE;
//获取该动态连接库导入函数列表,列表分为两个,第一个是导出的函数名称的列表,第二个是每一个导出函数对应的地址的列表
PIMAGE_THUNK_DATA pAPINameListHeader = LocateAddress(PIMAGE_THUNK_DATA,hModule,pDllListHeader->OriginalFirstThunk);
PIMAGE_THUNK_DATA pAPIAddressListHeader = LocateAddress(PIMAGE_THUNK_DATA,hModule,pDllListHeader->FirstThunk);
while(pAPINameListHeader->u1.Function)
{
PIMAGE_IMPORT_BY_NAME pAPINameEntry = LocateAddress(PIMAGE_IMPORT_BY_NAME,hModule,pAPINameListHeader->u1.AddressOfData);
if(stricmp((char*)pAPINameEntry->Name,pszAPIName)==0)
break;
pAPINameListHeader++;
pAPIAddressListHeader++;
}
//找到了导出函数的位置,替换为钩子函数
if(!pAPIAddressListHeader->u1.Function || !pAPINameListHeader->u1.Function) return FALSE;
*ppfnApiOriginal = (PROC)pAPIAddressListHeader->u1.Function;
pAPIAddressListHeader->u1.Function = (DWORD)pfnHookedAddr;
return TRUE;
}
HANDLE (WINAPI *GetCurrentProcessOld)();
HANDLE WINAPI GetCurrentProcessHooked()
{
printf("Your call to the GetCurrentProcess is intercepted.\n");
return GetCurrentProcessOld();
}
int (__stdcall *MessageBoxOld)( HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption,UINT uType);
int (__stdcall *MessageBoxCaller)( HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption,UINT uType);
int __stdcall HOOK_MessageBox( HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption,UINT uType)
{
printf("Your call to the MessageBoxW is intercepted.\n");
return MessageBoxOld(hWnd,lpText,lpCaption,uType);
}
int _tmain(int argc, _TCHAR* argv[])
{
HookApi(NULL,"Kernel32.dll","GetCurrentProcess",(PROC)GetCurrentProcessHooked,(PROC*)&GetCurrentProcessOld);
GetCurrentProcess();
HMODULE hDllAddr = ::LoadLibraryA("User32.dll");
HookApi(NULL,"User32.dll","MessageBoxW",(PROC)HOOK_MessageBox,(PROC*)&MessageBoxOld);
MessageBoxCaller = (int (__stdcall *)( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption,UINT uType))GetProcAddress(hDllAddr,"MessageBoxW");
MessageBoxCaller(0,0,0,0);
return 0;
}
从上面的代码可以看出来,对GetCurrentProcess拦截成功了,但是如果程序中通过动态加载DLL并用GetProcAddr这种方来调用API的话,拦截就不能成功,这算是一种缺陷