簡單說說SSDT

關於SSDT,描述得最清楚的應該算《SSDT Hook的妙用-對抗ring0 inline hook》一文了,做者是墮落天才。這裏引用一下他寫的開頭部分,略有個別字符的修改:

內核中有兩個系統服務描述符表,一個是KeServiceDescriptorTable,由ntoskrnl.exe導出,一個是 KeServieDescriptorTableShadow,沒有導出。這二者都是一個結構體,結構下面會給出。他們的區別 是,KeServiceDescriptorTable僅有 ntoskrnel一項,而KeServieDescriptorTableShadow則包含了ntoskrnel和win32k。通常的Native API的服務地址由KeServiceDescriptorTable分派,而gdi.dll和
user.dll的內核API調用服務地址,由 KeServieDescriptorTableShadow分派。還有要清楚一點的是win32k.sys只有在GUI線程中才加載,通常狀況下是不加載的。

他們的結構以下:
代碼:

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;


當系統須要使用一個本機API的時候,就會去查找SYSTEM_DESCRIPTOR_TABLE這個表,也就是由ntoskrnl.exe導出的KeServiceDescriptorTable:

代碼:

nt!RtlpBreakWithStatusInstruction:
80527fc8 cc              int     3
kd> dd KeServiceDescriptorTable
80553380  805021fc 00000000 0000011c 80502670
80553390  00000000 00000000 00000000 00000000
805533a0  00000000 00000000 00000000 00000000
805533b0  00000000 00000000 00000000 00000000
805533c0  00002710 bf80c227 00000000 00000000
805533d0  f9e6da80 f963a9e0 816850f0 806e0f40
805533e0  00000000 00000000 00000000 00000000
805533f0  97c5ac40 01c7abf5 00000000 00000000

能夠看到,KeServiceDescriptorTable的地址是80553380。如今看看這個地址保存的是什麼,由於這個地址的值就是 SYSTEM_SERVICE_TABLE的起始地址。好了,咱們看到這個地址保存的是805021fc,那麼也就是說,系統服務的地址表起始地址爲 805021fc了。看看這個表是些什麼鬼東西:
代碼:

kd> dd 805021fc
805021fc  80599746 805e6914 805ea15a 805e6946
8050220c  805ea194 805e697c 805ea1d8 805ea21c
8050221c  8060b880 8060c5d2 805e1cac 805e1904
8050222c  805ca928 805ca8d8 8060bea6 805ab334
8050223c  8060b4be 8059dbbc 805a5786 805cc406
8050224c  804ffed0 8060c5c4 8056be64 805353f2
8050225c  80604b90 805b19c0 805ea694 80619a56
8050226c  805eeb86 80599e34 80619caa 805996e6

這個過程是這樣的,最開始是SYSTEM_DESCRIPTOR_TABLE(80553380)保存了 SYSTEM_SERVICE_TABLE的地址(805021fc),SYSTEM_SERVICE_TABLE的地址(805021fc)又保存了很 多地址,這個地址就是系統服務的地址了,相似NtOpenProcess這樣的ring0的函數地址。這樣,系統就能夠方便的找到每個ring0函數去 調用。

咱們先看看第一個地址80599746是個什麼函數,反彙編一下:
代碼:

kd> u 80599746
nt!NtAcceptConnectPort:
80599746 689c000000      push    9Ch
8059974b 6820a14d80      push    offset nt!_real+0x128 (804da120)
80599750 e8abebf9ff      call    nt!_SEH_prolog (80538300)
80599755 64a124010000    mov     eax,dword ptr fs:[00000124h]
8059975b 8a8040010000    mov     al,byte ptr [eax+140h]
80599761 884590          mov     byte ptr [ebp-70h],al
80599764 84c0            test    al,al
80599766 0f84b9010000    je      nt!NtAcceptConnectPort+0x1df (80599925)

原來是NtAcceptConnectPort函數,第二個805e6914呢?咱們也看一下,
代碼:

