SSDT Hook實現內核級的進程保護


目錄

  1. SSDT Hook效果圖
  2. SSDT簡介
  3. SSDT結構
  4. SSDT HOOK原理
  5. Hook前准備
  6. 如何獲得SSDT中函數的地址呢
  7. SSDT Hook流程
  8. SSDT Hook實現進程保護
  9. Ring3與Ring0的通信
  10. 如何安裝啟動停止卸載服務
  11. 參考文獻
  12. 源碼附件
  13. 版權

SSDT Hook效果圖

加載驅動並成功Hook  NtTerminateProcess函數:
當對 指定的進程進行保護后,嘗試使用“任務管理器”結束進程的時候,會彈出“拒絕訪問”的窗口,說明,我們的目的已經達到:

SSDT簡介

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

 

這個表就是一個把 Ring3 的 Win32 API 和 Ring0 的內核 API 聯系起來。

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

通過修改此表的函數地址可以對常用 Windows 函數及 API 進行 Hook,從而實現對一些關心的系統動作進行過濾、監控的目的。

一些 HIPS、防毒軟件、系統監控、注冊表監控軟件往往會采用此接口來實現自己的監控模塊。

 

SSDT結構

SSDT即系統服務描述符表,它的結構如下(參考《Undocument Windows 2000 Secretes》第二章):
// KSYSTEM_SERVICE_TABLE 和 KSERVICE_TABLE_DESCRIPTOR
// 用來定義 SSDT 結構
typedef struct _KSYSTEM_SERVICE_TABLE
{
    PULONG  ServiceTableBase;                               // SSDT (System Service Dispatch Table)的基地址
    PULONG  ServiceCounterTableBase;                        // 用於 checked builds, 包含 SSDT 中每個服務被調用的次數
    ULONG   NumberOfService;                                // 服務函數的個數, NumberOfService * 4 就是整個地址表的大小
    ULONG   ParamTableBase;                                 // SSPT(System Service Parameter Table)的基地址
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
    KSYSTEM_SERVICE_TABLE   ntoskrnl;                       // ntoskrnl.exe 的服務函數
    KSYSTEM_SERVICE_TABLE   win32k;                         // win32k.sys 的服務函數(GDI32.dll/User32.dll 的內核支持)
    KSYSTEM_SERVICE_TABLE   notUsed1;
    KSYSTEM_SERVICE_TABLE   notUsed2;
}KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
    內核中有兩個系統服務描述符表,一個是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次都想不明白是怎么回事)。

SSDT HOOK原理

關於內核 Hook 有多種類型,下面也給出一副圖示:
SSDT HOOK只是其中一種Hook技術,本篇文章主要講解SSDT Hook的使用。
SSDT HOOK原理圖
通過Kernel Detective工具,我們可以發現,SSDT Hook前后,NtTerminateProcess的當前地址會發生變化,其中,變化后的當前地址:0xF885A110為我們自定義的Hook函數(即:HookNtTerminateProcess)的地址。這樣,以后每次執行NtTerminateProcess的時候,就會根據執行“當前地址”所指向的函數了,這也就是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 

  如上,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 原理所在。
另外,關於Ring3層轉入Ring0層的具體流程,可以參考下我的這篇博文,對加深理解SSDT Hook技術還是有幫助的: Ring3轉入Ring0跟蹤

Hook前准備

