全局鈎子注入
Windows中大部分的應用程序都是基於消息機制的,它們都有一個消息過程函數比如SetWindowsHookEx
函數
,根據不同的消息完成不同的功能。
消息鈎子是windows提供的一種消息過濾和預處理機制,可以用來截獲和監視系統中的消息。
按照鈎子作用范圍不同,又可以分為局部鈎子和全局鈎子。局部鈎子是針對某個線程的,而全局鈎子是作用於整個系統的基於消息的應用。全局鈎子需要使用DLL文件,在DLL文件中實現相應的鈎子函數。
核心函數介紹
HHOOK WINAPI SetWindowsHookExW(
_In_ int idHook,
_In_ HOOKPROC lpfn,
_In_opt_ HINSTANCE hmod,
_In_ DWORD dwThreadId);
1.idHook
要安裝的鈎子(HOOK)的類型,它決定了HOOKPROC
被調用的時機,可選參數如下。
WH_MSGFILTER = -1 |
線程級,截獲用戶與控件交互的消息 |
---|---|
WH_JOURNALRECORD = 0 |
系統級,記錄所有消息隊列送出的輸入消息 |
WH_JOURNALPLAYBACK = 1 |
系統級,回放由WH_JOURNALRECORD記錄的消息 |
WH_KEYBOARD = 2 |
系統級或線程級,截獲鍵盤消息 |
WH_GETMESSAGE = 3 |
系統級或線程級,截獲從消息隊列送出的消息 |
WH_CALLWNDPROC = 4 |
系統級或線程級,截獲發送到目標窗口的消息 |
WH_CBT = 5 |
系統級或線程級,截獲系統基本消息 例如:窗口的創建,激活,關閉,最大/最小化,移動等 |
WH_SYSMSGFILTER = 6 |
系統級,截獲系統范圍內用戶與控件交互的消息 |
WH_MOUSE = 7 |
系統級或線程級,截獲鼠標消息 |
WH_HARDWARE = 8 |
系統級或線程級,截獲非標准硬件(非鼠標,鍵盤)的消息 |
WH_DEBUG = 9 |
系統級或線程級,在其它鈎子調用前調用,用於調試鈎子 |
WH_SHELL = 10 |
系統級或線程級,截獲發給外殼應用程序的消息 |
WH_FOREGROUNDIDLE = 11 |
系統級或線程級,在程序前台線程空閑時調用 |
WH_CALLWNDPROCRET = 12 |
系統級或線程級,截獲目標窗口處理完的消息 在SendMessage被調用后發生 |
WH_KEYBOARD_LL = 13 |
系統級,截獲全局鍵盤消息 |
WH_MOUSE_LL = 14 |
系統級,截獲全局鼠標消息 |
2.lpfn
指向鈎子回調函數的指針。如果最后一個參數dwThreadId
為0或者是其它進程創建的線程標識符,則lpfn
參數必須指向DLL中的鈎子回調函數,即HOOKPROC
函數必須在DLL中實現。否則,lpfn
可以指向與當前進程相關聯的代碼中的鈎子過程。
3.hmod
包含由lpfn
參數指向的鈎子回調函數的DLL
句柄。如果dwThreadId參數指定由當前進程創建線程,並且鈎子回調函數位於當前進程關聯的代碼中,則hmod
參數必須設置為NULL
。
4.dwThreadId
與鈎子程序關聯的線程標識符(指定要HOOK
的線程 ID)。如果此參數為0,則鈎子過程與系統中所有線程相關聯,即全局消息鈎子
實現原理
通過SetWindowsHookExW
函數安裝一個用於過濾特定類型消息的鈎子函數(鈎子WH_GETMESSAGE
可以立即被觸發,而某些類型的鈎子需要在處理指定類型的消息時才能被觸發),當其它進程中產生了我們過濾的消息,就會調用HOOKPROC
,如果發現目標DLL
(HOOKPROC
實現的DLL
)尚未加載,就會使用KeUserModeCallback
函數回調User32.dll
的__ClientLoadLibrary()
函數,由User32.dll
把這個DLL
加載到目標進程中(低級鍵盤和鼠標鈎子的加載有所不同),從而實現DLL
注入其它進程的目的。
代碼及其實現過程
首先新建一個dll項目
在pch.h
中聲明這幾個我們定義的函數都是裸函數,由我們自己平衡堆棧
extern "C" _declspec(dllexport) int SetHook();
extern "C" _declspec(dllexport) LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam);
extern "C" _declspec(dllexport) BOOL UnsetHook();
在pch.cpp
里面寫入三個函數並創建共享內存(進程通信的方法有很多,比如自定義消息、管道、dll共享節、共享內存等等,這里就用共享內存來實現進程通信)
#include "pch.h"
#include <windows.h>
#include <stdio.h>
extern HMODULE g_hDllModule;
// 共享內存
#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")
//鈎子回調函數
LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam) {
return ::CallNextHookEx(g_hHook, code, wParam, lParam);
}
// 設置鈎子
BOOL SetHook() {
g_hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);
if (NULL == g_hHook) {
return FALSE;
}
return TRUE;
}
//這里第二個參數是回調函數,那么我們還需要寫一個回調函數的實現,這里就需要用到CallNextHookEx這個api,主要是第一個參數,這里傳入鈎子的句柄的話,就會把當前鈎子傳遞給下一個鈎子,若參數傳入0則對鈎子進行攔截
// 卸載鈎子
BOOL UnsetHook() {
if (g_hHook) {
UnhookWindowsHookEx(g_hHook);//UnhookWindowsHookEx這個api是來卸載鈎子的
}
return TRUE;
}
在dllmain.cpp
設置DLL_PROCESS_ATTACH
,然后編譯生成Dll1.dll
。
// dllmain.cpp : 定義 DLL 應用程序的入口點。
#include "pch.h"
HMODULE g_hDllModule = NULL;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
g_hDllModule = hModule;
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
之后再創建一個控制台項目
用LoadLibrabryW
加載dll,生成inject2.cpp
文件
// inject2.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
//
#include <iostream>
#include <Windows.h>
int main()
{
typedef BOOL(*typedef_SetGlobalHook)();
typedef BOOL(*typedef_UnsetGlobalHook)();
HMODULE hDll = NULL;
typedef_SetGlobalHook SetGlobalHook = NULL;
typedef_UnsetGlobalHook UnsetGlobalHook = NULL;
BOOL bRet = FALSE;
do
{
hDll = ::LoadLibraryW(TEXT("D:\\vs2017project\\Dll1\\Debug\\Dll1.dll"));
if (NULL == hDll)
{
printf("LoadLibrary Error[%d]\n", ::GetLastError());
break;
}
SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetHook");
if (NULL == SetGlobalHook)
{
printf("GetProcAddress Error[%d]\n", ::GetLastError());
break;
}
bRet = SetGlobalHook();
if (bRet)
{
printf("SetGlobalHook OK.\n");
}
else
{
printf("SetGlobalHook ERROR.\n");
}
system("pause");
UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetHook");
if (NULL == UnsetGlobalHook)
{
printf("GetProcAddress Error[%d]\n", ::GetLastError());
break;
}
UnsetGlobalHook();
printf("UnsetGlobalHook OK.\n");
} while (FALSE);
system("pause");
return 0;
}
執行測試
編譯inject2.cpp生成inject2.exe並運行
打開進程工具,搜索Dll1.dll是否加載,發現在大多數進程中都得到了加載
嘗試卸載鈎子
進程中的Dll1.dll已經被卸載