一、IAT HOOK介绍
IAT (Import Address Table) HOOK 是一种在 Windows 程序中进行函数钩子的技术。它通过修改程序的导入地址表来实现对目标函数的替换或拦截。
在 Windows 运行时,程序需要调用其他模块(DLL)中的函数来完成特定的功能。为了实现这一点,程序会在导入地址表中存储这些函数的地址。IAT 是一个数据结构,存储了程序所依赖的外部模块的函数地址。
IAT HOOK 的基本原理是通过修改导入地址表中的函数地址,将原始函数地址替换为自定义的函数地址。这样,在程序调用原始函数时,实际上会执行被替换的自定义函数,从而实现对目标函数的拦截和修改。
1、以下是 IAT HOOK 的一般步骤:
-
定位目标函数:首先,需要确定要钩子的目标函数。可以使用工具如 Dependency Walker、IDA Pro 等进行静态分析,或者使用动态调试工具来监视函数调用。
-
获取目标模块的基址:根据目标函数的模块名称或函数地址,可以通过 Windows API 函数如
GetModuleHandle
或LoadLibrary
来获取目标模块的基址。 -
获取导入地址表(IAT):通过解析目标模块的导入描述符表(Import Descriptor Table),可以获得导入地址表的位置。
-
修改导入地址表:将目标函数的地址替换为自定义函数的地址,即进行钩子操作。可以使用
VirtualProtect
函数来修改内存页面的访问权限,确保可以写入。 -
自定义函数的实现:编写自定义函数的代码,用于替代原始函数的功能。在自定义函数中,可以执行一些额外的操作,如记录日志、修改参数、阻止函数调用等。
-
调用原始函数:在自定义函数中,如果需要调用原始函数,可以通过函数指针来调用被替换的原始函数。
需要注意的是,IAT HOOK 是一种对二进制代码进行修改的技术,属于比较底层的操作。在实施 IAT HOOK 时,需要小心处理内存权限和线程同步等问题,以确保安全性和稳定性。同时,对于一些保护机制较为严密的程序,IAT HOOK 可能会受到一些防护措施的干扰,需要针对具体情况进行适配和调试。
二、编程前准备工作:
1、PE文件结构图
2、PE加载过程
1)根据SizeOfImage的大小,开辟一块缓冲区(ImageBuffer).
2)根据SizeOfHeader的大小,将头信息从FileBuffer拷贝到ImageBuffer
3)根据节表中的信息循环讲FileBuffer中的节拷贝到ImageBuffer中.
PE文件的组成:
数据目录表一共15张表:
导入表的数据结构:
在PE文件加载之前,两张表里的内容完全一样,都是存放的函数名称
加载之后:函数地址将被写入IAT表当中,INT依旧保留dll的函数名称
关于PE先讲这些,如果感兴趣专门做一期讲。
3、什么是IAT表和INT表
-
IAT(Import Address Table):IAT 是 PE 文件中的一个数据结构,用于存储被导入函数的地址。IAT 是由一系列函数指针构成的表格,每个函数指针指向一个被导入函数的地址。当 PE 文件加载到内存中时,操作系统会填充 IAT 表格,将被导入函数的地址写入相应的函数指针位置。通过 IAT 表,程序可以直接调用被导入函数,而无需在运行时解析函数地址。这样可以提高程序的执行效率。
-
INT(Import Name Table):INT 表是 PE 文件中的另一个数据结构,用于存储被导入函数的名称。INT 表包含一系列函数名称的字符串,每个字符串对应一个被导入函数的名称。INT 表的每个条目与 IAT 表的相应条目一一对应。在加载 PE 文件时,操作系统会根据 INT 表中的函数名称进行动态链接,查找并填充 IAT 表中的函数地址。
综合起来,IAT 表存储被导入函数的地址,INT 表存储被导入函数的名称。操作系统在加载 PE 文件时,会先根据 INT 表中的函数名称解析出函数地址,然后将地址填充到 IAT 表中。这样,在程序运行时,可以直接通过 IAT 表中的函数指针调用被导入函数,而无需进行动态链接和解析函数地址的过程。
三、案例介绍
事情是这样的,有一天坤坤需要在全民制作大赛上进行基本功大比武,他准备把自己练习两年半的篮球🏀舞展示给观众,但是由于台下ikuns太多了,有点影响周边的居民休息,于是我有一个朋友委托我可不可以把ikuns换成小黑子,这样就会少很多尖叫,进而达到控制噪声的效果,我想了一下这个功能还算比较简单,只需要hook掉ikuns的尖叫就可以了,于是就答应了他。
四、整体思路分析
1)首先使用GetFuncAddr获取需要替换的原函数的地址
2)使用新的函数地址(DWORD)MyFunc替换原来的地址
那么如何获取需要hook的函数地址呢?
以MessageBoxW为例,他存在于user32.dll这个dll文件当中,因此我们只需要遍历注入进程的导入表,首先找到user32.dll这个dll,然后再匹配这个dll里的MessageBoxW这个函数,此时*pIAT里保存的就是MessageBoxW的函数地址。
如何替换地址?
》使用VirtualProtect修改内存属性,然后*g_iatAddr=(DWORD)MyFunc;修改为我们自定义的函数地址即可。
注意事项:
hook的时候VirtualProtect一般成对出现,这就像做完坏事,记得要恢复现场😂😂😂
指针保存的是地址,而地址里的内容才是真正的数据
五、具体编程
以下是hookMsgBox.dll的完整代码,如果你需要ikun舞台的代码可以来私信我,或者评论区留言。
#include "pch.h"
#include<iostream>
using namespace std;
#include<Windows.h>
DWORD* g_iatAddr = NULL;
DWORD g_oldFuncAddr = 0;
DWORD g_newFuncAddr = 0;
void MessageBoxFormatted(const char* format, ...)
{
char buffer[256];
va_list args;
va_start(args, format);
vsprintf_s(buffer, sizeof(buffer), format, args);
va_end(args);
MessageBoxA(NULL, buffer, "Formatted MessageBox", MB_OK);
}
//自定义函数
int WINAPI MyMessageBoxA(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
)
{
MessageBoxA(0, "你干嘛,食不食油饼", "粉龄:两秒半", MB_OK);
return 0;
}
BOOL startHook() {
DWORD oldProtect;
g_newFuncAddr = (DWORD)(MyMessageBoxA);
VirtualProtect(g_iatAddr, sizeof(DWORD), PAGE_EXECUTE_READWRITE, &oldProtect);
*g_iatAddr = g_newFuncAddr;
VirtualProtect(g_iatAddr, sizeof(DWORD), oldProtect, &oldProtect);
MessageBoxA(0, "原函数已被替换!", "标题", MB_OK);
return TRUE;
}
BOOL endHook() {
DWORD oldProtect;
VirtualProtect(g_iatAddr, sizeof(DWORD), PAGE_EXECUTE_READWRITE, &oldProtect);
*g_iatAddr = g_oldFuncAddr;
VirtualProtect(g_iatAddr, sizeof(DWORD), oldProtect, &oldProtect);
MessageBoxA(0, "已经修改回来了", "标题", 0);
return TRUE;
}
DWORD* getFuncAddr(const char* dllName, const char* funcName) {
//获取exe的模块基地址(ImageBase)
HMODULE hModule = GetModuleHandleA(0);
//内存当中
int x = 0;
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((char*)hModule + pDosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = &(pNtHeader->OptionalHeader);
//导入表在数据目录表的第1张表
IMAGE_DATA_DIRECTORY dataDirectory = pOptionHeader->DataDirectory[1];
//datadirectory[1]里面保存了导入表所在的虚拟地址
//这里的virtualAddr是RVA
PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)(((DWORD)(hModule) + dataDirectory.VirtualAddress));
while (pImportTable->Name) {
char* DLLName = (char*)((DWORD)hModule + pImportTable->Name);
if (_stricmp(dllName, DLLName) == 0) {
MessageBoxFormatted("%s已经被找到了!", DLLName);
//获取INT表地址
PIMAGE_THUNK_DATA pINT = (PIMAGE_THUNK_DATA)((DWORD)hModule + pImportTable->OriginalFirstThunk);
//获取IAT表地址
PIMAGE_THUNK_DATA pIAT = (PIMAGE_THUNK_DATA)(((DWORD)hModule + pImportTable->FirstThunk));
while (pINT->u1.Function) {
//如果按照名称导入
if ((pINT->u1.Ordinal & 0x80000000) == 0) {
PIMAGE_IMPORT_BY_NAME pImportName = (PIMAGE_IMPORT_BY_NAME)((DWORD)hModule + pINT->u1.Ordinal);
if (strcmp(pImportName->Name, funcName) == 0) {
MessageBoxFormatted("函数名称:%s", pImportName->Name);
//*IAT里保存的是函数地址
return (DWORD*)pIAT;
}
}
//遍历导入表的下一张导入名称表
pIAT++;
pINT++;
}
}
//遍历下一张导入表
pImportTable++;
}
return NULL;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
MessageBoxA(0, "dll已加载到目标进程", "标题", MB_OK);
g_iatAddr = getFuncAddr("user32.dll", "MessageBoxW");
if (g_iatAddr == NULL) {
MessageBoxA(0, "没有找到需要hook的函数", "标题", MB_OK);
return -1;
}
g_oldFuncAddr = *g_iatAddr;
bool bRet = startHook();
break;
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH: {
endHook();
break;
}
}
return TRUE;
}
六、运行结果
修改之前:
IAT hook之后:
今天的内容就到这里了,喜欢的话多多点赞、收藏、关注吧!💗💗💗