在Micrisoft Windows中, 每一個進程都有本身的私有地址空間。當咱們用指針來引用內存的時候,指針的值表示的是進程本身的自制空間的一個內存地址。進程不能建立一個指針來引用屬於其餘進程的內存。編程
獨立的地址控件對開發人員和用戶來講都是很是有利的。對開發人員來講,系統更有可能捕獲錯誤的內存讀\寫。對用戶而言, 操做系統變得更加健壯。固然這樣的健壯性也是要付出代價的,由於它使咱們很難編寫可以與其餘進程通訊的應用程序或對其餘進程進行操控的應用程序。windows
在《Windows 核心編程》第二十二章《DLL注入和API攔截》中講解了多種機制,他們能夠將一個DLL注入到另外一個進程地址的空間中。一旦DLL代碼進入另外一個地址空間,那麼咱們就能夠在那個進程中爲所欲爲了。api
本文主要介紹瞭如何實現替換Windows上的API函數, 實現Windows API HOOK。API HOOK的實現方法大概不下五六種。本位主要介紹了其中的一種,即如何使用WIndows掛鉤來注入DLL。函數
這個是安裝狗子的函數聲明以下:工具
HHOOK WINAPI SetWindowsHookEx(操作系統
__in int idHook, \\鉤子類型線程
__in HOOKPROC lpfn, \\回調函數地址指針
__in HINSTANCE hMod, \\實例句柄orm
__in DWORD dwThreadId); \\線程ID,0表示全部繼承
讓咱們經過一個例子來學些下這個API。進程A(一個遊戲改建工具,須要接獲鍵盤消息),安裝了WH_KEYBOARD_LL掛鉤,以下所示:
HOOK hHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hInstDll, 0); 第一個參數表示掛鉤類型, 是一個低級鍵盤鉤子。第二個參數是一個函數地址(在咱們的地址空間內),在窗口即將有鍵盤消息的時候,系統應該調用這個函數。第三個參數hInstDll標識一個DLL, 這個DLL中包含了LowLevelKeyboardProc函數。在windows中, hInstDll的值就是就是DLL被映射到的虛擬內存地址。最後一個參數表示要給哪一個線程安裝鉤子,0表示給全部GUI線程安裝。
如今讓咱們看一看接下來會發生什麼。
1)進程B中的一個線程準備向一個窗口發送一個鍵盤消息
2)系統檢查該線程是否已經安裝了WH_KEYBOARD_LL鉤子。
3)系統檢查LowLevelKeyboardProc所在的dll是否已經被映射到進程B的地址空間中。
4)若是DLL還沒有被映射,那麼系統會強制將該DLL映射到進程B的地址空間中,並將進程B中該DLL的鎖計數器(lock count)遞增。
5)因爲DLL的hInstDll是在進程B中映射的, 所以系統會對它進行檢查,看它與該DLL在進程A中的位置是否相同。若是hInstDll相同,那麼在兩個進程地址空間中,LowLevelKeyboardProc函數位於相同的位置,在這種狀況下, 系統能夠直接在進程A的地址空間中調用LowLevelKeyboardProc。若是hInstDll不一樣,那麼系統必須肯定LowLevelKeyboardProc函數在進程B地址空間中的虛擬地址,這個地址經過下面的公式得出
LowLevelKeyboardProc B = hInstDll B + (LowLevelKeyboardProc A - hInstDll A);
經過把LowLevelKeyboardProc A減去hInstDll A, 咱們獲得LowLevelKeyboardProc函數的偏移量,以字節爲單位, 再把這個偏移量於hInstDll B相加就獲得LowLevelKeyboardProc在B地址空間中位置。
6)系統在進程B中遞增該DLL的鎖計數器(爲什麼再次遞增? 核心編程中這樣寫的,不是很明白)
7)系統在B的地址空間中調用LowLevelKeyboardProc函數
8)當LowLevelKeyboardProc返回的時候,系統減去DLL在進程B中的鎖計數器。
注意, 當系統把掛鉤過濾函數所在的DLL注入或映射到地址空間中時,會映射整個DLL,而不只僅是掛鉤過濾函數。這意味着,DLL內全部函數存在於進程B中,可以爲進程B中全部線程所調用。
此處參考兩個簡單消息鉤子的demo, 一個是相似於改建的鍵盤鉤子demo,這個demo展現瞭如何簡單的作一個遊戲改建; 一個是在MFC中使用鉤子勾取菜單的相關消息, MFC的菜單不繼承CWnd, 因此菜單相關的建立、銷燬、繪製等消息咱們都捕獲不到,因此重繪的時候要改變菜單的一些屬性就須要用到鉤子,。
api hook並非什麼特別不一樣的hook,它也須要經過基本的hook提升本身的權限,跨越不一樣進程間訪問的限制,達到修改api函數地址的目的。對於自身進程空間下使用到的api函數地址的修改,是不須要用到api hook技術就能夠實現的。
咱們知道,系統函數都是以DLL封裝起來的,應用程序應用到系統函數時,應首先把該DLL加載到當前的進程空間中,調用的系統函數的入口地址,能夠經過 GetProcAddress函數進行獲取。當系統函數進行調用的時候,首先把所必要的信息保存下來(包括參數和返回地址,等一些別的信息),而後就跳轉到函數的入口地址,繼續執行。其實函數地址,就是系統函數「可執行代碼」的開始地址。那麼怎麼才能讓函數首先執行咱們的函數呢?實際上就是把開始的那段可執行代碼替換爲咱們本身定製的一小段可執行代碼,這樣系統函數調用時,不就按咱們的意圖乖乖行事了嗎? 簡單的說,就能夠修改系統函數入口的地方,讓他調轉到咱們的函數的入口點就好了。採用彙編代碼就能簡單的實現Jmp XXXX, 其中XXXX就是要跳轉的相對地址。而Jmp後面要求的是相對偏移,也就是咱們的函數入口地址到系統函數入口地址之間的差別,再減去咱們這條指令的大小。用公式表達以下:
(1)int nDelta = UserFunAddr – SysFunAddr - (咱們定製的這條指令的大小);
(2)Jmp nDleta;
爲了保持原程序的健壯性,咱們的函數裏作完必要的處理後,要回調原來的系統函數,而後返回。 不然會發生本身調用本身的死循環。
那麼說下程序執行過程。
1) 咱們的dll「注射」入被hook的進程 (這一步只須要安裝一個鉤子就能實現)
2)保存系統函數入口處的代碼
3)替換掉進程中的系統函數入口指向咱們的函數
4)當系統函數被調用,當即跳轉到咱們的函數
5)咱們函數處理
6)恢復系統函數入口的代碼
7)調用原來的系統函數
8)再修改系統函數入口指向咱們的函數(爲了下次hook)-> 返回
來看咱們HooK自定義的Add函數、
首先咱們建立一個AddFunc的dll工程, 這個dll只有一個導出函數:
int WINAPI add(int a, int b);
這個add函數就是咱們稍後須要攔截的函數。有了dll後咱們就能能夠直接新建一個MFC工程調用Add函數 主要代碼以下:
// HOOK 個人Add方法
voidCHookDemoDlg::OnBnClickedButton7()
{
//函數原型定義
typedefint(WINAPI*AddProc)(inta,intb);
AddProcadd;
staticHINSTANCEs_instadd=NULL;
s_instadd=LoadLibrary(s_path+_T("\\AddFunc.dll"));//加載dll文件
if(s_instadd==NULL)
{
AfxMessageBox(_T("no AddFunc.dll!"));
return;
}
add= (AddProc)::GetProcAddress(s_instadd,"add");//獲取函數地址
intnRet=add(1,1);
CStringcstr;
cstr.Format(_T("%d + %d = %d"),1,1,nRet);
::MessageBoxW(NULL,cstr,NULL,MB_OK);
}
接下來, 咱們來進行HOOK即便Hook咱們AddFunc.dll中的add函數。新建一個win32的Dll工程HookDll。首先在頭部聲明以下變量:
//全局共享變量
#pragmadata_seg("MySec")
staticHINSTANCEg_hInstance=NULL;
staticHHOOKg_hook=NULL;
#pragmadata_seg()
#pragmacomment(linker,"/section:MySec,rws")
這兩個變量表示可以在全部調用該dll的進程中共享。若是不加#pragma data_seg()來聲明,g_hInstance 和g_hook將會在每一個進程空間中都有一份獨立的數據。編寫鼠標鉤子的安裝卸載函數,注意兩個函數導出。
//鼠標鉤子過程,什麼也不作,目錄是注入dll到程序中
LRESULTCALLBACKMouseProc(intnCode,WPARAMwParam,LPARAMlParam)
{
returnCallNextHookEx(hhk,nCode,wParam,lParam);
}
//鼠標鉤子安裝函數:
BOOLInstallHook()
{
hhk=::SetWindowsHookEx(WH_MOUSE,MouseProc,g_hInstance,0);
returnhhk!=NULL;
}
BOOLUninstallHook()
{
if(hhk!=NULL)
{
::UnhookWindowsHookEx(hhk);
hhk=NULL;
}
//HookMessageBoxW::HookOff();
//HookAddFuc::HookOff();
HookTextOutW::HookOff();
returnTRUE;
}
在DLL的入口處DLL_PROCESS_ATTACH添加初始化變臉和進行注入。
BOOLAPIENTRYDllMain(HMODULEhModule,
DWORD ul_reason_for_call,
LPVOIDlpReserved
)
{
g_hInstance=(HINSTANCE)hModule;
switch(ul_reason_for_call)
{
caseDLL_PROCESS_ATTACH:
{
DWORDdwPid=::GetCurrentProcessId();
HANDLEhProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);
//HookAddFuc::Inject(hProcess);
break;
}
caseDLL_THREAD_ATTACH:
caseDLL_THREAD_DETACH:
caseDLL_PROCESS_DETACH:
{
//HookAddFuc::HookOff();
break;
}
}
returnTRUE;
}
編寫HookAddFuc::Inject(hProcess)注入函數
voidHookAddFuc::Inject(HANDLEh)
{
hProcess=h;
if(!bInjectedAdd)
{
bInjectedAdd=true;
//獲取add.dll中的add()函數
HMODULEhmod=::LoadLibrary(s_path);
add=(AddProc)::GetProcAddress(hmod,"add");
pfadd=(FARPROC)add;
if(pfadd==NULL)
{
MessageBoxW(NULL,L"cannot locate add()",NULL,MB_OK);;
}
// 將add()中的入口代碼保存入OldCode[]
_asm
{
leaedi,OldCode
movesi,pfadd
cld
movsd
movsb
}
NewCode[0]=0xe9;//實際上0xe9就至關於jmp指令
//獲取Myadd()的相對地址
_asm
{
leaeax,Myadd
movebx,pfadd
subeax,ebx
subeax,5
movdwordptr[NewCode+1],eax
}
//填充完畢,如今NewCode[]裏的指令至關於Jmp Myadd
HookOn();//能夠開啓鉤子了
}
}
//恢復函數地址
voidHookAddFuc::HookOff()
{
DWORDdwTemp=0;
DWORDdwOldProtect;
VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect);
WriteProcessMemory(hProcess,pfadd,OldCode,5,0);
VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp);
}
//修改函數地址
voidHookAddFuc::HookOn()
{
DWORDdwTemp=0;
DWORDdwOldProtect;
//將內存保護模式改成可寫,老模式保存入dwOldProtect
VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect);
//將所屬進程中add()的前5個字節改成Jmp Myadd
WriteProcessMemory(hProcess,pfadd,NewCode,5,0);
//將內存保護模式改回爲dwOldProtect
VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp);
}
//而後,寫咱們本身的Myadd()函數
intWINAPIMyadd(inta,intb)
{
//截獲了對add()的調用,咱們給a,b都加1
a=a+1;
b=b+1;
HookAddFuc::HookOff();//關掉Myadd()鉤子防止死循環
intret;
ret=add(a,b);
HookAddFuc::HookOn();//開啓Myadd()鉤子
returnret;
}