1. 程式人生 > >從hook開始聊聊那些windows內核數據結構

從hook開始聊聊那些windows內核數據結構

typeinfo 過程 off 這就是 希望 objects head lease tca

總覽:

IAT HOOK Object Hook Ssdt Hook
源碼 內核知識及源碼 內核知識級源碼

一、IAT HOOK:
因為上一篇博客對已經對IAT Hook基本流程及作用進行了介紹,希望能先學懂PE再來看IATHook.下面貼上Iathook的源碼,源碼中有詳細的註釋,還記著為什麽不能結束360的進程嗎?參考思路如下圖(因為寫代碼的時候解決方案寫到了源碼中,不粘貼復制過來了):
技術分享圖片

以下代碼是DLL註入+iathook,通過測試procexp中的kill功能並沒有使用OpenProcess函數,所以需要逆向看一看他是如何結束的進程,下回與大家一起討論,64的註入是成功,但是記得要改DWORD等32位整型變量,整體思路是不變的。

代碼中有ZwCreateThreadEx註入,是更底層的函數,大家如果註入系統進程失敗,可以把註釋打開使用ZwCreateThreadEx進行註入,測試沒問題
聲明:

// 因為ZwCreateThreadEx沒有定義,所以自己定義一個偽函數
typedef DWORD(WINAPI* FnZwCreateThreadEx)(PHANDLE ThreadHandle,
    ACCESS_MASK DesiredAccess,
    LPVOID ObjectAttributes,
    HANDLE ProcessHandle,
    LPTHREAD_START_ROUTINE lpStartAddress,
    LPVOID lpParameter,
    ULONG CreateThreadFlags,
    SIZE_T ZeroBits,
    SIZE_T StackSize,
    SIZE_T MaximunStackSize,
    LPVOID pUnkown);
FnZwCreateThreadEx MyZwCreateThreadEx;

// DLL的路徑
    const char DllPath[MAX_PATH] = { "C:\\Users\\Administrator\\documents\\visual studio 2013\\Projects\\Text\\Debug\\TerminateProcessHook.dll" };

    const char DllPath1[MAX_PATH] = { "C:\\Users\\Administrator\\documents\\visual studio 2013\\Projects\\Text\\Debug\\HookDll.dll" };

    // 需要聲明的變量
    HANDLE hProc = NULL;

    HANDLE RemoteHandle = NULL;

