5.10 Windows驅動開發:摘除InlineHook內核鉤子

在筆者上一篇文章《內核層InlineHook掛鉤函數》中介紹了通過替換函數頭部代碼的方式實現Hook掛鉤,對於ARK工具來說實現掃描與摘除InlineHook鉤子也是最基本的功能,此類功能的實現一般可在應用層進行,而驅動層只需要保留一個讀寫字節的函數即可,將複雜的流程放在應用層實現是一個非常明智的選擇,與《內核實現進程反彙編》中所使用的讀寫驅動基本一致,本篇文章中的驅動只保留兩個功能,控制信號IOCTL_GET_CUR_CODE用於讀取函數的前16個字節的內存,信號IOCTL_SET_ORI_CODE則用於設置前16個字節的內存。

之所以是前16個字節是因爲一般的內聯Hook只需要使用兩條指令就可實現劫持,如下是通用ARK工具掃描到的被掛鉤函數的樣子。

首先將內核驅動程序代碼放到如下,內核驅動程序沒有任何特別的,僅僅只是一個通用驅動模板,在其基礎上使用CR3讀寫,如果不理解CR3讀寫的原理您可以去看《內核CR3切換讀寫內存》這一篇中的詳細介紹。

#include <ntifs.h>
#include <intrin.h>
#include <windef.h>

#define DEVICE_NAME         L"\\Device\\WinDDK"
#define LINK_NAME           L"\\DosDevices\\WinDDK"
#define LINK_GLOBAL_NAME    L"\\DosDevices\\Global\\WinDDK"

// 控制信號 IOCTL_GET_CUR_CODE 用於讀 | IOCTL_SET_ORI_CODE 用於寫
#define IOCTL_GET_CUR_CODE  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_SET_ORI_CODE  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)

// 引用__readcr0等函數必須增加
#pragma intrinsic(_disable)
#pragma intrinsic(_enable)

// 定義讀寫結構體
typedef struct
{
    PVOID Address;
    ULONG64 Length;
    UCHAR data[256];
} KF_DATA, *PKF_DATA;

KIRQL g_irql;

// 關閉寫保護
void WPOFFx64()
{
    ULONG64 cr0;
    g_irql = KeRaiseIrqlToDpcLevel();
    cr0 = __readcr0();
    cr0 &= 0xfffffffffffeffff;
    __writecr0(cr0);
    _disable();
}

// 開啓寫保護
void WPONx64()
{
    ULONG64 cr0;
    cr0 = __readcr0();
    cr0 |= 0x10000;
    _enable();
    __writecr0(cr0);
    KeLowerIrql(g_irql);
}

// 設備創建時觸發
NTSTATUS DispatchCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;

    DbgPrint("[LyShark] 設備已創建 \n");
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

