HOOK相關原理與例子

消息HOOKshell

原理:數組

1. 用戶輸入消息,消息被放到系統消息隊列。安全

2. 程序發生了某些須要獲取輸入的事件,就從系統消息隊列拿出消息放到程序消息隊列中。函數

3. 應用程序檢測到有新的消息進入到程序消息隊列中後,調用相應的事件去處理該消息。工具

因此在系統消息隊列與程序消息隊列的中間安裝hook,便可獲取消息隊列中的信息。編碼

安裝:spa

SetWindowsHookEx(鍵盤消息(WH_xxx),Hook函數(處理鍵盤輸入的函數),句柄(hook函數所在的DLL的句柄),線程ID(要hook的線程ID,0爲全部線程))線程

API在簡單高效的同時也有一個弊端,就是它只能監視較少的消息,如:擊鍵消息、鼠標移動消息、窗口消息。指針

 

SEH(調試)HOOK調試

原理:與調試器工做方式相似,讓進程發生異常,而後本身捕獲到異常,對於除於被調試狀態下的級進行操做。

1. 正常狀況下,進程未被其餘進程調試時,當進程發生異常事件,系統將捕獲該事件,並進行事件處理。

2. 當進程被其餘進程調試時,處理該進程的異常事件的工做則交給了調試進程。(調試進程未處理或不關心的調試事件由系統處理)

3. 調試HOOK的核心思路就是將API的第一個字節修改成0xCC(INT 3,留給調試工具的中斷,調試工具運行完後,會將下一條指令手動替換回原先的代碼),當API被調用時,因爲觸發了異常,控制權就被轉交給調試器(調試進程)。

利用調試技術來HOOK API函數的相關步驟以下

· 1對想要鉤取的進程進行附加操做,使之成爲被調試者。

· 2將要鉤取的API的起始地址的第一個字節修改成0xcc(或者使用硬件斷點)

· 3當調用目標API的時候,控制權就轉移到調試器進程。

· 4執行須要的操做。

· 5脫鉤,將API 函數的第一個字節恢復。

· 6運行相應的API

 

注入HOOK

原理:Hook的核心思想就是修改API的代碼,使用DLL注入技術,咱們將Hook的代碼寫入一個DLL(或直接一個shellcode),將此DLL注入到目標進程中,此時由於DLL在目標進程的內存中,因此就有權限直接修改目標進程內存中的代碼了。

shellcode:填充數據,利用軟件漏洞而執行的代碼,可在暫存器eip溢出後,塞入一段可以讓CPU執行的shellcode機器碼,讓電腦能夠執行攻擊者的任意指令。

API HOOK:又分爲IAT HOOK和inline HOOK,都是改變函數以達到跳轉到本身的HOOK API的目的。但又有差異。

1、

1.IAT HOOK:經過修改IAT(導入表)的函數地址,實現對API進行HOOK。在把原函數替換成目標函數,並在目標函數執行完後,必需要調用回原函數(HOOK前應保存原函數地址),這樣才能保證功能的完整性。

· 1.計算出導入表的位置

· 2.在導入表中找到原函數的位置(即將被執行的函數),保存該函數的地址

· 3.將原函數地址改成目標函數,運行完目標函數後,調用回原函數。

注:該操做會將程序中全部調用被hook的函數變爲調用hook後的函數,可加判斷,若是爲本身調用操做,則進行hook函數的處理,若是是系統調用,直接將數據調用給原函數去處理

 

2.inline HOOK:直接修改內存中任意函數的代碼,將其劫持至Hook API。同時,它比IAT Hook的適用範圍更廣,由於只要是內存中有的函數它都能Hook,然後者只能Hook IAT表裏存在的函數(有些程序會動態加載函數)。

inline HOOK的目標是系統函數,直接修改函數的前5字節,改成jmp目標函數地址,執行完後進行unhook(脫鉤)操做,以便將原函數恢復。Hook的目的是當調用某個函數時,咱們能劫持進程的執行流。如今咱們已經劫持了進程的執行流,即可以恢復原函數代碼,以便咱們的惡意代碼能夠正常調用。

· 1.獲取原API地址,保存起來(方便後續還原)