註入源碼:
這是寫在一個按鈕響應消息裏面的代碼,遠程線程掛起沒有測試,如果不行可以刪除。


    HMODULE hNtdHandle = LoadLibrary(L"ntdll.dll");

    // 獲取地址給偽函數(因為ZwCreateThreadEx沒有聲明)
    MyZwCreateThreadEx = (FnZwCreateThreadEx)GetProcAddress(hNtdHandle, "ZwCreateThreadEx");

    // 其實沒必要這樣寫,不過更為規範一些
    auto pFinAddress = GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "LoadLibraryA");

        // 第一次點擊按鈕,開啟保護(只執行一次), 第二次點擊按鈕會掛起遠程線程(暫停保護),第三次會在恢復......
    if ((IntHookFlag == FALSE) && (IntHookFlag == TRUE))
    {
        IntHookFlag = TRUE;
        // 掛起遠程線程
        SuspendThread(RemoteHandle);

        SetDlgItemText(IDC_STATIC3, L"×");
    }
    else
    {
        IntHookFlag = FALSE;
        // 恢復遠程線程
        ResumeThread(RemoteHandle);

        SetDlgItemText(IDC_STATIC3, L"√");
    }

    // IAT Hook 自我保護未開啟
    if (OneIntHookFlag == FALSE)
    {
        // 標記為真
        OneIntHookFlag = TRUE;

        // 1. 獲取被註入句柄
        // HANDLE hProc = FindWindow(L"CalcFrame",NULL);
        hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 5000);

        if (!hProc)
        {
            AfxMessageBox(L"FindWindow() failuer");

            return;
        }

        // DLL名稱大小
        SIZE_T dwSize = strlen(DllPath1) + 1;

        // 2. 被註入進程申請內存空間
        auto pDlladdress = VirtualAllocEx(hProc, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);

        if (!pDlladdress)
        {
            CloseHandle(hProc);

            AfxMessageBox(L"VirtualAllocEx() failuer");

            return;
        }

        // 3. 寫入內存數據
        if (!WriteProcessMemory(hProc, pDlladdress, DllPath1, dwSize, &dwSize))
        {
            VirtualFree(pDlladdress, dwSize, MEM_RELEASE);

            CloseHandle(hProc);

            AfxMessageBox(L"WriteProcessMemory() failuer");

            return;
        }

        // 補:這個地方創建信號量來傳遞Pid;
        DWORD m_Pid = GetCurrentProcessId();

        // HANDLE pProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_Pid);

        HANDLE hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 0x10, L"Pid");

        LPVOID hMapFile = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);

        // DLL裏面接收PID
        memcpy(hMapFile, &m_Pid, sizeof(HANDLE));

        // 4. 遠程註入 LoadLibraryA獲取函數地址可以直接函數名(編譯器會幫助你獲取VA),當然也可以GetProcess自己來獲取VA

        DWORD dwTid = 0;

        // LoadLibrary(L"");
        RemoteHandle = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, (LPVOID)pDlladdress, 0, NULL);

        // HANDLE RemoteHandle = NULL;

        // DWORD dwStatu = MyZwCreateThreadEx(&RemoteHandle, PROCESS_ALL_ACCESS, NULL, hProc, (LPTHREAD_START_ROUTINE)pFinAddress, (LPVOID)pDlladdress, 0, 0, 0, 0, NULL);

        SetDlgItemText(IDC_STATIC3, L"√");

        DWORD error = GetLastError();

        // 5. 這個地方就不等待執行後在返回了WaitForSingleObjectEx();
        // 6. 關閉遠程句柄(只是關閉了本進程獲取到的句柄,引用計數-1)
        CloseHandle(hProc);

DLL源碼:

// dllmain.cpp : 定義 DLL 應用程序的入口點。
#include "stdafx.h"

// Save New Function Address
BYTE g_NewAddress[5] = { 0xE9 };

// Save Old Function Address
BYTE g_OldAddress[5] = {};

// Save WriteAttrib
DWORD g_OldAttrib = 0;

// Save ProtectProcessPid
DWORD g_Pid = 0;

// Statement : Camouflage Function
HANDLE WINAPI MyOpenProcess(_In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ DWORD dwProcessId);

// Statement : Instanll Hook
void InstallHook();

// Statement : UnInstall Hook
void UnInstallHook();

// True OpenProcess
typedef 
HANDLE
(WINAPI*
FnOpenProcess)(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ DWORD dwProcessId
);
FnOpenProcess FOpenProcess;

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
    )
{
    switch (ul_reason_for_call)
    {
        // 被遠程線程創建時候會被調用
    case DLL_PROCESS_ATTACH:
    {
        ::MessageBox(NULL, L"X64任務管理器", L"註入", NULL);
        // 安裝HOOK
        InstallHook();
    }
    break;
    case DLL_PROCESS_DETACH:
    {
        // 卸載HOOK
        UnInstallHook();
    }
    break;
    }
    return TRUE;
}

// Implementation : Camouflage Function
HANDLE WINAPI MyOpenProcess(_In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ DWORD dwProcessId)
{
    /*
    做個過濾,因為我們需要保護進程只有一個,被保護的PID是多少?
    我們可以做用映射,信號量等把Pid傳到被註入進程中。
    */
    HANDLE hProce = NULL;

    if (dwProcessId == g_Pid)
    {
        // 先回復
        UnInstallHook();

        // 打開權限默認為NULL 拒絕訪問
        hProce = OpenProcess(NULL, bInheritHandle, dwProcessId);

        // 在安裝
        InstallHook();

        return hProce;
    }
    else
    {
        // 先回復
        UnInstallHook();

        // 調用正確的OPenProcess
        hProce = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);

        // 在安裝
        InstallHook();

        // 返回正確的句柄
        return hProce;//FOpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
    }

}

