1. 程式人生 > >Hook技術之API攔截(API Hook)

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函式而不是這些列舉程序函式,也就是這些函式被攔截了。當執行我們自己的

Hook函式的時候我們可以選擇性地顯示出正在執行的程序,從而最終實現程序的隱藏。

當然了這只是使用者態下其中一種通過API Hook實現的程序隱藏方式。一般情況下,在使用者態實現程序監控最終都會呼叫函式ZwQuerySystemInformation。因此,在系統上所有程序中掛鉤模組Ntdll.dll中的該函式就能實現程序隱藏。這種方法不再依賴預先對目標程序所使用的API函式資訊的獲取,適應性更強。它幾乎是使用者態上最有效的程序隱藏方式。

API HOOK 顧名思義是掛鉤API函式,攔截,控制某些API函式的呼叫,用於改變API執行結果的技術。需要注意的是API HOOK技術與windows

下的Messages Hook技術是不同的,在原理上就完全不一樣。訊息鉤子是截獲程式中的訊息或事件的。 API鉤子是攔截目標程式中呼叫的函式,替換或者修改呼叫函式的功能。

Hook 技術按照實現原理來分的話可以分為兩種:API  HOOKMessage  HOOK 

按照作用範圍來分可以分為全域性HOOk和區域性HOOK

API HOOK按照許可權來分可以分為核心鉤子和使用者鉤子,分別作用在Ring 0Ring 3 

當一個API函式被攔截後我們可以讓目標程式執行我們事先準備好的程式碼,hook API後,執行API函式的流程就變了,也就是我們構造的程式碼替換了原來的API函式,最後是否要呼叫原來

deAPIng由我們構造的程式碼來決定。這裡判斷是否要執行的因素是當前呼叫API的程序是否為我們編寫的木馬程序,若是就呼叫我們自己的構造程式碼,否則呼叫指定的API函式。

API HOOk技術在木馬編寫中用的非常廣泛,因為它可是很好地實現木馬的隱藏性。例如我們HookAPI函式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結構,替換目標APIIAT中的地址為鉤子函式的地址來實現。

要了解這種Hook技術,首先需要知道IATPE檔案結構。

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)的RVAIAT是一個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。