kd> u 805e6914
nt!NtAccessCheck:
805e6914 8bff            mov     edi,edi
805e6916 55              push    ebp
805e6917 8bec            mov     ebp,esp
805e6919 33c0            xor     eax,eax
805e691b 50              push    eax
805e691c ff7524          push    dword ptr [ebp+24h]
805e691f ff7520          push    dword ptr [ebp+20h]
805e6922 ff751c          push    dword ptr [ebp+1Ch]

原來是NtAccessCheck函數。

這樣咱們能夠清楚的看到,在這個起始地址爲0x805021fc的表中,保存了各個ring0函數的地址。下面我來作個簡單的比喻。

從前有一個很大的幫派,名字叫作Windows,功能不少而且很強大。由於這些各方面的能力由各個專人負責,他們一我的作一件事情。隨着人員增 多,幫主發現聯繫起來愈來愈困了。有一天幫主要找居然NtOpenProcess來調查一下他的一個手下是否是別的幫派派來的間諜,可是他發現 NtOpenProcess跑不見了。

因而軍師就想出了一個好辦法來解決這個問題:先創建一個封閉的密室,這個密室只有八袋長老以上的人才能進去。密室中間有一張紙條,上面寫着一個地 址——溫家堡,還有這個地址放着多少人的聯繫信息等內容。這個密室就是Ntdll.dll,這個紙條就是 SYSTEM_DESCRIPTOR_TABLE,上寫的地址就是SYSTEM_SERVICE_TABLE,也就是溫家堡了。這個溫家堡是一個有不少大 房間的地方,每一個房子有個房間號
,房間裏面又放着一張紙條,上面寫着各個手下的住所。好比說編號爲7A的房間,裏面放的是NtOpenProcess的家庭住址。

這樣一來,幫主要找人就容易了。先去密室找到紙條,看看上面寫的是溫家堡仍是白雲城,那個地方有多少我的的聯繫信息等。若是是溫家堡就跑到那裏 去,看看要找誰,找NtOpenProcess就去7A房間。在這個房間裏一看,啊,裏面寫着NtOpenProcess如今就住在密室的旁邊……搞定。

這裏就有一個新的問題,幫主假設這個裏面寫的東西都是正確的,沒有被人改過。因而就有了別派的間諜發現了,偷偷溜進密室,而後根據紙條的內容,又 跑到溫家堡。進到7A房間,神不知鬼不覺的把裏面記錄的NtOpenProcess的地址改爲了本身的家。因而,幫主再找人,發現找到對頭家裏去了。這個 就是傳說中的SSDT Hook了。

攻擊者進入ring0以後,找到KeServiceDescriptorTable地址的值,即SYSTEM_SERVICE_TABLE的地址 (進入密室,找到紙條寫的地址——溫家堡)。而後改寫SYSTEM_SERVICE_TABLE中一個特定函數的地址爲本身定義的函數入口處,截獲了系統 調用(來到溫家堡,改掉7A房間裏面寫的住所,改爲本身家)。一次HOOK就完成了。

下面我給一段簡單的代碼,演示怎麼樣讓一個特定的PID不會被殺死。這段代碼基本和《SSDT Hook的妙用-對抗ring0 inline hook》一文同樣,我只是註釋了一下而已,另外在MyNtOpenProcess處加了個判斷是否是某個特定PID的功能。
代碼:


