detours, x86 kernel hook 以及 x64 kernel hook

detours, x86 kernel hook 以及 x64 kernel hook

http://bbs.driverdevelop.com/htm_data/92/0702/99096.html


我假設讀者已經非常熟悉detours,閱讀此文只是爲了增強對detours的理解以及爲了實現x64 hook。有關detours原理部分不再多講。

X86 Kernel Hook
早些年,我把detours1.5移植到x86核心層,工作的不錯,我一直用它來hook系統一些內部函數,有時候也用來hook IoCreateFile這類導出函數。讓detours1.5在覈心工作穩定並不是一件困難的事情。可能有些c/c++的麻煩,但是很快就可以解決。唯一需要注意的地方是detours1.5用VirtualProtect來讓內存READ_WRITE_EXECUTE,在覈心層有2種方法,第一種是羣衆所喜聞樂見的清除cr0,第二種是在覈心層通過調用native api做VirtualProtect的事情。
detours的方法對比import/export方法有一些很明顯的好處,其最大的好處是可以用來hook內部函數。而且由於hook的方法是直接修改函數體,所以不管調用者怎麼玩花樣,都很難繞過hook。
detours的缺點主要如下:
1,detours x86無法hook小於5字節的函數
2,detours x86需要一個完備的反彙編器和解釋器,實際上detours代碼中並不包含這個,因此,如果需要寫一個函數阻止他人hook,可以這麼寫:
  proc near
      xor eax,eax
      jeax 1
      int 3
      ... // do something
    proc end
注意到這裏的這個jmp,因爲eax肯定爲0,所以該int3不會被調用,而被detours過的代碼則很可能走到int3上去了,爲了讓detours的代碼不走到int3,detours必須能夠解析出前面3行代碼的意思,並且修正jeax 1爲jeax 1+(trampoline-function)。用類似的技術,也可以欺騙detours。
3,detours x86無法處理如下函數:
  proc near
flag: ... // 函數前5個字節
    .... //do something
    jmp flag
    .... // do something
    proc end
該函數執行體中有一個jmp,跳到前5個字節。可是被detours過之後,該函數的前5個字節被修改了,而且改成了jmp trampoline。爲了能夠讓detours可以處理此操作,必須反彙編解析整個函數體,用2種所描述的方法修改jmp flag。

綜上述,detours思路很好,但是存在缺陷,要搞定這些缺陷,需要完整反彙編器。

X64 Kernel Hook
最近有一個需求要在x64下實現類似的hook模塊,我找到了detours2.1,給MS發了email,MS的答覆是,包含64bit的detours2.1,需要10000 USD。
於是我就刪掉了MS的email,開始自己動手來做這個事情了。我大致說一下原理和需要注意的地方。

x64 hook和x86 hook的原理相似,都是修改原函數的首地址。不同的是,x64下不存在
jmp 64_address這種指令,x86下要跨4G跳轉,必須是jmp [64_address],對應的彙編碼不再是e9 xxxxxxxx,而是ff15 [xxxxxxxx],其中xxxxxxxx保存的是一個64_address。注意xxxxxxxx依然是32位,所以,該內存也必須和function處於同一個4G。

這個限制對於普通的代碼編譯來說,並不存在太大的問題,因爲很少有exe超過4G的。所以編譯器生成的代碼依然使用e9 xxxxxxxx。對於import的dll來說,通常都是call [xxxxxxxx],以前是這樣,現在還是這樣,不同的是,[xxxxxxxx]以前指向32位的地址,現在指向64位的地址。這樣一來,dll加載的位置和exe所在的位置不在同一個4G也沒有關係了。

對於detours來說,受上面所述特性影響的是,trampoline通常位於heap memory/nonpaged pool,new_function位於我們自己所寫代碼的dll/driver中,old_function位於我們所需要hook的那個模塊中。這裏面存在一個基本矛盾是,new_function通常和old_function分別處於2個不同dll或者.sys中,系統很可能把他們加載到了距離很遠的空間中,也即abs(new_function-old_function)>4G。這樣一來,就無法使用e9 xxxxxxxx,而必須使用ff15 [xxxxxxxx]了,而且xxxxxxxx是一個32的偏移,所以[xxxxxxxx]還不能位於我們的dll/sys中。

根據以上的分析,最後可以得出如下算法:
1,找到需要hook的函數地址
2,解析從函數起始地址開始,至少6+8=14個字節的代碼。代碼不能斷開。以上2個過程和detourx86一樣,不同的是,detoursx86之需要e9 xxxxxxxx,也就是說只需要5個字節,而我們必須用ff15 [xxxxxxxx]。如果函數體小於14個字節,這意味着該函書無法detours。
不過函數體小於14字節多半是因爲裏面執行了一個call或者jmp,那麼解析該代碼,把函數起始地址設置爲jmp之後的地址,重新進行2過程。
3,把這14或者15,16...個字節拷貝到預先分配的一塊內存中,我們叫它trampoline。
4,把前6個字節改爲ff15 [0],也即ff15 00000000
5,在隨後的8個字節中保存new_function的起始地址
6,修正trampoline中的14字節的代碼,如果裏面有jmp,call等跳轉語句,修改偏移量,這時候通常又需要跨4G的跳轉,那麼按照上面的方法修改之,trampoline的字節數可能會增加。
7,在trampoline的代碼之後,插入ff15 [0],並且在隨後的8個字節中填充old_function+14。

trampoline可以預先分配一個100字節的buffer,初始化全部填充爲nop,在進行7的時候,可以從trampoline的底部,也即100-14的位置開始填入ff,15,00,00,00,00, 64_bit_old_function+14(15,16...)。

以上算法的缺點和x86 detours的缺點一樣,第一條爲無法hook函數體小於14字節的函數。

14個字節相當大,有時候這個缺陷不可忍受,爲此,介紹一種更爲骯髒的手段。

代碼加載到內存中時,通常有很多廢空間,也即,在這些空間中,只有nop,或者永遠不會執行。用IDA可以找到這些空間。如果能夠找到足夠大到,以至於可以保存一個64位地址的空間的話,那麼可以只修改前5個字節爲jmp [xxxxxxxx],同時只拷貝5個字節到trampoline。trampoline的底部14個字節照舊。

以上就是x64下的detours過程。

有一個x64下需要注意的問題,vc8不支持x64下的_asm關鍵字,所以
_asm{
cli
mov eax,cr0
and eax,not 1000h
mov cr0,eax }不能再用
取而代之的是
_disable();
uint64 cr0=__readcr0();
cr0 &= 0xfffffffffffeffff;
__writecr0(cr0);
當然還可以繼續用native api,不過以上方法簡潔而且爲廣大羣衆所喜聞樂見。有關於_disable等函數,請查閱新版msdn。

至於IA64,我對此一無所知。

順便說幾點:
1,EM64T的cpu上可以run win64os,但是,不知爲何,vmware無法在EM64T的cpu上install/run win64os。而amd64 cpu上即便安裝的是win32 os,也可以在其上的vmware裏install/run win64os。
2,softice已經停止開發,而且不支持x64,只有virtual模式才支持。鑑於其已經停止開發,建議大家都使用windbg。
3,idapro 5.0反彙編x64的代碼,錯誤百出,一團亂麻,基本上需要先U再C。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章