Windows Hook經驗總結之二:API Hook實踐

前一篇介紹了API Hook的幾種方法,本文則直接以CopyFile爲例採用DLL注入的方法來展示編碼流程。
一個完整的HOOK過程包括四個部分:
1)待注入的DLL
2)DLL注入器
3)目標進程
4)主程序

DLL注入器的實現

注入器需要實現的功能就是將用戶DLL注入到目標進程中。可設計爲一個獨立DLL,方便複用。DLL可規劃兩個接口,類似內存分配的malloc/free的配對使用。接口如下:

BOOL    WINAPI  Inject(LPCTSTR lpszUsrApiDLL, LPCTSTR lpszHookProcess);
BOOL    WINAPI  Eject(LPCTSTR lpszUsrApiDLL, LPCTSTR lpszHookProcess);

具體實現上代碼:

尋找目標進程PID

採用CreateToolhelp32SnapshotProcess32FirstProcess32NextModule32FirstModule32Next
一系列函數即可獲得進程列表及模塊列表。不是本文重點,就不上代碼了。

執行注入

直接上代碼

/* 注入,將dll注入到指定進程中,只給出核心代碼 */
int     InjectLib(DWORD dwProcessId, LPCTSTR lpszLib)
{
    PTHREAD_START_ROUTINE pfnRemote =(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA");

    /*打開目標進程*/  
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);

    /*在目標進程上分配可讀寫空間*/
    int nMemSize =strlen(lpszLib) + 1;
    void *pRemoteMem = VirtualAllocEx(hProcess, NULL, nMemSize, MEM_COMMIT, PAGE_READWRITE);

    /*寫目標進程,將用戶DLL加入目標進程空間(未加載)*/
    int ret = WriteProcessMemory(hProcess, pRemoteMem, (void*)lpszLib, nMemSize, NULL); 

    /*在目標進程中建立遠線程*/
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnRemote, pRemoteMem, 0, NULL); 

    /*目標進程中執行LoadLibrary即將用戶DLL真正加載起來生效*/
    WaitForSingleObject(hThread, INFINITE); 
    VirtualFreeEx(hProcess, pRemoteMem, 0, MEM_RELEASE);
    CloseHandle(hProcess);
    CloseHandle(hThread);

    return 0;
}
/* 反注入,將用戶dll從目標進程中剝離出來 */
int     EjectLib(DWORD dwProcessId, LPCTSTR lpszLib)
{
    PTHREAD_START_ROUTINE pfnRemote =(PTHREAD_START_ROUTINE)
        GetProcAddress(GetModuleHandle("Kernel32"), "FreeLibrary");

    /*打開目標進程*/
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);

    /*獲取目標進程的模塊列表,檢查用戶dll是否存在;不存在則直接返回*/
    /*代碼省略*/

    /*在目標進程中建立遠線程*/
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnRemote, hModule, 0, NULL);

    /*目標進程中執行FreeLibrary即將用戶DLL卸載掉*/
    WaitForSingleObject(hThread, INFINITE);

    CloseHandle(hProcess);
    CloseHandle(hThread);

    return 0;
}

至此,注入器DLL的核心功能已實現。需要注意的是,注入前必須用戶足夠的權限,即必須先提權限,代碼如下:

BOOL    ObtainSeDebugPrivilege()
{
    HANDLE hToken;
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    {
        return FALSE;
    }

    LUID luid;
    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))
    {
        return FALSE;
    }

    TOKEN_PRIVILEGES TokenPrivileges;
    TOKEN_PRIVILEGES PreviousTokenPrivileges;

    DWORD dwPreviousTokenPrivilegesSize = sizeof(TOKEN_PRIVILEGES);

    TokenPrivileges.PrivilegeCount            = 1;
    TokenPrivileges.Privileges[0].Luid        = luid;
    TokenPrivileges.Privileges[0].Attributes  = 0;

    if(!AdjustTokenPrivileges(hToken, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES),
        &PreviousTokenPrivileges, &dwPreviousTokenPrivilegesSize))
    {
        return FALSE;
    }

    PreviousTokenPrivileges.PrivilegeCount = 1;
    PreviousTokenPrivileges.Privileges[0].Luid = luid;
    PreviousTokenPrivileges.Privileges[0].Attributes |= SE_PRIVILEGE_ENABLED;

    if(!AdjustTokenPrivileges(hToken, FALSE, &PreviousTokenPrivileges,
        dwPreviousTokenPrivilegesSize, NULL, NULL))
    {
        return FALSE;
    }

    return TRUE;
}

待注入的DLL

此DLL主要實現用戶功能以及系統API的替換,本質上就是將系統(或其他第三方)的API地址改寫爲自己實現的函數的地址。這裏包括兩個方面:

實現自己的API

這個比較簡單,即自己實現和目標api完全一致的函數(返回值和參數列表必須一致),CopyFile的實例如下,因爲有ANSI和Wide兩個版本,所以都需實現(貌似系統最終都是調用Wide版本)。如果hook是攔截原api的行爲,則執行完自己的代碼即可返回;否則還需繼續執行原api行爲。示例代碼如下:

