InlineHook 是一種電腦保安程式設計技術,其原理是在計算機程式執行期間進行攔截、修改、增強現有函數功能。它使用勾點函數(也可以稱為回撥函數)來截獲程式執行的各種事件,並在事件發生前或後進行自定義處理,從而控制或增強程式行為。Hook技術常被用於系統加速、功能增強、等領域。本章將重點講解Hook是如何實現的,並手動封裝實現自己的Hook掛鉤模板。
首先我們來探索一下Hook技術是如何實現的,如下圖所示是一個簡單的彈窗程式,當讀者點選測試彈窗按鈕時則會彈出一個MessageBox
提示視窗,本次實現目標很簡單,通過向目標內注入一個DLL庫,實現Hook掛鉤住MessageBox
彈窗,從而實現去除彈窗的目的;
我們先來看看如何實現Hook思路;
GetModuleHandle
函數來獲取到user32.dll
模組的基址GetProcAddress
函數獲取到MessageBoxA
彈窗的基址VirtualProtect
來修改MessageBoxA
前5個位元組記憶體屬性Dest - MsgBox - 5
重定位跳轉地址,並寫入JMP
跳轉指令Dest + Offset + 5 = MsgBox +5
得到需要跳轉回RET
的位置VirtualProtect
來將記憶體屬性修改為原始狀態我們載入帶有MessageBoxA
彈窗的程式,然後在x64dbg
上按下Ctrl+G
輸入MessageBoxA
找到我們需要Hook的位置(或者說替換),如下圖所示為了完成彈窗轉向功能,只需要在函數開頭寫入jmp
無條件跳轉指令即可,在32位元系統中JMP
指令預設佔用5個位元組,前三條指令恰好5個位元組,為了能夠保持堆疊平衡,我們需要記下前三條指令,並在自己的中轉函數中對其進行補齊。
此外,我們還需要計算出程式的返回地址,使用0x76600BE5 - 0x76600BA0 = 0x45
從而得出返回地址就是基址加上0x45
,這裡的返回地址其實就是返回到原MessageBoxA
彈窗的RET 0x10
的位置76600BE6
,從這裡可以看出遮蔽彈窗的原理就是通過中轉函數跳過了原始彈窗函數的執行。
由於開頭位置被替換為了我們自己的Transfer()
函數,當程式中彈窗被呼叫時預設會路由到我們自己的函數內,首先執行補齊原函數的替換部分,並執行自定義功能區中的增加內容,當執行結束後則通過jmp ebx
的方式跳轉回原函數的ret 0x10
的位置處,從而實現增加功能的目的。這裡讀者需要注意__declspec(naked)
的意思是不新增任何的組合修飾,當使用了此修飾符時則編譯器會只編譯我們自己的組合指令,並不會增加預設的函數開場或離場原語。
__declspec(naked) void Transfer()
{
__asm{
mov edi, edi
push ebp
mov ebp, esp
// 自定義功能區
mov ebx, jump // 取出跳轉地址
jmp ebx // 無條件轉向
}
}
通過應用上述案例中的知識點我們能很容易的實現對彈窗的替換功能,以下程式碼中實現了對MessageBoxA
彈窗的遮蔽功能,也就是通過跳過彈窗實現流程實現的一種劫持方法,讀者可自行編譯這段DLL程式,但需要注意一點,讀者在編譯DLL時應該關閉DLL的DEP
以及ASLR
模式,否則會出現無法定位的問題。
#include <Windows.h>
#include <stdio.h>
DWORD jump = 0;
// 不新增任何的組合修飾
__declspec(naked) void Transfer()
{
__asm
{
mov edi, edi
push ebp
mov ebp, esp
mov ebx, jump // 取出跳轉地址
jmp ebx // 無條件轉向
}
}
// DLL程式入口地址
bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
// 取程序內模組基址
HMODULE hwnd = GetModuleHandle(TEXT("user32.dll"));
DWORD base = (DWORD)GetProcAddress(hwnd, "MessageBoxA");
DWORD oldProtect = 0;
// 將記憶體設定為可讀可寫可執行狀態,並將原屬性儲存在oldProtect方便恢復
if (VirtualProtect((LPVOID)base, 5, PAGE_EXECUTE_READWRITE, &oldProtect))
{
DWORD value = (DWORD)Transfer - base - 5; // 計算出需要Hook的地址
jump = base + 0x45; // 計算出返回地址
// 替換頭部組合程式碼
__asm
{
mov eax, base
mov byte ptr[eax], 0xe9 // e9 = jmp 指令機器碼
inc eax // 遞增指標
mov ebx, value // 需要跳轉到的地址
mov dword ptr[eax], ebx
}
// 恢復記憶體的原始屬性
VirtualProtect((LPVOID)base, 5, oldProtect, &oldProtect);
}
return true;
}
讀者可通過注入軟體將hook.dll
動態連結庫注入到程序內,此時我們可以再次觀察0x76600BA0
位置處的程式碼片段,讀者應該能看到已經被JMP替換,如下圖所示;
繼續跟進則讀者能看到,在跳轉指令的下方則是我們自己補齊的組合指令,此處由於沒有做任何事就被返回了,這就導致當讀者再次點選彈窗時,彈窗失效;
當我們需要替換程式標題時同樣可是使用該方式實現,一般來說程式設定標題會呼叫SetWindowTextA
函數,我們可以攔截這個函數,並傳入自定義的視窗名稱,從而實現修改指定視窗的標題的目的,程式碼只是在上面程式碼的基礎上稍微改一下就能實現效果,只要程式使用了該函數設定標題,則可以實現替換的目的;
#include <Windows.h>
#include <stdio.h>
DWORD jump = 0;
// 組合中轉函數
__declspec(naked) bool _stdcall Transfer(HWND hwnd, LPCSTR lpString)
{
__asm
{
mov edi, edi
push ebp
mov ebp, esp
mov ebx, jump
jmp ebx
}
}
// 自己的設定表單標題函數
bool __stdcall MySetWindowTextA(HWND hwnd, LPCSTR lpString)
{
char * lpText = "LyShark 修改版";
return Transfer(hwnd, lpText);
}
// DLL程式入口地址
bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
HMODULE hwnd = GetModuleHandle(TEXT("user32.dll"));
DWORD base = (DWORD)GetProcAddress(hwnd, "SetWindowTextA");
DWORD oldProtect = 0;
if (VirtualProtect((LPVOID)base, 5, PAGE_EXECUTE_READWRITE, &oldProtect))
{
DWORD value = (DWORD)MySetWindowTextA - base - 5;
jump = base + 5;
__asm
{
mov eax, base
mov byte ptr[eax], 0xe9
inc eax
mov ebx, value
mov dword ptr[eax], ebx
}
VirtualProtect((LPVOID)base, 5, oldProtect, &oldProtect);
}
return true;
}
本文作者: 王瑞
本文連結: https://www.lyshark.com/post/f695c6c3.html
版權宣告: 本部落格所有文章除特別宣告外,均採用 BY-NC-SA 許可協定。轉載請註明出處!