在筆者的上一篇文章《驅動開發:內核特征碼掃描PE代碼段》
中LyShark
帶大家通過封裝好的LySharkToolsUtilKernelBase
函式實作了動態獲取內核模塊基址,并通過ntimage.h
頭檔案中提供的系列函式決議了指定內核模塊的PE節表
引數,本章將繼續延申這個話題,實作對PE檔案匯出表的決議任務,匯出表無法動態獲取,決議匯出表則必須讀入內核模塊到記憶體才可繼續決議,所以我們需要分兩步走,首先讀入內核磁盤檔案到記憶體,然后再通過ntimage.h
中的系列函式決議即可,
當PE檔案執行時Windows裝載器將檔案裝入記憶體并將匯入表中登記的DLL檔案一并裝入,再根據DLL檔案中函式的匯出資訊對可執行檔案的匯入表(IAT)進行修正,匯出函式在DLL檔案中,匯出資訊被保存在匯出表,匯出表就是記載著元件的一些匯出資訊,通過匯出表,DLL檔案可以向系統提供匯出函式的名稱、序號和入口地址等資訊,以便Windows裝載器能夠通過這些資訊來完成動態鏈接的整個程序,
匯出函式存盤在PE檔案的匯出表里,匯出表的位置存放在PE檔案頭中的資料目錄表中,與匯出表對應的專案是資料目錄中的首個IMAGE_DATA_DIRECTORY
結構,從這個結構的VirtualAddress
欄位得到的就是匯出表的RVA值,匯出表同樣可以使用函式名或序號這兩種方法匯出函式,
匯出表的起始位置有一個IMAGE_EXPORT_DIRECTORY
結構,與匯入表中有多個IMAGE_IMPORT_DESCRIPTOR
結構不同,匯出表只有一個IMAGE_EXPORT_DIRECTORY
結構,該結構定義如下:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp; // 檔案的產生時刻
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; // 指向檔案名的RVA
DWORD Base; // 匯出函式的起始序號
DWORD NumberOfFunctions; // 匯出函式總數
DWORD NumberOfNames; // 以名稱匯出函式的總數
DWORD AddressOfFunctions; // 匯出函式地址表的RVA
DWORD AddressOfNames; // 函式名稱地址表的RVA
DWORD AddressOfNameOrdinals; // 函式名序號表的RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
上面的_IMAGE_EXPORT_DIRECTORY
結構如果總結成一張圖,如下所示:
在上圖中最左側AddressOfNames
結構成員指向了一個陣列,陣列里保存著一組RVA,每個RVA指向一個字串即匯出的函式名,與這個函式名對應的是AddressOfNameOrdinals
中的結構成員,該對應項存盤的正是函式的唯一編號并與AddressOfFunctions
結構成員相關聯,形成了一個匯出鏈式結構體,
獲取匯出函式地址時,先在AddressOfNames
中找到對應的名字MyFunc1
,該函式在AddressOfNames
中是第1項,然后從AddressOfNameOrdinals
中取出第1項的值這里是1,然后就可以通過匯出函式的序號AddressOfFunctions[1]
取出函式的入口RVA,然后通過RVA加上模塊基址便是第一個匯出函式的地址,向后每次相加匯出函式偏移即可依次遍歷出所有的匯出函式地址,
其決議程序與應用層基本保持一致,如果不懂應用層如何決議也可以去看我以前寫過的《PE格式:手寫PE結構決議工具》
里面具體詳細的分析了決議流程,
首先使用InitializeObjectAttributes()
打開檔案,打開后可獲取到該檔案的句柄,InitializeObjectAttributes
宏初始化一個OBJECT_ATTRIBUTES
結構體, 當一個例程打開物件時由此結構體指定目標物件的屬性,此函式的微軟定義如下;
VOID InitializeObjectAttributes(
[out] POBJECT_ATTRIBUTES p, // 權限
[in] PUNICODE_STRING n, // 檔案名
[in] ULONG a, // 輸出檔案
[in] HANDLE r, // 權限
[in, optional] PSECURITY_DESCRIPTOR s // 0
);
當權限句柄被初始化后則即呼叫ZwOpenFile()
打開一個檔案使用權限FILE_SHARE_READ
打開,打開檔案函式微軟定義如下;
NTSYSAPI NTSTATUS ZwOpenFile(
[out] PHANDLE FileHandle, // 回傳打開檔案的句柄
[in] ACCESS_MASK DesiredAccess, // 打開的權限,一般設為GENERIC_ALL,
[in] POBJECT_ATTRIBUTES ObjectAttributes, // OBJECT_ATTRIBUTES結構
[out] PIO_STATUS_BLOCK IoStatusBlock, // 指向一個結構體的指標,該結構體指明打開檔案的狀態,
[in] ULONG ShareAccess, // 共享的權限,可以是FILE_SHARE_READ 或者 FILE_SHARE_WRITE,
[in] ULONG OpenOptions // 打開選項,一般設為 FILE_SYNCHRONOUS_IO_NONALERT,
);
接著檔案被打開后,我們還需要呼叫ZwCreateSection()
該函式的作用是創建一個Section
節物件,并以PE結構中的SectionALignment
大小對齊映射檔案,其微軟定義如下;
NTSYSAPI NTSTATUS ZwCreateSection(
[out] PHANDLE SectionHandle, // 指向 HANDLE 變數的指標,該變數接收 section 物件的句柄,
[in] ACCESS_MASK DesiredAccess, // 指定一個 ACCESS_MASK 值,該值確定對 物件的請求訪問權限,
[in, optional] POBJECT_ATTRIBUTES ObjectAttributes, // 指向 OBJECT_ATTRIBUTES 結構的指標,該結構指定物件名稱和其他屬性,
[in, optional] PLARGE_INTEGER MaximumSize, // 指定節的最大大小(以位元組為單位),
[in] ULONG SectionPageProtection, // 指定要在 節中的每個頁面上放置的保護,
[in] ULONG AllocationAttributes, // 指定確定節的分配屬性的SEC_XXX 標志的位掩碼,
[in, optional] HANDLE FileHandle // (可選)指定打開的檔案物件的句柄,
);
最后讀取匯出表就要將一個磁盤中的檔案映射到記憶體中,記憶體映射核心檔案時ZwMapViewOfSection()
該系列函式在應用層名叫MapViewOfSection()
只是一個是內核層一個應用層,這兩個函式引數傳遞基本一致,以ZwMapViewOfSection
為例,其微軟定義如下;
NTSYSAPI NTSTATUS ZwMapViewOfSection(
[in] HANDLE SectionHandle, // 接收一個節物件
[in] HANDLE ProcessHandle, // 行程句柄,此處使用NtCurrentProcess()獲取自身句柄
[in, out] PVOID *BaseAddress, // 指定填充地址
[in] ULONG_PTR ZeroBits, // 0
[in] SIZE_T CommitSize, // 每次提交大小 1024
[in, out, optional] PLARGE_INTEGER SectionOffset, // 0
[in, out] PSIZE_T ViewSize, // 瀏覽大小
[in] SECTION_INHERIT InheritDisposition, // ViewShare
[in] ULONG AllocationType, // 分配型別 MEM_TOP_DOWN
[in] ULONG Win32Protect // 權限 PAGE_READWRITE(讀寫)
);
將如上函式研究明白那么代碼就變得很容易了,首先InitializeObjectAttributes
設定檔案權限與屬性,然后呼叫ZwOpenFile
打開檔案,接著呼叫ZwCreateSection
創建節物件,最后呼叫ZwMapViewOfSection
將磁盤檔案映射到記憶體,這段代碼實作起來很簡單,完整案例如下所示;
// 署名權
// 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>
// 記憶體映射檔案
NTSTATUS KernelMapFile(UNICODE_STRING FileName, HANDLE *phFile, HANDLE *phSection, PVOID *ppBaseAddress)
{
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
OBJECT_ATTRIBUTES objectAttr = { 0 };
IO_STATUS_BLOCK iosb = { 0 };
PVOID pBaseAddress = NULL;
SIZE_T viewSize = 0;
// 設定檔案權限
InitializeObjectAttributes(&objectAttr, &FileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
// 打開檔案
status = ZwOpenFile(&hFile, GENERIC_READ, &objectAttr, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
if (!NT_SUCCESS(status))
{
return status;
}
// 創建節物件
status = ZwCreateSection(&hSection, SECTION_MAP_READ | SECTION_MAP_WRITE, NULL, 0, PAGE_READWRITE, 0x1000000, hFile);
if (!NT_SUCCESS(status))
{
ZwClose(hFile);
return status;
}
// 映射到記憶體
status = ZwMapViewOfSection(hSection, NtCurrentProcess(), &pBaseAddress, 0, 1024, 0, &viewSize, ViewShare, MEM_TOP_DOWN, PAGE_READWRITE);
if (!NT_SUCCESS(status))
{
ZwClose(hSection);
ZwClose(hFile);
return status;
}
// 回傳資料
*phFile = hFile;
*phSection = hSection;
*ppBaseAddress = pBaseAddress;
return status;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("驅動卸載 \n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
PVOID pBaseAddress = NULL;
UNICODE_STRING FileName = {0};
// 初始化字串
RtlInitUnicodeString(&FileName, L"\\??\\C:\\Windows\\System32\\ntoskrnl.exe");
// 記憶體映射檔案
status = KernelMapFile(FileName, &hFile, &hSection, &pBaseAddress);
if (NT_SUCCESS(status))
{
DbgPrint("讀取記憶體地址 = %p \n", pBaseAddress);
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
運行這段程式,即可讀取到ntoskrnl.exe
磁盤所在檔案的記憶體映像基地址,效果如下所示;
如上代碼讀入了ntoskrnl.exe
檔案,接下來就是決議匯出表,首先將pBaseAddress
決議為PIMAGE_DOS_HEADER
獲取DOS頭,并在DOS頭中尋找PIMAGE_NT_HEADERS
頭,接著在NTHeader
頭中得到資料目錄表,此處指向的就是匯出表PIMAGE_EXPORT_DIRECTORY
通過pExportTable->NumberOfNames
可得到匯出表的數量,通過(PUCHAR)pDosHeader + pExportTable->AddressOfNames
得到匯出表的地址,依次回圈讀取即可得到完整的匯出表,
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
PVOID pBaseAddress = NULL;
UNICODE_STRING FileName = { 0 };
LONG FunctionIndex = 0;
// 初始化字串
RtlInitUnicodeString(&FileName, L"\\??\\C:\\Windows\\System32\\ntoskrnl.exe");
// 記憶體映射檔案
status = KernelMapFile(FileName, &hFile, &hSection, &pBaseAddress);
if (NT_SUCCESS(status))
{
DbgPrint("[LyShark] 讀取記憶體地址 = %p \n", pBaseAddress);
}
// Dos 頭
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;
// NT 頭
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);
// 匯出表
PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress);
// 有名稱的匯出函式個數
ULONG ulNumberOfNames = pExportTable->NumberOfNames;
DbgPrint("[LyShark.com] 匯出函式個數: %d \n\n", ulNumberOfNames);
// 匯出函式名稱地址表
PULONG lpNameArray = (PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfNames);
PCHAR lpName = NULL;
// 開始遍歷匯出表(輸出ulNumberOfNames匯出函式)
for (ULONG i = 0; i < ulNumberOfNames; i++)
{
lpName = (PCHAR)((PUCHAR)pDosHeader + lpNameArray[i]);
// 獲取匯出函式地址
USHORT uHint = *(USHORT *)((PUCHAR)pDosHeader + pExportTable->AddressOfNameOrdinals + 2 * i);
ULONG ulFuncAddr = *(PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfFunctions + 4 * uHint);
PVOID lpFuncAddr = (PVOID)((PUCHAR)pDosHeader + ulFuncAddr);
// 獲取SSDT函式Index
FunctionIndex = *(ULONG *)((PUCHAR)lpFuncAddr + 4);
DbgPrint("序號: [ %d ] | Hint: %d | 地址: %p | 函式名: %s \n", i, uHint, lpFuncAddr, lpName);
}
// 釋放指標
ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);
ZwClose(hSection);
ZwClose(hFile);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
代碼運行后即可獲取到當前ntoskrnl.exe
程式中的所有匯出函式,輸出效果如下所示;
- SSDT表通常會決議
\\??\\C:\\Windows\\System32\\ntoskrnl.exe
- SSSDT表通常會決議
\\??\\C:\\Windows\\System32\\win32k.sys
根據上方的函式流程將其封裝為GetAddressFromFunction()
用戶傳入DllFileName
指定的PE檔案,以及需要讀取的pszFunctionName
函式名,即可輸出該函式的匯出地址,
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
// 尋找指定函式得到記憶體地址
ULONG64 GetAddressFromFunction(UNICODE_STRING DllFileName, PCHAR pszFunctionName)
{
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
PVOID pBaseAddress = NULL;
// 記憶體映射檔案
status = KernelMapFile(DllFileName, &hFile, &hSection, &pBaseAddress);
if (!NT_SUCCESS(status))
{
return 0;
}
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress);
ULONG ulNumberOfNames = pExportTable->NumberOfNames;
PULONG lpNameArray = (PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfNames);
PCHAR lpName = NULL;
for (ULONG i = 0; i < ulNumberOfNames; i++)
{
lpName = (PCHAR)((PUCHAR)pDosHeader + lpNameArray[i]);
USHORT uHint = *(USHORT *)((PUCHAR)pDosHeader + pExportTable->AddressOfNameOrdinals + 2 * i);
ULONG ulFuncAddr = *(PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfFunctions + 4 * uHint);
PVOID lpFuncAddr = (PVOID)((PUCHAR)pDosHeader + ulFuncAddr);
if (_strnicmp(pszFunctionName, lpName, strlen(pszFunctionName)) == 0)
{
ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);
ZwClose(hSection);
ZwClose(hFile);
return (ULONG64)lpFuncAddr;
}
}
ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);
ZwClose(hSection);
ZwClose(hFile);
return 0;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("驅動卸載 \n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
UNICODE_STRING FileName = { 0 };
ULONG64 FunctionAddress = 0;
// 初始化字串
RtlInitUnicodeString(&FileName, L"\\??\\C:\\Windows\\System32\\ntdll.dll");
// 取函式記憶體地址
FunctionAddress = GetAddressFromFunction(FileName, "ZwQueryVirtualMemory");
DbgPrint("ZwQueryVirtualMemory記憶體地址 = %p \n", FunctionAddress);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
如上程式所示,當運行后即可獲取到ntdll.dll
模塊內ZwQueryVirtualMemory
的匯出地址,輸出效果如下所示;
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553915.html
標籤:C++
上一篇:Java中如何中斷執行緒
下一篇:返回列表