/*獲取進程中指定DLL的函數地址*/
PROC    GetProcAddr(LPCTSTR pszApiName, LPCTSTR pszOwnerModule)
{
    HMODULE hOwner = GetModuleHandle(pszOwnerModule);       
    PROC pfn = GetProcAddress(hOwner, pszApiName);
    return pfn;
}
typedef BOOL    (WINAPI *PFN_CopyFileA)(LPCSTR, LPCSTR, BOOL);
BOOL    WINAPI  HOOK_CopyFileA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, BOOL bFailIfExists)
{
    /*TODO::Add code here ...*/

    /*非攔截的情況下會調用原始的api*/
    PFN_CopyFileA pfnAPI = (PFN_CopyFileA) GetProcAddr("CopyFileA", "kernel32.dll");
    return pfnAPI(lpExistingFileName, lpNewFileName, bFailIfExists);
}

註冊API

之前注入器已經在目標進程中開了遠線程並執行了LoadLibrary / FreeLibrary,它會首先執行到DllMain函數,原型如下:

BOOL APIENTRY DllMain( HANDLE hModule, 
                      DWORD  ul_reason_for_call, 
                      LPVOID lpReserved
                      )
{   
    switch(ul_reason_for_call)
    {
    /*LoadLibrary*/
    case DLL_PROCESS_ATTACH:        
    case DLL_THREAD_ATTACH:
        /*註冊用戶API*/
        return TRUE;
    /*FreeLibray*/
    case DLL_PROCESS_DETACH:
    case DLL_THREAD_DETACH:
        /*解除用戶API,恢復原API*/
        return TRUE;

    default:
        break;
    }

    return TRUE;
}
typedef struct _APIHOOK32_ENTRY
{
    CHAR        pszApiName[64];
    CHAR        pszOwnerModule[64];
    PROC        pfnOriginAddress;
    PROC        pfnDummyAddress;
    HMODULE     hCallerModule;
}APIHOOK32_ENTRY, *PAPIHOOK32_ENTRY;

BOOL    HOOKUPAPI(APIHOOK32_ENTRY* api)
{
    ULONG uSize = 0;

    /*獲取指向PE文件中的Import中IMAGE_DIRECTORY_DESCRIPTOR數組的指針*/    
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
        ImageDirectoryEntryToData(api->hCallerModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &uSize);

    /*查找記錄,看看有沒有我們想要的DLL*/  
    for (; pImportDesc->Name; pImportDesc++)
    {
        LPSTR pszDLL = (LPSTR)((PBYTE)api->hCallerModule + pImportDesc->Name);
        if (lstrcmpiA(pszDLL, api->pszOwnerModule) == 0)
            break;
    }

    /*尋找我們想要的函數*/

    PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA) ((PBYTE)api->hCallerModule + pImportDesc->FirstThunk);//IAT
    for (; pThunk->u1.Function; pThunk++)
    {
        //ppfn記錄了與IAT表項相應的函數的地址     
        PROC* pfn= (PROC *)&pThunk->u1.Function;        
        if (*pfn == api->pfnOriginAddress) 
        {
            /*如果地址相同,也就是找到了我們想要的函數,進行改寫,將其指向我們所定義的函數*/      
            WriteProcessMemory(GetCurrentProcess(), pfn, &(api->pfnDummyAddress), sizeof(api->pfnDummyAddress), NULL);          
            return TRUE;
        }
    }
    return FALSE;
}

BOOL    HookWindowsAPI(APIHOOK32_ENTRY* api)
{   
    if (api->hCallerModule == NULL)
    {
        MEMORY_BASIC_INFORMATION memInfo;

        VirtualQuery(HOOKUPAPI, &memInfo, sizeof(memInfo));
        HMODULE hHookModule = (HMODULE)memInfo.AllocationBase;

        MODULEENTRY32 me = {sizeof(MODULEENTRY32)}; 
        CHAR szExeFile[MAX_PATH] = { 0 };

        HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
        BOOL bOK = Module32First(hSnapshot, &me);
        while (bOK)
        {
            if (me.hModule != hHookModule)
            {
                ZeroMemory(szExeFile, sizeof(szExeFile));
                strcpy(szExeFile, me.szExePath);
                strlwr(szExeFile);

                api->hCallerModule = me.hModule;
                if (HOOKUPAPI(api))
                {}
            }

            bOK = Module32Next(hSnapshot, &me);
        }

        return TRUE;        
    }

    return HOOKUPAPI(api);
}

Hook與Unhook都調用HookWindowsAPI函數,區別在於將自定義結構體APIHOOK32_ENTRY中的兩個PROC字段互換即可。

目標進程

當然,目標進程不是我們寫的,否則就不用hook這麼麻煩了,( ̄▽ ̄)”

主程序

主程序就比較簡單了,只需要調用注入器,在需要的時候調用Inject接口,用完再Eject即可,這裏就不上代碼了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章