我們要修改SSDT表,首先這個表必須是可寫的,但在xp以后的系統中他都是只讀的,三個辦法來修改內存保護機制
(1) 更改注冊表 
恢復頁面保護:HKLM\SYSTEM\CurrentControlset\Control\Session Manger\Memory Management\EnforceWriteProtection=0
去掉頁面保護:HKLM\SYSTEM\CurrentControlset\Control\Session Manger\Memory Management\DisablePagingExecutive=1
(2)改變CR0寄存器的第1位
Windows對內存的分配,是采用的分頁管理。其中有個CR0寄存器,如下圖:
其中第1位叫做保護屬性位,控制着頁的讀或寫屬性。如果為1,則可以讀/寫/執行;如果為0,則只可以讀/執行。
SSDT,IDT的頁屬性在默認下都是只讀,可執行的,但不能寫。
代碼如下:
//設置為不可寫
void DisableWrite()
{
    __try
    {
        _asm
        {
            mov eax, cr0 
            or  eax, 10000h 
            mov cr0, eax 
            sti 
        }
    }
    __except(1)
    {
        DbgPrint("DisableWrite執行失敗!");
    }
}
// 設置為可寫
void EnableWrite()
{
    __try
    {
        _asm
        {
            cli
            mov eax,cr0
            and eax,not 10000h //and eax,0FFFEFFFFh
            mov cr0,eax
        }
    }
    __except(1)
    {
        DbgPrint("EnableWrite執行失敗!");
    }
}
(3)通過Memory Descriptor List(MDL)

具體做法可以google下,這里就不介紹了

 

如何獲得SSDT中函數的地址呢?

  這里主要使用了兩個宏:

①獲取指定服務的索引號:SYSCALL_INDEX

②獲取指定服務的當前地址:SYSCALL_FUNCTION

這兩個宏的具體定義如下:

//根據 ZwServiceFunction 獲取 ZwServiceFunction 在 SSDT 中所對應的服務的索引號 #define SYSCALL_INDEX(ServiceFunction) (*(PULONG)((PUCHAR)ServiceFunction + 1)) //根據ZwServiceFunction 來獲得服務在 SSDT 中的索引號,然后再通過該索引號來獲取ntServiceFunction的地址 #define SYSCALL_FUNCTION(ServiceFunction) KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[SYSCALL_INDEX(ServiceFunction)]

SSDT Hook流程

在驅動的入口函數中(DriverEntry),對未進行SSDT Hook前的SSDT表進行了備份(用一個數組保存),備份時,一個索引號對應一個當前地址,如上圖所示。

這樣,在解除Hook的時候,就可以從全局數組中根據索引號獲取未Hook前的服務名的當前地址,以便將原來的地址寫回去,這一步很重要。

當用戶選擇保護某個進程的時候,就會通過DeviceIoControl發送一個IO_INSERT_PROTECT_PROCESS控制碼給驅動程序,此時驅動程序會生成一個IRP:IRP_MJ_DEVICE_CONTROL,我們事先已經在驅動程序中為

IRP_MJ_DEVICE_CONTROL指定了一個派遣函數:SSDTHook_DispatchRoutine_CONTROL。在該派遣函數中:我們通過獲取控制碼(是保護進程還是取消保護進程),如果是要保護某個進程,則通過
DeviceIoControl的第3個參數將要保護的進程的pid傳遞給驅動程序。然后在派遣函數SSDTHook_DispatchRoutine_CONTROL中從緩沖區中讀取該pid,如果是要保護進程,則將要“保護進程”的pid添加到一個數組中,如果是要“取消保護進程”,則將要取消保護的進程PID從數組中移除。
在Hook NtTermianteProcess函數后,會執行我們自定義的函數:HookNtTerminateProcess,在HookNtTerminateProcess函數中,我們判斷當前進程是否在要保護的進程數組中,如果該數組中存在該pid,則我們返回一個“權限不夠”的異常,如果進程保護數組中不存在該pid,則直接調用原來 SSDT 中的 NtTerminateProcess 來結束進程。

SSDT Hook實現進程保護