· 2.修改內存屬性爲RWX(即read(編號4)write(2)execute執行(1)

· 3.備份原代碼(同1

· 4.實時計算JMP的相對偏移

· 5.最後修改API5字節的代碼(跳轉到目標函數地址)

· 6.恢復內存屬性。

注:

自編inline hook:從hook的位置跳到本身的函數中執行想要的動做,但須要將原指令再執行一遍,再跳轉回到被hook的位置+指令長度的位置。以達成棧平衡的目的。

若是hook的位置爲跳轉指令,則須要將該指令所跳轉的目標地址給計算並保存起來(普通跳轉指令(非FF15 FF25 push時),跳轉的爲偏移量,一旦hook了偏移量就再也不適用了),並在本身的函數中跳轉到目標地址。

mhook庫:編寫一個參數 返回類型與被HOOK函數同樣的新函數,hook後,代替原函數被調用,注意該hook只能hook一整個函數,而且會將因此調用原函數的行爲用於調用新函數,所以應該加個判斷條件,判斷是系統進行調用仍是咱們要hook的程序進行調用。

2、

HotFix HOOK

原理:Code Hook存在一個效率的問題,由於每次Code Hook都要進行「掛鉤+脫鉤」的操做(對API的前5字節修改兩次),當要進行全局Hook的時候,系統運行效率會受影響。並且,當一個線程嘗試運行某段代碼時,若另外一個線程正在對該段代碼進行「寫」操做,會程序衝突,最終引起一些錯誤。

API的起始代碼上都有這樣的特色,5個NOP(空)指令,1個「MOV EDI,EDI」(佔2字節),這7字節的指令實際沒有任何意義,所以能夠經過修改這7字節來實現HOOK操做,這種方法可使得進程處於運行狀態時臨時更改進程內存中的庫文件,所以被稱爲打「熱補丁」。

在上述5字節代碼修改技術中,unhook(脫鉤)是爲了調用原函數,但使用HotFix HOOK API時,在API代碼被修改的狀態下仍然可以正常的調用原API(從[原API起始地址+2]開始,仍能正常調用原API,且 執行動做一致)。

· 1.將內存屬性修改成RWX

· 2.計算HOOK函數與被HOOK函數之間的地址偏移

· 3.將JMP [獲得的結果]寫入原函數-5的位置(即5NOP

· 4.再將JMP-7寫到原函數的位置(MOV EDI,EDI)

· 5.恢復內存屬性。

因爲HotFix Hook須要修改7個字節的代碼,因此並非全部API都適用這種方法,若不適用,請使用5字節代碼修改技術。

 

SSDT HOOK

原理:SSDT Hook屬於內核層Hook,也是最底層的Hook。因爲用戶層的API最後實質也是調用內核API(Kernel32->Ntdll->Ntoskrnl),因此該Hook方法最爲強大。

SSDT(System Service Descriptor Table):系統服務描述符表

定義類型變量:

DD(Define Dword):雙字類型,一個雙字數據佔4字節。DW:字類型,佔2字節。DB:字節類型,佔1字節

內核經過SSDT調用各類內核函數,SSDT就是一個函數表,只要獲得一個索引值,就能根據這個索引值在該表中獲得想要的函數地址。

SSDT所在地址後面的第一個32位數據即爲SSDT的基地址,跳到基地址後,第一個位32位數據即爲SSDT表中第一個函數的地址,對該地址反彙編後,就能獲得該函數相關的信息(包括該函數的索引號)。

例:要找AABBCC函數的地址,先對該函數進行反彙編u nt!ZwAABBCC,獲得它的索引號爲0x12;那麼它的地址爲:基地址+0x12 對其反彙編後,便可獲得該函數的詳細信息。

· 1.修改內存屬性爲RWX(即read(編號4)write(2)execute執行(1)

· 2.實時計算JMP的相對偏移

· 3.備份原代碼頭5字節(同1

· 4.將頭5字節替換成2.的彙編碼

· 5.運行完後還原頭5字節

· 6.恢復內存屬性。

 

 

 

 

 

 

例子及博主自身理解:

API HOOK——IAT HOOK實例

技巧:

經過模塊句柄,獲得PE頭:(PBYTE)PE=(PBYTE)進程句柄

經過PE頭,得到dos頭:PIMAGE_DOS_HEADER  dos=(PIMAGE_DOS_HEADER)PE

經過PE頭和dos頭,得到NT PIMAGE_NT_HEADERS NT=(PIMAGE_NT_HEADERS)(PE+ dos->e_lfanew)

經過NT頭的結構成員的數組成員,得到導入表信息:

IMAGE_DATA_DIRECTORY 數據表=NT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]

導入表的尺寸=數據表.size 起始地址(基址)=數據表.VirtualAddress

 獲取函數的地址(DWORD),GetProcAddress(GetModuleHandleA(DllName),ProcName

經過PE頭+導入表基址定位到導入表:PIMAGE_IMPORT_DESCRIPTOR 導入表 = (PIMAGE_IMPORT_DESCRIPTOR)(PE + 導入表基址);

經過對比DLL名 循環查找每一個導入表,找到DLL所在的結構體

 strcmp((char*)DLL名,(char*)(PE+導入表-name))找到目標DLL所在結構體; PIMAGE_THUNK_DATA 結構體= (PIMAGE_THUNK_DATA)(PE + 導入表->FirstThunk);

經過對比函數地址,循環查找每一個結構體的u1.Function成員,找到目標函數結構體->u1.Function == (DWORD)要被替換的函數地址

!!切記,應在32位下的Release版本生成dll,不然替換新的函數地址處會出錯

找到目標函數位置後,替換成新的函數的地址,pthunk->u1.Function = NewFuncAddress;

而後將位置(這位置保存的是地址)保存起來,方便過後還原。(保存至全局變量)

DWORD g_dwIatAddr = (DWORD)&pthunk->u1.Function;(<-保存一個指針指向的變量所保存的地址)

過後還原:DWORD * pdwAddr = (DWORD*)g_dwIatAddr;//把舊地址所在的位置傳遞給一個指針

*pdwAddr = oldFuncAddress; //將舊地址放入該位置 達到還原

 

注意,每次更改導入表的函數地址時,都應該調用VirtualProtect來修改保護屬性

VirtualProtect((LPVOID)&pthunk->u1.Function, 4, PAGE_EXECUTE_READWRITE, &oldprotect);

 

 

經過REG注入DLL (即經過註冊表HOOK)

平時用程序來進行DLL注入時,須要被注入程序在運行起來以後才能進行注入,在某些初始化階段就想經過HOOK來控制程序時,達不到效果。此時,咱們能夠經過註冊表表進行注入,被注入的程序在運行前就已經被HOOK。注:被注入的進程與DLL必須同爲32/64位。

REG注入原理:利用在Windows 系統中,當REG如下鍵值中存在有DLL文件路徑時,會跟隨EXE文件的啓動加載這個DLL文件路徑中的DLL文件。當若是遇到有多個DLL文件時,須要用逗號或者空格隔開多個DLL文件的路徑。

1.經過WIN+R運行regedit打開註冊表

2.分別打開:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows

把路徑下的AppInit_DLLs改成將要注入的DLL路徑;

3.並將LoadAppInit_DLLs註冊表項的值修改成1:(64位改2個 32位改1個)

注:注入表注入的DLL由mHOOK生成,由於注入表HOOK會給全部進程HOOK

因此要加入限制條件:獲取當前進程名,當進程名=目標進程名時,往下執行HOOK的操做

 

自寫inline hook

 

1.自定義一個全局變量 讓hook只運行一次

2.經過進程IDGetProcessId(HANDLE(-1))獲得當前進程ID 和程序名(自定義),得到目標進程的模塊入口地址(即HOOK進程的基地址)(要HOOK的地方屬於哪一個模塊,就獲取哪一個模塊的句柄)

2/1.直接GetModuleHandle("進程名");得到模塊入口地址或者經過下面4個步驟

2-1.根據進程ID得到進程快照,該快照包含全部包含該進程ID的模塊 CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid); 

2-2.經過該快照,得到與該進程相關的第一個模塊的信息 Module32First(2-1返回的句柄, &存放模塊信息的結構)

2-3.經過對比,找到目標模塊srcmp(存放模塊信息的結構.szModule, 進程名(即1的程序名)) == 0) ,若是不匹配,Module32Nex(2-1返回的句柄, &存放模塊信息的結構)查看下一個。(進程名可經過任務管理器-詳細信息查看)

2-4.找到目標模塊,返回模塊句柄存放模塊信息的結構.hModule

 

3.獲得要替換的原指令的絕對偏移位置RVA=VA-IB(要被替換的第一條指令—模塊入口地址) (可經過MDebug查看,如要HOOK的爲自定義函數,則該模塊名稱爲進程名.exe,若是要HOOK的爲系統函數,則模塊名爲該系統函數所在的DLL)(找到合適的HOOK位置是逆向的關鍵)

4.獲得當前要hook的指令所在位置。原指令的絕對偏移地址(3獲得的)+基地址(不定,每次加載時都容易改變,因此要經過2來求得)

5.保存等下要返回的地址 返回地址= 當前要hook的指令所在位置+hook指令的長度(5字節或以上,替換掉了多長就加多長)

6.保存原始指令內容UCHAR szOldInstruct[指令長度]={指令內容},指令內容爲從左到右從上到下保存。 如不還原,則可不保存

7.計算跳轉位置。DWORD 跳轉位置 = (DWORD)本身編寫函數(彙編編寫)- 要hook的指令的起始位置- 指令所佔長度;這樣跳轉後即能略過本來指令,執行自定義指令(遠跳轉指令e9佔5字節,近跳轉EB佔2字節,跳轉的位置爲偏移量)

偏移量=目標地址 - 指令地址 - 指令長度

8.編寫跳轉指令UCHAR 跳轉指令[5] = { 0xe9, 0, 0, 0, 0 };e9爲跳轉指令jmp),若是指令長於5字節,則在地址後填充nop空指令(0x90)

也可定死爲5字節,由於跳轉指令由e9+4字節地址組成,而返回地址自動跳過了該指令的位置,因此只改前面5字節數據亦可

memcpy(跳轉指令 + 1(e9佔1位), &(3獲得的), sizeof(3獲得的));

9.修改要hook的指令所在位置的保護屬性 VirtualProtect((LPVOID)(4獲得的),, 5, PAGE_EXECUTE_READWRITE, &保存舊屬性的變量);

10.將要hook的指令替換成要跳轉的指令 memcpy((LPVOID)(4獲得的), (8獲得的), sizeof((8獲得的)));

11.還原指令所在位置的保護屬性 此時inline hook完成

 

注:跳轉指令所指的函數必須爲彙編指令編寫

 

 

經過上面例子生成DLL後,經過注入HOOK實例

在生成完DLL後(具體參照前面例子),若有.lib文件 需拉到同一目錄並加載

 

_T在項目定義了UNICODE時,等價於L,即(_T"123")同理(L"123"); 不然就是多字節。

在數組中 TCHAR若是定義了寬字節UNICODE TCHAR就爲wchar_t 不然爲char

DLL所在地址可用UnICODE,方便處理漢字這種雙字節字符。

 

1.搜索指定類名或窗口名(窗口標題)findwindow(類名,窗口名);,獲得窗口句柄。

2.GetWindowThreadProcessId(窗口句柄,&保存ID的變量);獲得該進程的ID。

3.OpenProcess(PROCESS_ALL_ACCESS,FALSE,進程ID);獲得該進程的句柄

4.VirtualAllocEx(進程句柄,內存地址,內存大小,分配方式(MEM_COMMIT),權限(PAGE_READWRITE));在該進程中申請一個內存空間(用於打開DLL)

5.將DLL路徑寫入對方進程中

WriteProcessMemory (進程句柄(3),內存位置(4),DLL所在地址(絕對路徑),路徑所佔空間,&寫入字節數)

注意,此時的路徑含有漢字,用的爲寬字符聲明:L"c\\xxx",因此可用wcslen( )求得長度,長度*2+2(結束符) 即獲得寬字符路徑所佔空間

6.建立遠程線程,讓目標進程調用LoadLibrary來打開注入的DLL

CreateRemoteThread(進程句柄,安全屬性(NULL),線程大小(0,即默認),目標線程調用的函數名(函數名即函數起始位置),

傳遞給函數的參數(此處爲4申請的空間,該空間存放着DLL的路徑),線程建立標誌(NULL),線程ID的指針(不須要保存則爲NULL) )

這裏傳遞的函數名爲LoadLibrary 用來加載DLL

7.檢測句柄的信號狀態WaitForSingleObject (線程句柄(6),等待時間(-1無限));當參1現場爲有信號狀態,或者到了參2時間,該函數返回。不然掛起

8.釋放目標進程的空間VirtualFreeEx(進程句柄(3),空間首地址(4),空間大小(與申請時一致),釋放類型(MEM_DECOMMIT));

注意:可直接跳到步驟3開始執行 進程ID從任務管理器-詳細信息-目標進程的PID中得到

 

 

 

小技巧:若是想實現嵌套hook 應該保存被hook的函數指令,而後在hook的函數裏面 用函數再次實現該指令,若是爲jmp指令,則需得到跳轉指令後接的4字節偏移地址 :由於跳轉指令後接的爲偏移量,而hook位置的地址爲該指令所在的地址,並不是偏移量

具體操做以下:1.memcpy(g_szOldInstruct, (char*)(HOOK的位置), 5); //將hook位置的指令保存下來(指令多長就複製多長 若是是jmp的話通常都是5字節E9 或2字節EB)

2.lea eax, dword ptr[g_szOldInstruct]; //取出這行指令的值(爲E9 XXXX XXXX)

3 add eax, 1; mov eax, dword ptr[eax]; //+1爲跳過e9,而後取4字節則爲取出跳轉的偏移地址

獲得了偏移地址 就能夠求得被hook的指令所要實現的跳轉位置 跳轉的目標地址=偏移地址+跳轉指令長度+跳轉指令所在的位置(即被hook前 這行指令所在的位置)

 

調試DLL是否執行成功,可在生成DLL的函數的代碼段添加__asm int 3;來進行調試

在下斷點後,若是運行起來一直會回到斷點處,說明系統在該程序運行時會一直調用斷點處的函數,此時應在hook函數前加判斷條件,若是是本身操做的HOOK下來,若是是系統調用的,就調用原函數

 

相關文章
相關標籤/搜索