【windows核心編程】系統消息與自定義鈎子(Hook)使用


一、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學習筆記與心得

http://bbs.pediy.com/thread-193729.htm


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM