iat hook,也就是修改导入表实现函数调用的hook(运行时hook本进程)。注:iat(import address table,导入函数地址表,在这里说明一下:导入表是image import table,导入函数地址表是 image import address table,这是两个不同的pe文件中的表)
要实现iat hook,首先得清楚iat和导入表的结构。pe文件中,不管是在硬盘上的文件,还是内存中的映像,导入表和iat都会存在,而且在硬盘上和在映像中的组织方式相同(但是整个pe文件在硬盘上和在内存中的组织方式有很大不同)。pe文件的结构在这篇文章中不会讨论太多,但是理解这篇文章需要pe文件结构的知识,请在了解pe文件结构的情况下阅读这篇文章,如果还不了解,可以参考这本书:windows pe权威指南,写的非常详尽,特别是pe文件头和各种表。
pe文件中的导入表的起始是一组叫做image_import_descriptor的结构体,每一个结构体20个字节,对应于一个模块,以一个全0的结构体结束。用汇编定义如下:IMAGE_IMPORT_DESCRIPTOR STRUCT
union
Characteristics dd ?
OriginalFirstThunk dd ?
ends
TimeDateStamp dd ?
ForwarderChain dd ?
Name1 dd ? ; 我们需要关注这一个成员
FirstThunk dd ? ;还有这一个成员
IMAGE_IMPORT_DESCRIPTOR ENDS
这里我们只关心在内存中他们会指向哪里,而不关心在硬盘上的文件,所以我们的hook是在运行时完成的。上面的结构体中,Name1是一个rva(relative virtual address,相对虚拟地址),也就是说它是一个相对于imagebase(模块基地址)的一个偏移,模块基地址可以用GetModuleHandle(ModuleName)来获取,如果ModuleName我们传入NULL,可以获取这个函数在被执行时所在的模块。Name1指向的是什么呢?
我们前面说过,每一个IMAGE_IMPORT_DESCRIPTOR对应于一个模块,这个Name1指向的就是这个模块的名字(以0结尾什么的就不需要我说明了)。FirstThunk也是一个rva,它指向Name1对应的动态链接库的所有的被当前程序导入了的函数,实际上它指向的地方就位于iat中,可以说导入表和iat是你中有我,我中有你。那么iat到底是怎么组织的呢?参考下面:链接库A导入函数1的地址
链接库A导入函数2的地址
00000000
链接库B导入函数1的地址
链接库B导入函数2的地址
链接库B导入函数3的地址
00000000
..................
iat是分成一块一块的,每一块对应于一个被程序导入的动态链接库,每一块都是一个接一个的地址,最后以双字的0表示这一个模块的函数地址已经全部存储好了。实际上,在硬盘上iat的组织方式不是这样的,但是我们只关心在内存中它是怎样组织的。了解了上面的知识后,我们就可以看看当我们在程序中,当调用一个函数的时候到底发生了什么。比如下面这句:int pid=GetCurrentProcessId();
我们要分解的就是GetCurrentProcessId到底是怎么完成的。GetCurrentProcessId()位于kernle32.dll中,如果我们在程序中调用了这个函数,编译链接器就知道,我们的程序在运行的时候需要知道GetCurrentProcessId在内存的哪个位置,于是就在pe文件中的导入表中加入一个image import descriptor,结构中的Name1指向一个字符串kernel32.dll,这样,pe加载器在将我们的程序加载到内存中时,它就知道,我们的程序需要调用kernel32.dll中的函数,它就把kernel32.dll映射到我们的进程的空间中(要深入说明这个很复杂,我们可以简单的理解为,我们的程序的地址空间中已经把整个的kerner32.dll映射了)。
虽然这个需要的dll已经映射好了,但是我们要的函数的地址放在哪里呢?这里就是iat起作用的地方了,仔细看上面的iat的组织方式,我们假设链接库A就是kernel32.dll,那pe加载器在加载的时候,就把GetCurrentProcessId的地址放到上面那个结构中“链接库A导入函数1的地址”那个地方。
OK,我们假设需要的kernel32.dll已经映射好了,我们要用的函数的地址也找到了并且放到了前面说的那个地方,程序中调用这个函数的时候到底怎么跳转的呢?如果反汇编的话,它就变成下面这个模样:(地址我随意写的,大概这样)
0x00400078 call 0x00403456
.
.
0x00403456 jmp [0x00401234]
而在0x00401234里面放的是这个东西:0x7c8099b0,那上面的jmp就跳到x07c8099b0开始执行。再看看上面这个流程,在0x00400078这个地方,有一个call指令,这个call指令就是代替源程序中的GetCurrentProcessId()这一句。call到0x00403456后,发现是一个jmp指令,jmp到的地方是0x00401234这个地址中存放的一个地址,也就在是先到0x00401234中取出一个双字值X,然后跳转到X处开始执行。
这个流程有点拐弯抹角,可以仔细看看。重要的地方在0x00401234,这个地方存放的就是GetCurrentProcessId()的地址,而0x00401234自己在的地方就是iat中,就是前面的“链接库A导入函数1的地址”在的地方。 理解了上面的call流程,我们就可以想想,如果我们把0x00401234这个地址里面的东西换掉,它不是原来是0x7c8099b0吗,也就是GetCurrentProcessId()函数在内存中的位置吗,我们把它换成0x00400000,或者随便什么东西,0x00000000什么的,如果我们的程序中,在后面调用GetCurrentProcessId()的时候,它就跑到我们写的那个地方去了。
栗子:
// .H
#pragma once
#include "stdafx.h"
BOOL ImportAddressTableHook(IN HMODULE hModule, IN LPCSTR pImageName, IN LPCVOID pTargetFuncAddr, IN LPCVOID pReplaceFuncAddr);
DWORD WINAPI GetCurrentProcessId_Hook()
{
// to do...
MessageBoxA(NULL, "This is GetCurrentProcessId_Hook", "caption", MB_OK);
return 0;
}
int WINAPI MyMessageBoxW(
__in_opt HWND hWnd,
__in_opt LPCWSTR lpText,
__in_opt LPCWSTR lpCaption,
__in UINT uType)
{
//todo ...
return MessageBoxA(NULL, "This Is MyMessageBoxW!", "caption", MB_OK);
}
BOOL Rookits(IN HMODULE hModule, IN LPCSTR pImageName, IN LPCSTR pTargetFuncName, IN LPVOID pReplaceFuncAddr)
{
LPDWORD pTargetFuncAddr = NULL;
HMODULE hLib = LoadLibraryA(pImageName);
if (NULL != hLib)
{
pTargetFuncAddr = (LPDWORD)GetProcAddress(hLib, pTargetFuncName);
return ImportAddressTableHook(hModule, pImageName, pTargetFuncAddr, pReplaceFuncAddr);
}
return FALSE;
}
BOOL ImportAddressTableHook
(
IN HMODULE hModule,
IN LPCSTR pImageName,
IN LPCVOID pTargetFuncAddr,
IN LPCVOID pReplaceFuncAddr
)
{
IMAGE_DOS_HEADER* pImageDosHearder = (IMAGE_DOS_HEADER*)hModule;
IMAGE_OPTIONAL_HEADER* pImageOptionalHeader = (IMAGE_OPTIONAL_HEADER*)((DWORD)hModule + pImageDosHearder->e_lfanew + 24);
IMAGE_IMPORT_DESCRIPTOR* pImageImportDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)
((DWORD)hModule + pImageOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (pImageImportDescriptor != 0)
{
LPCSTR pszModName = (PSTR)((PBYTE)GetModuleHandle(NULL) + pImageImportDescriptor->Name);
if (_stricmp(pszModName, pImageName) == 0)
break;
pImageImportDescriptor++;
}
if (pImageImportDescriptor == NULL)
return FALSE;
IMAGE_THUNK_DATA* pImageThunkData = (IMAGE_THUNK_DATA*)((DWORD)hModule + pImageImportDescriptor->FirstThunk);
if (pImageThunkData == NULL)
return FALSE;
while (pImageThunkData->u1.Function)
{
LPDWORD lpFunctionAddress = (LPDWORD)&(pImageThunkData->u1.Function);
if (*lpFunctionAddress == (DWORD)pTargetFuncAddr)
{
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(mbi.BaseAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);
if (!WriteProcessMemory((HANDLE)-1, lpFunctionAddress, &pReplaceFuncAddr, sizeof(lpFunctionAddress), NULL))
{
return FALSE;
}
DWORD dwProtectOld;
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwProtectOld);
return TRUE;
}
pImageThunkData++;
}
return FALSE;
}
#pragma pack(1)
typedef struct _JMPCODE
{
BYTE jmp;
DWORD addr;
}JMPCODE, *PJMPCODE;
typedef FARPROC (WINAPI* GETPROCADDRESS)(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName);
typedef int (WINAPI* MESSAGEBOXA)(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType);
typedef int (WINAPI* MESSAGEBOXW)(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType);
GETPROCADDRESS realGetProcAddress = NULL;
_declspec(naked) FARPROC WINAPI MyGetProcAddress(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName)
{
_asm
{
push ebp
mov ebp, esp
}
// 如果是MessageBoxW, 把MyMessageBox返回
if (hModule == ::GetModuleHandle(L"user32.dll") && _strcmpi(lpProcName, "MessageBoxW") == 0)
{
_asm
{
mov eax, MyMessageBoxW;
mov esp, ebp
pop ebp
ret
}
}
else{
_asm
{
mov ebx, realGetProcAddress
add ebx, 5
jmp ebx
}
}
}
void Hook_GetProcAddr()
{
realGetProcAddress = GetProcAddress;
JMPCODE jumpCode;
jumpCode.jmp = 0xe9;
jumpCode.addr = (DWORD)MyGetProcAddress - ((DWORD)realGetProcAddress + 5);
DWORD dwNewProtect = PAGE_EXECUTE_READWRITE;
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(realGetProcAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, dwNewProtect, &mbi.Protect);
WriteProcessMemory(GetCurrentProcess(), realGetProcAddress, &jumpCode, sizeof(jumpCode), NULL);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwNewProtect);
}
void UnHook_GetProcAddr()
{
BYTE backCode[5] = { 0 };
backCode[0] = 0x8B; // mov edi, edi
backCode[1] = 0xFF;
backCode[2] = 0x55; // push ebp
backCode[3] = 0x8B; // mov ebp, esp
backCode[4] = 0xEC;
DWORD dwNewProtect = PAGE_EXECUTE_READWRITE;
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(GetProcAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, dwNewProtect, &mbi.Protect);
WriteProcessMemory(GetCurrentProcess(), GetProcAddress, backCode, sizeof(backCode) / sizeof(backCode[0]), NULL);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwNewProtect);
}
//.CPP
#include "stdafx.h"
#include "Demo1.h"
#include <iostream>
using namespace std;
int main()
{
Rookits(::GetModuleHandle(NULL), "User32.dll", "MessageBoxW", MyMessageBoxW);
Hook_GetProcAddr();
MessageBoxW(NULL, L"hello world2", L"caption", MB_OK);
MESSAGEBOXW MBW = (MESSAGEBOXW)GetProcAddress(GetModuleHandleA("User32.dll"), "MessageBoxW");
if(MBW)
MBW(NULL, L"hello world3", L"caption", MB_OK);
MESSAGEBOXA MBA = (MESSAGEBOXA)GetProcAddress(GetModuleHandleA("User32.dll"), "MessageBoxA");
if (MBA)
MBA(NULL, "hello world4", "caption", MB_OK);
UnHook_GetProcAddr();
//Rookits(::GetModuleHandle(NULL), "KERNEL32.dll", "GetCurrentProcessId", GetCu
//cout << "current pid : " << GetCurrentProcessId() << endl;
system("pause");
return 0;
}
此例子中:只修改了EXE的IAT和实现了使用GetProcAddresss的方式如何HOOK,栗子中的Hook_GetProcAddr采用的API HOOK
优化点:
1.枚举所有模块实现用IAT HOOK
2.API HOOK LoadLibrary相关API,当新的模块被加载后,为新的模块实现HOOK
检测被IAT HOOK的方法:
通过函数地址是否在某一个范围内(用户区 or 内核区,是否在原模块的地址空间范围内)。
参考如下:
http://bbs.pediy.com/showthread.php?p=1161499
http://bbs.pediy.com/showthread.php?p=1287440