跟进程打过一定交道之后你肯定会知道,进程在开始执行的时候会被加载到一个基地址,也就是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的话,拦截就不能成功,这算是一种缺陷