SSDT Hook的妙用-對抗ring0 inline hook

*******************************************************
*標題:【原創】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函數

 

  1. #include<ntddk.h>  
  2. typedef struct _SERVICE_DESCRIPTOR_TABLE  
  3. {  
  4.   PVOID   ServiceTableBase;  
  5.   PULONG  ServiceCounterTableBase;  
  6.   ULONG   NumberOfService;  
  7.   ULONG   ParamTableBase;  
  8. }SERVICE_DESCRIPTOR_TABLE,*PSERVICE_DESCRIPTOR_TABLE; //因爲KeServiceDescriptorTable只有一項,這裏就簡單點了  
  9. extern PSERVICE_DESCRIPTOR_TABLE    KeServiceDescriptorTable;//KeServiceDescriptorTable爲導出函數  
  10. /////////////////////////////////////  
  11. VOID Hook();  
  12. VOID Unhook();  
  13. VOID OnUnload(IN PDRIVER_OBJECT DriverObject);  
  14. //////////////////////////////////////  
  15. ULONG JmpAddress;//跳轉到NtOpenProcess裏的地址  
  16. ULONG OldServiceAddress;//原來NtOpenProcess的服務地址  
  17. //////////////////////////////////////  
  18. __declspec(naked) NTSTATUS __stdcall MyNtOpenProcess(PHANDLE ProcessHandle,  
  19.                ACCESS_MASK DesiredAccess,  
  20.                POBJECT_ATTRIBUTES ObjectAttributes,  
  21.                PCLIENT_ID ClientId)   
  22. {  
  23.   DbgPrint("NtOpenProcess() called");  
  24.   __asm{  
  25.     push    0C4h  
  26.     push    804eb560h  //共十個字節  
  27.     jmp     [JmpAddress]       
  28.   }  
  29. }  
  30. ///////////////////////////////////////////////////  
  31. NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)  
  32. {  
  33.   DriverObject->DriverUnload = OnUnload;  
  34.   DbgPrint("Unhooker load");  
  35.   Hook();  
  36.   return STATUS_SUCCESS;  
  37. }  
  38. /////////////////////////////////////////////////////  
  39. VOID OnUnload(IN PDRIVER_OBJECT DriverObject)  
  40. {  
  41.   DbgPrint("Unhooker unload!");  
  42.   Unhook();  
  43. }  
  44. /////////////////////////////////////////////////////  
  45. VOID Hook()  
  46. {  
  47.   ULONG  Address;  
  48.   Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;//0x7A爲NtOpenProcess服務ID  
  49.   DbgPrint("Address:0x%08X",Address);  
  50.   OldServiceAddress = *(ULONG*)Address;//保存原來NtOpenProcess的地址  
  51.   DbgPrint("OldServiceAddress:0x%08X",OldServiceAddress);  
  52.   DbgPrint("MyNtOpenProcess:0x%08X",MyNtOpenProcess);  
  53.   JmpAddress = (ULONG)NtOpenProcess + 10; //跳轉到NtOpenProcess函數頭+10的地方,這樣在其前面寫的JMP都失效了  
  54.   DbgPrint("JmpAddress:0x%08X",JmpAddress);  
  55.       
  56.   __asm{//去掉內存保護  
  57.     cli  
  58.          mov  eax,cr0  
  59.     and  eax,not 10000h  
  60.     mov  cr0,eax  
  61.   }  
  62.   *((ULONG*)Address) = (ULONG)MyNtOpenProcess;//HOOK SSDT  
  63.   __asm{//恢復內存保護    
  64.           mov  eax,cr0  
  65.     or   eax,10000h  
  66.     mov  cr0,eax  
  67.     sti  
  68.   }  
  69. }  
  70. //////////////////////////////////////////////////////  
  71. VOID Unhook()  
  72. {  
  73.   ULONG  Address;  
  74.   Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;//查找SSDT  
  75.   __asm{  
  76.     cli  
  77.           mov  eax,cr0  
  78.     and  eax,not 10000h  
  79.     mov  cr0,eax  
  80.   }  
  81.   *((ULONG*)Address) = (ULONG)OldServiceAddress;//還原SSDT  
  82.   __asm{    
  83.          mov  eax,cr0  
  84.     or   eax,10000h  
  85.     mov  cr0,eax  
  86.     sti  
  87.   }  
  88.   DbgPrint("Unhook");  
  89. }  


××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
    就這麼多了,或許有人說,不必那麼複雜,直接恢復NtOpenProcess不就好了嗎?對於象「冰刃」「Rookit Unhooker」這些「善良」之輩的話是沒問題的,可是象NP這些「窮兇極惡」之流的話,它會不斷檢測NtOpenProcess是否是已經被寫回去,是的話,嘿嘿,機器立刻重啓。這也是這種方法的一點點妙用。spa

相關文章
相關標籤/搜索