// 設備關閉時觸發
NTSTATUS DispatchClose(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;

    DbgPrint("[LyShark] 設備已關閉 \n");
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

// 主派遣函數
NTSTATUS DispatchIoctl(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
    NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
    PIO_STACK_LOCATION pIrpStack;
    ULONG uIoControlCode;
    PVOID pIoBuffer;
    ULONG uInSize;
    ULONG uOutSize;

    // 獲取當前設備棧
    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;

    // 獲取緩衝區
    pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;

    // 獲取緩衝區長度
    uInSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;

    // 輸出緩衝區長度
    uOutSize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;

    switch (uIoControlCode)
    {
        // 讀內存
    case IOCTL_GET_CUR_CODE:
    {
        KF_DATA dat = { 0 };

        // 將緩衝區格式化爲KF_DATA結構體
        RtlCopyMemory(&dat, pIoBuffer, 16);
        WPOFFx64();

        // 將數據寫回到緩衝區
        RtlCopyMemory(pIoBuffer, dat.Address, dat.Length);
        WPONx64();
        status = STATUS_SUCCESS;
        break;
    }
    // 寫內存
    case IOCTL_SET_ORI_CODE:
    {
        KF_DATA dat = { 0 };

        // 將緩衝區格式化爲KF_DATA結構體
        RtlCopyMemory(&dat, pIoBuffer, sizeof(KF_DATA));
        WPOFFx64();

        // 將數據寫回到緩衝區
        RtlCopyMemory(dat.Address, dat.data, dat.Length);
        WPONx64();
        status = STATUS_SUCCESS;
        break;
    }
    }

    if (status == STATUS_SUCCESS)
        pIrp->IoStatus.Information = uOutSize;
    else
        pIrp->IoStatus.Information = 0;

    pIrp->IoStatus.Status = status;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return status;
}

// 驅動卸載
VOID DriverUnload(PDRIVER_OBJECT pDriverObj)
{
    UNICODE_STRING strLink;

    // 刪除符號鏈接卸載設備
    RtlInitUnicodeString(&strLink, LINK_NAME);
    IoDeleteSymbolicLink(&strLink);
    IoDeleteDevice(pDriverObj->DeviceObject);
}

// 驅動程序入口
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegistryString)
{
    NTSTATUS status = STATUS_SUCCESS;
    UNICODE_STRING ustrLinkName;
    UNICODE_STRING ustrDevName;
    PDEVICE_OBJECT pDevObj;

    // 初始化派遣函數
    pDriverObj->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;
    pDriverObj->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
    pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;

    DbgPrint("hello lysahrk.com \n");

    // 初始化設備名
    RtlInitUnicodeString(&ustrDevName, DEVICE_NAME);

    // 創建設備
    status = IoCreateDevice(pDriverObj, 0, &ustrDevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDevObj);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    // 創建符號鏈接
    RtlInitUnicodeString(&ustrLinkName, LINK_NAME);
    status = IoCreateSymbolicLink(&ustrLinkName, &ustrDevName);
    if (!NT_SUCCESS(status))
    {
        IoDeleteDevice(pDevObj);
        return status;
    }

    pDriverObj->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

接着來分析下應用層做了什麼,首先GetKernelBase64函數的作用,該函數內部通過GetProcAddress()函數動態尋找到ZwQuerySystemInformation()函數的內存地址(此函數未被到處所以只能動態找到),找到後調用ZwQuerySystemInformation()直接拿到系統中的所有模塊信息,通過pSystemModuleInformation->Module[0].Base得到系統中第一個模塊的基地址,此模塊就是ntoskrnl.exe,該模塊也是系統運行後的第一個啓動的,此時我們即可拿到KernelBase也就是系統內存中的基地址。

此時通過LoadLibraryExA()函數動態加載,此時加載的是磁盤中的被Hook函數的所屬模塊,獲得映射地址後將此地址裝入hKernel變量內,此時我們擁有了內存中的KernelBase以及磁盤中加載的hKernel,接着調用RepairRelocationTable()讓兩者的重定位表保持一致。

此時當用戶調用GetSystemRoutineAddress()則執行如下流程,想要獲取當前內存地址,則需要使用當前內存中的KernelBase模塊基址加上通過GetProcAddress()動態獲取到的磁盤基址中的函數地址減去磁盤中的基地址,將內存中的KernelBase加上磁盤中的相對偏移就得到了當前內存中加載函數的實際地址。

  • address1 = KernelBase + (ULONG64)GetProcAddress(hKernel, "NtWriteFile") - (ULONG64)hKernel
  • address2 = KernelBase - (ULONG64)hKernel + (ULONG64)GetProcAddress(hKernel, "NtWriteFile")

調用GetOriginalMachineCode()則用於獲取相對偏移地址,該地址的獲取方式如下,用戶傳入一個Address當前地址,該地址減去KernelBase內存中的基址,然後再加上hKernel磁盤加載的基址來獲取到相對偏移。

  • OffsetAddress = Address - KernelBase + hKernel

有了這兩條信息那麼功能也就實現了,通過GetOriginalMachineCode()得到指定內存地址處原始機器碼,通過GetCurrentMachineCode()得到當前內存機器碼,兩者通過memcmp()函數比對即可知道是否被掛鉤了,如果被掛鉤則可以通過CR3切換將原始機器碼覆蓋到特定位置替換即可,這段程序的完整代碼如下;

#include <stdio.h>
#include <Windows.h>

#pragma comment(lib,"user32.lib")
#pragma comment(lib,"Advapi32.lib")

#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#endif

#define BYTE_ARRAY_LENGTH 16
#define SystemModuleInformation 11
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)

