API钩取:通过DLL注入钩取IAT表
字数 1241 2025-08-25 22:58:20

API钩取:通过DLL注入钩取IAT表技术详解

概述

本文详细讲解通过DLL注入方式钩取目标进程IAT表来完成API钩取的技术。该技术属于DLL注入和代码注入两种主要注入方式中的DLL注入方式。

技术原理

IAT表基础

  1. PE文件的可选文件头中包含IMAGE_DATA_DIRECTORY结构体,存储了指向IAT结构的指针
  2. 未加壳的PE文件可以通过解析其二进制数据找到IAT结构的具体地址
  3. IAT表中存储了PE文件引用的所有外部函数的真实地址
  4. 程序运行时调用外部函数时,会从IAT表中查找该函数的真实地址

调用机制示例

例如程序调用外部函数A:

  • 函数A在内存中的真实地址:0x77C84590
  • 该地址存储在IAT中的位置:0x01001110
  • 实际调用指令:CALL DWORD PTR[01001110]
  • 等效于:CALL 77C84590

钩取原理

通过修改IAT表中存储的API函数地址为自定义函数地址,可以劫持程序执行流:

  1. 当程序调用该API时,实际执行自定义函数
  2. 根据需求设计自定义函数的返回操作:
    • 仅修改参数:返回执行原始API
    • 完全截取:直接返回

目标API选择

选择方法

  1. 使用PE解析工具查看目标程序的导入函数
  2. 根据功能需求判断可能涉及的API

示例场景

将计算器的数字显示从阿拉伯数字改为汉字:

  1. 涉及界面显示功能
  2. 主要查找USER32.dll中的API
  3. 确定目标API为SetWindowTextW(设置控件标题文本)

DLL实现详解

主要组件

  1. 伪造函数(MySetWindowsTextW)
  2. IAT钩取函数(IAT_Hook)
  3. DLL主函数(DllMain)

伪造函数实现

typedef BOOL(WINAPI* PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);
FARPROC g_orgFunc = NULL;

BOOL WINAPI MySetWindowsTextW(HWND hWnd, LPWSTR lpString)
{
    wchar_t cNum[] = L"零一二三四五六七八九";
    wchar_t temp[2] = { 0, }; // Unicode中文字符使用两个字节
    int i = 0, len = 0, index = 0;

    len = wcslen(lpString);
    for (i = 0; i < len; i++)
    {
        if (L'0' <= lpString[i] && lpString[i]<= L'9')
        {
            temp[0] = lpString[i];
            index = _wtoi(temp);
            lpString[i] = cNum[index];
        }
    }

    return ((PFSETWINDOWTEXTW)g_orgFunc)(hWnd, lpString);
}

IAT钩取函数实现

BOOL IAT_Hook(LPCSTR DllName, PROC OrgFuncAddr, PROC NewFuncAddr)
{
    HMODULE hModule = NULL;
    LPCSTR szLibName = NULL;
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
    PIMAGE_THUNK_DATA pThunk;
    DWORD dwOldProtect;
    DWORD dwRVA;
    PBYTE pAddr = NULL;

    // 获取当前模块基址
    hModule = GetModuleHandle(NULL);
    pAddr = (PBYTE)hModule;

    // 定位PE头结构
    pAddr += *((DWORD*)&pAddr[0x3C]); // 找到IMAGE_NT_HEADER
    dwRVA = *((DWORD*)&pAddr[0x80]); // 找到IMAGE_DATA_DIRECTORY Import的RVA
    pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hModule + dwRVA);

    // 遍历导入描述符
    for (; pImportDesc->Name; pImportDesc++)
    {
        szLibName = (LPCSTR)((DWORD)hModule + pImportDesc->Name);
        if (!_stricmp(DllName, szLibName)) // 匹配目标DLL
        {
            pThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + pImportDesc->FirstThunk);
            // 遍历IAT表
            for (; pThunk->u1.Function; pThunk++)
            {
                if (pThunk->u1.Function == (DWORD)OrgFuncAddr)
                {
                    // 修改内存保护属性
                    VirtualProtect((LPVOID)&pThunk->u1.Function, 4, 
                                 PAGE_EXECUTE_READWRITE, &dwOldProtect);
                    // 修改IAT表项
                    pThunk->u1.Function = (DWORD)NewFuncAddr;
                    // 恢复内存保护属性
                    VirtualProtect((LPVOID)&pThunk->u1.Function, 4, 
                                 dwOldProtect, &dwOldProtect);
                    return TRUE;
                }
            }
        }
    }
    return FALSE;
}

