Hook技術之API攔截(API Hook)
一.關於API Hook
API Hook可以通過Hook指定的訊息和API來實現程序隱藏,檔案隱藏,埠隱藏等。
通過攔截程序API來隱藏程序,通過攔截檔案讀寫API來隱藏檔案,通過攔截網路相關API可以隱藏埠。如果這些Hook都針對的是使用者模式下的API,那麼這就屬於核心Hook;如果這些Hook針對的是核心模式下的Hook,這就屬於是使用者Hook。
舉個例子:如果現在我想隱藏程序,通常情況,使用者態程序使用CreateToolhelp32Snapshot等API獲取程序列表,那麼我就Hook這個列舉相關程序的API函式,那麼當呼叫這些API函式的時候,系統會直接通知我們自己的Hook函式而不是這些列舉程序函式,也就是這些函式被攔截了。當執行我們自己的
當然了這只是使用者態下其中一種通過API Hook實現的程序隱藏方式。一般情況下,在使用者態實現程序監控最終都會呼叫函式ZwQuerySystemInformation。因此,在系統上所有程序中掛鉤模組Ntdll.dll中的該函式就能實現程序隱藏。這種方法不再依賴預先對目標程序所使用的API函式資訊的獲取,適應性更強。它幾乎是使用者態上最有效的程序隱藏方式。
API HOOK 顧名思義是掛鉤API函式,攔截,控制某些API函式的呼叫,用於改變API執行結果的技術。需要注意的是API HOOK技術與windows
Hook 技術按照實現原理來分的話可以分為兩種:API HOOK和Message HOOK 。
按照作用範圍來分可以分為全域性HOOk和區域性HOOK
API HOOK按照許可權來分可以分為核心鉤子和使用者鉤子,分別作用在Ring 0和Ring 3 。
當一個API函式被攔截後我們可以讓目標程式執行我們事先準備好的程式碼,hook API後,執行API函式的流程就變了,也就是我們構造的程式碼替換了原來的API函式,最後是否要呼叫原來
API HOOk技術在木馬編寫中用的非常廣泛,因為它可是很好地實現木馬的隱藏性。例如我們Hook住API函式CreateProcess,改變它執行的流程,那麼我們建立的程式可以不在工作管理員中出現,較好地實現了隱藏。
二.API Hook 的實現方式
根據實現的方法可以分為:Inline Hook (內聯Hook),IAT Hook(匯入表Hook),替換Windows訊息處理函式實現Hook等等。
(1)Inline Hook
程式在編譯連結後成了二進位制程式碼,我們可以找到需要Hook的函式的地址,然後把這個函式在記憶體中的二進位制程式碼改為一個JMP指令,令其跳轉到執行我們自己構造的函式。
貌似有點難以理解,來看看詳細的原理解釋:
函式一般都存在於DLL中,當DLL中某個函式被呼叫後,其所在的DLL將會被對映到程序地址空間中。我們可以通過DLL這個模組找到我們需要Hook的函式的地址。然後在記憶體中改變其地址,使跳轉到我們制定的位置。
如果還不理解,那麼來看看一個例項的分析:
我們現在需要Hook 函式CreateFile,這個函式存在於Kernel32.DLL檔案中。首先我們必須要知道這個函式在程序中的地址,然後修改這個函式的首地址為JMP MyProc指令。
而MyProc函式可以是API函式,也可以是我們自己構造的函式,如果是我們自己構造的函式,那麼我們有兩種方法把我們的函式注入進目標程序,那就是通過遠端執行緒注入的兩種方法。
(2)IAT Hook
這種Hook技術是通過分析目標程式PE結構,替換目標API在IAT中的地址為鉤子函式的地址來實現。
要了解這種Hook技術,首先需要知道IAT和PE檔案結構。
PE檔案時Windows下可執行檔案和DLL等檔案的一種規範。實際上,exe檔案在磁碟中的對映就是PE格式,可以通過PE工具檢視exe檔案來進行研究。
當程式被載入的時候,windows載入函式會定位所有的匯入資料和程式碼,則樣的實際做法就是將DLL檔案對映到程序的地址空間中,實際上這個對映的過程就是通過PE檔案的頭部資訊來實施的,因為PE檔案頭部中儲存了所有需要匯入的DLL的模組名稱以及匯入函式。
實際上IAT儲存了程序中所有的匯入的DLL和其對應的匯入函式的資訊,要找到某個匯入函式的地址,那麼必須要定位到匯入表。因為匯入表額儲存位置就在PE檔案頭中,所以瞭解PE檔案死非常有必要的。
PE檔案的真正頭部緊接Dos頭,在Dos頭的最後一個欄位e_lfanew指向的是PE頭的地址,在PE Header的結構IMAGE_NT_HEADER中,有一個結構IMAGE_OPTIONAL_HEADER,這個結構中有一個IMAGE_DATA_DIRECTORY(資料目錄)型別的陣列,其中第二個元素就是儲存的匯入表的相對虛擬地址和大小,通過這個可以定位到匯入表的地址,從而找到對應的函式的地址。
匯入表的和結構為IAMGE_IMPORT_DESCRIPTOR ,它的第一個引數OriginFirstThunk和最後一個引數FirstThunk分別指向的是同一種結構IMAGE_THUNK_DATA,但是因為這個結構體中是一個聯合結構,所以根據這個DWORD型別的值的不同所表示的意義也不同。OriginFirstThunk指向的這個結構表示的是一個匯入序號;而FirstThunk指向的這個結構表示的是函式的名字,這時DWORD的值表示的是一個RVA,並指向個IMAGE_IMPORT_BY_NAME結構。
在裝載PE檔案的時候,裝載器會遍歷OriginFirstThunk指向的IMAGE_THUNK_DATA陣列,找到每個IAMGE_THUNK_DATA結構中函式所對應的地址,然後載入器用函式真正的入口地址來代替FirstThunk指向的陣列,這個地址陣列我們稱之為IAT(匯入地址表)。
介紹了原理,那麼現在實現起來就較簡單了,我們只需要修改IAT表即可。
那麼我們的鉤子函式從何而來呢?依然是兩種方法可以獲得,第一種是API函式;另一種是我們自己構造的函式,而我們構造的函式可以通過直接注入程式碼到目標程序或通過注入DLL而來匯入我們的函式進入目標程序。
PE結構與IAT
匯入表是PE檔案結構中的一個表結構,它是PE頭結構IAMGE_NT_HEADER中的可擴充套件頭結構IMAGE_OPTONAL_HEADER中的結構,它的型別為IMAGE_DATA_DIRECTORY的一個數組,其中匯入表是這個陣列中的第二個元素。記錄的是該目錄的相對虛擬地址的起始值和大小。
在可執行檔案中使用其他的DLL中的可執行程式碼或資料時成為匯入。當PE檔案被載入時,windows載入器會定位所有的匯入函式或資料,做法就是將DLL對映到程序的地址空間。當然了,這個對映的過程就是通過PE檔案頭中的匯入表的資訊來進行操作的,因為匯入表中存放了使用DLL的模組的名稱以及匯入的函式。
在開始講解IAT HOOK之前我們很有必要了解匯入表的結構,因為IAT HOOk就是通過修改匯入表中的引數來進行HOOk 的。
實際上,匯入表就是一系列IMAGE_IMPORT_DESCRIPTOR結構組成的,結構的數量取決於程式使用DLL的數目,每一個結構對應一個DLL檔案。
匯入表的結構定義如下:
Typedef struct _IMAGE_IMPORT_DESCRIPTOR
{
Union
{
DWORD Charatorics ;
DWORD OriginalFirstThunk ;
} ;
DWORD TimeDataStamp ;
DWORD ForwarderChain ;
DWORD Name ;
DWORD FirstThunk ;
} IMAGE_IMPORT_DESCRIPTOR ;
其中有幾個欄位比較重要:
OriginFirstThunk:該欄位指向了匯入表的RVA,該表是一個IMAGE_THUNK_DATA的結構體陣列。
Name:該欄位為DLL名稱的指標,該指標也為一個RVA。
FirstThunk:該欄位包含了匯入地址表(IAT)的RVA,IAT是一個IMAGE_THUNK_DATA的結構體陣列。
IMAGE_THUNK_DATA的結構定義如下:
Typedef struct _IMAGE_THUNK_DATA
{
Union
{
PBYTE ForwarderString ;
PWORD Function ;
DWORD Ordinal ;
PIMAGE_IMPORT_BY_NAME AddressOfData ;
} u1 ;
} IMAGE_THUNK_DATA32 ;
這個結構中是一個聯合體,那麼該結構變只有一個成員變數,該結構體實際上是一個DWORDl 型別的變數。
當IMAGE_THUNK_DATA值的最高位為1時,表示函式以序號方式匯入,而這時第31位被看做為一個匯入序號。當IMAGE_THUNK_DATA值的最高位為0時表示函式以函式名字字串的方式匯入,這時DWORD的值表示一個RVA,並指向一個IMAGE_IMPORT_BY_NAME結構。其結構定義如下:
Typedef struct _IMAGE_IMPORT_BY_NAME
{
WORD Hint ;
BYTE Name[1] ;
} IMAGE _IMPORT_BY_NAME , *PIMAGE_IMPORT_BY_NAME ;
引數說明:
(1)Hint :該欄位表示該函式在其DLL中的匯入表中的符號。
(2)Name : 該欄位表示匯入函式的函式名,匯入函式是以一個ASCII編碼的字串,並以NULL結尾。
圖中INT表示的是匯入名字表(Import Name Table );而IAT表示的是匯入地址表(Import Address Table )。前者記錄的是函式名字,後者記錄的是函式的地址。
PE 裝載器首先搜尋 OriginalFirstThunk ,找到之後載入程式迭代搜尋陣列中的每個指標,找到每個 IMAGE_IMPORT_BY_NAME 結構所指向的輸入函式的地址,然後載入器用函式真正入口地址來替代由 FirstThunk 陣列中的一個入口,因此我們稱為輸入地址表(IAT)。所以,當我們的 PE 檔案裝載記憶體後準備執行時,剛剛的圖就會轉化為下圖:
好了,匯入表介紹完了,下面就是我們最重要的東西了——IAT HOOK。
實際上到這裡我們的思路已經很清晰了,只要修改IAT中函式的入口點地址為我們構造的函式的地址,那麼程式呼叫此API時實際上呼叫的就是我們自己構造的函式。既然要修改IAT中的內容,那麼就必須要定位IAT的地址。
上面的過程可以描述為:對於一個PE檔案映像,從偏移位置0開始就是Dos Header,在Dos頭中我們知道有一個指標e_lfanew指向真正的PE檔案頭,通過PE檔案頭可以得到資料目錄,而資料目錄的第二個元素就是儲存的匯入表的資訊,通過匯入表的資訊我們可以定位到匯入表,其結構為IMAGE_IMPORT_DESCRIPTOR,再通過遍歷所有的IMAGE_IMPORT_DESCRIPTOR結構可以得到所有的DLL檔名,通過遍歷每個結構的FirstThunk成員可以得到每個函式的地址,通過遍歷每個結構的OriginalFirstThunk所指向的IMAGE_IMPORT_BY_NAME可以得到每個函式的匯出函式名。
(3)替換Windows訊息處理函式實現Hook
在這種方法中,仍然需要使用WriteProcessMemory和CreateRemoteThread函式把掛鉤程式碼注入到目標程序的地址空間中。向遠端程序中注入相關的變數和函式。這種方法主要是通過函式SetWindowLong來替換原來的訊息處理函式。SetWidowLong還可以為指定的視窗設定其他的資訊。
最主要的就是把新的回撥函式和其需要的資料注入到指定的程序,並最終能夠使用這個回撥函式達到Hook的效果。
這些工作都將通過建立的一個遠端執行緒來處理。
因為篇幅有限,在後續的博文中會陸續給出相應的Demo。