【Inline Hook基礎篇】掛鉤系統API
阿新 • • 發佈:2018-11-09
- 對於怎麼掛鉤系統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);
}