/* 演示HOOK系統服務調用表中的NtOpenProcess函數,保護須要保護的進程被,防止被殺掉 */ #include /* KeServiceDescriptorTable僅有ntoskrnel一項,沒有包含win32k,並且後面的兩個字段都沒有使用,所 覺得了簡便直接把SystemServiceDescriptorTable定義成SYSTEM_SERVICE_TABLE,省得訪問多個結構體的 字段,麻煩。這裏明白就好了。 */ typedef struct _SystemServiceDescriptorTable {     PVOID    ServiceTableBase;     PULONG    ServiceCounterTableBase;     ULONG    NumberOfService;     ULONG    ParamTableBase; }SystemServiceDescriptorTable,*PSystemServiceDescriptorTable; // KeServiceDescriptorTable爲ntoskrnl.exe導出 extern    PSystemServiceDescriptorTable    KeServiceDescriptorTable; // 定義一下NtOpenProcess的原型,下面若是用匯編調用就不用定義了,可是我想盡可能不用匯編 typedef    NTSTATUS    (__stdcall *NTOPENPROCESS)( OUT PHANDLE ProcessHandle,                                                  IN ACCESS_MASK AccessMask,                                                  IN POBJECT_ATTRIBUTES ObjectAttributes,                                                  IN PCLIENT_ID ClientId                                                  ); NTOPENPROCESS    RealNtOpenProcess; // 定義函數原型 VOID Hook(); VOID Unhook(); VOID OnUnload(IN PDRIVER_OBJECT DriverObject); // 真實的函數地址,咱們會在自定義的函數中調用 ULONG    RealServiceAddress; // 須要被驅動保護的進程ID HANDLE    MyPID; // 自定義的NtOpenProcess函數 NTSTATUS __stdcall MyNtOpenProcess( OUT    PHANDLE ProcessHandle,                     IN    ACCESS_MASK DesiredAccess,                     IN    POBJECT_ATTRIBUTES ObjectAttributes,                     IN    PCLIENT_ID ClientId ) {     NTSTATUS    rc;     ULONG        PID;          //DbgPrint( "NtOpenProcess() called.\n" );          rc = (NTSTATUS)(NTOPENPROCESS)RealNtOpenProcess( ProcessHandle, DesiredAccess, ObjectAttributes, ClientId );          if( (ClientId != NULL) )     {         PID = (ULONG)ClientId->UniqueProcess;         //DbgPrint( "%d was opened,Handle is %d.\n", PID, (ULONG)ProcessHandle );                  // 若是進程PID是1520,直接返回權限不足,並將句柄設置爲空         if( PID == 1520 )         {             DbgPrint( "Some want to open pid 1520!\n" );                          ProcessHandle = NULL;                                      rc = STATUS_ACCESS_DENIED;         }     }          return rc; } // 驅動入口 NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath ) {     DriverObject->DriverUnload = OnUnload;     Hook();          return STATUS_SUCCESS; } // 驅動卸載 VOID OnUnload(IN PDRIVER_OBJECT DriverObject) {     Unhook( ); } //  此處修改SSDT中的NtOpenProcess服務地址 VOID Hook() {     ULONG            Address;          // 0x7A爲Winxp+SP2下NtOpenProcess服務ID號     // Adress是個地址A,這個地址的數據仍是一個地址B,這個地址B就是NtOpenProcess的地址了     // (ULONG)KeServiceDescriptorTable->ServiceTableBase就是溫家堡的第一個房間     // Address是第7A個房間。     Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;     // 取得地址A的值,也就是NtOpenProcess服務的地址了,保存原來NtOpenProcess的地址之後恢 複用     RealServiceAddress = *(ULONG*)Address;          RealNtOpenProcess = (NTOPENPROCESS)RealServiceAddress;          DbgPrint( "Address of Real NtOpenProcess: 0x%08X\n", RealServiceAddress );     DbgPrint(" Address of MyNtOpenProcess: 0x%08X\n", MyNtOpenProcess );     // 去掉內存保護     __asm     {         cli         mov    eax, cr0         and    eax, not 10000h         mov    cr0, eax     }          // 修改SSDT中NtOpenProcess服務的地址    *((ULONG*)Address) = (ULONG)MyNtOpenProcess;     // 恢復內存保護     __asm     {         mov    eax, cr0         or    eax, 10000h         mov    cr0, eax         sti     } } ////////////////////////////////////////////////////// VOID Unhook() {    ULONG   Address;    Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;     __asm     {         cli         mov    eax, cr0         and    eax, not 10000h         mov    cr0, eax     }     // 還原SSDT     *((ULONG*)Address) = (ULONG)RealServiceAddress;          __asm     {         mov    eax, cr0         or    eax, 10000h         mov    cr0, eax         sti     }     DbgPrint("Unhook"); }
相關文章
相關標籤/搜索