【Inline Hook基础篇】挂钩系统API

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fzuim/article/details/81301912
  • 对于怎么挂钩系统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);
}

猜你喜欢

转载自blog.csdn.net/fzuim/article/details/81301912