// Implementation : Instanll Hook
void InstallHook()
{
    // 前奏工作
    HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"Pid");

    LPVOID hAddr = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);

    g_Pid = *(DWORD*)hAddr;

    // 1. 保存原來的地址(是指令長度)
    memcpy(g_OldAddress, OpenProcess, 5);

    FOpenProcess = (FnOpenProcess)OpenProcess;

    // 2. 計算偏移
    DWORD dwOffset = (DWORD)MyOpenProcess - (DWORD)OpenProcess - 5;

    // 3. 數組填充新偏移 
    // memcpy(&g_NewAddress[1], &dwOffset, 4);  切記不可用這種方式 當年這個BUG卡了好久 內存大小端字符排序 內存拷貝是正向的 偏移錯誤
    *(DWORD *)(g_NewAddress + 1) = dwOffset;

    VirtualProtect(OpenProcess, 5, PAGE_EXECUTE_READWRITE, &g_OldAttrib);

    // 4. 寫入地址
    memcpy(OpenProcess, g_NewAddress, 5);

    VirtualProtect(OpenProcess, 5, g_OldAttrib, &g_OldAttrib);
}

// Implementation : UnInstall Hook
void UnInstallHook()
{
    VirtualProtect(OpenProcess, 5, PAGE_EXECUTE_READWRITE, &g_OldAttrib);

    // 寫回去就行了
    memcpy(OpenProcess, g_OldAddress, 5);

    VirtualProtect(OpenProcess, 5, g_OldAttrib, &g_OldAttrib);
}

二、ObjectHook:
ObjectHook相關內核數據結構知識分享,如果你寫過windows內核編程,那麽更容易理解一些。

技術分享圖片

如上圖所示:這就是Windows內核對象數據結構,表示註冊表、進程、線程等等。對象數據結構當然是對象管理器管理,所有的對象內部都會有OBJECT_HEADER的結構體,用來維護生命周期。

對象頭上面是對象頭引導,如OBJECT_HEADER_QUOTA_INFO、OBJECT_HEADER_HANDLE_INFO還有後面兩個,描述了相關對象額外的屬性,這些結構體對應著OBJECT_HEADER中的成員變量,如下介紹:
1、OBJECT_HEADER_NAME_INFO --> NameInfoOffset
2、OBJECT_HEADER_HANDLE_INFO --> HandleInfoOffset
3、OBJECT_HEADER_QUOTA_INFO --> QuotaInfoOffset

圖中OBJECT_HEADER結構體中前兩個PointerCount與HandleCount是引用計數。
1、PointerCount:內核模式對象引用數量
2、HandleCount:句柄數量
補充一些:
1. 內核中的指針引用. 一旦內核中新增了一個對象的引用, 則對象的引用計數自增一,如果一個對象的引用不再有用,則引用計數自減一. 這兩種引用的增減是使用ObReferenceObjectByPointer和ObDereferenceObject導致的.
2. 一個進程打開一個對象並成功獲得一個句柄, 會使得對象頭中的句柄計數自增一. 當一個句柄不再被使用時, 句柄計數自減一. 這兩種引用的增減來自ObpIncrementHandleCount和ObpDecrementHandleCount函數.

+0x10指向的是_OBJECT_CREATE_INFORMATION
技術分享圖片
在創建CreateProcess時候會給該結構體申請空間,後面會填充該結構體信息。

我們重點看一下偏移為+0x008 OBJECT_TYPE,表示對於通用屬性(對象的通用屬性)存儲,如下圖所示:

技術分享圖片

OBJECT_TYPE.TypeInfo指向了OBJECT_TYPE_INITIALIZER結構體,這個結構體包含特定對象類型的函數,對象管理器用於各類的執行操作,如下圖所示:
技術分享圖片
你會發現我用紅色框框標記了上圖的一些成員,所謂的ObjectHook就是他們(替換地址),這些函數過程會在特定時機被調用。

