SSDT表概念詳解

SSDT 的全稱是 System Services Descriptor Table,系統服務描述符表windows

這個表就是一個把 Ring3 的 Win32 API 和 Ring0 的內核 API 聯繫起來。Ring3下調用的全部函數最終都會先進入到ntdll裏面的,好比ReadFile,就會進入ntdll的ZwReadFile數組

 

SSDT 並不只僅只包含一個龐大的地址索引表,它還包含着一些其它有用的信息,諸如地址索引的基地址、服務函數個數等。函數

1. //系統服務描述符表-在ntoskrnl.exe中導出KeServiceDescriptorTable這個表  學習

2. #pragma pack(1)  ui

3. typedef struct _ServiceDescriptorTable  this

4. {  spa

5.     //System Service Dispatch Table的基地址  操作系統

6.     PVOID ServiceTableBase;        .net

7.     //SSDT中每一個服務被調用次數的計數器。這個計數器通常由sysenter 更新。  3d

8.     PVOID ServiceCounterTable;   

9.     //由 ServiceTableBase 描述的服務數目。  

10.     unsigned int NumberOfServices;    

11.     //每一個系統服務參數字節數表的基地址-系統服務參數表SSPT   

12.     PVOID ParamTableBase;   

13. }*PServiceDescriptorTable;    

14. #pragma pack()  

 

經過修改此表的函數地址能夠對經常使用 Windows 函數及 API 進行 Hook,從而實現對一些關心的系統動做進行過濾、監控的目的。ZwOpenProcessZwLoadDriver。一些 HIPS、防毒軟件、系統監控、註冊表監控軟件每每會採用此接口來實現本身的監控模塊。

 

在 NT 4.0 以上的 Windows 操做系統中(windows2000),默認就存在兩個系統服務描述表,這兩個調度表對應了兩類不一樣的系統服務,這兩個調度表爲:

SSDT:KeServiceDescriptorTable

 ShadowSSDT:KeServiceDescriptorTableShadow

 

KeServiceDescriptorTable 主要是處理來自 Ring3 層的 Kernel32.dll 中的系統調用

好比函數 OpenProcess、ReadFile 等函數。從kernel32.dll--->ntdll.dll--->進入內核 ntoskrnl.exe(有些機器可能不是這個名字)

 

KeServiceDescriptorTableShadow 則主要處理來自 User32.dll 和 GDI32.dll 中的系統調用

好比常見的PostMessage、SendMessage、FindWindow,Win32k.sys等。

 

不少人必定很奇怪,爲何系統中有不少內核文件 ntoskrnl.exe 、ntkrnlpa.exe,簡單來講就是他們都是同一套源代碼根據編譯選項的不一樣而編譯出四個可執行文件,分別用於: 

ntoskrnl - 單處理器,不支持PAE(物理地址擴展)

ntkrnlpa - 單處理器,支持PAE 

ntkrnlmp - 多處理器,不支持PAE 

ntkrpamp - 多處理器,支持PAE 

 

在Vista以前,安裝程序會在安裝時根據系統的配置選擇兩個多處理器或者兩個單處理器的版本複製到目標系統 system32中。從Vista開始之後,會統一使用多處理器版本,由於多處理器版本運行在單處理器上只是效率稍微低一些。

 

SSDT表已經導出了,經過ntoskrnl.exe的導出表能夠查看到。既然KeServiceDescriptorTable是一個導出的全局變量(數組),那麼咱們來看wrk,你們都知道在編寫代碼的時候,要導出一個函數,一般使用def文件。因此ntoskrnl在編寫的時候,一樣也用到了def來導出導出文件是ntosx86.def,咱們翻看wrk:

 

*********** ntosx86.def-->導出了 KeServiceDescriptorTable CONSTANT ***********

 

有了上面的介紹後,咱們能夠簡單的將 KeServiceDescriptor 看作是一個數組了(其實質也就是個數組),在應用層 ntdll.dll 中的 API 在這個系統服務描述表(SSDT)中都存在一個與之相對應的服務.

 

Ntdll ZwReadFile  111h  

Ntos mov  eax,       111h

 

當咱們的應用程序調用 ntdll.dll 中的 API 時,最終會調用內核中與之相對應的系統服務,因爲有了 SSDT,因此咱們只須要告訴內核須要調用的服務所在 SSDT 中的索引就 OK 了,而後內核根據這個索引值就能夠在 SSDT 中找到相對應的服務了,而後再由內核調用服務完成應用程序 API 的調用請求便可。

 

 

 

 

 

