驅動開發:PE匯出函數與RVA轉換

2023-06-07 12:01:21

在筆者上篇文章《驅動開發:核心掃描SSDT掛鉤狀態》中簡單介紹瞭如何掃描被掛鉤的SSDT函數,並簡單介紹瞭如何解析匯出表,本章將繼續延申PE匯出表的解析,實現一系列靈活的解析如通過傳入函數名解析出函數的RVA偏移,ID索引,Index下標等引數,並將其封裝為可直接使用的函數,以在後期需要時可以被直接參照,同樣為了節約篇幅本章中的LoadKernelFile()記憶體對映函數如需要使用請去前一篇文章中自行摘取。

首先實現GetRvaFromModuleName()函數,當用戶傳入引數後自動將函數名解析為對應的RVA偏移或Index下標索引值,該函數接收三個引數傳遞,分別是wzFileName模組名,FunctionName所在模組內的函數名,Flag標誌引數,函數輸出ULONG64型別的資料。

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

// 從指定模組中得到特定函數的RVA或相對序號相對偏移
ULONG64 GetRvaFromModuleName(WCHAR *wzFileName, UCHAR *FunctionName, INT Flag)
{
	// 載入核心模組
	PVOID BaseAddress = LoadKernelFile(wzFileName);

	// 取出匯出表
	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;
		}
	}
	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;
		}
	}
	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;
		}
	}
	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);

		// 如果找到則返回RVA
		if (!_stricmp((const char *)FunctionName, FunName))
		{
			// 等於1則返回RVA
			if (Flag == 1)
			{
				TargetOff = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];
				// DbgPrint("索引 [ %p ] 函數名 [ %s ] 相對RVA [ %p ] \n", *AddressOfNameOrdinals, FunName, TargetOff);
				return TargetOff;
			}
			// 返回索引
			else if (Flag == 0)
			{
				return *AddressOfNameOrdinals;
			}
		}
	}

	// 結束後釋放記憶體
	ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");
	return 0;
}

呼叫該函數很容易,傳入模組路徑以及該模組內的函數名,解析出RVA地址或Index下標。

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	// 函數分別傳入 [模組路徑,函數名,標誌=1] 返回該匯出函數的RVA
	ULONG64 get_rva = GetRvaFromModuleName(L"\\SystemRoot\\system32\\ntoskrnl.exe", "NtReadFile", 1);
	DbgPrint("NtReadFile RVA = %p \n", get_rva);

	// 函數分別傳入 [模組路徑,函數名,標誌=0] 返回該匯出函數的ID下標
	ULONG64 get_id = GetRvaFromModuleName(L"\\SystemRoot\\system32\\ntoskrnl.exe", "NtReadFile", 0);
	DbgPrint("NtReadFile ID = %d \n", get_id);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

編譯並執行程式,分別獲取到ntoskrnl.exe模組內NtReadFile函數的RVA,Index索引,呼叫效果如下;

第二個函數GetModuleNameFromRVA()則實現傳入RVA或者函數Index序號,解析出函數名,具體實現方法與如上函數基本一致,僅僅只是在過濾時做了調整。

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

// 根據傳入的函數RVA或Index下標,獲取該函數的函數名
PCHAR GetModuleNameFromRVA(WCHAR *wzFileName, ULONG64 uRVA, INT Flag)
{
	// 載入核心模組
	PVOID BaseAddress = LoadKernelFile(wzFileName);

	// 取出匯出表
	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;
		}
	}
	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;
		}
	}
	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;
		}
	}
	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);
		TargetOff = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];

		// 等於1則通過RVA返回函數名
		if (Flag == 1)
		{
			if (uRVA == TargetOff)
			{
				return FunName;
			}
		}
		// 返回索引
		else if (Flag == 0)
		{
			if (uRVA == *AddressOfNameOrdinals)
			{
				return FunName;
			}
		}
	}

	// 結束後釋放記憶體
	ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");
	return "None";
}

呼叫GetModuleNameFromRVA()並傳入相應的RVA偏移或Index下標。

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("hello lyshark.com \n");

	PCHAR function_name;

	// 傳入函數RVA得到函數名
	function_name = GetModuleNameFromRVA(L"\\SystemRoot\\system32\\ntoskrnl.exe", 0x5e5220, 1);
	DbgPrint("根據RVA得到函數名 = %s \n", function_name);

	// 傳入函數下標得到函數名
	function_name = GetModuleNameFromRVA(L"\\SystemRoot\\system32\\ntoskrnl.exe", 1472, 0);
	DbgPrint("根據Index得到函數名 = %s \n", function_name);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

編譯並執行程式,呼叫後分別獲取到RVA=0x5e5220Index=1472的函數名;