名字起得好,Inline hook,乍一聽,似乎很高深。此處的Inline,我以為,意指將匯編代碼直接寫入內核API的內存區域。Inline
Hook不像用戶態Hook或SSDT hook(用C語言就足夠),它需要在程序中嵌入匯編代碼(Inline
Assembly)以操作堆棧和執行內核API對應的部分匯編指令。當然,這些都須以驅動的形式進行。
所謂API
Hook,就是用自己寫的函數去替代系統API的“職位”,此后,自己寫的函數便掌握了以前由被hook的API所“經手”的一切“事宜”。
Windows系統分用戶態和內核態,API也就有了用戶級和內核級兩大類。想要比較底層、徹底地做點事情,當然要hook內核API了(不過hook用
戶態API也有諸多用途)。
Hook內核API比較常見的是SSDT hook,一句話——Windows把需要調用的內核API地址全都存在了一個表中(System Service
Dispatch Table),
要想hook一個內核API,比較簡單的辦法就是把該內核API在表(SSDT)中保存的地址修改為自己撰寫的函數地址。這個道理類似于把
"Windows"先生的"內核API電話簿"給篡改了,當老先生想要打電話給"被hook的api先生"時,他找到的"電話號碼"其實已被我們篡改,撥
通電話,我們的"函數小子"開始應答,信息過濾自此開始。
非常不幸,ICESword等檢測工具可以輕松判斷SSDT是否被篡改,并且會以適當的方式通知用戶(比如檢索結果的字體變紅)。
Inline
Hook要比SSDT Hook來得更徹底一點。如果說SSDT
Hook只是把某位"內核API先生"綁架,然后用我們的“自己人”來接管其工作,而ICESword卻可以從其他聯系途徑找到被綁架的"內核API先生
"并“報警”,那么——Inline Hook可以說是給"內核API先生"動了手術,讓他成為"我們陣營的一分子"。Inline
Hook通過硬編碼的方式向內核API的內存空間(通常是開始的一段字節,且一般在第一個call之前,這么做是為了防止堆棧混亂)寫入跳轉語句,這樣,
該API只要被調用,程序就會跳轉到我們的函數中來,我們在自己寫的函數里需要完成3個任務:
1)重新調整當前堆棧。程序流程在剛剛跳轉的時候,內核API并沒有執行完,而我們的函數需要根據其結果來進行信息過濾,所以我們需要保證內核API能在順利執行完畢后返回到我們的函數中來,這就要求對當前堆棧做一個調整。
2)
執行遺失的指令。我們向內核API地址空間些如跳轉指令(jmp
xxxxxxxx)時,勢必要覆蓋原先的一些匯編指令,所以我們一定要保證這些被覆蓋的指令能夠順利執行(否則,你的及其就要BSOD了,呵呵,Blue
Screen Of
Death)。關于這部分指令的執行,一般是將其放在我們的函數中,讓我們的函數“幫助”內核API執行完被覆蓋的指令,然后再跳回內核API中被覆蓋內
后后的地址繼續執行剩余內容。跳回去的時候,一定要算好是跳回到什么地址,是內核API起始地址后的第幾個字節。
一個朋友曾提出把內核API的被覆蓋內容還原,然后執行之——這種方法,我沒有試驗,但我認為應該不會很穩定,因為內核里常有線程切換,如果你把內核API還原,萬一自己函數的線程被掛起,而又有線程要調用給API,這就會出現“Hook 遺漏”。
3)信息過濾。這個就不用多說了,內核API順利執行并返回到我們的函數中,我們自然要根據其結果做一些信息過濾,這部分內容因被hook的API以及Hook目的的不同而不同。
Inline hook的工作流程:
1)驗證內核API的版本(特征碼匹配)。
2)撰寫自己的函數,要完成以上三項任務。
2)獲取自己函數的地址,覆蓋內核API內存,供跳轉。
Inline Hook的缺點:
1) 不夠通用。各個windows版本中,內核API的開始一段不盡相同,要想通吃,就要多寫幾個版本或者做一個特征碼搜索(因為有的內核API在各個版本中非常相似,只是在“特征碼”之前或之后加一點東西)。
2) 已被一些檢測工具列入檢測范圍,如果直接從內核API第一個字節開始覆蓋,那么很容易被檢測,如果把覆蓋范圍往后推,并加以變形,也許能抵擋一氣。具體情況,我才疏學淺,尚未試驗。
上文權且當作是以下兩文的讀書筆記:
1) kernel inline hook 繞過vice檢測——xfocus上的文章
2) 實現kernel-mode inline function hook的簡單方法(http://www.phpfav.com/?p=35)——5eCur!ty上的文章
大家可以去參看原文,文章1中的代碼可以用文章2的方法優化一下。