*******************************************************
*標題:【原創】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再也毫無影響。
***************************************************************************************************************************
#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為導出函數

/////////////////////////////////////
VOID Hook();
VOID Unhook();
VOID OnUnload(IN PDRIVER_OBJECT DriverObject);
//////////////////////////////////////
ULONG JmpAddress;//跳轉到NtOpenProcess里的地址
ULONG OldServiceAddress;//原來NtOpenProcess的服務地址
//////////////////////////////////////
__declspec(naked) NTSTATUS __stdcall MyNtOpenProcess(PHANDLE ProcessHandle,
??????????????? ACCESS_MASK DesiredAccess,
??????????????? POBJECT_ATTRIBUTES ObjectAttributes,
??????????????? PCLIENT_ID ClientId)
{
?? DbgPrint("NtOpenProcess() called");
?? __asm{
???? push???? 0C4h
???? push???? 804eb560h?? //共十個字節
???? 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服務ID
?? DbgPrint("Address:0x%08X",Address);

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

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

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

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

?? __asm{//恢復內存保護??
?????????? 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");
}
××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
???? 就這么多了,或許有人說,沒必要那么復雜,直接恢復NtOpenProcess不就行了嗎?對于象“冰刃”“Rookit Unhooker”這些“善良”之輩的話是沒問題的,但是象NP這些“窮兇極惡”之流的話,它會不斷檢測NtOpenProcess是不是已經被寫回去, 是的話,嘿嘿,機器馬上重啟。這也是這種方法的一點點妙用。