在筆者上一篇文章《驅動開發:內核實作SSDT掛鉤與摘鉤》
中介紹了如何對SSDT
函式進行Hook
掛鉤與摘鉤的,本章將繼續實作一個新功能,如何檢測SSDT
函式是否掛鉤,要實作檢測掛鉤狀態
有兩種方式,第一種方式則是類似于《驅動開發:摘除InlineHook內核鉤子》
文章中所演示的通過讀取函式的前16個位元組與原始位元組
做對比來判斷掛鉤狀態,另一種方式則是通過對比函式的當前地址
與起源地址
進行判斷,為了提高檢測準確性本章將采用兩種方式混合檢測,
具體原理,通過決議內核檔案PE結構
找到匯出表,依次計算出每一個內核函式的RVA
相對偏移,通過與內核模塊基址
相加此相對偏移得到函式的原始地址,然后再動態獲取函式當前地址,兩者作比較即可得知指定內核函式是否被掛鉤,
在實作這個功能之前我們需要解決兩個問題,第一個問題是如何得到特定內核模塊的記憶體模塊基址
此處我們需要封裝一個GetOsBaseAddress()
用戶只需要傳入指定的內核模塊即可得到該模塊基址,如此簡單的代碼沒有任何解釋的必要;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include <ntifs.h>
#include <ntimage.h>
#include <ntstrsafe.h>
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashLinks;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
// 得到內核模塊基址
ULONGLONG GetOsBaseAddress(PDRIVER_OBJECT pDriverObject, WCHAR *wzData)
{
UNICODE_STRING osName = { 0 };
// WCHAR wzData[0x100] = L"ntoskrnl.exe";
RtlInitUnicodeString(&osName, wzData);
LDR_DATA_TABLE_ENTRY *pDataTableEntry, *pTempDataTableEntry;
//雙回圈鏈表定義
PLIST_ENTRY pList;
//指向驅動物件的DriverSection
pDataTableEntry = (LDR_DATA_TABLE_ENTRY*)pDriverObject->DriverSection;
//判斷是否為空
if (!pDataTableEntry)
{
return 0;
}
//得到鏈表地址
pList = pDataTableEntry->InLoadOrderLinks.Flink;
// 判斷是否等于頭部
while (pList != &pDataTableEntry->InLoadOrderLinks)
{
pTempDataTableEntry = (LDR_DATA_TABLE_ENTRY *)pList;
if (RtlEqualUnicodeString(&pTempDataTableEntry->BaseDllName, &osName, TRUE))
{
return (ULONGLONG)pTempDataTableEntry->DllBase;
}
pList = pList->Flink;
}
return 0;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("驅動卸載 \n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("Hello LyShark.com \n");
ULONGLONG kernel_base = GetOsBaseAddress(Driver, L"ntoskrnl.exe");
DbgPrint("ntoskrnl.exe => 模塊基址: %p \n", kernel_base);
ULONGLONG hal_base = GetOsBaseAddress(Driver, L"hal.dll");
DbgPrint("hal.dll => 模塊基址: %p \n", hal_base);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
如上直接編譯并運行,即可輸出ntoskrnl.exe
以及hal.dll
兩個內核模塊的基址;
其次我們還需要實作另一個功能,此時想像一下當我告訴你一個記憶體地址,我想要查該記憶體地址屬于哪個模塊該如何實作,其實很簡單只需要拿到這個地址依次去判斷其是否大于等于該模塊的基地址,并小于等于該模塊的結束地址,那么我們就認為該地址落在了此模塊上,在這個思路下LyShark
實作了以下代碼片段,
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include <ntifs.h>
#include <ntimage.h>
#include <ntstrsafe.h>
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashLinks;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
// 掃描指定地址是否在某個模塊內
VOID ScanKernelModuleBase(PDRIVER_OBJECT pDriverObject, ULONGLONG address)
{
LDR_DATA_TABLE_ENTRY *pDataTableEntry, *pTempDataTableEntry;
PLIST_ENTRY pList;
pDataTableEntry = (LDR_DATA_TABLE_ENTRY*)pDriverObject->DriverSection;
if (!pDataTableEntry)
{
return;
}
// 得到鏈表地址
pList = pDataTableEntry->InLoadOrderLinks.Flink;
// 判斷是否等于頭部
while (pList != &pDataTableEntry->InLoadOrderLinks)
{
pTempDataTableEntry = (LDR_DATA_TABLE_ENTRY *)pList;
ULONGLONG start_address = (ULONGLONG)pTempDataTableEntry->DllBase;
ULONGLONG end_address = start_address + (ULONG)pTempDataTableEntry->SizeOfImage;
// 判斷區間
// DbgPrint("起始地址 [ %p ] 結束地址 [ %p ] \n",start_address,end_address);
if (address >= start_address && address <= end_address)
{
DbgPrint("[LyShark] 當前函式所在模塊 [ %ws ] \n", (CHAR *)pTempDataTableEntry->FullDllName.Buffer);
}
pList = pList->Flink;
}
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("驅動卸載 \n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("Hello LyShark.com \n");
ScanKernelModuleBase(Driver, 0xFFFFF8051AF5D030);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
我們以0xFFFFF8051AF5D030
地址為例對其進行判斷可看到輸出了如下結果,此地址被落在了hal.dll
模塊上;
為了能讀入磁盤PE檔案到記憶體此時我們還需要封裝一個LoadKernelFile()
函式,該函式的作用是讀入一個內核檔案到記憶體空間中,此處如果您使用前一篇《驅動開發:內核決議PE結構匯出表》
文章中的記憶體映射函式來讀寫則會藍屏,原因很簡單KernelMapFile()
是映射而映射一定無法一次性完整裝載其次此方法本質上還在占用原檔案,而LoadKernelFile()
則是讀取磁盤檔案并將其完整拷貝一份,這是兩者的本質區別,如下代碼則是實作完整拷貝的實作;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include <ntifs.h>
#include <ntimage.h>
#include <ntstrsafe.h>
// 將內核檔案裝載入記憶體(磁盤)
PVOID LoadKernelFile(WCHAR *wzFileName)
{
NTSTATUS Status;
HANDLE FileHandle;
IO_STATUS_BLOCK ioStatus;
FILE_STANDARD_INFORMATION FileInformation;
// 設定路徑
UNICODE_STRING uniFileName;
RtlInitUnicodeString(&uniFileName, wzFileName);
// 初始化打開檔案的屬性
OBJECT_ATTRIBUTES objectAttributes;
InitializeObjectAttributes(&objectAttributes, &uniFileName, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL);
// 打開檔案
Status = IoCreateFile(&FileHandle, FILE_READ_ATTRIBUTES | SYNCHRONIZE, &objectAttributes, &ioStatus, 0, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0, CreateFileTypeNone, NULL, IO_NO_PARAMETER_CHECKING);
if (!NT_SUCCESS(Status))
{
return 0;
}
// 獲取檔案資訊
Status = ZwQueryInformationFile(FileHandle, &ioStatus, &FileInformation, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation);
if (!NT_SUCCESS(Status))
{
ZwClose(FileHandle);
return 0;
}
// 判斷檔案大小是否過大
if (FileInformation.EndOfFile.HighPart != 0)
{
ZwClose(FileHandle);
return 0;
}
// 取檔案大小
ULONG64 uFileSize = FileInformation.EndOfFile.LowPart;
// 分配記憶體
PVOID pBuffer = ExAllocatePoolWithTag(NonPagedPool, uFileSize + 0x100, (ULONG)"LyShark");
if (pBuffer == NULL)
{
ZwClose(FileHandle);
return 0;
}
// 從頭開始讀取檔案
LARGE_INTEGER byteOffset;
byteOffset.LowPart = 0;
byteOffset.HighPart = 0;
Status = ZwReadFile(FileHandle, NULL, NULL, NULL, &ioStatus, pBuffer, uFileSize, &byteOffset, NULL);
if (!NT_SUCCESS(Status))
{
ZwClose(FileHandle);
return 0;
}
// ExFreePoolWithTag(pBuffer, (ULONG)"LyShark");
ZwClose(FileHandle);
return pBuffer;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("驅動卸載 \n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
// 加載內核模塊
PVOID BaseAddress = LoadKernelFile(L"\\SystemRoot\\system32\\ntoskrnl.exe");
DbgPrint("BaseAddress = %p\n", BaseAddress);
// 決議PE頭
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeaders;
// DLL記憶體資料轉成DOS頭結構
pDosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
// 取出PE頭結構
pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)BaseAddress + pDosHeader->e_lfanew);
DbgPrint("[LyShark] => 映像基址: %p \n", pNtHeaders->OptionalHeader.ImageBase);
// 結束后釋放記憶體
ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
運行如上這段程式,則會將ntoskrnl.exe
檔案載入到記憶體,并讀取出其中的OptionalHeader.ImageBase
映像基址,如下圖所示;
有了上述方法,最后一步就是組合并實作判斷即可,如下代碼通過對匯出表的決議,并過濾出所有的Nt
開頭的系列函式,然后依次對比起源地址與原地址是否一致,得出是否被掛鉤,完整代碼如下所示;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
ULONGLONG ntoskrnl_base = 0;
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("Hello LyShark.com \n");
// 加載內核模塊
PVOID BaseAddress = LoadKernelFile(L"\\SystemRoot\\system32\\ntoskrnl.exe");
DbgPrint("BaseAddress = %p\n", BaseAddress);
// 獲取內核模塊地址
ntoskrnl_base = GetOsBaseAddress(Driver, L"ntoskrnl.exe");
// 取出匯出表
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeaders;
PIMAGE_SECTION_HEADER pSectionHeader;
ULONGLONG FileOffset;
PIMAGE_EXPORT_DIRECTORY pExportDirectory;
// DLL記憶體資料轉成DOS頭結構
pDosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
// 取出PE頭結構
pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)BaseAddress + pDosHeader->e_lfanew);
// 判斷PE頭匯出表表是否為空
if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
{
return 0;
}
// 取出匯出表偏移
FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
// 取出節頭結構
pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;
// 遍歷節結構進行地址運算
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 匯出表地址
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)BaseAddress + FileOffset);
// 取出匯出表函式地址
PULONG AddressOfFunctions;
FileOffset = pExportDirectory->AddressOfFunctions;
// 遍歷節結構進行地址運算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 這里注意一下foa和rva
AddressOfFunctions = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
// 取出匯出表函式名字
PUSHORT AddressOfNameOrdinals;
FileOffset = pExportDirectory->AddressOfNameOrdinals;
// 遍歷節結構進行地址運算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 注意一下foa和rva
AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)BaseAddress + FileOffset);
// 取出匯出表函式序號
PULONG AddressOfNames;
FileOffset = pExportDirectory->AddressOfNames;
// 遍歷節結構進行地址運算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 注意一下foa和rva
AddressOfNames = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
// 分析匯出表
ULONG uOffset;
LPSTR FunName;
ULONG uAddressOfNames;
ULONG TargetOff = 0;
for (ULONG uIndex = 0; uIndex < pExportDirectory->NumberOfNames; uIndex++, AddressOfNames++, AddressOfNameOrdinals++)
{
uAddressOfNames = *AddressOfNames;
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= uAddressOfNames && uAddressOfNames <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
uOffset = uAddressOfNames - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
FunName = (LPSTR)((ULONGLONG)BaseAddress + uOffset);
if (FunName[0] == 'N' && FunName[1] == 't')
{
// 得到相對RVA
TargetOff = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];
// LPSTR -> UNCODE
// 先轉成ANSI 然后在轉成 UNCODE
ANSI_STRING ansi = { 0 };
UNICODE_STRING uncode = { 0 };
RtlInitAnsiString(&ansi, FunName);
RtlAnsiStringToUnicodeString(&uncode, &ansi, TRUE);
// 得到當前地址
PULONGLONG local_address = MmGetSystemRoutineAddress(&uncode);
/*
// 讀入內核函式前6個位元組
unsigned char local_opcode[6] = { 0 };
unsigned char this_opcode[6] = { 0 };
RtlCopyMemory(local_opcode, (void *)local_address, 6);
RtlCopyMemory(this_opcode, (void *)(ntoskrnl_base + TargetOff), 6);
// 當前機器碼
for (int x = 0; x < 6; x++)
{
DbgPrint("當前 [ %d ] 機器碼 [ %x ] ", x, local_opcode[x]);
}
// 起源機器碼
for (int y = 0; y < 6; y++)
{
DbgPrint("起源 [ %d ] 機器碼 [ %x ] ", y, this_opcode[y]);
}
*/
// 檢測是否被掛鉤 [不相等則說明被掛鉤了]
if (local_address != (ntoskrnl_base + TargetOff))
{
DbgPrint("索引 [ %d ] RVA [ %p ] \n --> 起源地址 [ %p ] | 當前地址 [ %p ] | 函式名 [ %s ] \n\n",
*AddressOfNameOrdinals, TargetOff, ntoskrnl_base + TargetOff, local_address, FunName);
}
// 檢查當前地址所在模塊
// ScanKernelModuleBase(Driver, (PULONGLONG)local_address);
}
}
// 結束后釋放記憶體
ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
使用ARK工具手動改寫幾個Nt開頭的函式,并運行這段代碼,觀察是否可以輸出被掛鉤的函式詳情;
文章出處:https://www.cnblogs.com/LyShark/p/17149534.html
本博客中的文章,轉載請注明出處,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/554422.html
標籤:C++
上一篇:演算法 in Go:Binary Search(二分查找)
下一篇:返回列表