Rookit技術之SSDT_Hook

Rookit技術之SSDT_Hook

0x0 SSDT簡介

SSDT全稱爲System Services Descriptor Table,中文爲系統服務描述符表,ssdt表就是把ring3的Win32 API和ring0的內核API聯繫起來。SSDT並不僅僅只包含一個龐大的地址索引表,它還包含着一些其它有用的信息,諸如地址索引的基地址、服務函數個數等。SSDT通過修改此表的函數地址可以對常用windows函數進行hook,從而實現對一些核心的系統動作進行過濾、監控的目的。一些HIPS、防毒軟件、系統監控、註冊表監控軟件往往會採用此接口來實現自己的監控模塊(引用百度)

SSDT_Hook即掛鉤系統服務描述表中函數.這是一種古老的技術,相傳是在病毒中首次使用這樣的技術.惡意軟件爲了提高自己的權限,來進行底層的掛鉤,於是SSDT這個表被利用了.
我們知道,用戶層調用函數都會調用到ntdll.dll中,其實這個ntdll.dll就是用戶層到內核層的接口.裏邊維護着兩張表-KeServiceDescriptorTable 和KeServiceDescriptorTableShadow.
前者簡稱SSDT,其中是函數地址數組,多數操作系統進程相關的函數都在其中,後者簡稱shadowSSDT,是用戶界面相關的函數地址數組,比如來自user32.dll或者GDI32.dll的函數

0x1 測試環境

系統: 虛擬機 Windows 7 32bit
工具: Windbg,驅動加載工具等

這個技術可以穩定的在windows 32位的系統下進行,32位系統其實被各種Hook技術被用爛了,所以現在在64位上的Hook技術被微軟限制了

0x2 達到目標

Hook NtOpenProcess函數,使我們的目標程序(計算器程序)不能被其他任務使用這個函數獲取句柄,結束這個程序.

0x3 主要思路

1.加載驅動,獲取需要保護進程的PID
2.保存系統的老的NtOpenProcess函數地址
3.修改內存保護屬性使得SSDT表可讀可寫
4.修改SSDT表中NtOpenProcess函數處的地址爲我們自己寫的函數的地址,調用時候執行我們的函數
5.函數中做判斷,如果是我們要保護的進程就直接返回,不讓其獲得句柄,如果不是,就主動調用老的系統NtOpenProcess函數
6.驅動卸載,還原被Hook的地址

0x4 關鍵代碼

#include <ntddk.h>


//SSDT HOOK
//Hook  NtOpenProcess 使進程對象不會再次被打開

//SSDT表結構體
typedef struct _ServiceDesriptorEntry
{
    ULONG *ServiceTableBase;        // 服務表基址
    ULONG *ServiceCounterTableBase; // 計數表基址
    ULONG NumberOfServices;         // 表中項的個數
    UCHAR *ParamTableBase;          // 參數表基址
}SSDTEntry, *PSSDTEntry;

// 導入SSDT
NTSYSAPI SSDTEntry KeServiceDescriptorTable;

//被保護程序的PID
ULONG g_uProtectPID = 0;

//OpenProcess函數原型
typedef NTSTATUS(NTAPI *NTOPENPROCESS)(__out PHANDLE  ProcessHandle,
    __in ACCESS_MASK  DesiredAccess,
    __in POBJECT_ATTRIBUTES  ObjectAttributes,
    __in_opt PCLIENT_ID  ClientId
    );

//老的OpenProcess函數地址
NTOPENPROCESS g_OldOpenProcess = NULL;


//自己的HookNtProcess函數
NTSTATUS NTAPI HookNtOpenProcess(__out PHANDLE  ProcessHandle,
    __in ACCESS_MASK  DesiredAccess,
    __in POBJECT_ATTRIBUTES  ObjectAttributes,
    __in_opt PCLIENT_ID  ClientId)
{
    //判斷是不是要針對的進程PID
    if ((ULONG)ClientId->UniqueProcess == g_uProtectPID)
    {
        return STATUS_ABANDONED;
    }
    //過濾完成再次調用原來的NtOpenprocess
    return g_OldOpenProcess(
        ProcessHandle,
        DesiredAccess,
        ObjectAttributes,
        ClientId
    );
}

//關閉內存保護裸函數
void _declspec(naked)OffMemoryProtect()
{
    __asm { //關閉內存保護
        push eax;
        mov eax, cr0;
        and eax, ~0x10000;
        mov cr0, eax;
        pop eax;
        ret;
    }
}
//開啓內存保護裸函數
void  _declspec(naked)OnMemoryProtect()
{
    __asm { //恢復內存保護
        push eax;
        mov eax, cr0;
        or eax, 0x10000;
        mov cr0, eax;
        pop eax;
        ret;
    }
}

//開啓Hook
void OnHook()
{
    //保存環境,OpenProcess函數在SSDT表中的第0xBE項(190)
    g_OldOpenProcess= (NTOPENPROCESS)KeServiceDescriptorTable.ServiceTableBase[0xBE];
    //開始Hook前需要修改內存屬性,修改完地址後恢復內存屬性
    OffMemoryProtect();
    KeServiceDescriptorTable.ServiceTableBase[0xBE] = (ULONG)HookNtOpenProcess;
    OnMemoryProtect();
}

//關閉Hook
void OffHook()
{
    //修改地址前,先修改內存屬性,改完後還原內存屬性
    OffMemoryProtect();
    KeServiceDescriptorTable.ServiceTableBase[0xBE] = (ULONG)g_OldOpenProcess;
    OnMemoryProtect();
}


//驅動卸載函數
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
    OffHook();
    DbgPrint("Driver Unload\n");
}


//驅動入口函數
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath)
{
    //DbgBreakPoint();
    DbgPrint("Hello World\n");
    g_uProtectPID = 4000;
    //開啓Hook
    OnHook();
    pDriver->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

0x5 測試效果

爲了簡單,直接在驅動中寫死了PID,所以我們在任務管理器中查看其PID,然後在寫到驅動中.
這裏寫圖片描述
加載驅動,使用任務管理器結束”計算器”程序
這裏寫圖片描述
這裏寫圖片描述
卸載驅動,再次結束程序測試,正常結束了計算器.

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