DLL主函数实现

BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        // 保存原始函数地址并钩取
        g_orgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"), "SetWindowTextW");
        IAT_Hook("user32.dll", g_orgFunc, (PROC)MySetWindowsTextW);
        break;
    case DLL_PROCESS_DETACH:
        // 卸载时恢复原始函数地址
        IAT_Hook("user32.dll", (PROC)MySetWindowsTextW, g_orgFunc);
        break;
    }
    return TRUE;
}

注入工具实现

主要功能

  1. 提权(EnableDebugPriv)
  2. DLL注入(Inject)
  3. DLL卸载(Eject)

注入函数实现

BOOL Inject(DWORD dwPID, LPCTSTR szDllName)
{
    HANDLE hProcess = NULL;
    HANDLE hThread = NULL;
    LPVOID pfRemoteBuf = NULL;
    DWORD dwBufSize = (DWORD)(_tcslen(szDllName) + 1) * sizeof(TCHAR);
    LPTHREAD_START_ROUTINE pThreadProc;

    // 打开目标进程
    if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
    {
        printf("OpenProcess failed!\n");
        return FALSE;
    }

    // 在目标进程中分配内存
    pfRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, 
                                MEM_COMMIT, PAGE_READWRITE);
    // 写入DLL路径
    WriteProcessMemory(hProcess, pfRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);

    // 获取LoadLibraryW地址
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(
        GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");

    // 创建远程线程执行注入
    hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pfRemoteBuf, 0, NULL);

    if (hThread)
    {
        printf("inject successfully!");
    }
    else
    {
        printf("inject failed!");
        CloseHandle(hProcess);
        CloseHandle(hThread);
        return FALSE;
    }

    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hProcess);
    CloseHandle(hThread);
    return TRUE;
}

卸载函数实现

BOOL Eject(DWORD dwPID, LPCTSTR szDllName)
{
    BOOL bMore = FALSE, bFound = FALSE;
    HANDLE hSnapshot = NULL;
    HANDLE hProcess = NULL;
    HANDLE hThread = NULL;
    MODULEENTRY32 me = { sizeof(MODULEENTRY32) };
    LPTHREAD_START_ROUTINE pThreadProc;

    // 获取进程模块快照
    if ((hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)) == INVALID_HANDLE_VALUE)
    {
        printf("Snapshot get failed!\n");
        return FALSE;
    }

    // 查找目标模块
    bMore = Module32First(hSnapshot, &me);
    for (; bMore; bMore = Module32Next(hSnapshot, &me))
    {
        if (!_tcsicmp(me.szModule, szDllName) || !_tcsicmp(me.szExePath, szDllName))
        {
            bFound = TRUE;
            break;
        }
    }

    if (!bFound)
    {
        printf("Module SnapShot not found!\n");
        CloseHandle(hSnapshot);
        return FALSE;
    }

    // 打开目标进程
    if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
    {
        printf("OpenProcess failed!\n");
        CloseHandle(hSnapshot);
        return FALSE;
    }

    // 获取FreeLibrary地址
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(
        GetModuleHandle(L"kernel32.dll"), "FreeLibrary");

    // 创建远程线程执行卸载
    hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, 
                                me.modBaseAddr, 0, NULL);

    if (hThread)
    {
        printf("Eject successfully!");
    }
    else
    {
        printf("Eject failed!");
        CloseHandle(hProcess);
        CloseHandle(hThread);
        return FALSE;
    }

    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hSnapshot);
    CloseHandle(hProcess);
    CloseHandle(hThread);
    return TRUE;
}

调试与分析

调试步骤

  1. 使用x32dbg打开目标程序(如计算器)
  2. 设置中断于新模块载入
  3. 运行程序至出现操作窗口
  4. 执行注入操作(需管理员权限)
  5. 调试器将在DLL入口点暂停

