1. 程式人生 > 實用技巧 >x32 Inline Hook 程式碼封裝

x32 Inline Hook 程式碼封裝

Hook 技術常被叫做掛鉤技術,掛鉤技術其實早在DOS時代就已經存在了,該技術是Windows系統用於替代DOS中斷機制的具體實現,鉤子的含義就是在程式還沒有呼叫系統函式之前,鉤子捕獲呼叫訊息並獲得控制權,在執行系統呼叫之前執行自身程式,簡單來說就是函式劫持.

HOOK技術的實現方法比較多,常見的HOOK方法有 Inline Hook、IAT Hook、EAT Hook 這三種,鉤子的應用範圍非常廣泛,比如輸入監控、API攔截、訊息捕獲、改變程式執行流程等,防毒軟體也會HOOK鉤住一些特殊的API函式,起到監控系統執行狀態的目的,黑客們也會通過鉤子技術截獲一些有價值的資料,例如鍵盤訊息等.

該筆記是針對32位Hook的簡易封裝,自己留著也沒什麼意思,還是發出來吧,轉載請加出處


Hook 實現去彈窗: 首先我們來實現一個小功能,這裡有一個小程式,當我們點選彈窗時會自動的彈出一個MessageBox提示,我們的目標是通過注入DLL的方式Hook鉤掛住MessageBox從而實現去除這個彈窗的目的,先來看一下Hook的思路:

1.呼叫 GetModuleHandle 來獲取到user32.dll模組的基址
2.呼叫 GetProcAddress 獲取到MessageBoxA彈窗的基址
3.呼叫 VirtualProtect 來修改MsgBox前5個位元組記憶體屬性
4.計算 Dest - MsgBox - 5 重定位跳轉地址,並寫入JMP跳轉指令
5.計算 Dest + Offset + 5 = MsgBox +5 得到需要跳轉回ret的位置
6.最後呼叫 VirtualProtect 來將記憶體屬性修改為原始狀態

首先我們載入帶有MsgBox彈窗的程式,然後在X64DBG上按下Ctrl+G輸入MessageBoxA找到我們需要Hook的地方,如下所示我們為了完成彈窗轉向功能,只需要在函式開頭寫入jmp無條件跳轉指令即可,在32位系統中JMP指令預設佔用5個位元組,前三條指令恰好5個位元組,為了能夠保持堆疊平衡,我們需要記下前三條指令,並在自己的中轉函式中補齊.

759F1F70 | 8BFF                     | mov edi,edi                    | Src 替換為 jmp xxxx
759F1F72 | 55                       | push ebp                       | 替換為 jmp xxxx
759F1F73 | 8BEC                     | mov ebp,esp                    | 替換為 jmp xxxx
759F1F75 | 6A FF                    | push 0xFFFFFFFF                |
759F1F77 | 6A 00                    | push 0x0                       |
759F1F79 | FF75 14                  | push dword ptr ss:[ebp+0x14]   |
759F1F7C | FF75 10                  | push dword ptr ss:[ebp+0x10]   |
759F1F7F | FF75 0C                  | push dword ptr ss:[ebp+0xC]    |
759F1F82 | FF75 08                  | push dword ptr ss:[ebp+0x8]    |
759F1F85 | E8 D6010000              | call <MessageBoxTimeoutA>      |
759F1F8A | 5D                       | pop ebp                        | Dest
759F1F8B | C2 1000                  | ret 0x10                       |

我們還需要計算出程式的返回地址,使用759F1F8A - 772A1F70 = 1A從而得出返回地址就是基址加上1A,這裡的返回地址其實就是返回到原MessageBox彈窗的ret 0x10的位置759F1F8B,從這裡可以看出遮蔽彈窗的原理就是通過中轉函式跳過了彈窗函式的執行,我們直接編譯這段程式碼,並注入到彈窗程式測試,會發現彈窗被去除了.

#include <Windows.h>
#include <stdio.h>

