Hook API技术介绍

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

发表评论