*******************************************************
*標(biāo)題:【原創(chuàng)】SSDT Hook的妙用-對抗ring0 inline hook?? *
*作者:墮落天才???????????????????????????????????????? *
*日期:2007年3月10號??????????????????????????????????? *
*聲明:本文章的目的僅為技術(shù)交流討論???????????????????? *
*******************************************************

1,SSDT
????? SSDT即系統(tǒng)服務(wù)描述符表,它的結(jié)構(gòu)如下(參考《Undocument Windows 2000 Secretes》第二章):
????? typedef struct _SYSTEM_SERVICE_TABLE
????? {
??????? PVOID??? ServiceTableBase;???????? //這個指向系統(tǒng)服務(wù)函數(shù)地址表
??????? PULONG?? ServiceCounterTableBase;
??????? ULONG??? NumberOfService;????????? //服務(wù)函數(shù)的個數(shù),NumberOfService*4 就是整個地址表的大小
??????? ULONG??? ParamTableBase;
????? }SYSTEM_SERVICE_TABLE,*PSYSTEM_SERVICE_TABLE;???
?????
????? typedef struct _SERVICE_DESCRIPTOR_TABLE
????? {
??????? SYSTEM_SERVICE_TABLE??? ntoskrnel;?? //ntoskrnl.exe的服務(wù)函數(shù)
??????? SYSTEM_SERVICE_TABLE??? win32k;????? //win32k.sys的服務(wù)函數(shù),(gdi.dll/user.dll的內(nèi)核支持)
??????? SYSTEM_SERVICE_TABLE??? NotUsed1;
??????? SYSTEM_SERVICE_TABLE??? NotUsed2;
????? }SYSTEM_DESCRIPTOR_TABLE,*PSYSTEM_DESCRIPTOR_TABLE;
?????
????? 內(nèi)核中有兩個系統(tǒng)服務(wù)描述符表,一個是KeServiceDescriptorTable(由ntoskrnl.exe導(dǎo)出),一個是 KeServieDescriptorTableShadow(沒有導(dǎo)出)。兩者的區(qū)別是,KeServiceDescriptorTable僅有 ntoskrnel一項,KeServieDescriptorTableShadow包含了ntoskrnel以及win32k。一般的Native API的服務(wù)地址由KeServiceDescriptorTable分派,gdi.dll/user.dll的內(nèi)核API調(diào)用服務(wù)地址由 KeServieDescriptorTableShadow分派。還有要清楚一點的是win32k.sys只有在GUI線程中才加載,一般情況下是不加 載的,所以要Hook KeServieDescriptorTableShadow的話,一般是用一個GUI程序通過IoControlCode來觸發(fā)(想當(dāng)初不明白這點,藍 屏死機了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中就只有第一項有數(shù)據(jù),其他都是0。其中804e3d20就是
KeServiceDescriptorTable.ntoskrnel.ServiceTableBase,服務(wù)函數(shù)個數(shù)為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 這些就是系統(tǒng)服務(wù)函數(shù)的地址了。比如當(dāng)我們在ring3調(diào)用OpenProcess時,進入sysenter的ID是0x7A(XP SP2),然后系統(tǒng)查KeServiceDescriptorTable,大概是這樣 KeServiceDescriptorTable.ntoskrnel.ServiceTableBase(804e3d20) + 0x7A * 4 = 804E3F08,然后804E3F08 ->8057559e 這個就是OpenProcess系統(tǒng)服務(wù)函數(shù)所在,我們再跟蹤看看:
???? 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函數(shù)所在的起始地址。??
???? 嗯,如果我們把8057559e改為指向我們函數(shù)的地址呢?比如 MyNtOpenProcess,那么系統(tǒng)就會直接調(diào)用MyNtOpenProcess,而不是原來的NtOpenProcess了。這就是SSDT HOOK 原理所在。

?? 3, ring0 inline hook
????? ring0 inline hook 跟ring3的沒什么區(qū)別了,如果硬說有的話,那么就是ring3發(fā)生什么差錯的話程序會掛掉,ring0發(fā)生什么差錯的話系統(tǒng)就掛掉,所以一定要很小 心。inline hook的基本思想就是在目標(biāo)函數(shù)中JMP到自己的監(jiān)視函數(shù),做一些判斷然后再JMP回去。一般都是修改函數(shù)頭,不過再其他地方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函數(shù)頭看到這樣的“奇觀”,第一個jmp是“冰刃”的,第二個jmp是“Rootkit Unhooker”的。他們這樣是防止被惡意程序通過TerminateProcess關(guān)閉。當(dāng)然“冰刃”還Hook了 NtTerminateProcess等函數(shù)。

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
???? 好了,道理就說完了,下面就進入本文正題。
???? 對付ring0 inline hook的基本思路是這樣的,自己寫一個替換的內(nèi)核函數(shù),以NtOpenProcess為例,就是MyNtOpenProcess。然后修改SSDT表, 讓系統(tǒng)服務(wù)進入自己的函數(shù)MyNtOpenProcess。而MyNtOpenProcess要做的事就是,實現(xiàn)NtOpenProcess前10字節(jié)指 令,然后再JMP到原來的NtOpenProcess的十字節(jié)后。這樣NtOpenProcess函數(shù)頭寫的JMP都失效了,在ring3直接調(diào)用 OpenProcess再也毫無影響。
***************************************************************************************************************************
#include<ntddk.h>

typedef struct _SERVICE_DESCRIPTOR_TABLE
{
?? PVOID??? ServiceTableBase;
?? PULONG?? ServiceCounterTableBase;
?? ULONG??? NumberOfService;
?? ULONG??? ParamTableBase;
}SERVICE_DESCRIPTOR_TABLE,*PSERVICE_DESCRIPTOR_TABLE; //由于KeServiceDescriptorTable只有一項,這里就簡單點了
extern PSERVICE_DESCRIPTOR_TABLE???? KeServiceDescriptorTable;//KeServiceDescriptorTable為導(dǎo)出函數(shù)

/////////////////////////////////////
VOID Hook();
VOID Unhook();
VOID OnUnload(IN PDRIVER_OBJECT DriverObject);
//////////////////////////////////////
ULONG JmpAddress;//跳轉(zhuǎn)到NtOpenProcess里的地址
ULONG OldServiceAddress;//原來NtOpenProcess的服務(wù)地址
//////////////////////////////////////
__declspec(naked) NTSTATUS __stdcall MyNtOpenProcess(PHANDLE ProcessHandle,
??????????????? ACCESS_MASK DesiredAccess,
??????????????? POBJECT_ATTRIBUTES ObjectAttributes,
??????????????? PCLIENT_ID ClientId)
{
?? DbgPrint("NtOpenProcess() called");
?? __asm{
???? push???? 0C4h
???? push???? 804eb560h?? //共十個字節(jié)
???? jmp????? [JmpAddress]?????
?? }
}
///////////////////////////////////////////////////
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
{
?? DriverObject->DriverUnload = OnUnload;
?? DbgPrint("Unhooker load");
?? Hook();
?? return STATUS_SUCCESS;
}
/////////////////////////////////////////////////////
VOID OnUnload(IN PDRIVER_OBJECT DriverObject)
{
?? DbgPrint("Unhooker unload!");
?? Unhook();
}
/////////////////////////////////////////////////////
VOID Hook()
{
?? ULONG?? Address;
?? Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;//0x7A為NtOpenProcess服務(wù)ID
?? DbgPrint("Address:0x%08X",Address);

?? OldServiceAddress = *(ULONG*)Address;//保存原來NtOpenProcess的地址
?? DbgPrint("OldServiceAddress:0x%08X",OldServiceAddress);

?? DbgPrint("MyNtOpenProcess:0x%08X",MyNtOpenProcess);

?? JmpAddress = (ULONG)NtOpenProcess + 10; //跳轉(zhuǎn)到NtOpenProcess函數(shù)頭+10的地方,這樣在其前面寫的JMP都失效了
?? DbgPrint("JmpAddress:0x%08X",JmpAddress);
????
?? __asm{//去掉內(nèi)存保護
???? cli
????????? mov?? eax,cr0
???? and?? eax,not 10000h
???? mov?? cr0,eax
?? }

?? *((ULONG*)Address) = (ULONG)MyNtOpenProcess;//HOOK SSDT

?? __asm{//恢復(fù)內(nèi)存保護??
?????????? mov?? eax,cr0
???? or??? eax,10000h
???? mov?? cr0,eax
???? sti
?? }
}
//////////////////////////////////////////////////////
VOID Unhook()
{
?? ULONG?? Address;
?? Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;//查找SSDT

?? __asm{
???? cli
?????????? mov?? eax,cr0
???? and?? eax,not 10000h
???? mov?? cr0,eax
?? }

?? *((ULONG*)Address) = (ULONG)OldServiceAddress;//還原SSDT

?? __asm{??
????????? mov?? eax,cr0
???? or??? eax,10000h
???? mov?? cr0,eax
???? sti
?? }

?? DbgPrint("Unhook");
}
××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
???? 就這么多了,或許有人說,沒必要那么復(fù)雜,直接恢復(fù)NtOpenProcess不就行了嗎?對于象“冰刃”“Rookit Unhooker”這些“善良”之輩的話是沒問題的,但是象NP這些“窮兇極惡”之流的話,它會不斷檢測NtOpenProcess是不是已經(jīng)被寫回去, 是的話,嘿嘿,機器馬上重啟。這也是這種方法的一點點妙用。