*******************************************************
*標題:【原創】SSDT Hook的妙用-對抗ring0 inline hook *
*做者:墮落天才 *
*日期:2007年3月10號 *
*聲明:本文章的目的僅爲技術交流討論 *
*******************************************************
1,SSDT
SSDT即系統服務描述符表,它的結構以下(參考《Undocument Windows 2000 Secretes》第二章):
typedef struct _SYSTEM_SERVICE_TABLE
{
PVOID ServiceTableBase; //這個指向系統服務函數地址表
PULONG ServiceCounterTableBase;
ULONG NumberOfService; //服務函數的個數,NumberOfService*4 就是整個地址表的大小
ULONG ParamTableBase;
}SYSTEM_SERVICE_TABLE,*PSYSTEM_SERVICE_TABLE;
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
SYSTEM_SERVICE_TABLE ntoskrnel; //ntoskrnl.exe的服務函數
SYSTEM_SERVICE_TABLE win32k; //win32k.sys的服務函數,(gdi.dll/user.dll的內核支持)
SYSTEM_SERVICE_TABLE NotUsed1;
SYSTEM_SERVICE_TABLE NotUsed2;
}SYSTEM_DESCRIPTOR_TABLE,*PSYSTEM_DESCRIPTOR_TABLE;
內核中有兩個系統服務描述符表,一個是KeServiceDescriptorTable(由ntoskrnl.exe導出),一個是KeServieDescriptorTableShadow(沒有導出)。二者的區別是,KeServiceDescriptorTable僅有ntoskrnel一項,KeServieDescriptorTableShadow包含了ntoskrnel以及win32k。通常的Native API的服務地址由KeServiceDescriptorTable分派,gdi.dll/user.dll的內核API調用服務地址由KeServieDescriptorTableShadow分派。還有要清楚一點的是win32k.sys只有在GUI線程中才加載,通常狀況下是不加載的,因此要Hook KeServieDescriptorTableShadow的話,通常是用一個GUI程序經過IoControlCode來觸發(想當初不明白這點,藍屏死機了N次都想不明白是怎麼回事)。
2,SSDT HOOK
SSDT HOOK 的原理其實很是簡單,咱們先實際看看KeServiceDescriptorTable是什麼樣的。
lkd> dd KeServiceDescriptorTable
8055ab80 804e3d20 00000000 0000011c 804d9f48
8055ab90 00000000 00000000 00000000 00000000
8055aba0 00000000 00000000 00000000 00000000
8055abb0 00000000 00000000 00000000 00000000
在windbg.exe中咱們就看得比較清楚,KeServiceDescriptorTable中就只有第一項有數據,其餘都是0。其中804e3d20就是
KeServiceDescriptorTable.ntoskrnel.ServiceTableBase,服務函數個數爲0x11c個。咱們再看看804e3d20地址裏是什麼東西:
lkd> dd 804e3d20
804e3d20 80587691 805716ef 8057ab71 80581b5c
804e3d30 80599ff7 80637b80 80639d05 80639d4e
804e3d40 8057741c 8064855b 80637347 80599539
804e3d50 8062f4ec 8057a98c 8059155e 8062661f
如上,80587691 805716ef 8057ab71 80581b5c 這些就是系統服務函數的地址了。好比當咱們在ring3調用OpenProcess時,進入sysenter的ID是0x7A(XP SP2),而後系統查KeServiceDescriptorTable,大概是這樣KeServiceDescriptorTable.ntoskrnel.ServiceTableBase(804e3d20) + 0x7A * 4 = 804E3F08,而後804E3F08 ->8057559e 這個就是OpenProcess系統服務函數所在,咱們再跟蹤看看:
lkd> u 8057559e
nt!NtOpenProcess:
8057559e 68c4000000 push 0C4h
805755a3 6860b54e80 push offset nt!ObReferenceObjectByPointer+0x127 (804eb560)
805755a8 e8e5e4f6ff call nt!InterlockedPushEntrySList+0x79 (804e3a92)
805755ad 33f6 xor esi,esi
原來8057559e就是NtOpenProcess函數所在的起始地址。
嗯,若是咱們把8057559e改成指向咱們函數的地址呢?好比 MyNtOpenProcess,那麼系統就會直接調用MyNtOpenProcess,而不是原來的NtOpenProcess了。這就是SSDT HOOK 原理所在。
3, ring0 inline hook
ring0 inline hook 跟ring3的沒什麼區別了,若是硬說有的話,那麼就是ring3發生什麼差錯的話程序會掛掉,ring0發生什麼差錯的話系統就掛掉,因此必定要很當心。inline hook的基本思想就是在目標函數中JMP到本身的監視函數,作一些判斷而後再JMP回去。通常都是修改函數頭,不過再其餘地方JMP也是能夠的。下面咱們來點實際的吧:
lkd> u nt!NtOpenProcess
nt!NtOpenProcess:
8057559e e95d6f4271 jmp f199c500
805755a3 e93f953978 jmp f890eae7
805755a8 e8e5e4f6ff call nt!InterlockedPushEntrySList+0x79 (804e3a92)
...
同時打開「冰刃」跟「Rootkit Unhooker」咱們就能在NtOpenProcess函數頭看到這樣的「奇觀」,第一個jmp是「冰刃」的,第二個jmp是「Rootkit Unhooker」的。他們這樣是防止被惡意程序經過TerminateProcess關閉。固然「冰刃」還Hook了NtTerminateProcess等函數。
×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
好了,道理就說完了,下面就進入本文正題。
對付ring0 inline hook的基本思路是這樣的,本身寫一個替換的內核函數,以NtOpenProcess爲例,就是MyNtOpenProcess。而後修改SSDT表,讓系統服務進入本身的函數MyNtOpenProcess。而MyNtOpenProcess要作的事就是,實現NtOpenProcess前10字節指令,而後再JMP到原來的NtOpenProcess的十字節後。這樣NtOpenProcess函數頭寫的JMP都失效了,在ring3直接調用OpenProcess再也毫無影響。
***************************************************************************************************************************app
[cpp] view plain copy函數
××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
就這麼多了,或許有人說,不必那麼複雜,直接恢復NtOpenProcess不就好了嗎?對於象「冰刃」「Rookit Unhooker」這些「善良」之輩的話是沒問題的,可是象NP這些「窮兇極惡」之流的話,它會不斷檢測NtOpenProcess是否是已經被寫回去,是的話,嘿嘿,機器立刻重啓。這也是這種方法的一點點妙用。spa