1. 使用Detours HOOK 技術(強烈建議)
強烈建議使用Detours進行 HOOK API,穩定性已經得到普遍認同。官方版是微軟的,但也有開源實現(實際應該是付費用戶在微軟官方源碼基礎上開放出來的),百度一下就有,多種語言都有相應的庫。
使用Detours時針對多線程有以下建議:
1)訪問資源使用臨界區或多讀一寫鎖
2)開發過程中對“同步保護”的優先級高於“運行效率”
2. 自主實現的INLINE HOOK
自主實現的INLINE HOOK需要考慮很多穩定性問題,本人不建議了,以下是本人一些經歷和建議(由於本人已經選擇Detours技術,所以下面說的有些不重要了)
在使用INLINE HOOK API實現對系統API的攔截時,正常情況下並沒有太大問題,但一旦涉及到多線程,不管是修改IAT還是JMP,2種方法均會出現不可預料的問題,特別是在HOOK一些復雜的大型系統軟件時,會被時不時的一個內存錯誤搞得心浮氣躁。
HOOK API數量越多,需要注意的內容越多,最近實現HOOK FileSystem API,由於是Kernel32中的函數,所有涉及到文件(filename,filehandle)的API均要被HOOK,同時需要在HOOK中實現overlapped功能,而這個功能點相對比較復雜,連帶涉及到完成端口、事件等函數的HOOK。
經過上百個版本的更新調試,目前初步穩定下來,下面列出需要注意的地方,主要是與FileSystem API相關的,其他API(如網絡API、內存API等)可能不太適用:
1)從穩定性方面考慮,建議使用JMP方式實現HOOK API,修改IAT的方式不是不可以,而是太容易被其他程序修改而導致不穩定的問題了,大部分殺毒軟件均會對IAT進行修改同時保護,所以這種方法要么容易報病毒,要么HOOK失敗,同時系統也會對IAT進行修補,對於小型系統或者小量API可能不出現問題,但是對於FileSystem API,個人不推薦。
2)對Vista、WIN7以上的兼容:一開始在XP中進行HOOK時,一切正常,當切換到WIN7后,發現HOOK不起作用,這是由於VISTA、WIN7以上的APP在原來調用Kernel32.DLL的函數時,由於系統AppPatch的功能,已被自動重定向到KernelBase.dll中,而微軟有很奇怪的做法,當然估計也是由於兼容性的考慮(即在EXE右鍵屬性中選擇兼容XP,VISTA運行時,AppPatch會自動選擇重定向哪些API),某些API同時存在於Kernel32和KernelBase中,而不知道是不是重定向功能的不完善還是其他什么原因,同一個App中兩個模塊即使調用同一個API,也有可能分別跳轉到Kernel32和KernelBase中,適用JMP的方式進行HOOK API時,由於通過GetProcAddress獲取函數地址,所以均會被自動重定向到KernelBase中,因此需要自己實現一個GetProcAddress函數。
3)多線程同步問題:普通讀寫鎖個人建議使用臨界區RTL_CRITICAL_SECTION。 或 針對“特殊場景”使用 多讀一寫鎖,不管是開發效率還是運行效率,臨界區是首選,對於HOOK后的API中使用不同進程資源的情況,也是在使用臨界區進行同步處理后,再去使用其他方法實現的。
4)多線程HOOK后經常卡死的問題,WINDOWS的FileSystem API存在嵌套調用的問題,如CopyFile內部會調用CreateFile等函數,Kernel32的CreateFile會調用KernelBase中的CreateFile,GetFileSize函數內部可能會調用GetFileSizeEx的函數等等,如果在這些函數中同時使用一個內核對象進行同步,必定導致卡死,因此需要考慮這些問題,比如在調用CopyFile時,需要和CreateFile使用不同的內核對象進行同步,而在Kernel32的CreateFile中,需要對KernelBase的CreateFile進行解除HOOK。
5)在HOOK和UNHOOK過程中,需要使用同一個內核對象對WriteProcessMemory進行同步保護,否則將導致進程中某些不可預料的地址內存數據損壞的問題(這個應該是WriteProcessMemory函數內部不支持多線程導致的)。
6)在使用RTL_CRITICAL_SECTION或其他同步函數中,鎖的進入和離開函數,盡量使用inline,減少SP的跳轉可增加速度,可大大增加多線程切換寄存器的穩定性;
題外話:從硬件上講CPU只有一套寄存器,但是對於系統運行環境來說,每一個線程(甚至是纖程,即協程或微線程)都有一套虛擬寄存器,這樣才能實現不同線程切換時還原所謂的“CPU運行現場”,而每一次SP的跳轉從目前各種編譯器技術上來講均會進行所謂的“現場保護”,也就是push各種寄存器,inline函數就是為了減少這種操作的,在於內核打交道的過程中,有些操作是可能被中斷的,減少“運行現場”的切換次數必定是大大增強穩定性!