在ntdll下NtQuerySystemInformationZwQuerySystemInformation 的開頭雖然是ntzw兩套函數,實際上是同樣的。咱們看IDA,咱們先看Nt*系列的函數地址:

.text:77F061F8 _NtQuerySystemInformation@16

 

在ntdll中,zw和nt的兩套函數其實他們都是同一個主體

.text:77F061F8 mov   eax, 105h       ; NtQuerySystemInformation

.text:77F061F8         ; RtlGetNativeSystemInformation

.text:77F061FD mov   edx, 7FFE0300h

.text:77F06202 call    dword ptr [edx]

.text:77F06204 retn    10h

 

而後再對比圖片:

 

 

Mode檢查 是 usermode 仍是kernelmode

衆所周知 Ntdll.dll 中的 API 都只不過是一個簡單的包裝函數而已,當 Kernel32.dll 中的 API 經過 Ntdll.dll 時(好比:ReadFile --->ZwReadFile),會完成參數的檢查,再調用一箇中斷(int 2Eh 或者 SysEnter 指令),從而實現從 Ring3 進入 Ring0 層,而且將所要調用的服務號(也就是在 SSDT 數組中的索引值)存放到寄存器 EAX 中 mov  eax, 105h(好比看IDA,而後對比xuetr是否一致:結果吻合),而且將參數地址放到指定的寄存器 EDX 中( mov  edx, 7FFE0300h),再將參數複製到內核地址空間中,再根據存放在 EAX 中的索引值來在 SSDT 數組中調用指定的服務。

 

咱們來看內核下這個函數:

windbg的命令 nt!ZwQuerySystemInformation

 

 nt!ZwQuerySystemInformation:
804ffb1c b8ad000000      mov     eax,0ADh
804ffb21 8d542404          lea     edx,[esp+4]
804ffb25 9c                        pushfd
804ffb26 6a08                   push    8
804ffb28 e854e90300     call    nt!KeReleaseInStackQueuedSpinLockFromDpcLevel+0x95d (8053e481)
804ffb2d c21000               ret     10h
804ffb30 b8ae000000     mov     eax,0AEh
804ffb35 8d542404          lea     edx,[esp+4]

 

能夠看到在 Ring0 下的 ZwQuerySystemInformation 將 105h 放入了寄存器 eax 中,

lkd> ZwQuerySystemInformation

 

nt!ZwQuerySystemInformation:

84456c38 b805010000      mov  eax,105h     //將 105h 放入了寄存器eax中

84456c3d 8d542404          lea    edx,[esp+4]

84456c41 9c                         pushfd

84456c42 6a08                    push   8

84456c44 e835140000      call    nt!KiSystemService (8445807e)

84456c49 c21000                 ret     10h

 

而後調用了系統服務分發函數 KiSystemService,而這個 KiSystemService 函數則是根據 eax 寄存器中的索引值,而後再到SSDT 數組中找到索引值爲eax 寄存器中存放的值的那個 SSDT 項,最後就是根據這個 SSDT 項中所存放的系統服務的地址來調用這個系統函數了。好比在這裏就是調用 KeServiceDescriptorTable[105h] 處所保存的地址所對應的系統服務了也就是調用 Ring0 下的 NtQuerySystemInformation了。

 

說明一下內核中 ZwNt兩套函數的區別

lkd> u ZwQuerySystemInformation

 

nt!ZwQuerySystemInformation:

84456c38 b805010000      mov    eax,105h //將 105h 放入了寄存器 eax 中

84456c3d 8d542404        lea     edx,[esp+4]

84456c41 9c              pushfd

84456c42 6a08            push    8

84456c44 e835140000      call    nt!KiSystemService (8445807e)

84456c49 c21000          ret     10h

 

lkd> u NtQuerySystemInformation  l 10

nt!NtQuerySystemInformation:

8464ae3e 8bff             mov    edi,edi

8464ae40 55              push    ebp

8464ae41 8bec            mov     ebp,esp

8464ae43 8b5508          mov     edx,dword ptr [ebp+8]

8464ae46 83fa53          cmp     edx,53h

8464ae49 7f21            jg      nt!NtQuerySystemInformation+0x2e (8464ae6c)

8464ae4b 7440            je      nt!NtQuerySystemInformation+0x4f (8464ae8d)

主體,就是nt系列函數。

 

因此結論就是:Zw系列函數只是相似一個過渡而Nt系列函數纔是真正的執行主體

至此,在應用層中調用 NtQuerySystemInformation 的所有流程也就結束了 ~

 

 

