HOOK技術的一些簡單總結

很久沒寫博客了, 一個月一篇仍是要儘可能保證,今天談下Hook技術。

在Window平臺上開發任何稍微底層一點的東西,基本上都是Hook滿天飛, 普通應用程序如此,安全軟件更是如此, 這裏簡單記錄一些經常使用的Hook技術。

基本上作Windows開發都知道這個API, 它給咱們提供了一個攔截系統事件和消息的機會, 而且它能夠將咱們的DLL注入到其餘進程。
可是隨着64位時代的到來和Vista以後的UAC機制開啓,這個API不少時候不能正常工做了:

首先,32位DLL無法直接注入到64位的應用程序裏面, 由於他們的地址空間徹底不同的。固然儘管無法直接注入,可是在權限範圍內,系統會盡可能以消息的方式讓你能收到64位程序的消息事件。

其次,UAC打開的狀況下低權限程序無法Hook高權限程序, 實際上低權限程序以高權限程序窗口爲Owner建立窗口也會失敗, 低權限程序在高權限程序窗口上模擬鼠標鍵盤也會失敗。

有人說咱們能夠關閉UAC, Win7下你確實能夠,可是Win8下微軟已經不支持真正關閉UAC, 從這裏咱們也能夠看到微軟技術過渡的方式, 中間會提供一個選項來讓你慢慢適應,最後再把這個選項關掉, UAC和Aero模式都是如此。

那麼咱們如何解決這些問題?

對於64位問題 , 解決方法是提供2個DLL,分別能夠Hook32和64位程序。

對於權限問題, 解決方法是提高權限, 經過註冊系統服務, 由服務程序建立咱們的工做進程。這裏爲何要建立一個其餘進程而不直接在服務進程裏幹活? 由於Vista後咱們有了Session隔離機制,服務程序運行在Session 0,咱們的其餘程序運行在Session 1, Session 2等, 若是咱們直接在服務程序裏幹活,咱們就只能在Session 0裏工做。經過建立進程,咱們能夠在 DuplicateTokenEx後將Token的SessionID設置成目標Session,而且在 CreateProcessAsUser時指定目標WinStation和Desktop, 這樣咱們就既得到了System權限,而且也能夠和當前桌面進程交互了。

不少人可能都不知道這個API, 可是這個API其實挺重要的, 看名字就知道它是Hook事件(Event)的, 具體哪些事件能夠看 這裏.

爲何說這個API重要, 由於這個API大部分時候沒有SetWindowsHookEx的權限問題, 也就是說這個API可讓你Hook到高權限程序的事件, 它同時支持進程內( WINEVENT_INCONTEXT)和進程外( WINEVENT_OUTOFCONTEXT)2種Hook方式, 你能夠以進程外的方式Hook到64位程序的事件。

爲何這個API沒有權限問題, 由於它是給Accessibility用的, 也就是它是給自動測試和殘障工具用的, 因此它要保證有效。

我曾經看到這樣一個程序,當任何程序(不管權限高低)有窗口拖動(拖標題欄改變位置或是拖邊框改變大小), 程序都能捕獲到, 當時很好奇它是怎麼作到的?
Spy了下窗口消息, 知道有這樣2個消息: WM_ENTERSIZEMOVE和 WM_EXITSIZEMOVE表示進入和退出這個事件, 可是那也只能得到本身的消息,其餘程序的消息它是如何捕獲到的?當時懷疑用的是Hook, 卻發現沒有DLL注入。查遍了Windows API 也沒有發現有API能夠查詢一個窗口是否在這個拖動狀態。最後發現用的是SetWinEventHook EVENT_SYSTEM_MOVESIZESTART和EVENT_SYSTEM_MOVESIZEEND。

API Hook
常見的API Hook包括2種, 一種是基於PE文件的導入表(IAT), 還有 一種是修改前5個字節直接JMP的inline Hook.

對於基於IAT的方式, 原理是PE文件裏有個導入表, 表明該模塊調用了哪些外部API,模塊被加載到內存後, PE加載器會修改該表,地址改爲外部API重定位後的真實地址, 咱們只要直接把裏面的地址改爲咱們新函數的地址, 就能夠完成對相應API的Hook。《Windows核心編程》裏第22章 有個封裝挺好的CAPIHook類,咱們能夠直接拿來用。
我曾經用API Hook來實現自動測試,見這裏 API Hook在TA中的應用