如何找到對象頭?
我先們在windbg下來看一看,你要知道的一點是:內核變量ObTypeIndexTable是一個指針數組,它的每個成員都指向一種對象類型的OBJECT_TYPE結構體,也就是說它是由一個指針數組維護的。
那麽我們就好辦了,找到這個指針數組看一看
技術分享圖片
到底對不對?我們來測試一下?
技術分享圖片
OBJECT_TYPE了+0x28就是_OBJECT_TYPE_INITIALIZER。windbg下面能找到,編寫代碼的時候如何獲取OBJECT_HEADER呢?

先來看一下,OBJECT_HEADER + 0x18,是成員變量Boby,這是什麽?這是對象主體,我們可以看到對象主體中OBJECT_DIRECTORY,DRIVER_OBJECT,DEVICE_OBJECT等對象結構,我們在編寫windows內核編程的時候會創建驅動對象,設備對象,這樣就好說了。
假設驅動對象地址是OBJECT_HEADER + 0x18偏移的地方,那麽驅動對象地址-0x18則是OBJECT_HEADER的地址,如下圖所示:
技術分享圖片
上面代碼就是用匯編進行了的ObjectHook,如果理解了以上結構體概念,這些匯編代碼應該沒有難度。
為什麽不用結構體去編程?因為我不想定義那麽多結構體,部分結構體windows是沒有公開的,需要自己在頭文件中定義,但是代碼中仍然給出了完整的結構體,可以用結構體實現,源碼如下:
頭文件定義:
#include <ntddk.h>

/*定義的結構體信息*/
typedef struct _OBJECT_TYPE_INITIALIZER
{
    USHORT Length;
    USHORT type;
    PVOID ObjectTypeCode;
    PVOID InvalidAttributes;
    GENERIC_MAPPING GenericMapping;
    PVOID ValidAccessMask;
    PVOID RetainAccess;
    POOL_TYPE PoolType;
    PVOID DefaultPagedPoolCharge;
    PVOID DefaultNonPagedPoolCharge;
    PVOID DumpProcedure;
    PVOID OpenProcedure;
    PVOID CloseProcedure;
    PVOID DeleteProcedure;
    PVOID ParseProcedure;
    PVOID SecurityProcedure;
    PVOID QueryNameProcedure;
    USHORT OkayToCloseProcedure;
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;

typedef struct _OBJECT_TYPE
{
    LIST_ENTRY TypeList;         //         : _LIST_ENTRY
    UNICODE_STRING Name;         //             : _UNICODE_STRING
    PVOID DefaultObject;         //    : Ptr32 Void
    ULONG Index;         //            : UChar
    ULONG TotalNumberOfObjects;         // : Uint4B
    ULONG TotalNumberOfHandles;         // : Uint4B
    ULONG HighWaterNumberOfObjects;         // : Uint4B
    ULONG HighWaterNumberOfHandles;         // : Uint4B
    OBJECT_TYPE_INITIALIZER TypeInfo;         //         : _OBJECT_TYPE_INITIALIZER
    PVOID TypeLock;         //         : _EX_PUSH_LOCK
    ULONG Key;         //              : Uint4B
    LIST_ENTRY CallbackList;         //     : _LIST_ENTRY
} OBJECT_TYPE, *POBJECT_TYPE;

typedef struct _OBJECT_CREATE_INFORMATION
{
    ULONG Attributes;
    HANDLE RootDirectory;
    KPROCESSOR_MODE ProbeMode;
    ULONG PagedPoolCharge;
    ULONG NonPagedPoolCharge;
    ULONG SecurityDescriptorCharge;
    PVOID SecurityDescriptor;
    PSECURITY_QUALITY_OF_SERVICE SecurityQos;
    SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;
} OBJECT_CREATE_INFORMATION, *POBJECT_CREATE_INFORMATION;

typedef struct _OBJECT_HEADER
{
    //對象頭部的指針計數,對對象頭指針引用的計數
    LONG_PTR PointerCount;
    union
    {
        //句柄引用計數
        LONG_PTR HandleCount;
        PVOID NextToFree;
    };
    POBJECT_TYPE Type;
    //OBJECT_HEADER_NAME_INFO相對於此結構的偏移
    UCHAR NameInfoOffset;
    //OBJECT_HEADER_HANDLE_INFO相對於此結構的偏移
    UCHAR HandleInfoOffset;
    //OBJECT_HEADER_QUOTA_INFO相對於此結構的偏移
    UCHAR QuotaInfoOffset;
    UCHAR Flags;

    union
    {
        //創建對象是用於創建對象附加頭的結構
        //裏面保存了和附加對象頭類似的信息
        PVOID ObjectCreateInfo;
        PVOID QuotaBlockCharged;
    };
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    QUAD Body;
} OBJECT_HEADER, *POBJECT_HEADER;

// 獲取頭信息
#define OBJECT_TO_OBJECT_HEADER(o)            CONTAINING_RECORD((o),OBJECT_HEADER,Body)
#define CONTAINING_RECORD(address,type,field)            ((type*)(((ULONG_PTR)address)-(ULONG_PTR)(&(((type*)0)->field))))

代碼實現:


#include "HookHead.h"

VOID UnLoadDriver()
{

}

NTSTATUS MyDeleteProcedure();

NTSTATUS MaDefaultFunction(DEVICE_OBJECT* pDeviceObj, IRP* Irp)
{
    Irp->IoStatus.Information = 0;

    Irp->IoStatus.Status = STATUS_SUCCESS;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return STATUS_SUCCESS;
}

NTSTATUS DriverEntry(DRIVER_OBJECT* pDeviceObj, UNICODE_STRING* RegistryPath)
{
    pDeviceObj->DriverUnload = UnLoadDriver;

    for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i)
    {
        pDeviceObj->MajorFunction[i] = MaDefaultFunction;
    }

    // 1. 獲取OBJECT_HEAD
    __asm
    {
        // 保存環境
        pushad;
        pushfd;
        // 1 設備對象就是 OBJECT_BODY 也就是 -0x18是OBJECT_HREAD -0x10是OBJECT_TYPE(OBJECT_HANDLE + 0x8)
        lea eax, pDeviceObj;
        sub eax, 0x10;
        // 2 獲取到OBJECT_TYPE之後 地址加上0x3c則是DeleteProcdure(其實已經在OBJECT_TYPE_INITIALZER結構體中)
        lea eax, [eax + 0x3c];
        // 3 DeleteProcdure地址替換成的MyDeleteProcedure地址
        lea esi, MyDeleteProcedure;
        mov eax, esi;
        // 恢復環境
        popad;
        popfd;
    }

    // 2. 獲取OBJECT_TYPE

    // 3. 替換相對應的函數

}

