HOOK API技术介绍

来源:互联网 发布:角斗士软件背单词 编辑:程序博客网 时间:2024/06/05 00:57

在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了。