Ring3!ZwQuerySystemInformation 或者 NtQuerySystemInformation 

進入內核

Ntos 105h ntos!ZwQuerySystemInformation 

接着經過ssdt索引,找到

ntos!NtQuerySystemInformation 執行主體。

 

說了那麼多理論知識,咱們windbg來看下SSDT表的結構:

 

lkd> dd KeServiceDescriptorTable

84583b00  84498d5c 00000000 00000191 844993a4

 

84498d5c 就是SSDT表的起始地址

00000191 就是SSDT表的個數 unsigned int NumberOfServices //這個成員就是個數

 

lkd> dd 84498d5c

84498d5c  84693e78 844db3ad 84623c60 8443f8ba

84498d6c  8469574f 84518306 84705f53 84705f9c

84498d7c  846184af 8471f7c2 84720a17 8460ec87

84498d8c  8469fd8d 846f8ca9 8464bbc0 8461b7c4

84498d9c  845b19ae 846eab84 84602240 84644bcc

84498dac  84691041 845f22bc 8469044e 8460fcfe

84498dbc  846a1814 84612381 846a15f4 84699d4c

84498dcc  846241e8 846e5927 84697119 846a1a46

這些是nt函數的主體:

 

lkd> u  84693e78

nt!NtAcceptConnectPort:

84693e78 8bff            mov     edi,edi

84693e7a 55              push    ebp

84693e7b 8bec            mov     ebp,esp

84693e7d 64a124010000    mov     eax,dword ptr fs:[00000124h]

84693e83 66ff8884000000   dec     word ptr [eax+84h]

84693e8a 56              push    esi

84693e8b 57              push    edi

84693e8c 6a01            push    1

 

所謂主體,就是真正的彙編執行代碼而不是直接的過渡代碼。

 

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

首地址 84498d5c ring0 服務號 105h

 

[Address] = SSDT首地址 +  4 * 索引號

 

ntos!NtQuerySystemInformation = 84498d5c + 4 * 105h = [84499170h]

 

[84499170h] = 8464ae3eh

 

lkd> u 8464ae3e

nt!NtQuerySystemInformation:

8464ae3e 8bff            mov     edi,edi

8464ae40 55             push    ebp

8464ae41 8bec            mov     ebp,esp

8464ae43 8b5508          mov     edx,dword ptr [ebp+8]

8464ae46 83fa53          cmp     edx,53h

8464ae49 7f21            jg      nt!NtQuerySystemInformation+0x2e (8464ae6c)

8464ae4b 7440            je      nt!NtQuerySystemInformation+0x4f (8464ae8d)

8464ae4d 83fa08          cmp     edx,8

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

SSDT表的遍歷詳細代碼示例:http://blog.csdn.net/qq1084283172/article/details/41077983

 

下面也提供一份簡單的SSDT表的遍歷代碼:

 

 
  1. #include <ntifs.h>

  2.  
  3. typedef struct _SERVICE_DESCRIPTOR_TABLE {

  4. /*

  5. * Table containing cServices elements of pointers to service handler

  6. * functions, indexed by service ID.

  7. */

  8. PULONG ServiceTable;

  9. /*

  10. * Table that counts how many times each service is used. This table

  11. * is only updated in checked builds.

  12. */

  13. PULONG CounterTable;

  14. /*

  15. * Number of services contained in this table.

  16. */

  17. ULONG TableSize;

  18. /*

  19. * Table containing the number of bytes of parameters the handler

  20. * function takes.

  21. */

  22. PUCHAR ArgumentTable;

  23. } SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;

  24.  
  25. //ssdt表已經導出了,這裏例行公事下

  26. extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

  27.  
  28. //卸載函數

  29. VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)

  30. {

  31. DbgPrint("卸載完成!\n");

  32. }

  33.  
  34. //入口函數

  35. NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)

  36. {

  37. int i = 0;

  38.  
  39. DriverObject->DriverUnload = DriverUnload;

  40.  
  41. for (i=0;i<KeServiceDescriptorTable->TableSize;i++)

  42. {

  43. DbgPrint("Number:%d Address:0x%08X\r\n\r\n",i, KeServiceDescriptorTable->ServiceTable[i]);

  44. }

  45.  
  46. return STATUS_SUCCESS;

  47. }

 

本文文檔和代碼的下載地址:http://download.csdn.net/detail/qq1084283172/8837431

 

註釋:

學習資料整理於AGP講課資料,感受還不錯。

圖片來源於網上。

相關文章
相關標籤/搜索