函数钩子-Dll注入

本文详细介绍了Windows系统下的API Hook技术,包括消息钩子的设置与释放方法、DLL注入及提权过程,并提供了具体的编程实例,展示了如何通过修改IAT表来实现API的Hook。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在应用层可以设置的钩子方法有许多种,其中经典的钩子是消息钩子,消息钩子分为两种,一种是系统级全局钩子,另外一种是线程级局部钩子,它们都是通过下面这一组函数来实现消息勾取,实现相对简单。

设置钩子: SetWindowsHookEx
释放钩子: UnhookWindowsHookEx
继续钩子: CallNextHookEx

钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。《百度百科》

除此之外,还可以利用修改目标进程空间中PE文件的IAT表中的指定API的地址,使它指向自定的DLL函数的API地址来达到API勾取的目的,在自定的API执行完毕之后,为了保证程序的正确执行,又要把相关的修改的地方还原,其中需要注意的是修改IAT中的地址的时候需要先通过VirtualProtect()函数获得对相关内存的读写权限。

BOOL VirtualProtect(
LPVOID lpAddress, // 目标地址起始位置
DWORD dwSize, // 大小
DWORD flNewProtect, // 请求的保护方式
PDWORD lpflOldProtect // 保存老的保护方式
);

其中实现的原理如下图(参考逆向工程核心原理332页)
函数勾取前
函数勾取后
下面直接引用书上的源代码
InjectDll.exe

#include "stdio.h"
#include "windows.h"
#include "tlhelp32.h"
#include "winbase.h"
#include "tchar.h"


void usage()
{
    printf("\nInjectDll.exe by ReverseCore\n"
           "- blog  : http://www.reversecore.com\n"
           "- email : reversecore@gmail.com\n\n"
           "- USAGE : InjectDll.exe <i|e> <PID> <dll_path>\n\n");
}


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

    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
    {
        DWORD dwErr = GetLastError();
        return FALSE;
    }

    pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

    WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);

    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
    hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
    WaitForSingleObject(hThread, INFINITE); 

    CloseHandle(hThread);
    CloseHandle(hProcess);

    return TRUE;
} 


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

    if( INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)) )
        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 )
    {
        CloseHandle(hSnapshot);
        return FALSE;
    }

    if( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
    {
        CloseHandle(hSnapshot);
        return FALSE;
    }

    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "FreeLibrary");
    hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
    WaitForSingleObject(hThread, INFINITE); 

    CloseHandle(hThread);
    CloseHandle(hProcess);
    CloseHandle(hSnapshot);

    return TRUE;
}


DWORD _EnableNTPrivilege(LPCTSTR szPrivilege, DWORD dwState)
{
    DWORD dwRtn = 0;
    HANDLE hToken;
    if (OpenProcessToken(GetCurrentProcess(),
        TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    {
        LUID luid;
        if (LookupPrivilegeValue(NULL, szPrivilege, &luid))
        {
            BYTE t1[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)];
            BYTE t2[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)];
            DWORD cbTP = sizeof(TOKEN_PRIVILEGES) + sizeof (LUID_AND_ATTRIBUTES);

            PTOKEN_PRIVILEGES pTP = (PTOKEN_PRIVILEGES)t1;
            PTOKEN_PRIVILEGES pPrevTP = (PTOKEN_PRIVILEGES)t2;

            pTP->PrivilegeCount = 1;
            pTP->Privileges[0].Luid = luid;
            pTP->Privileges[0].Attributes = dwState;

            if (AdjustTokenPrivileges(hToken, FALSE, pTP, cbTP, pPrevTP, &cbTP))
                dwRtn = pPrevTP->Privileges[0].Attributes;
        }

        CloseHandle(hToken);
    }

    return dwRtn;
}

DLL注入以及提权的代码和普通DLL注入的一样,没有变化,在这里有一个问题,DLL注入之后就可以直接实现运行自定义代码,又何必费力去勾取特定函数?我思考之后发现原因可能有二:
1、从恶意代码的角度考虑,它是防止杀软检测的一种手段(具体原因还没有搞清楚)
2、从函数监控或者函数补丁的角度来看它显然用处明显
int _tmain(int argc, TCHAR* argv[])
{
    if( argc != 4 )
    {
        usage();
        return 1;
    }

    // adjust privilege
    _EnableNTPrivilege(SE_DEBUG_NAME, SE_PRIVILEGE_ENABLED);

    // InjectDll.exe <i|e> <PID> <dll_path>
    if( !_tcsicmp(argv[1], L"i") )
        InjectDll((DWORD)_tstoi(argv[2]), argv[3]);
    else if(!_tcsicmp(argv[1], L"e") )
        EjectDll((DWORD)_tstoi(argv[2]), argv[3]);

    return 0;
}

hookiat.dll

// include
#include "stdio.h"
#include "wchar.h"
#include "windows.h"


// typedef
typedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);


// globals
FARPROC g_pOrgFunc = NULL;


