函数钩子-Dll注入

来源:互联网 发布:数据透视表汇总求和 编辑:程序博客网 时间:2024/05/17 01:12

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

设置钩子: 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"// typedeftypedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);// globalsFARPROC 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_iatBOOL 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,整个过程到此结束,对于编程过程中的一些细节以后还需要花时间去整理,对此项技术与其他技术的结合使用也还需要进一步去探索,离找工作时间有限,先了解核心思想为先、、、

0 1
原创粉丝点击