关键调试点

  1. IAT_Hook函数调用位置
  2. GetModuleHandle和GetProcAddress调用
  3. IAT表遍历和比较过程
  4. IAT表项修改操作

实际应用示例

操作流程

  1. 启动目标程序(如计算器)
  2. 运行注入工具注入DLL:
    IATHook.exe i <PID> IATinjectDll.dll
    
  3. 观察目标程序行为变化(数字显示变为汉字)
  4. 卸载DLL恢复原始行为:
    IATHook.exe e <PID> IATinjectDll.dll
    

效果验证

  1. 注入前:计算器显示阿拉伯数字
  2. 注入后:计算器显示中文数字
  3. 卸载后:恢复阿拉伯数字显示

技术要点总结

  1. PE结构解析:准确找到IAT表位置是关键
  2. 内存保护修改:修改IAT表项前需调整内存属性
  3. 函数劫持设计:伪造函数需正确处理参数和返回值
  4. 注入/卸载对称:确保卸载后程序能恢复正常运行
  5. 错误处理:完善的错误检查确保操作可靠性

参考资料

  1. 《逆向工程核心原理》[韩] 李承远
  2. MSDN官方文档
  3. PE文件格式规范
API钩取:通过DLL注入钩取IAT表技术详解 概述 本文详细讲解通过DLL注入方式钩取目标进程IAT表来完成API钩取的技术。该技术属于DLL注入和代码注入两种主要注入方式中的DLL注入方式。 技术原理 IAT表基础 PE文件的可选文件头中包含 IMAGE_DATA_DIRECTORY 结构体,存储了指向IAT结构的指针 未加壳的PE文件可以通过解析其二进制数据找到IAT结构的具体地址 IAT表中存储了PE文件引用的所有外部函数的真实地址 程序运行时调用外部函数时,会从IAT表中查找该函数的真实地址 调用机制示例 例如程序调用外部函数A: 函数A在内存中的真实地址:0x77C84590 该地址存储在IAT中的位置:0x01001110 实际调用指令: CALL DWORD PTR[01001110] 等效于: CALL 77C84590 钩取原理 通过修改IAT表中存储的API函数地址为自定义函数地址,可以劫持程序执行流: 当程序调用该API时,实际执行自定义函数 根据需求设计自定义函数的返回操作: 仅修改参数:返回执行原始API 完全截取:直接返回 目标API选择 选择方法 使用PE解析工具查看目标程序的导入函数 根据功能需求判断可能涉及的API 示例场景 将计算器的数字显示从阿拉伯数字改为汉字: 涉及界面显示功能 主要查找USER32.dll中的API 确定目标API为 SetWindowTextW (设置控件标题文本) DLL实现详解 主要组件 伪造函数(MySetWindowsTextW) IAT钩取函数(IAT_ Hook) DLL主函数(DllMain) 伪造函数实现 IAT钩取函数实现 DLL主函数实现 注入工具实现 主要功能 提权(EnableDebugPriv) DLL注入(Inject) DLL卸载(Eject) 注入函数实现 卸载函数实现 调试与分析 调试步骤 使用x32dbg打开目标程序(如计算器) 设置中断于新模块载入 运行程序至出现操作窗口 执行注入操作(需管理员权限) 调试器将在DLL入口点暂停 关键调试点 IAT_ Hook函数调用位置 GetModuleHandle和GetProcAddress调用 IAT表遍历和比较过程 IAT表项修改操作 实际应用示例 操作流程 启动目标程序(如计算器) 运行注入工具注入DLL: 观察目标程序行为变化(数字显示变为汉字) 卸载DLL恢复原始行为: 效果验证 注入前:计算器显示阿拉伯数字 注入后:计算器显示中文数字 卸载后:恢复阿拉伯数字显示 技术要点总结 PE结构解析 :准确找到IAT表位置是关键 内存保护修改 :修改IAT表项前需调整内存属性 函数劫持设计 :伪造函数需正确处理参数和返回值 注入/卸载对称 :确保卸载后程序能恢复正常运行 错误处理 :完善的错误检查确保操作可靠性 参考资料 《逆向工程核心原理》[ 韩 ] 李承远 MSDN官方文档 PE文件格式规范