有了上面的理論基礎之后,接下來可以談談SSDT Hook實現進程保護的具體實現了。
實現進程保護,可以Hook NtTermianteProcess,另外也可以Hook NtOpenProcess,這里,我是Hook NtTermianteProcess。
SSDT Hook原理一節中已經說過,SSDT Hook原理的本質是:自定義一個函數(HookNtTerminateProcess),讓系統服務NtTermianteProcess的當前地址指向我們自定義函數地址。
這一步工作是在驅動入口函數中執行的。當驅動加載的時候,將自定義函數的地址寫入SSDT表中NtTermianteProcess服務的當前地址:
// 實現 Hook 的安裝,主要是在 SSDT 中用 newService 來替換掉 oldService
NTSTATUS InstallHook(ULONG oldService, ULONG newService)
{
    __try
    {
        ULONG uOldAttr = 0;        
        EnableWrite();    //去掉頁面保護    
        KdPrint(("偽造NtTerminateProcess地址: %x\n",(int)newService));
        //KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[SYSCALL_INDEX(oldService)] = newService;
        SYSCALL_FUNCTION(oldService) = newService;//
        DisableWrite();    //恢復頁面保護
        return STATUS_SUCCESS;
    }
    __except(1)
    {
        KdPrint(("安裝Hook失敗!"));
    }
}
這里需要注意的是:在Hook前,需要去掉內存的頁面保護屬性,Hook后,需要回復內存的頁面保護屬性。
HookNtTerminateProcess函數的代碼如下:
//************************************
// 函數名稱 : HookNtTerminateProcess
// 描    述 : 自定義的 NtOpenProcess,用來實現 Hook Kernel API
// 日    期 : 2013/06/28
// 參    數 : ProcessHandle:進程句柄 ExitStatus:
// 返 回 值 : 
//************************************
NTSTATUS HookNtTerminateProcess(__in_opt HANDLE ProcessHandle,__in NTSTATUS ExitStatus)
{
    ULONG uPID;
    NTSTATUS rtStatus;
    PCHAR pStrProcName;
    PEPROCESS pEProcess;
    ANSI_STRING strProcName;

    // 通過進程句柄來獲得該進程所對應的 FileObject 對象,由於這里是進程對象,自然獲得的是 EPROCESS 對象
    rtStatus = ObReferenceObjectByHandle(ProcessHandle, FILE_READ_DATA, NULL, KernelMode, (PVOID*)&pEProcess, NULL);
    if (!NT_SUCCESS(rtStatus))
    {
        return rtStatus;
    }
    // 保存 SSDT 中原來的 NtTerminateProcess 地址
    pOldNtTerminateProcess = (NTTERMINATEPROCESS)oldSysServiceAddr[SYSCALL_INDEX(ZwTerminateProcess)];

    // 通過該函數可以獲取到進程名稱和進程 ID,該函數在內核中實質是導出的(在 WRK 中可以看到)
    // 但是 ntddk.h 中並沒有到處,所以需要自己聲明才能使用
    uPID = (ULONG)PsGetProcessId(pEProcess);
    pStrProcName = _strupr((TCHAR *)PsGetProcessImageFileName(pEProcess));//使用微軟未公開的PsGetProcessImageFileName函數獲取進程名

    // 通過進程名來初始化一個 ASCII 字符串
    RtlInitAnsiString(&strProcName, pStrProcName);

    if (ValidateProcessNeedProtect(uPID) != -1)
    {
        // 確保調用者進程能夠結束(這里主要是指 taskmgr.exe)
        if (uPID != (ULONG)PsGetProcessId(PsGetCurrentProcess()))
        {
            // 如果該進程是所保護的的進程的話,則返回權限不夠的異常即可
            return STATUS_ACCESS_DENIED;
        }
    }
    // 對於非保護的進程可以直接調用原來 SSDT 中的 NtTerminateProcess 來結束進程
    rtStatus = pOldNtTerminateProcess(ProcessHandle, ExitStatus);
    return rtStatus;
}

Ring3與Ring0的通信

請看考:張帆《Windows驅動開發技術詳解》一書第7章:派遣函數

如何安裝、啟動、停止、卸載服務

請參考我的另一篇博文: NT式驅動加載器(附帶源碼)

參考文獻

SSDT Hook的妙用-對抗ring0 inline hook:http://bbs.pediy.com/showthread.php?t=40832
進程隱藏與進程保護(SSDT Hook 實現):http://www.cnblogs.com/BoyXiao/archive/2011/09/03/2164574.html
張帆的《Windows驅動開發技術詳解》一書

源碼附件

版權

  版權所有, 迎轉載,但轉載請注明: 轉載自   曾是土木人


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM