什麼是HOOK技術?
病毒木馬為何慘遭殺軟攔截?
商業軟體為何頻遭免費破解?
系統漏洞為何能被補丁修復?
這一切的背後到底是人性的扭曲,還是道德的淪喪,盡請收看今天的專題文章:
《什麼是HOOK技術?》
上面是開個玩笑,言歸正傳,今天來聊的話題就是安全領域一個非常重要的技術:HOOK技術。
HOOK,英文意思是“鉤子”
在計算機程式設計中,HOOK是一種「劫持」程式原有執行流程,新增額外處理邏輯的一種技術。
按照這個定義,其實我們Python中的裝飾器和Java中的註解,這種面向切面程式設計的手法在某種程度上來說,也算是HOOK。
不同的是,本文要探討的HOOK並非屬於程式原有的邏輯,而是在程式已經編譯成可執行檔案甚至已經在執行中的時候,如何劫持和修改程式的流程。
按照劫持的目標不同,常見的HOOK有以下這些型別:
Inline HOOKIAT HOOKC++ virtable HOOKSEH HOOKIDT HOOKSSDT HOOKIRP HOOKTDI HOOK && NDIS HOOKWindows Message HOOK
接下來,咱們挨個來看一下。
Inline HOOK
程式和程式碼是給程式設計師們看的,計算機要執行,最終是要編譯成CPU的機器指令才能執行。
Inline HOOK
的目標就是直接修改程式編譯後的指令,屬於最基礎也最常見的HOOK技術。
下面我們以一個例項來感受一下Inline HOOK的效果:
void functionA() { cout << “this is function A” << endl;}void hookFunction() { cout << “this is hookFunction” << endl;}int main() { cout << “before hook” << endl; functionA(); // prepare hook unsigned char code[5] = { 0xe9, 0x00, 0x00, 0x00, 0x00 }; unsigned int offset = (unsigned int)hookFunction - ((unsigned int)functionA + 5); *(unsigned int*)&code[1] = offset; // install hook unsigned long old = 0; VirtualProtect(functionA, 0x1000, PAGE_EXECUTE_READWRITE, &old); memcpy(functionA, code, 5); cout << “after hook” << endl; functionA(); return 0;}
輸出:
程式碼中定義了目標函式functionA和hook函式hookFunction。
第一次呼叫,輸出顯示呼叫了原函式。
然後安裝一個HOOK,準備了一條jmp指令,覆蓋函式入口處的指令。此時觀察覆蓋前後的函式指令變化對比:
再次呼叫該函式,則一進入就發生跳轉,我們安裝的HOOK函式得到了執行。
所以第二次輸出顯示HOOK函式得到了呼叫。
大部分情況下,我們習慣於在函式入口處執行HOOK,但這並不是絕對的,還需要具體問題具體分析。比如如果我們需要等待函式執行完畢時拿到返回值才能介入處理,這個時候就需要在函式return的地方進行HOOK。甚至有可能需要在函式中途某個地方介入,這個時候就需要更進一步的對函式的反編譯指令進行分析,確定HOOK的點位和處理邏輯。
執行Inline HOOK非常關鍵的幾點:
指令所在的記憶體頁是否允許寫入操作,若只讀,須先新增寫入許可權
需要動態解析目標位置處的指令,不能像上面那樣暴力覆蓋,否則會影響原來函式的執行邏輯
如果在HOOK處理函式中需要呼叫原函式,注意別陷入死迴圈
如果有引數,需要處理好堆疊平衡
Inline HOOK由於是直接在CPU機器指令層面上的操作,所以首先是無法做到跨平臺。同時,還需要對CPU的指令集有一定程式的瞭解,具備一定的組合語言功底。
好在,IT行業從來不缺造輪子的人,已經有不少優秀的開源專案將Inline HOOK封裝成庫,比如:
Detours
。
除了直接修改函式的機器指令,還有一類HOOK,它們修改的是某些重要的函式指標,從而達到劫持執行的目的。
形形色色的函式指標,就衍生出各式各樣的HOOK技術。
IAT HOOK
一個程式的所有程式碼一般不會全部都編譯到一個模組中,分拆到不同的模組既有利於合作開發,也有利於程式碼管理,降低耦合。
動態連結庫就提供了這樣的能力,將不同的模組編譯成一個個的動態庫檔案,在使用時引入呼叫。
在Windows平臺上,動態連結庫一般以DLL檔案的形式存在,主程式模組一般是EXE檔案形式存在。無論是EXE還是DLL,都是屬於PE檔案。
一個模組引用了哪些模組的哪些函式,是被記錄在PE檔案的匯入表IAT中。這個表格位於PE檔案的頭部,裡面記錄了模組的名字,函式的名字。
在模組載入時,模組載入器將解析對應函式的實際地址,填入到匯入表中。
透過修改匯入表IAT中函式的地址,這種HOOK叫
IAT HOOK
。
SEH HOOK
SEH是Windows作業系統上
結構化異常處理
的縮寫,在程式碼中透過try/except來捕獲異常時,作業系統將會線上程的棧空間裡安置一個異常處理器(其實就是一個數據結構),裡面定義了發生異常時該去執行哪裡的程式碼處理異常。
異常處理可以多級巢狀,那多個異常處理就構成了一個連結串列,存在於棧空間之上。
當發生異常時,作業系統系統就從最近的異常處理器進行尋求處理,如果能處理則罷了,不能處理就繼續尋求更上一級的異常處理器,直到找到能處理的異常處理器。如果都沒法處理,那對不起,只好彈出那個經典的報錯對話方塊,程序崩潰。
SEH HOOK
針對的目標就是修改這些異常處理器中記錄的函式指標,當異常發生時就能獲得執行,從而劫持到執行流!因為這些異常處理器都位於執行緒的棧空間,修改起來並非難事。
C++ virtable HOOK
C++是一門面向物件的程式語言,支援面向物件的三大特性:封裝性、繼承性、多型性。
其中的多型性,各個C++編譯器基本上都是透過一種叫虛擬函式表的機制來實現。
下面透過一個實際的例子來感受一下虛擬函式表在C++多型性上發揮的作用。
#include
輸出:
透過上面的輸出,可以看到,fish和cat物件都只佔據4個位元組。因為這兩個類都沒有成員變數,唯一需要儲存的就是一個虛擬函式表指標。
以cat物件為例,看一下它的地址,是0x005cfc30:
看一下這個地址起始的4個位元組,是什麼:
虛表指標是0x000d9b90(這裡需要注意位元組順序)。
透過這個地址,找到虛擬函式表,裡面有兩個函式地址:
檢視這兩個地址,都是指向了一個jmp指令,分別跳到了Cat類的兩個虛擬函式。
透過上面的例項,總結一下物件、虛擬函式表和虛擬函式程式碼之間的關係如下圖所示:
每個包含虛擬函式的類物件,在記憶體中都有一個指標,位於物件頭部,指向的是一個虛擬函式表,表中的每一項都是虛擬函式地址。
類繼承後,如果重寫了父類的虛擬函式,子類物件指向的表格中對應函式的地址將會更新為子類的函式。
這樣,使用父類指標指向子類物件,透過指標呼叫虛擬函式時,就能呼叫到子類重寫的虛擬函式,從而實現多型性。
既然是記錄函式地址的表格,那就有存在被篡改的可能,這就是C++ virtable HOOK。
透過篡改對應虛擬函式的地址,實現對相應函式呼叫的攔截。
實施這種
HOOK
,需要逆向分析目標C++物件的結構,掌握虛擬函式表中各個函式的位置,才能精準打擊。
上面幾種HOOK,修改的都是應用層的函式指標,而作業系統核心中還有一些非常重要的表格,它們的表項中記錄了一些更加關鍵的函式,HOOK這些表格中的函式是非常高危的操作,操作不當將導致作業系統崩潰。當然,高風險高回報,HOOK這些函式,能實現一些非常強大的功能,是病毒、木馬、安全軟體非常愛乾的事情。
SSDT HOOK
系統呼叫
是作業系統提供給應用程式的程式設計介面API,應用程式透過這些API得以操作計算機的資源(如程序、網路、檔案等)。
執行系統呼叫的時候,CPU將從使用者模式切換到核心模式,進入核心後,將會根據系統呼叫的API編號,去找到對應的系統服務函式,實現對應API的功能。
作業系統將所有的系統服務函式地址,存放在了一個表格中,這個表格就是系統
服務描述符表
。在Linux上,這個表格的名字叫s
ys_call_table
,在Windows上,它叫
KeServiceDescriptorTable
,簡稱
SSDT
。
Windows上的SSDT向來是兵家必爭之地,安全軟體為了監控應用程式的行為,通常都會替換SSDT表格中的系統服務函式地址為它們的函式。當系統呼叫觸發時,安全軟體將會及時知曉,並透過應用程式的引數來判定是否“放行”這次呼叫。
IDT HOOK
核心中除了記錄系統服務的SSDT,還有一個非常重要的表格:
中斷描述符表IDT。
IDT用於記錄CPU執行過程中遇到中斷、異常等情況時,該轉向哪裡去處理這些情況的函式地址。
HOOK IDT有一個注意事項,不同於SSDT是全域性唯一的,IDT是與CPU核心緊密相關的,對於多核處理器,會對應多個IDT表。如果想透過HOOK IDT中的函式來搞事情的話,可能需要同時處理多個表。
除了直接修改函式指令和修改函式指標之外,還有一類特殊的HOOK,它們透過系統提供的機制攔截某些訊息、通知,從而有機會介入監聽、攔截。
IRP HOOK
在Windows系統上,使用者程式和核心驅動之間的互動是透過一種稱為IRP的資料結構實現的,你可以簡單將其理解為應用程式傳送了一個訊息下去,這個訊息就是一個IRP。
而接收訊息的目標,是驅動程式建立的
裝置Device
。注意,這個裝置不一定是物理裝置,也可能完全不存在的虛擬裝置,驅動程式可以任意建立一個不存在的裝置。
Windows核心中提供了驅動裝置的掛載操作,允許別的驅動程式對指定裝置進行掛載,從而可以截獲傳送給該裝置的“訊息”,這種HOOK方式被稱為
IRP HOOK
。
國內一些安全軟體為了互相攻擊,經常用這種方式攔截對方驅動程式的訊息,從而可以“保護”自己不被對方幹掉。
TDI HOOK && NDIS HOOK
這兩種HOOK方式與Windows核心中的網路子系統密切相關。
Windows核心中的網路結構是分層式設計。從最上層的API socket層、到TCP/IP協議棧層、再到底層的網絡卡驅動程式,分了很多個層次。
而層與層之間的互動,是透過一系列標準介面來實現的,其中最重要的兩個介面標準就是
TDI
和
NDIS
。TDI封裝了不同協議棧的差異(Windows不止支援TCP/IP協議棧)提供給上層統一的呼叫介面。NDIS則封裝了底層不同網絡卡的驅動程式介面差異,提供給上層統一的收發資料包介面。
Windows為了擴充套件性支援,允許類似防火牆之類的軟體透過這些介面接入,從而能夠截獲到網路通訊流量,進行安全審計。
既然開了這些介面,一些流氓軟體和木馬病毒也就盯上了它們,透過這些介面就能輕鬆監聽、篡改網路資料,達到邪惡的目的。
Windows Message HOOK
Windows作業系統的UI互動是以訊息來驅動的,使用者的鍵盤輸入、滑鼠操作都會被作業系統以訊息的形式傳送到各個應用程式處理。
Windows提供了API介面,可以被程式用於捕獲這些訊息,從而實現一些特定的功能。
HHOOK SetWindowsHookEx( int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId);
這種機制叫做Windows訊息鉤子,最常見的就要數鍵盤鉤子了,在十多年前流氓軟體和木馬病毒大行其道的時候,這些惡意軟體經常喜歡透過這種方式來監聽使用者的鍵盤輸入,從而來盜取QQ密碼(當然,現在肯定是不行的了)。
總結
以上就是要介紹的全部HOOK技術了。當然有HOOK,就有反HOOK,很多安全軟體都會檢查關鍵的位置是否被篡改。不僅如此,因為流氓軟體隨意修改系統,Windows從Win7 x64開始加入了PatchGuard機制,針對作業系統核心資料結構都加入了定時檢測機制,一旦發現被篡改,立刻藍色畫面給你看,而且在隨著系統升級換代,這個檢查的粒度和強度變得越來越強。
最後來回到文章開頭的幾個問題:
病毒木馬為何慘遭殺軟攔截?
因為安全軟體在核心中HOOK了大量的關鍵位置,病毒木馬的程序、檔案、網路行為都將受到監控,一舉一動都難逃殺軟的眼睛,想要攔截易如反掌。
商業軟體為何頻遭免費破解?
透過逆向分析加上Inline HOOK,破解者可以篡改掉商業軟體的註冊校驗機制,讓校驗函式返回成功,繞開軟體的限制。
系統漏洞為何能被補丁修復?
統一透過Inline HOOK,作業系統能夠修改原來有bug的程式碼,轉而執行修復後的新版本,解決系統漏洞。
需要
HOOK技術文件
的同學可以
點贊+關注
後
私信【技術文件】
即可免費領取
【HOOK技術文件】
** 你看,技術就是一柄雙刃劍,善或惡,一念之間。