NTSTATUS MyDeleteProcedure()
{

}

三、SsdtHook:
SSDT:System Service Descriptor Table,系統服務描述符表。這個表保存啥的?其實就是把三環零環的API聯起來,不單單是索引表,而且還包含一些索引基址、服務函數個數等。

先有個基本的概念,下面來看一張圖:

技術分享圖片

我們發現不論是System support process,還是Service processes他們都會進入內核模式之前都會經過Ntdll.dll模塊。然後通過系統服務調度程序到接口,到微內核,到HAL(驅動硬件相關聯的地方)。
所以內核層下也有微內核與HVL層,前面博客中介紹過一些相關的結構體如_EPROCESS裏面內嵌_KPROCESS,_EPROCESS被內核層執行,而_KPROCESS是微內核層調度。

用OD隨便跟蹤函數:
技術分享圖片
下面函數有點奇怪並不是想象中的CALL [EDX],而是wow64cpu函數是在用戶模式下實現的,作為 ntdll.dll 和內核之間的層。如果你的應用程序是32位,為了能在64位的系統上運行起來,就會調用這個函數兼容,Windows下的一個子系統。
該函數有三種返回值:
1、64位運行在64位系統下,不是WOW64模式,return 0;
2、32位運行在64位系統下,WOW64模式,return 1;
3、32位運行在32位系統下,return 0;
有點像傀儡進程一樣,函數過程大概是這樣,原進程會被創建(包括註冊表),然後判斷該程序信息,如果不是64位在C盤下(具體位置記不清楚),Temp的文件夾下創建一個64位的線程,修改註冊表信息等(不太準確,只是以前逆向的時候觀察過整個的過程)。
MOV EAX, 0x22(0x23) --> 保存調用號 ssdt表中的序號
EAX寄存器保存保存函數的調用號,其實到內核以後就靠調用號來確認調用的是哪個函數。