對於基於Jmp方式的inline hook, 原理是修改目標函數的前5個字節, 直接Jmp到咱們的新函數。雖然原理挺簡單, 可是由於用到了平臺相關的彙編代碼, 通常人很難寫穩定。真正在項目中用仍是要求穩定, 因此咱們通常用微軟封裝好的Detours, 對於Detours的原理,這裏有篇不錯的文章  微軟研究院Detour開發包之API攔截技術

比較一下2種方式: 
IAT的方式比較安全簡單, 可是隻適用於Hook導入函數方式的API。
Inline Hook相對來講複雜點, 可是它能Hook到任何函數(API和內部函數),可是它要求目標函數大於5字節, 同時把握好修改時機或是Freeze其餘線程, 由於多線程中改寫可能會引發衝突。

COM Hook
Window上由於有不少開發包是以COM方式提供的(好比DirectX), 因此咱們就有了攔截COM調用的COM Hook。
由於COM裏面很關鍵的是它的接口是C++裏虛表的形式提供的, 因此COM的Hook不少是時候其實就是虛表 (vtable)的Hook。
關於C++ 對象模型和虛表能夠看我這篇  探索C++對象模型

對於COMHook,考慮下面2種case:

一種是咱們Hook程序先運行,而後啓動某個遊戲程序(DirectX 9), 咱們想Hook遊戲的繪畫內容。

這種方式下, 咱們能夠先Hook API  Direct3DCreate9, 而後咱們繼承於IDirect3D9, 本身實現一個COM對象返回回去, 這樣咱們就能夠攔截到全部對該對象的操做, 隨心所欲了, 固然咱們本身現實的COM對象內部會調用真正的 Direct3DCreate9,封裝真正的 IDirect3D9。

固然有時咱們可能不用替代整個COM組件,咱們只須要修改其中一個或幾個COM函數, 這種狀況下咱們能夠建立真正的 IDirect3D9對象後直接修改它的虛表, 把其中某些函數改爲咱們本身的函數地址就能夠了。

其實ATL就是用接口替代的方式來調試和記錄COM接口引用計數的次數, 具體能夠看我這篇  理解ATL中的一些彙編代碼

還有一種case是遊戲程序已經在運行了, 而後才啓動咱們的Hook進程, 咱們怎麼樣才能Hook到裏面的內容?

這種狀況下咱們首先要對程序內存有比較詳細的認識, 才能思考建立出來的D3D對象的虛表位置, 從而進行Hook, 關於程序內存佈局,可見我這篇  理解程序內存

理論上說COM對象若是是以C++接口的方式實現, 虛表會位於PE文件的只讀數據節(.rdata), 而且全部該類型的對象都共享該虛表, 因此咱們只要建立一個該類型對象,咱們就能夠得到其餘人建立的該類型對象的虛表位置,咱們就能夠改寫該虛表實現Hook(實際操做時須要經過VirtualProtect修改頁面的只讀屬性才能寫入)。

可是實際上COM的虛表只是一塊內存, 它並不必定是以C++實現, 因此它能夠存在於任何內存的任何地方。另外對象的虛表也不必定是全部同類型的對象共享同一虛表, 咱們徹底能夠每一個對象都有本身的一份虛表。好比我發現 IDirect3D9是你們共享同一虛表的(存在D3D9.dll的), 可是IDirect3DDevice9就是每一個對象都有本身的虛表了(存在於堆heap)。因此若是你要Hook  IDirect3DDevice9接口,經過修改虛表實際上無法實現。

可是儘管有時每一個對象的虛表不同,同類型對象虛表裏的函數地址卻都是同樣的, 因此這種狀況下咱們能夠經過inline Hook直接修改函數代碼。固然有些狀況下若是是靜態連接庫,即便函數代碼也是每一個模塊都有本身的一份, 這種狀況下就只能反彙編獲取虛表和函數的地址了。

最後,總結一下, 上面主要探討了Windows上的各類Hook技術,經過將這些Hook技術組起來, 能夠實現不少意想不到的功能, 好比咱們徹底能夠經過Hook D3D實現Win7任務欄那種Thumbnail預覽的效果(固然該效果能夠直接由DWM API實現, 可是若是咱們能夠經過HOOK已動畫的方式實現是否是更有趣 )。
相關文章
相關標籤/搜索