在筆者上一篇文章《驅動開發:核心層InlineHook掛鉤函數》
中介紹了通過替換函數
頭部程式碼的方式實現Hook
掛鉤,對於ARK工具來說實現掃描與摘除InlineHook
勾點也是最基本的功能,此類功能的實現一般可在應用層進行,而驅動層只需要保留一個讀寫位元組
的函數即可,將複雜的流程放在應用層實現是一個非常明智的選擇,與《驅動開發:核心實現程序反組合》
中所使用的讀寫驅動基本一致,本篇文章中的驅動只保留兩個功能,控制訊號IOCTL_GET_CUR_CODE
用於讀取函數的前16個位元組的記憶體,訊號IOCTL_SET_ORI_CODE
則用於設定前16個位元組的記憶體。
之所以是前16個位元組是因為一般的內聯Hook
只需要使用兩條指令就可實現劫持,如下是通用ARK工具掃描到的被掛鉤函數的樣子。
首先將核心驅動程式程式碼放到如下,核心驅動程式沒有任何特別的,僅僅只是一個通用驅動模板,在其基礎上使用CR3讀寫,如果不理解CR3讀寫的原理您可以去看《驅動開發:核心CR3切換讀寫記憶體》
這一篇中的詳細介紹。
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#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
加上磁碟中的相對偏移
就得到了當前記憶體中載入函數的實際地址。
呼叫GetOriginalMachineCode()
則用於獲取相對偏移地址,該地址的獲取方式如下,使用者傳入一個Address
當前地址,該地址減去KernelBase
記憶體中的基址,然後再加上hKernel
磁碟載入的基址來獲取到相對偏移。
有了這兩條資訊那麼功能也就實現了,通過GetOriginalMachineCode()
得到指定記憶體地址處原始機器碼,通過GetCurrentMachineCode()
得到當前記憶體機器碼,兩者通過memcmp()
函數比對即可知道是否被掛鉤了,如果被掛鉤則可以通過CR3切換將原始機器碼覆蓋到特定位置替換即可,這段程式的完整程式碼如下;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#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個函數的掛鉤情況。
WIN64核心程式設計基礎 胡文亮