對windows內核比較熟悉的應該知道,用戶的堆棧與內核的堆棧不是同一個堆棧空間,而且用戶層沒有權限去訪問內核層的數據,通過什麽進入內核層?一條匯編指令 SYSENTER,如下圖所示:
技術分享圖片

cs:ip執行這一條匯編指令之後,你將進入到內核。如何做到的呢?其實在SYSENTRY指令之前,先會用edx保存esp的值,執行SYSENTER時候會讀取特殊寄存器MSR模組寄存器,如下圖所示:
技術分享圖片
沒有名字,只有編號通過以下兩條匯編指令:

操作碼 指令 說明
0F 32 RDMSR 將 ECX 指定的 MSR 加載到 EDX:EAX
操作碼 指令 說明
0F 30 WRMSR 將 EDX:EAX 中的值寫入 ECX 指定的 MSR

詳細:
1、RDMSR:將 ECX 寄存器指定的 64 位型號專用寄存器 (MSR) 的內容加載到寄存器 EDX:EAX。EDX 寄存器中加載 MSR 的高 32 位,EAX 寄存器中加載低 32 位。在讀取的 MSR 中,如果實現的位數小於 64,則返回 EDX:EAX 中未實現的位的值未定義。
2、WRMSR :將寄存器 EDX:EAX 的內容寫入 ECX 寄存器指定的 64 位型號專用寄存器 (MSR)。高 32 位從 EDX 復制,低 32 位從 EAX 復制。MSR 中未定義或保留的位總是設置為上次讀取時的值。
補充一下:其實有些KiFastCallEntry Hook大家看到這裏應該明白,其實就是改變編號0x176保存的地址。

當SYSENTER執行時候,就把寄存器的值初始化成真正的寄存器CS,ESP,EIP寄存器的數據。這時候就跑到KiFastCallEntry。

KiFastCallEntry函數大家有興趣可以分析下,那麽對上面的流程更為清晰,怎樣去分析呢,windbg下就可以,如下圖所示:
技術分享圖片

竟然進入到了內核層,把用戶棧的內容拷貝到內核棧,但是拷貝多少個字節?參數個數?

通過eax在用戶層保存的序號,就能找到函數地址。通過調用號作為序號,就能找到參數個數,個數*4就是總字節,其實這張表就是SSDT,還有一張表叫ShadowSSDT,專門用於保存和用戶界面相關服務,內核中還有兩張沒有使用的表,如下圖所示(兩個結構體)。

技術分享圖片
_KSYSTEM_SERVICE_TABLE便是SSDT的結構體,通過上圖我們知道了SSDT的結構,另一個結構體保存了這四張表。

windbg如何找到ssdt?
1、dd KeServiceDescriptorTable(由ntoskrnl.exe導出)。
2、還可以通過_KTHREAD + 0xbc來找到ServiceTableBase結構體基址。
技術分享圖片
技術分享圖片
使用結構體解析地址看一下,如下圖所示:
技術分享圖片
圖中標紅便對應著結構體成員值,函數地址表首地址、每個函數被調用次數、服務函數個數191個,參數表地址。
HOOK的是什麽? 其實HOOK的就是函數地址表中的地址,當內核層通過調用號找到ssdt中的索引號,調用的是我們自己的函數地址即可。
到底是不是這樣?經過測試確實是這樣,下圖是源碼測試圖:
技術分享圖片
技術分享圖片技術分享圖片
我們用ark工具看一下,如下圖所示:
技術分享圖片
源碼如下:
頭文件:

#pragma once
#include <ntddk.h>

#define CTL_SSDT_ENABLE     CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
#define CTL_SSDT_DISABLE     CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)