// 自定用于勾取的替代函数
BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
    wchar_t* pNum = L"零一二三四五六七八九";
    wchar_t temp[2] = {0,};
    int i = 0, nLen = 0, nIndex = 0;

    nLen = wcslen(lpString);
    for(i = 0; i < nLen; i++)
    {
        // 将阿拉伯数字转换为中文数字
        //   lpString 是wide-character (2 byte) 字符串
        if( L'0' <= lpString[i] && lpString[i] <= L'9' )
        {
            temp[0] = lpString[i];
            nIndex = _wtoi(temp);
            lpString[i] = pNum[nIndex];
        }
    }

    // user32!SetWindowTextW() API 

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


// hook_iat

BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
    HMODULE hMod;
    LPCSTR szLibName;
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc; 
    PIMAGE_THUNK_DATA pThunk;
    DWORD dwOldProtect, dwRVA;
    PBYTE pAddr;

    // hMod, pAddr = ImageBase of calc.exe
    //             = VA to MZ signature (IMAGE_DOS_HEADER)
    hMod = GetModuleHandle(NULL);
    pAddr = (PBYTE)hMod;

    // pAddr = VA to PE signature (IMAGE_NT_HEADERS)
    pAddr += *((DWORD*)&pAddr[0x3C]);

    // dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
    dwRVA = *((DWORD*)&pAddr[0x80]);

    // pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
    pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

    for( ; pImportDesc->Name; pImportDesc++ )
    {
        // szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name
        szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
        if( !_stricmp(szLibName, szDllName) )
        {
            // pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
            //        = VA to IAT(Import Address Table)
            pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod + 
                                         pImportDesc->FirstThunk);

            // pThunk->u1.Function = VA to API
            for( ; pThunk->u1.Function; pThunk++ )
            {
                if( pThunk->u1.Function == (DWORD)pfnOrg )
                {
   // 这里是编程中容易遗忘的部分,它充分的考虑到了内存的权限问题
   //更改内存属性
                    VirtualProtect(
                    (LPVOID)&pThunk>u1.Function, 
                              4,                                                                   PAGE_EXECUTE_READWRITE, 
                           &dwOldProtect);

                    // IAT值修改
                 pThunk->u1.Function = (DWORD)pfnNew;

                    // 恢复内存属性
              VirtualProtect(
              (LPVOID)&pThunk->u1.Function, 
                                   4, 
                                   dwOldProtect, 
                                   &dwOldProtect);                      

                    return TRUE;
                }
            }
        }
    }

    return FALSE;
}



BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch( fdwReason )
    {
        case DLL_PROCESS_ATTACH : 
            // 保存原API地址
            g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),                                             "SetWindowTextW");

            // # hook 
  // 用hookiat!MySetWindowText()勾取user32!SetWindowTextW()
            hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
            break;

        case DLL_PROCESS_DETACH :
            // # unhook
            // 将calc.exe IAT恢复原值
            hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
            break;
    }

    return TRUE;
}

小结:
整个思路不算复杂,但是实际编程中有一些需要注意的细节不能出错,否则就会是函数勾取失败,比如DLL注入中额提权操作一定是不能遗忘,还有在IAT地址替换的过程中通过查找IID结构,再一步一步查找IAT中的函数地址也是需要很熟悉PE文件结构才能准确查找的,对IID查找流程中的编码我现在也不太明白代码如下,是比较通用查找流程

// hMod, pAddr = ImageBase of calc.exe
    //             = VA to MZ signature (IMAGE_DOS_HEADER)
    hMod = GetModuleHandle(NULL);
    pAddr = (PBYTE)hMod;

    // pAddr = VA to PE signature (IMAGE_NT_HEADERS)
    pAddr += *((DWORD*)&pAddr[0x3C]);

    // dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
    dwRVA = *((DWORD*)&pAddr[0x80]);

    // pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
    pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

    for( ; pImportDesc->Name; pImportDesc++ )
    {
        // szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name
        szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
        if( !_stricmp(szLibName, szDllName) )
        {
            // pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
            //        = VA to IAT(Import Address Table)
            pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod + 
                                         pImportDesc->FirstThunk);

            // pThunk->u1.Function = VA to API

找到IID结构体之后,还需要了解INT和IAT指向的IMAGE_THUNK_DATA结构体才能准确查找API地址

typedef struct _IMAGE_THUNK_DATA32 {
union {
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;
PIMAGE_IMPORT_BY_NAME AddressOfData;
} u1;
} IMAGE_THUNK_DATA32;

ForwarderString 指向一个转向者字符串的RVA;
Function 被输入的函数的内存地址;
Ordinal 被输入的API的序数值
AddressOfData 指向IMAGE_IMPORT_BY_NAME
IID结构的查找在内存中汇编表现为[EDI+3C]、[EDI+EAX+80],只要看到此特征,就能猜测它正在查找IID,整个过程到此结束,对于编程过程中的一些细节以后还需要花时间去整理,对此项技术与其他技术的结合使用也还需要进一步去探索,离找工作时间有限,先了解核心思想为先、、、

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值