typedef long(__stdcall *ZWQUERYSYSTEMINFORMATION)
(
    IN ULONG SystemInformationClass,
    IN PVOID SystemInformation,
    IN ULONG SystemInformationLength,
    IN PULONG ReturnLength OPTIONAL
);

typedef struct
{
    ULONG Unknow1;
    ULONG Unknow2;
    ULONG Unknow3;
    ULONG Unknow4;
    PVOID Base;
    ULONG Size;
    ULONG Flags;
    USHORT Index;
    USHORT NameLength;
    USHORT LoadCount;
    USHORT ModuleNameOffset;
    char ImageName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY;

typedef struct
{
    ULONG Count;
    SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

typedef struct
{
    PVOID Address;
    ULONG64 Length;
    UCHAR data[256];
} KF_DATA, *PKF_DATA;

HANDLE hDriver = 0;
HMODULE hKernel = 0;
ULONG64 KernelBase = 0;
CHAR NtosFullName[260] = { 0 };

// 生成控制信號
DWORD CTL_CODE_GEN(DWORD lngFunction)
{
    return (FILE_DEVICE_UNKNOWN * 65536) | (FILE_ANY_ACCESS * 16384) | (lngFunction * 4) | METHOD_BUFFERED;
}

// 發送控制信號的函數
BOOL IoControl(HANDLE hDrvHandle, DWORD dwIoControlCode, PVOID lpInBuffer, DWORD nInBufferSize, PVOID lpOutBuffer, DWORD nOutBufferSize)
{
    DWORD lDrvRetSize;
    return DeviceIoControl(hDrvHandle, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, &lDrvRetSize, 0);
}

// 動態獲取ntdll.dll模塊的基地址
ULONG64 GetKernelBase64(PCHAR NtosName)
{
    ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation;
    PSYSTEM_MODULE_INFORMATION pSystemModuleInformation;
    ULONG NeedSize, BufferSize = 0x5000;
    PVOID pBuffer = NULL;
    NTSTATUS Result;

    // 該函數只能通過動態方式得到地址
    ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwQuerySystemInformation");
    do
    {
        pBuffer = malloc(BufferSize);
        if (pBuffer == NULL) return 0;

        // 查詢系統中的所有模塊信息
        Result = ZwQuerySystemInformation(SystemModuleInformation, pBuffer, BufferSize, &NeedSize);
        if (Result == STATUS_INFO_LENGTH_MISMATCH)
        {
            free(pBuffer);
            BufferSize *= 2;
        }
        else if (!NT_SUCCESS(Result))
        {
            free(pBuffer);
            return 0;
        }
    } while (Result == STATUS_INFO_LENGTH_MISMATCH);

    // 取模塊信息結構
    pSystemModuleInformation = (PSYSTEM_MODULE_INFORMATION)pBuffer;

    // 得到模塊基地址
    ULONG64 ret = (ULONG64)(pSystemModuleInformation->Module[0].Base);

    // 拷貝模塊名
    if (NtosName != NULL)
    {
        strcpy(NtosName, pSystemModuleInformation->Module[0].ImageName + pSystemModuleInformation->Module[0].ModuleNameOffset);
    }

    free(pBuffer);
    return ret;
}

// 判斷並修復重定位表
BOOL RepairRelocationTable(ULONG64 HandleInFile, ULONG64 BaseInKernel)
{
    PIMAGE_DOS_HEADER       pDosHeader;
    PIMAGE_NT_HEADERS64     pNtHeader;
    PIMAGE_BASE_RELOCATION  pRelocTable;
    ULONG i, dwOldProtect;

    // 得到DOS頭並判斷是否符合DOS規範
    pDosHeader = (PIMAGE_DOS_HEADER)HandleInFile;
    if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
    {
        return FALSE;
    }

    // 得到Nt頭
    pNtHeader = (PIMAGE_NT_HEADERS64)((ULONG64)HandleInFile + pDosHeader->e_lfanew);

    // 是否存在重定位表
    if (pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size)
    {
        // 獲取到重定位表基地址
        pRelocTable = (PIMAGE_BASE_RELOCATION)((ULONG64)HandleInFile + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);

        do
        {
            // 得到重定位號
            ULONG   numofReloc = (pRelocTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
            SHORT   minioffset = 0;
            
            // 得到重定位數據
            PUSHORT pRelocData = (PUSHORT)((ULONG64)pRelocTable + sizeof(IMAGE_BASE_RELOCATION));

            // 循環或直接判斷*pRelocData是否爲0也可以作爲結束標記
            for (i = 0; i<numofReloc; i++)
            {
                // 需要重定位的地址
                PULONG64 RelocAddress;

                // 重定位的高4位是重定位類型,判斷重定位類型
                if (((*pRelocData) >> 12) == IMAGE_REL_BASED_DIR64)
                {
                    // 計算需要進行重定位的地址
                    // 重定位數據的低12位再加上本重定位塊頭的RVA即真正需要重定位的數據的RVA
                    minioffset = (*pRelocData) & 0xFFF; // 小偏移

                    // 模塊基址+重定位基址+每個數據表示的小偏移量
                    RelocAddress = (PULONG64)(HandleInFile + pRelocTable->VirtualAddress + minioffset);

                    // 直接在RING3修改: 原始數據+基址-IMAGE_OPTINAL_HEADER中的基址
                    VirtualProtect((PVOID)RelocAddress, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect);

                    // 因爲是R3直接LOAD的所以要修改一下內存權限
                    *RelocAddress = *RelocAddress + BaseInKernel - pNtHeader->OptionalHeader.ImageBase;
                    VirtualProtect((PVOID)RelocAddress, 4, dwOldProtect, NULL);
                }
                // 下一個重定位數據
                pRelocData++;
            }
            // 下一個重定位塊
            pRelocTable = (PIMAGE_BASE_RELOCATION)((ULONG64)pRelocTable + pRelocTable->SizeOfBlock);
        } while (pRelocTable->VirtualAddress);

        return TRUE;
    }
    return FALSE;
}

// 初始化
BOOL InitEngine(BOOL IsClear)
{
    if (IsClear == TRUE)
    {
        // 動態獲取ntdll.dll模塊的基地址
        KernelBase = GetKernelBase64(NtosFullName);
        printf("模塊基址: %llx | 模塊名: %s \n", KernelBase, NtosFullName);
        if (!KernelBase)
        {
            return FALSE;
        }
            
        // 動態加載模塊到內存,並獲取到模塊句柄
        hKernel = LoadLibraryExA(NtosFullName, 0, DONT_RESOLVE_DLL_REFERENCES);

        if (!hKernel)
        {
            return FALSE;
        }

        // 判斷並修復重定位表
        if (!RepairRelocationTable((ULONG64)hKernel, KernelBase))
        {
            return FALSE;
        }
        return TRUE;
    }
    else
    {
        FreeLibrary(hKernel);
        return TRUE;
    }
}

// 獲取原始函數機器碼
VOID GetOriginalMachineCode(ULONG64 Address, PUCHAR ba, SIZE_T Length)
{
    ULONG64 OffsetAddress = Address - KernelBase + (ULONG64)hKernel;
    RtlCopyMemory(ba, (PVOID)OffsetAddress, Length);
}

// 獲取傳入函數的內存地址
ULONG64 GetSystemRoutineAddress(PCHAR FuncName)
{
    return KernelBase + (ULONG64)GetProcAddress(hKernel, FuncName) - (ULONG64)hKernel;
}

// 獲取當前函數機器碼
VOID GetCurrentMachineCode(ULONG64 Address, PUCHAR ba, SIZE_T Length)
{
    ULONG64 dat[2] = { 0 };
    dat[0] = Address;
    dat[1] = Length;
    IoControl(hDriver, CTL_CODE_GEN(0x800), dat, 16, ba, Length);
}

// 清除特定位置的機器碼
VOID ClearInlineHook(ULONG64 Address, PUCHAR ba, SIZE_T Length)
{
    KF_DATA dat = { 0 };
    dat.Address = (PVOID)Address;
    dat.Length = Length;

    // 直接調用寫出控制碼
    RtlCopyMemory(dat.data, ba, Length);
    IoControl(hDriver, CTL_CODE_GEN(0x801), &dat, sizeof(KF_DATA), 0, 0);
}

// 打印數據
VOID PrintBytes(PCHAR DescriptionString, PUCHAR ba, UINT Length)
{
    printf("%s", DescriptionString);
    for (UINT i = 0; i<Length; i++)
    {
        printf("%02x ", ba[i]);
    }
    printf("\n");
}

int main(int argc, char *argv[])
{
    UCHAR OriginalMachineCode[BYTE_ARRAY_LENGTH];
    UCHAR CurrentMachineCode[BYTE_ARRAY_LENGTH];
    ULONG64 Address = 0;

    hDriver = CreateFileA("\\\\.\\WinDDK", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    // 初始化
    if (!InitEngine(TRUE) || hDriver == 0)
    {
        return 0;
    }

    // 需要獲取的函數列表
    CHAR *FunctionList[128] = { "PsLookupProcessByProcessId", "NtCommitEnlistment", "NtCommitComplete", "NtCommitTransaction" };

    for (size_t i = 0; i < 4; i++)
    {
        // 清空緩存
        RtlZeroMemory(OriginalMachineCode, 0, BYTE_ARRAY_LENGTH);
        RtlZeroMemory(CurrentMachineCode, 0, BYTE_ARRAY_LENGTH);

        // 獲取到當前函數地址
        Address = GetSystemRoutineAddress(FunctionList[i]);

        printf("\n函數地址: %p | 函數名: %s\n", Address, FunctionList[i]);
        if (Address == 0 || Address < KernelBase)
        {
            return 0;
        }

        GetOriginalMachineCode(Address, OriginalMachineCode, BYTE_ARRAY_LENGTH);
        PrintBytes("原始機器碼: ", OriginalMachineCode, BYTE_ARRAY_LENGTH);

        GetCurrentMachineCode(Address, CurrentMachineCode, BYTE_ARRAY_LENGTH);
        PrintBytes("當前機器碼: ", CurrentMachineCode, BYTE_ARRAY_LENGTH);

        /*
        // 不相同則詢問是否恢復
        if (memcmp(OriginalMachineCode, CurrentMachineCode, BYTE_ARRAY_LENGTH))
        {
            printf("按下[ENTER]恢復鉤子");
            getchar();
            ClearInlineHook(Address, OriginalMachineCode, BYTE_ARRAY_LENGTH);
        }
        */
    }

    // 註銷
    InitEngine(FALSE);
    system("pause");

    return 0;
}

首先編譯驅動程序WinDDK.sys並通過KmdManager將驅動程序拉起來,運行客戶端lyshark.exe程序會輸出當前FunctionList列表中,指定的4個函數的掛鉤情況。

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