typedef  struct  _KSERVICE_TABLE_DESCRIPTOR
{
    KSYSTEM_SERVICE_TABLE   ntoskrnl;   // ntoskrnl.exe的服務函數,即SSDT
    KSYSTEM_SERVICE_TABLE   win32k;     // win32k.sys的服務函數(GDI32.dll/User32.dll 的內核支持),即ShadowSSDT
    KSYSTEM_SERVICE_TABLE   notUsed1;   // 不使用
    KSYSTEM_SERVICE_TABLE   notUsed2;   // 不使用
}KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;

typedef  struct  _KSYSTEM_SERVICE_TABLE
{
    PULONG  ServiceTableBase;           // 函數地址表的首地址
    PULONG  ServiceCounterTableBase;    // 函數表中每個函數被調用的次數
    ULONG   NumberOfService;            // 服務函數的個數, NumberOfService * 4 就是整個地址表的大小
    UCHAR*   ParamTableBase;            // 參數個數表首地址
} KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

// 偽函數(Hook的函數)
typedef NTSTATUS(NTAPI*FnNtOpenProcess)(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);

// 保存舊的地址
FnNtOpenProcess g_OldNtOpenProcess;

// 定義KeServieDescriptorTableShadow
KSERVICE_TABLE_DESCRIPTOR* g_ServiceTab = NULL;

// 保存被保護進程PID
HANDLE g_Pid = 0;

驅動層:

#include "SSDTHookHead.h"

// 聲明:驅動卸載
VOID DriverUnLoad(DRIVER_OBJECT* pDeviceobj);

// 聲明:默認初始化
NTSTATUS DefaultFunction(DEVICE_OBJECT* pDeviceObj, IRP* Irp);

// 聲明:控制碼
NTSTATUS ControlCode(DEVICE_OBJECT* pDeviceObj, IRP* Irp);

// 聲明:安裝HOOK
VOID InstallHook();

// 聲明:卸載HOOK
VOID UnInstallHook();

// 聲明:關閉分頁保護
NTSTATUS ShudowMemoryPageProtect();

// 聲明:開啟分頁保護
NTSTATUS StartMemoryPageProtect();

// Hook實現函數
NTSTATUS MyOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId);

// 聲明:入口點
NTSTATUS DriverEntry(DRIVER_OBJECT* pDriverObj, UNICODE_STRING* RegistryPath)
{
    UNREFERENCED_PARAMETER(RegistryPath);

    DEVICE_OBJECT* pDeviceObj = NULL;
    UNICODE_STRING DevName;
    UNICODE_STRING SymbolicLinkName;
    NTSTATUS Status = STATUS_SUCCESS;

    RtlInitUnicodeString(&DevName, L"\\Device\\SsdtHook");
    RtlInitUnicodeString(&SymbolicLinkName, L"\\DosDevices\\SymbolicLinkName");

    // DbgBreakPoint();

    pDriverObj->DriverUnload = DriverUnLoad;

    for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i)
    {
        pDriverObj->MajorFunction[i] = DefaultFunction;
    }

    // 設備對象
    Status = IoCreateDevice(pDriverObj, 0, &DevName, FILE_DEVICE_UNKNOWN, 0, 0, &pDeviceObj);

    if (!NT_SUCCESS(Status))
        return Status;

    // 使用緩沖區的方式進行3環與0環通訊
    pDriverObj->Flags = DO_BUFFERED_IO;

    // 符號對象暴露給三環使用
    Status = IoCreateSymbolicLink(&SymbolicLinkName, &DevName);

    if (!NT_SUCCESS(Status))
        return Status;

    pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ControlCode;

    return STATUS_SUCCESS;
}

VOID DriverUnLoad(DRIVER_OBJECT* pDeviceobj)
{
    UNICODE_STRING DeleteSymblolicLinkName;

    RtlInitUnicodeString(&DeleteSymblolicLinkName, L"\\DosDevices\\SymbolicLinkName");

    IoDeleteSymbolicLink(&DeleteSymblolicLinkName);

    IoDeleteDevice(pDeviceobj->DeviceObject);
}

