在Windows 操作系统里面,API是指由操作系统提供功能的、由应用程序调用的函数。这些函数在Windows操作系统里面有上千个之多,分布于不同的DLL文件里面或者EXE文件里面。应用程序通过调用这些函数来获得一些功能的支持。API HOOK技术是一种用于改变API执行结果的技术,例如翻译软件可以通过Hook TextOut函数或其他相关的API函数,在执行系统真正的API之前,截获TextOut的参数(即要输出的字符串),然后实现翻译功能。再如通过Hook LoadLibrary函数,阻止加载某些DLL等等。本文将介绍两种Hook当前进程系统API的方法:
1、通过修改IAT方式实现
Windows9x、Windows NT、Windows 2000/XP/2003等操作系统中所使用的可执行文件格式是纯32位PE(Portable Executable)文件格式,其具体格式本文不做详细介绍,PE文件中的输入表(Import Table)是来放置输入函数(Imported functions)的一个表。输入函数就是被程序调用的位于外部DLL的函数,这些函数称为输入函数。它们的代码位于DLL之中,程序通过引用其DLL来访问这些函数。输入表中放置的是这些函数的名称(或者序号)以及函数所在的DLL路径等有关信息。程序通过这些信息找到相应的DLL,从而调用这些外部函数。这个过程是在运行过程中发生的,因此属于动态链接。由于操作系统的API也是在DLL之中实现的,因此应用程序调用API也要通过动态连接。当我们知道了IAT中的地址所在位置,便可以把原来的API 的地址修改为新的API的地址。这样,进程在调用API的时候就会调用我们所提供的新的API的地址,之后,我们在自定义的函数里面,调用原有的API,就可以实现HOOK API。IAT的结构本文不做详细描述,使用以下函数即可实现置换IAT:
/*
pDllName [in] - 要HOOK的API所在的DLL
pApiName [in] - 要HOOK的API的名称
iNewApi [in] - 新的API入口地址
pOldApi [out] - 用于输出源API入口地址,
*/
int ReplaceIAT(const char *pDllName, const char *pApiName, INT_PTR iNewApi, INT_PTR *pOldApi)
{
HANDLE hProcess = ::GetModuleHandle (NULL);
DWORD dwSize = 0;
PIMAGE_IMPORT_DESCRIPTOR pImageImport = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hProcess,TRUE,
IMAGE_DIRECTORY_ENTRY_IMPORT,&dwSize);
if (NULL == pImageImport)
return 1;
PIMAGE_IMPORT_BY_NAME pImageImportByName = NULL;
PIMAGE_THUNK_DATA pImageThunkOriginal = NULL;
PIMAGE_THUNK_DATA pImageThunkReal = NULL;
while (pImageImport->Name)
{
char *pName = (char*)(PBYTE)hProcess+pImageImport->Name;
if (0 == strcmpi((char*)((PBYTE)hProcess+pImageImport->Name),pDllName))
{
break;
}
++pImageImport;
}
if (!pImageImport->Name) return 2;
pImageThunkOriginal = (PIMAGE_THUNK_DATA)((PBYTE)hProcess+pImageImport->OriginalFirstThunk);
pImageThunkReal = (PIMAGE_THUNK_DATA)((PBYTE)hProcess+pImageImport->FirstThunk);
while (pImageThunkOriginal->u1.Function)
{
if ((pImageThunkOriginal->u1.Ordinal & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG)
{
pImageImportByName = (PIMAGE_IMPORT_BY_NAME)((PBYTE)hProcess+pImageThunkOriginal->u1.AddressOfData);
if (0 == strcmpi(pApiName,(char*)pImageImportByName->Name))
{
MEMORY_BASIC_INFORMATION mbi_thunk;
VirtualQuery(pImageThunkReal, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));
VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize, PAGE_READWRITE, &mbi_thunk.Protect);
*pOldApi =(INT_PTR) pImageThunkReal->u1.Function;
pImageThunkReal->u1.Function = (DWORD)iNewApi;
DWORD dwOldProtect;
VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, mbi_thunk.Protect, &dwOldProtect);
break;
}
}
++pImageThunkOriginal;
++pImageThunkReal;
}
return 0;
}
例如:以下代码实现HOOK LoadLibaryExW:
typedef HMODULE (__stdcall *LoadLibraryExAFunc)(LPCSTR lpFileName, HANDLE hFile, DWORD dwFlags);
//用于保存原LoadLibraryExA地址
LoadLibraryExAFunc g_PreLoadLibraryExA = NULL;
//定义新的LoadLibraryExA
HMODULE __stdcall MyLoadLibraryExA(LPCSTR lpFileName, HANDLE hFile, DWORD dwFlags)
{
//TODO:执行自定义的操作
//调用原API
return g_PreLoadLibraryExA(lpFileName, hFile, dwFlags);
}
ReplaceIAT("Kernel32.dll","LoadLibraryExA", (INT_PTR)MyLoadLibraryExA, (INT_PTR*)&g_PreLoadLibraryExA);
2.在原API的代码中注入JMP指令
上文已介绍了使用修改IAT的方式HOOK系统的API,当在实验过程中发现,这种方式仅对自己主动调用的时候有用,当系统自己调用LoadLibrary加载时,并不会调用到自己定义的新API,下文,将介绍另一种HOOK API技术。该方式实现方式是将原API的代码的前面N个字节,修改为一条JMP指令,跳转到我们自己定义的函数,执行完自己需要的操作后,再跳转回原API继续执行。具体实施细节如下:
(1) 分配一片缓存区,将原API(假设为preAPI)的前N个字节(注意:N的大小不是随意指定的,N必须大于5,并为API前面若干条完整机器指令的总字节数),保存到这个缓存区,假设为fakeAPI;
(2) 在fakeAPI的第N个字节出,增加一条JMP指令,跳转回原API;
(3) 将原API的前5个字节修改为JMP指令,跳转到自己定义的API,注意,这个API的参数必须和原API一致。
(4) 在新的API中调用fakeAPI。
如此操作之后,当调用系统的API函数时,就会先跳转到自己定义的函数中,等执行完自己的操作后,在跳会原API继续执行,从而实现HOOK API。以下函数,实现保存原API前N个字节并注入JMP指令:
/*
preEntry - 原API入口地址
newEntry - 新API入口地址
fakeAPI - 用于保存原API前N个字节的缓冲区
nFirstBytes - 指定要保存多少个字节
*/
void HookApi(DWORD preEntry, DWORD newEntry, LPVOID fakeAPI, DWORD nFirstBytes)
{
LPBYTE pfnRaw = (LPBYTE)preEntry;
BYTE* fnFake = (BYTE*)fakeAPI;
//保存原来API前面nFirstBytes 个字节
memcpy(fnFake,pfnRaw,nFirstBytes);
//在tempBuffer最后加上跳转指令跳转到原来的API
fnFake[nFirstBytes] = 0xE9;
*(UINT32*)(fnFake + nFirstBytes + 1) = (UINT32)pfnRaw + nFirstBytes - (UINT32)(fnFake + nFirstBytes + 5);
//将原API的前个字节修改为JMP指令,跳转到新的API
DWORD dwOldProtect = 0;
VirtualProtect(pfnRaw,nFirstBytes,PAGE_READWRITE,&dwOldProtect);
*(UINT32*)pfnRaw = 0xE9;
*(UINT32*)(pfnRaw+1) = (UINT32)newEntry - (UINT32)(pfnRaw + 5);
VirtualProtect(pfnRaw, nFirstBytes, dwOldProtect, 0);
}
下面,已HOOK LoadLibraryExW函数,实例如何实现HOOK系统API:
首先,必须确定N的大小,使用VS的反编译功能,可以看到LoadLibraryExW的机器指令:
如图所示,LoadLibraryExW的前面4条指令共6个字节,因此,N取6比较合适。
HMODULE (WINAPI *rawLoadLibraryExW)(LPCWSTR lpLibFileName,HANDLE hFile,DWORD dwFlags);
//用于保留原API前个字节,并且在后面添加一条JMP指令
static BYTE fakeLoadLibraryExW[11];
//定义新的API
HMODULE WINAPI MyLoadLibraryExW(LPCWSTR lpLibFileName,HANDLE hFile,DWORD dwFlags)
{
TRACE(_T("Hook LoadLibraryEx: %s\r\n"), lpLibFileName);
//调用原来的API
return rawLoadLibraryExW(lpLibFileName, hFile, dwFlags);
}
void HookLoadLibraryEx()
{
HookApi((DWORD)LoadLibraryExW, (DWORD)MyLoadLibraryExW, fakeLoadLibraryExW, 6);
LPDWORD pRaw = (LPDWORD)&rawLoadLibraryExW;
*pRaw = (DWORD)fakeLoadLibraryExW;
}
调用HookLoadLibraryEx()之后,就可以HOOK到系统的LoadLibraryEx了。
|