DWORD jump = 0;
// naked 關鍵字的作用是,不給我新增任何的彙編修飾
__declspec(naked) void Transfer(){
	__asm{
		mov edi, edi
		push ebp
		mov ebp, esp
		mov ebx, jump     // 取出跳轉地址
		jmp ebx           // 無條件轉向
	}
}

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 + 0x1a;                          // 計算出返回地址
		__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 實現改標題: 通常情況下,程式設定標題會呼叫SetWindowTextA這個API函式,我們可以攔截這個函式,並傳入自定義的視窗名稱,從而實現修改指定視窗的標題的目的,程式碼只是在上面程式碼的基礎上稍微改一下就能實現效果.

#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);
}

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;
}

針對Hook程式碼封裝: 上面程式碼並不具備通用性,這裡我們可以使用C++將其封裝成類,這樣使用會更方便,通常封裝類都會存在兩個檔案,這裡我們將標頭檔案定義為hook.h將實現檔案定義為hook.cpp分別實現這兩個檔案程式碼邏輯.

#pragma once
#include <Windows.h>

#ifdef __cplusplus
extern "C"{
#endif

class MyHook
{
public:
	PROC m_pfnOrig;       // 儲存函式地址
	BYTE m_bOldBytes[5];  // 儲存函式入口程式碼
	BYTE m_bNewBytes[5];  // 儲存Inlie Hook程式碼
public:
	MyHook();
	~MyHook();

	BOOL Hook(LPSTR pszModuleName, LPSTR pszFuncName, PROC pfnHookFunc);
	BOOL UnHook();
	BOOL ReHook();
};
#ifdef __cplusplus
}
#endif

如下是程式碼實現部分MyHook()建構函式用來初始化,解構函式用來清空並恢復鉤子,Hook則是具體實現掛鉤的細節,在Hook()成員函式中完成了3項工作,首先是獲得了被HOOK函式的函式地址,接下來是儲存了被HOOK函式的前5位元組,最後是用構造好的跳轉指令來修改被HOOK函式的前5位元組的內容.

#include "hook.h"

// 建構函式: 負責初始化
MyHook::MyHook()
{
	m_pfnOrig = NULL;
	ZeroMemory(m_bOldBytes, 5);
	ZeroMemory(m_bNewBytes, 5);
}

MyHook::~MyHook()
{
	UnHook();
	m_pfnOrig = NULL;
	ZeroMemory(m_bOldBytes, 5);
	ZeroMemory(m_bNewBytes, 5);
}

// 掛鉤
BOOL MyHook::Hook(LPSTR pszModuleName, LPSTR pszFuncName, PROC pfnHookFunc)
{
	BOOL bRet = FALSE;

	// 獲取指定模組中函式的地址
	m_pfnOrig = (PROC)GetProcAddress(GetModuleHandle(pszModuleName),pszFuncName);

	if (m_pfnOrig != NULL)
	{
		// 儲存該地址處 5 位元組的內容
		DWORD dwNum = 0;
		ReadProcessMemory(GetCurrentProcess(),m_pfnOrig,m_bOldBytes,5,&dwNum);

		// 構造 JMP 指令
		m_bNewBytes[0] = '\xe9'; // jmp Opcode

		// pfnHookFunc 是 HOOK 後的目標地址
		// m_pfnOrig 是原來的地址
		// 5 是指令長度
		*(DWORD *)(m_bNewBytes + 1) = (DWORD)pfnHookFunc - (DWORD)m_pfnOrig - 5;

		// 將構造好的地址寫入該地址處
		WriteProcessMemory(GetCurrentProcess(),m_pfnOrig,m_bNewBytes,5,&dwNum);
		bRet = TRUE;
	}
	return bRet;
}
// 恢復鉤子
BOOL MyHook::UnHook()
{
	if (m_pfnOrig != 0)
	{
		DWORD dwNum = 0;
		WriteProcessMemory(GetCurrentProcess(),m_pfnOrig,m_bOldBytes,5,&dwNum);
	}
	return TRUE;
}
// 重新掛鉤
BOOL MyHook::ReHook()
{
	BOOL bRet = FALSE;
	if (m_pfnOrig != 0)
	{
		DWORD dwNum = 0;
		WriteProcessMemory(GetCurrentProcess(),m_pfnOrig,m_bNewBytes,5,&dwNum);
		bRet = TRUE;
	}
	return bRet;
}

