您现在的位置是:首页 >技术杂谈 >IAT Hook网站首页技术杂谈

IAT Hook

Tandy12356_ 2024-06-27 18:01:02
简介IAT Hook

一、IAT HOOK介绍

IAT (Import Address Table) HOOK 是一种在 Windows 程序中进行函数钩子的技术。它通过修改程序的导入地址表来实现对目标函数的替换或拦截。

在 Windows 运行时,程序需要调用其他模块(DLL)中的函数来完成特定的功能。为了实现这一点,程序会在导入地址表中存储这些函数的地址。IAT 是一个数据结构,存储了程序所依赖的外部模块的函数地址。

IAT HOOK 的基本原理是通过修改导入地址表中的函数地址,将原始函数地址替换为自定义的函数地址。这样,在程序调用原始函数时,实际上会执行被替换的自定义函数,从而实现对目标函数的拦截和修改。

1、以下是 IAT HOOK 的一般步骤:

  1. 定位目标函数:首先,需要确定要钩子的目标函数。可以使用工具如 Dependency Walker、IDA Pro 等进行静态分析,或者使用动态调试工具来监视函数调用。

  2. 获取目标模块的基址:根据目标函数的模块名称或函数地址,可以通过 Windows API 函数如 GetModuleHandleLoadLibrary 来获取目标模块的基址。

  3. 获取导入地址表(IAT):通过解析目标模块的导入描述符表(Import Descriptor Table),可以获得导入地址表的位置。

  4. 修改导入地址表:将目标函数的地址替换为自定义函数的地址,即进行钩子操作。可以使用 VirtualProtect 函数来修改内存页面的访问权限,确保可以写入。

  5. 自定义函数的实现:编写自定义函数的代码,用于替代原始函数的功能。在自定义函数中,可以执行一些额外的操作,如记录日志、修改参数、阻止函数调用等。

  6. 调用原始函数:在自定义函数中,如果需要调用原始函数,可以通过函数指针来调用被替换的原始函数。

需要注意的是,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表

  1. IAT(Import Address Table):IAT 是 PE 文件中的一个数据结构,用于存储被导入函数的地址。IAT 是由一系列函数指针构成的表格,每个函数指针指向一个被导入函数的地址。当 PE 文件加载到内存中时,操作系统会填充 IAT 表格,将被导入函数的地址写入相应的函数指针位置。通过 IAT 表,程序可以直接调用被导入函数,而无需在运行时解析函数地址。这样可以提高程序的执行效率。

  2. 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之后:

今天的内容就到这里了,喜欢的话多多点赞、收藏、关注吧!??? 

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。