1. 程式人生 > >【Inline Hook基礎篇】掛鉤系統API

【Inline Hook基礎篇】掛鉤系統API

  • 對於怎麼掛鉤系統API的實現,網上對此的解釋有很多也很詳細。這邊暫不進行長篇大論,就簡單的說明下原理:修改系統API的前幾個位元組,並寫入 JMP 0x15a123 彙編指令,實現呼叫系統API自動跳轉到我們的API的過程。
  • 對於API HOOK的實現,現成的有MHOOK、DETOUR等類似的框架實現。既然我們要清晰的認識具體怎麼實現,那麼我們就自己編寫實現方式,不採用第三方庫。其實也很簡單,沒啥特別的難點。。。請看以下實現API掛鉤程式碼:
typedef struct _APIHOOK32_ENTRY
{
    char    szAPIName[MAX_PATH];
    char
szCallerModuleName[MAX_PATH]; PROC pfnOriginApiAddress; DWORD dwOldBytes[2];// = {0,0}; //修改API入口為 mov eax 00400000;jmp eax是程式能跳轉到自己的函式 BYTE byNewBytes[8];// = { 0xB8, 0x0, 0x0, 0x40, 0x0, 0xFF, 0xE0, 0x0 }; DWORD dwNewAddress; HMODULE hModCallerModule; //事務,解決同步問題 HANDLE hEvent; HANDLE hProcess; _APIHOOK32_ENTRY(const
char* v_szDllName, const char* v_szAPIName, DWORD v_dwProcessID) { strcpy(szAPIName, v_szAPIName); strcpy(szCallerModuleName, v_szDllName); //建立事務 hEvent = CreateEvent( NULL, FALSE, TRUE, NULL ); hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, v_dwProcessID); memset(dwOldBytes, 0
, sizeof(dwOldBytes)); memset(byNewBytes, 0, sizeof(byNewBytes)); byNewBytes[0] = 0xB8; byNewBytes[3] = 0x40; byNewBytes[5] = 0xFF; byNewBytes[6] = 0xE0; } ~_APIHOOK32_ENTRY() { if ( NULL != hEvent) { WaitForSingleObject( hEvent, INFINITE ); DWORD dwOldProc; DWORD dwNewProc; //改變頁面屬性為讀寫 VirtualProtectEx( hProcess, (void*)pfnOriginApiAddress, 8, PAGE_READWRITE, &dwOldProc ); //恢復API的首8個位元組 WriteProcessMemory( INVALID_HANDLE_VALUE, (void*)pfnOriginApiAddress, (void*)dwOldBytes, sizeof(DWORD)*2, NULL ); //恢復頁面檔案的屬性 VirtualProtectEx( hProcess, (void*)pfnOriginApiAddress, 8, dwOldProc, &dwNewProc ); CloseHandle(hEvent); hEvent = NULL; } } }APIHOOK32_ENTRY, *PAPIHOOK32_ENTRY;
  • 其實這段實現方式的程式碼,網上應該是有類似的:修改API前8個位元組,將0040000的值設定為我們API的地址值即可:
mov eax 00400000
jmp eax
  • 呼叫例項:
PAPIHOOK32_ENTRY phk = NULL; //簡單設成全域性變數,正常做法是在New完後儲存到全域性快取裡面
/*
 *  API劫持實現方式:重寫API開頭8個位元組,寫入自己的跳轉語句
 */
void InitHook(const char *v_szDllName, const char *v_szAPIName, DWORD v_dwNewAddress)
{
    phk = new APIHOOK32_ENTRY(v_szDllName, v_szAPIName, g_dwProcessID);
    phk->dwNewAddress = v_dwNewAddress;
    //重寫API開頭的8位元組      
    phk->hModCallerModule = LoadLibrary( phk->szCallerModuleName );      
    if ( NULL == phk->hModCallerModule)
        return;

    phk->pfnOriginApiAddress = GetProcAddress( phk->hModCallerModule, phk->szAPIName ); 
    //儲存原始位元組      
    ReadProcessMemory( INVALID_HANDLE_VALUE, (void*)phk->pfnOriginApiAddress, (void*)phk->dwOldBytes, sizeof(DWORD) * 2, NULL );
    //將00400000改寫為我們函式的地址,why +1? 因為NewBytes第一個位元組0xB8為mov eax     
    *(DWORD*)(phk->byNewBytes + 1) = phk->dwNewAddress;      
    WriteProcessMemory( INVALID_HANDLE_VALUE, (void*)phk->pfnOriginApiAddress, (void*)phk->byNewBytes, sizeof(DWORD) * 2, NULL );     
}
InitHook("user32.dll", "GetClipboardData", (DWORD)hook_GetClipboardData);

//簡單的限制剪下板函式,貼上的時候直接返回NULL,貼上不成功
HANDLE _stdcall hook_GetClipboardData( UINT uFormat )
{ 
    return NULL;
} 
  • 當然我們也想在自己的函式裡面繼續呼叫原系統API時怎麼辦呢?畢竟系統API已經被我們掛鉤了自己的函式,再次呼叫豈不是進入了死迴圈了。。。所以我們需要進行一次API現場恢復,才能使用原系統的API。
#define GET_EVENT(p) \
    WaitForSingleObject( p->hEvent, INFINITE );\
    ResetEvent(p->hEvent);
#define SET_EVENT(p) \
    SetEvent(p->hEvent);
#define RESTORE_OLDADDRESS(p) \
    GET_EVENT(p); \
    WriteProcessMemory( INVALID_HANDLE_VALUE, (void*)p->pfnOriginApiAddress, (void*)p->dwOldBytes, sizeof(DWORD)*2, NULL );
#define SET_NEWADDRESS(p) \
    WriteProcessMemory( INVALID_HANDLE_VALUE, (void*)p->pfnOriginApiAddress, (void*)p->byNewBytes, sizeof(DWORD)*2, NULL ); \
    SET_EVENT(p);  
HANDLE _stdcall hook_GetClipboardData( UINT uFormat )
{ 
    HANDLE hRet;   

    //恢復API頭8個位元組   
    RESTORE_OLDADDRESS(phk); //正常這個phk要快取到記憶體裡面,編寫例項就不實現了
    /* 這裡可以新增想要進行的處理過程*/    
    //真正執行API函式   
    hRet = GetClipboardData( uFormat );    
    //寫入跳轉語句,繼續Hook   
    SET_NEWADDRESS(phk);

    return hRet;
} 

這邊有個注意點:在恢復系統API頭8個位元組的這個過程,如果程序中的其他執行緒剛好也呼叫此API,則不會被我們Hook,這是Inline Hook的一個不足點。MHook之類的框架,對這點有進行改進,貼一段Mhook的原始碼:

BOOL Mhook_SetHook(PVOID *ppSystemFunction, PVOID pHookFunction) {
    MHOOKS_TRAMPOLINE* pTrampoline = NULL;
    PVOID pSystemFunction = *ppSystemFunction;
    // ensure thread-safety
    EnterCritSec();
    ODPRINTF((L"mhooks: Mhook_SetHook: Started on the job: %p / %p", pSystemFunction, pHookFunction));
    // find the real functions (jump over jump tables, if any)
    pSystemFunction = SkipJumps((PBYTE)pSystemFunction);
    pHookFunction   = SkipJumps((PBYTE)pHookFunction);
    ODPRINTF((L"mhooks: Mhook_SetHook: Started on the job: %p / %p", pSystemFunction, pHookFunction));
    // figure out the length of the overwrite zone
    MHOOKS_PATCHDATA patchdata = {0};
    DWORD dwInstructionLength = DisassembleAndSkip(pSystemFunction, MHOOK_JMPSIZE, &patchdata);
    if (dwInstructionLength >= MHOOK_JMPSIZE) {
        ODPRINTF((L"mhooks: Mhook_SetHook: disassembly signals %d bytes", dwInstructionLength));
        // suspend every other thread in this process, and make sure their IP 
        // is not in the code we're about to overwrite.
        SuspendOtherThreads((PBYTE)pSystemFunction, dwInstructionLength);
        // allocate a trampoline structure (TODO: it is pretty wasteful to get
        // VirtualAlloc to grab chunks of memory smaller than 100 bytes)
        pTrampoline = TrampolineAlloc((PBYTE)pSystemFunction, patchdata.nLimitUp, patchdata.nLimitDown);
        if (pTrampoline) {
            ODPRINTF((L"mhooks: Mhook_SetHook: allocated structure at %p", pTrampoline));
            // open ourselves so we can VirtualProtectEx
            HANDLE hProc = GetCurrentProcess();
            DWORD dwOldProtectSystemFunction = 0;
            DWORD dwOldProtectTrampolineFunction = 0;
            // set the system function to PAGE_EXECUTE_READWRITE
            if (VirtualProtectEx(hProc, pSystemFunction, dwInstructionLength, PAGE_EXECUTE_READWRITE, &dwOldProtectSystemFunction)) {
                ODPRINTF((L"mhooks: Mhook_SetHook: readwrite set on system function"));
                // mark our trampoline buffer to PAGE_EXECUTE_READWRITE
                if (VirtualProtectEx(hProc, pTrampoline, sizeof(MHOOKS_TRAMPOLINE), PAGE_EXECUTE_READWRITE, &dwOldProtectTrampolineFunction)) {
                    ODPRINTF((L"mhooks: Mhook_SetHook: readwrite set on trampoline structure"));

                    // create our trampoline function
                    PBYTE pbCode = pTrampoline->codeTrampoline;
                    // save original code..
                    for (DWORD i = 0; i<dwInstructionLength; i++) {
                        pTrampoline->codeUntouched[i] = pbCode[i] = ((PBYTE)pSystemFunction)[i];
                    }
                    pbCode += dwInstructionLength;
                    // plus a jump to the continuation in the original location
                    pbCode = EmitJump(pbCode, ((PBYTE)pSystemFunction) + dwInstructionLength);
                    ODPRINTF((L"mhooks: Mhook_SetHook: updated the trampoline"));

                    // fix up any IP-relative addressing in the code
                    FixupIPRelativeAddressing(pTrampoline->codeTrampoline, (PBYTE)pSystemFunction, &patchdata);

                    DWORD_PTR dwDistance = (PBYTE)pHookFunction < (PBYTE)pSystemFunction ? 
                        (PBYTE)pSystemFunction - (PBYTE)pHookFunction : (PBYTE)pHookFunction - (PBYTE)pSystemFunction;
                    if (dwDistance > 0x7fff0000) {
                        // create a stub that jumps to the replacement function.
                        // we need this because jumping from the API to the hook directly 
                        // will be a long jump, which is 14 bytes on x64, and we want to 
                        // avoid that - the API may or may not have room for such stuff. 
                        // (remember, we only have 5 bytes guaranteed in the API.)
                        // on the other hand we do have room, and the trampoline will always be
                        // within +/- 2GB of the API, so we do the long jump in there. 
                        // the API will jump to the "reverse trampoline" which
                        // will jump to the user's hook code.
                        pbCode = pTrampoline->codeJumpToHookFunction;
                        pbCode = EmitJump(pbCode, (PBYTE)pHookFunction);
                        ODPRINTF((L"mhooks: Mhook_SetHook: created reverse trampoline"));
                        FlushInstructionCache(hProc, pTrampoline->codeJumpToHookFunction, 
                            pbCode - pTrampoline->codeJumpToHookFunction);

                        // update the API itself
                        pbCode = (PBYTE)pSystemFunction;
                        pbCode = EmitJump(pbCode, pTrampoline->codeJumpToHookFunction);
                    } else {
                        // the jump will be at most 5 bytes so we can do it directly
                        // update the API itself
                        pbCode = (PBYTE)pSystemFunction;
                        pbCode = EmitJump(pbCode, (PBYTE)pHookFunction);
                    }

                    // update data members
                    pTrampoline->cbOverwrittenCode = dwInstructionLength;
                    pTrampoline->pSystemFunction = (PBYTE)pSystemFunction;
                    pTrampoline->pHookFunction = (PBYTE)pHookFunction;

                    // flush instruction cache and restore original protection
                    FlushInstructionCache(hProc, pTrampoline->codeTrampoline, dwInstructionLength);
                    VirtualProtectEx(hProc, pTrampoline, sizeof(MHOOKS_TRAMPOLINE), dwOldProtectTrampolineFunction, &dwOldProtectTrampolineFunction);
                } else {
                    ODPRINTF((L"mhooks: Mhook_SetHook: failed VirtualProtectEx 2: %d", gle()));
                }
                // flush instruction cache and restore original protection
                FlushInstructionCache(hProc, pSystemFunction, dwInstructionLength);
                VirtualProtectEx(hProc, pSystemFunction, dwInstructionLength, dwOldProtectSystemFunction, &dwOldProtectSystemFunction);
            } else {
                ODPRINTF((L"mhooks: Mhook_SetHook: failed VirtualProtectEx 1: %d", gle()));
            }
            if (pTrampoline->pSystemFunction) {
                // this is what the application will use as the entry point
                // to the "original" unhooked function.
                *ppSystemFunction = pTrampoline->codeTrampoline;
                ODPRINTF((L"mhooks: Mhook_SetHook: Hooked the function!"));
            } else {
                // if we failed discard the trampoline (forcing VirtualFree)
                TrampolineFree(pTrampoline, TRUE);
                pTrampoline = NULL;
            }
        }
        // resume everybody else
        ResumeOtherThreads();
    } else {
        ODPRINTF((L"mhooks: disassembly signals %d bytes (unacceptable)", dwInstructionLength));
    }
    LeaveCritSec();
    return (pTrampoline != NULL);
}