// 實現:默認初始化
NTSTATUS DefaultFunction(DEVICE_OBJECT* pDeviceObj, IRP* Irp)
{
    UNREFERENCED_PARAMETER(pDeviceObj);

    Irp->IoStatus.Information = 0;

    Irp->IoStatus.Status = STATUS_SUCCESS;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return STATUS_SUCCESS;
}

// 實現:控制碼
NTSTATUS ControlCode(DEVICE_OBJECT* pDeviceObj, IRP* Irp)
{
    // DbgBreakPoint();

    UNREFERENCED_PARAMETER(pDeviceObj);

    // 通過Irp棧數據獲取控制碼
    // NTSTATUS nStatus = STATUS_SUCCESS;

    PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(Irp);

    ULONG uControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;

    // 通過MDL獲取需要保護的Pid
    // g_Pid = (HANDLE)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);

    PVOID Pbuf = Irp->AssociatedIrp.SystemBuffer;

    RtlCopyMemory((PVOID)&g_Pid, Pbuf, sizeof(ULONG));

    switch (uControlCode)
    { 
    case CTL_SSDT_ENABLE:
    {
        // DbgBreakPoint();
        InstallHook();
    }
    break;
    case CTL_SSDT_DISABLE:
    {
        UnInstallHook();
    }
    break;
    default:
        break;
    }

    Irp->IoStatus.Information = 0;

    Irp->IoStatus.Status = STATUS_SUCCESS;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return STATUS_SUCCESS;
}

// 實現:安裝HOOK
VOID InstallHook()
{
    // DbgBreakPoint();

    // 1.1 獲取當前線程
    PETHREAD pThread = PsGetCurrentThread();

    // 1.2 線程結構體 +0xbc 獲取的是 ServiceTable 
    g_ServiceTab = (KSERVICE_TABLE_DESCRIPTOR*)(*(ULONG*)((ULONG_PTR)pThread + 0xbc));

    // 1.3 獲取SSDT地址基址且保存原始的函數VA
    g_OldNtOpenProcess = (FnNtOpenProcess)g_ServiceTab->ntoskrnl.ServiceTableBase[0xBE];

    // 1.4 替換修改地址(這個地方先要關閉頁保護)  
    /*
        只介紹其中的三種:
            PE - 是否啟用保護模式,置1則啟用
            PG - 是否使用分頁模式, 置1則開啟分頁模式, 此標誌置1時,PE標誌也必須置1,否則CPU報異常.
            WP - WP==1時, 不能修改只讀的內存頁 , WP==0 時, 可以修改只讀的內存頁.
    */
    ShudowMemoryPageProtect();

    g_ServiceTab->ntoskrnl.ServiceTableBase[0xBE] = (ULONG)MyOpenProcess;

    StartMemoryPageProtect();
}

// 實現:卸載HOOK
VOID UnInstallHook()
{
    ShudowMemoryPageProtect();

    g_ServiceTab->ntoskrnl.ServiceTableBase[0xBE] = (ULONG)g_OldNtOpenProcess;

    // DbgBreakPoint();

    StartMemoryPageProtect();
}

// 實現:關閉分頁保護
NTSTATUS ShudowMemoryPageProtect()
{
    __asm
    {
        pushad;
        pushfd;

        mov eax, cr0;
        // 前提內存保護一定是開啟的 WP = 1 否則..就給開啟了
        and eax, ~0x10000;
        mov cr0, eax;

        popfd;
        popad;

    }
}

// 實現:開啟分頁保護
NTSTATUS StartMemoryPageProtect()
{
    __asm
    {
        pushad;
        pushfd;

        mov eax, cr0;
        or eax, 0x10000;
        mov cr0, eax;

        popfd;
        popad;

    }
}

// 實現:HOOK函數
NTSTATUS MyOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId)
{
    // 訪問權限PROCESS_ALL_ACCESS改為NULL 
    if (ClientId->UniqueProcess == g_Pid)
    {
        DbgBreakPoint();

        DesiredAccess = 0;
    }

    return g_OldNtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
}

從hook開始聊聊那些windows內核數據結構