到此為止整個Inline Hook的封裝已經完成了,在後面的程式碼中,可以很容易地實現對函式的HOOK功能,這裡我再多說一句,如果我們需要在自己實現的MessageBox函式中要呼叫原始的API函式,則需要恢復Inline Hook,否則程式將會崩潰.

#include <Windows.h>
#include "hook.h"

MyHook MsgHook;

int WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
	// 先來恢復Hook 之所以要恢復是因為我們需要呼叫原始的MsgBox彈窗
	MsgHook.UnHook();

	MessageBoxA(hWnd, "hook inject", lpCaption, uType);

	// 彈窗完成後重新Hook
	MsgHook.ReHook();
	return 0;
}

int main(int argc, char * argv[])
{
	// 開始Hook
	MsgHook.Hook("user32.dll", "MessageBoxA", (PROC)MyMessageBoxA);
	MessageBoxA(NULL, "hello lyshark", "Msg", MB_OK);
	// 結束Hook
	MsgHook.UnHook();
	return 0;
}

第二種封裝方式: 該封裝方式直接將定義與實現寫到hook.h標頭檔案中,使用時直接包含一個檔案即可.

#pragma once
#include <Windows.h>

#ifdef __cplusplus
extern "C"{
#endif

#pragma once
	class MyHook
	{
	public:
		static DWORD Hook(LPCWSTR lpModule, LPCSTR lpFuncName, PROC lpFunction)
		{
			DWORD dwAddr = (DWORD)GetProcAddress(GetModuleHandle(lpModule), lpFuncName);
			BYTE jmp[] =
			{
				0xe9,                      // jmp
				0x00, 0x00, 0x00, 0x00,    // address
				0xc3                       // retn
			};

			ReadProcessMemory(GetCurrentProcess(), (LPVOID)dwAddr, MemoryAddress(), 6, 0);
			DWORD dwCalc = ((DWORD)lpFunction - dwAddr - 5);
			memcpy(&jmp[1], &dwCalc, 4);
			WriteProcessMemory(GetCurrentProcess(), (LPVOID)dwAddr, jmp, 6, 0);

			return dwAddr;
		}

		static BOOL UnHook(LPCWSTR lpModule, LPCSTR lpFuncName)
		{
			DWORD dwAddr = (DWORD)GetProcAddress(GetModuleHandle(lpModule), lpFuncName);
			if (WriteProcessMemory(GetCurrentProcess(), (LPVOID)dwAddr, MemoryAddress(), 6, 0))
				return TRUE;
			return FALSE;
		}

		static BYTE* MemoryAddress()
		{
			static BYTE backup[6];
			return backup;
		}
	};
#ifdef __cplusplus
}
#endif
#include "hook.h"

MyHook MsgHook;

int WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
	// 先來恢復Hook 之所以要恢復是因為我們需要呼叫原始的MsgBox彈窗
	MsgHook.UnHook(L"user32.dll","MessageBoxA");

	MessageBoxA(hWnd, "hook inject", lpCaption, uType);

	// 彈窗完成後重新Hook
	MsgHook.Hook(L"user32.dll", "MessageBoxW", (PROC)MyMessageBoxA);
	return 0;
}

int main(int argc, char * argv[])
{
	// 開始Hook
	MsgHook.Hook(L"user32.dll", "MessageBoxA", (PROC)MyMessageBoxA);

	MessageBoxA(NULL, "hello lyshark", "Msg", MB_OK);

	// 結束Hook
	MsgHook.UnHook(L"user32.dll", "MessageBoxA");
	return 0;
}