轉:SSDT Hook的妙用-對抗ring0 inline hook

標 題: 【原創】SSDT Hook的妙用-對抗ring0 inline hook
作 者: 墮落天才
時 間: 2007-03-10,15:18:32
鏈 接: http://bbs.pediy.com/showthread.php?t=40832

*******************************************************
*標題:【原創】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是不是已經被寫回去,是的話,嘿嘿,機器馬上重啓。這也是這種方法的一點點妙用。

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