一、HOOk
Hook是程序設計中最為靈活多變的技巧之一,在windows下,Hook有兩種含義:
1、系統提供的消息Hook機制
2、自定義的Hook編程技巧
其中,由系統提供的消息鈎子機制是由一系列的API提供的一種服務,這個系統的API可以完成對大多數應用程序關鍵節點的Hook操作,為此,windows為每種Hook類型維護了一個鈎子鏈表,我們可以通過一個系統API來完成對整個系統所有符合此機制的關鍵點的Hook。
另一種自定義的Hook編程技巧則是基於特定系統結構、文件結構、匯編語言的一種高級技術,運用自如后猶如手握屠龍刀倚天劍。
二、系統消息鈎子的使用
windows操作系統是以事件驅動的。事件被包裝成了消息發送給窗口,比如點擊菜單,按鈕,移動窗口,按下鍵盤,正常消息:
當按下鍵盤,產生一個消息,按鍵消息加入到系統消息隊列
操作系統從消息隊列中取出消息,添加到相應的程序的消息隊列中
應用程序使用消息磊從自身的消息隊列中取出消息WM_KEYDOWN,調用消息處理函數。
我們可以在系統消息隊列之間添加消息鈎子,從而使得在系統消息隊列消息發給應用程序之前捕獲到消息。
可以多次添加鈎子,從而形成一個鈎子鏈,可以依次調用函數。
消息鈎子是windows操作系統提供的機制,SPY++截獲窗口消息的功能就是基於這樣的機制。
SetWindowsHookExW
設置鈎子
WINUSERAPI
HHOOK
WINAPI
SetWindowsHookEx(
//鈎子類型
_In_ int idHook,
//回調函數地址
_In_ HOOKPROC lpfn,
//實例句柄(包含有鈎子函數)
_In_opt_ HINSTANCE hmod,
//線程ID,欲勾住的線程(為0則不指定,全局)
_In_ DWORD dwThreadId);
能夠設置的鈎子類型
宏值 | 含義 |
---|---|
WH_MSGFILTER | 截獲用戶與控件交互的消息 |
WH_KEYBOARD | 截獲鍵盤消息 |
WH_GETMESSAGE | 截獲從消息隊列送出的消息 |
WH_CBT | 截獲系統基本消息,激活,建立,銷毀,最小化,最大化,移動,改變尺寸等窗口事件 |
WH_MOUSE | 截獲鼠標消息 |
WH_CALLWNDPROCRET | 截獲目標窗口處理完畢的消息 |
CallNextHookEx
為鈎子鏈中的下一個子程序設置鈎子。在鈎子子程中調用得到控制權的鈎子函數在完成對消息的處理后,如果想要該消息繼續傳遞,那么它必須調用另外一個 SDK中的API函數CallNextHookEx來傳遞它,以執行鈎子鏈表所指的下一個鈎子子程。
WINUSERAPI
LRESULT
WINAPI
CallNextHookEx(
//鈎子句柄,由SetWindowsHookEx()函數返回。
_In_opt_ HHOOK hhk,
//鈎子事件代碼,回調函數的鈎子過程的事件代碼
_In_ int nCode,
//傳給鈎子子程序的wParam值
_In_ WPARAM wParam,
//傳給鈎子子程序的lParam值
_In_ LPARAM lParam);
UnhookWindowsHookEx
卸載鈎子API,鈎子在使用完之后需要用UnhookWindowsHookEx()卸載,否則會造成麻煩。
WINUSERAPI
BOOL
WINAPI
UnhookWindowsHookEx(
//要刪除的鈎子的句柄。這個參數是上一個函數SetWindowsHookEx的返回值.
_In_ HHOOK hhk);
返回值
類型: BOOL
如果函數成功,返回值為非零值。
如果函數失敗,返回值為零。
要獲得更多的錯誤信息,調用GetLastError函數.
GetKeyboardState
256個虛擬鍵的狀態復制到指定的緩沖區。
WINUSERAPI
_Check_return_
BOOL
WINAPI
GetKeyboardState(
//指向一個256字節的數組,數組用於接收每個虛擬鍵的狀態。
_Out_writes_(256) PBYTE lpKeyState);
返回值
若函數調用成功,則返回非0值。若函數調用不成功,則返回值為0。若要獲得更多的錯誤信息,可以調用GetLastError函數。
操作方法
將設置消息鈎子的函數寫在一個DLL中,當鈎住一個GUI線程后,產生消息時,假如系統發現包含鈎子函數的DLL不在本進程中,就會將此DLL強行的加載到對方的進程中。
1、新建一個DLL項目,DllMain函數中將模塊地址保存在一個模塊變量中
dllmain.cpp內容
#include "stdafx.h"
HMODULE g_Module = 0;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_Module = hModule;
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
新建MessageHook.h頭文件,聲明C語法的函數名
MessageHook.h 內容
#pragma once
extern"C" _declspec(dllexport) bool OnHook();
extern"C" _declspec(dllexport) bool UnHook();
新建MessageHook.cpp文件,實現OnHook()與UnHook()函數的用法
#include "stdafx.h"
#include "MessageHook.h"
#include "wchar.h"
#include "stdlib.h"
extern HMODULE g_Module;
HHOOK g_Hook = 0;
//鈎子回調函數
LRESULT CALLBACK KeyboardProc(
_In_ int code,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
// 判斷是否wParam與lParam都有鍵盤消息,是的話則執行打印操作
if (code == HC_ACTION){
// 將256個虛擬鍵的狀態拷貝到指定的緩沖區中,如果成功則繼續
BYTE KeyState[256] = { 0 };
//虛擬鍵盤碼存儲
if (GetKeyboardState(KeyState)) {
// 得到第16–23位,鍵盤虛擬碼
LONG KeyInfo = lParam;
UINT keyCode = (KeyInfo >> 16) & 0x00ff;
WCHAR wKeyCode = 0;
ToAscii((UINT)wParam, keyCode, KeyState, (LPWORD)&wKeyCode, 0);
// 將其打印出來
WCHAR szInfo[512] = { 0 };
swprintf_s(szInfo, _countof(szInfo), L"Hook--鍵盤記錄-->%c", (char)wKeyCode);
//將內容輸出到debug信息中
OutputDebugString(szInfo);
return 0;
}
}
return CallNextHookEx(g_Hook, code, wParam, lParam);
}
bool OnHook()
{
if (g_Hook == 0)
{
g_Hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_Module, 0);
return true;
}
return false;
}
bool UnHook()
{
if (g_Hook!=0)
{
return UnhookWindowsHookEx(g_Hook);
}
return false;
}
2、新建VS控制台項目
調用前一個DLL項目的頭文件MessageHook.h與生成后的lib文件MessageHook.lib
mian.cpp內容
#include "stdafx.h"
#include"..\MessageHook\MessageHook.h"
#pragma comment(lib,"../Debug/MessageHook.lib")
int _tmain(int argc, _TCHAR* argv[])
{
OnHook();
printf("按任意鍵停止");
getchar();
UnHook();
return 0;
}
演示圖:
三、自定義鈎子的使用
鈎子的主要含義其實是改變程序原有的執行流程,讓程序執行我們自己的代碼。我們可以通過修改程序代碼的方式來實現這一點。 還有一種情況是要調用的函數存儲在某一個地方,需要調用這個函數的時候,去相應的位置找到函數地址。
假如們能夠提前修改掉某些位置存儲的函數地址,將其改為我們自己的函數u,那么當調用目標函數的時候,就會調用我們自己的函數。
而代碼修改跳轉地址需要代入一個公式:
- JMP 指令地址換算公式
- 地址偏移 = 目標地址 - JMP所在地址 -5
操作方法
DLL主要是為了截獲exe里的所有調用MessageBox API的按鈕,HOOK后調用的是我們自己的自定義函數
dllmain.cpp內容
// dllmain.cpp : 定義 DLL 應用程序的入口點。
#include "stdafx.h"
//關閉HOOK函數
void OffHook();
//HOOK函數
void OnHook();
char NewCode[5] = {};
char OldCode[5] = {};
//劫持MessageBox,替換的自定義函數
int WINAPI MyMessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType)
{
DWORD dwResault = 0;
lpText = L"你的按鈕已經被Hook";
OffHook();
dwResault = MessageBox(hWnd, lpText, lpCaption, uType);
OnHook();
return dwResault;
}
void OnHook()
{
DWORD dwOld = 0;
//修改一塊虛擬內存的屬性,設置為可寫可執行
VirtualProtect(MessageBoxW, 1, PAGE_EXECUTE_READWRITE, &dwOld);
memcpy(MessageBoxW, NewCode, 5);
VirtualProtect(MessageBoxW, 1, dwOld, &dwOld);
}
void OffHook()
{
DWORD dwOld = 0;
VirtualProtect(MessageBoxW, 1, PAGE_EXECUTE_READWRITE, &dwOld);
memcpy(MessageBoxW, OldCode, 5);
VirtualProtect(MessageBoxW, 1, dwOld, &dwOld);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
//准備基本工作
NewCode[0] = 0xE9; //實際上0xe9就相當於jmp指令
//地址偏移 = 目標地址-JMP所在地址-5
DWORD dwOffset = (DWORD)MyMessageBoxW - (DWORD)MessageBoxW - 5;
//*(DWORD*)(NewCode + 1) = dwOffset;
memcpy(NewCode + 1, &dwOffset, 4);
memcpy(OldCode, MessageBoxW, 5);
//
OnHook();
}
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
//OffHook();
break;
}
return TRUE;
}
演示圖:
使用15PB的tSourceCounter做MessageBox hook測試
參考:
HOOK學習筆記與心得