驅動開發:核心讀寫記憶體多級偏移

2023-06-27 12:01:46

讓我們繼續在《核心讀寫記憶體浮點數》的基礎之上做一個簡單的延申,如何實現多級偏移讀寫,其實很簡單,讀寫函數無需改變,只是在讀寫之前提前做好計算工作,以此來得到一個記憶體偏移值,並通過呼叫記憶體寫入原函數實現寫出資料的目的。

以讀取偏移記憶體為例,如下程式碼同樣來源於本人的LyMemory讀寫驅動專案,其中核心函數為WIN10_ReadDeviationIntMemory()該函數的主要作用是通過使用者傳入的基地址與偏移值,動態計算出當前的動態地址。

函數首先將基地址指向要讀取的變數,並將其轉換為LPCVOID型別的指標。然後將指向變數值的緩衝區轉換為LPVOID型別的指標。接下來,函數使用PsLookupProcessByProcessId函數查詢目標程序並返回其PEPROCESS結構體。隨後,函數從偏移地址陣列的最後一個元素開始迭代,每次迴圈都從目標程序中讀取4位元組整數型資料,並將其儲存在Value變數中。然後,函數將基地址指向Value和偏移地址的和,以便在下一次迴圈中讀取更深層次的變數。最後,函數將基地址指向最終變數的地址,讀取變數的值,並返回。

如下案例所示,使用者傳入程序基址以及offset偏移值時,只需要動態計算出該偏移地址,並與基址相加即可得到動態地址。

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

// 普通Ke記憶體讀取
NTSTATUS KeReadProcessMemory(PEPROCESS Process, PVOID SourceAddress, PVOID TargetAddress, SIZE_T Size)
{
	PEPROCESS SourceProcess = Process;
	PEPROCESS TargetProcess = PsGetCurrentProcess();
	SIZE_T Result;
	if (NT_SUCCESS(MmCopyVirtualMemory(SourceProcess, SourceAddress, TargetProcess, TargetAddress, Size, KernelMode, &Result)))
		return STATUS_SUCCESS;
	else
		return STATUS_ACCESS_DENIED;
}

// 讀取整數記憶體多級偏移
/*
  Pid: 目標程序的程序ID。
  Base: 變數的基地址。
  offset: 相對基地址的多級偏移地址,用於定位變數。
  len: 偏移地址的數量。
*/
INT64 WIN10_ReadDeviationIntMemory(HANDLE Pid, LONG Base, DWORD offset[32], DWORD len)
{
	INT64 Value = 0;
	LPCVOID pbase = (LPCVOID)Base;
	LPVOID rbuffer = (LPVOID)&Value;

	PEPROCESS Process;
	PsLookupProcessByProcessId((HANDLE)Pid, &Process);

	for (int x = len - 1; x >= 0; x--)
	{
		__try
		{
			KeReadProcessMemory(Process, pbase, rbuffer, 4);
			pbase = (LPCVOID)(Value + offset[x]);
		}
		__except (EXCEPTION_EXECUTE_HANDLER)
		{
			return 0;
		}
	}

	__try
	{
		DbgPrint("讀取基址:%x \n", pbase);
		KeReadProcessMemory(Process, pbase, rbuffer, 4);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		return 0;
	}

	return Value;
}

// 驅動解除安裝例程
VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint("Uninstall Driver \n");
}

// 驅動入口地址
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark \n");

	DWORD PID = 4884;
	LONG PBase = 0x6566e0;
	LONG Size = 4;
	DWORD Offset[32] = { 0 };

	Offset[0] = 0x18;
	Offset[1] = 0x0;
	Offset[2] = 0x14;
	Offset[3] = 0x0c;

	// 讀取記憶體資料
	INT64 read = WIN10_ReadDeviationIntMemory(PID, PBase, Offset, Size);

	DbgPrint("PID: %d 基址: %p 偏移長度: %d \n", PID, PBase, Size);
	DbgPrint("[+] 1級偏移: %x \n", Offset[0]);
	DbgPrint("[+] 2級偏移: %x \n", Offset[1]);
	DbgPrint("[+] 3級偏移: %x \n", Offset[2]);
	DbgPrint("[+] 4級偏移: %x \n", Offset[3]);

	DbgPrint("[ReadMemory] 讀取偏移資料: %d \n", read);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

編譯並執行如上這段程式碼,則可獲取到PID=4884PBase的動態地址中的資料,如下圖所示;

至於如何將資料寫出四級偏移的基址上面,則只需要取出pbase裡面的基址,並通過原函數WIN10_WriteProcessMemory直接寫出資料即可,此出的原函數在《核心MDL讀寫程序記憶體》中已經做了詳細介紹,實現寫出程式碼如下所示;

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

// 普通Ke記憶體讀取
NTSTATUS KeReadProcessMemory(PEPROCESS Process, PVOID SourceAddress, PVOID TargetAddress, SIZE_T Size)
{
	PEPROCESS SourceProcess = Process;
	PEPROCESS TargetProcess = PsGetCurrentProcess();
	SIZE_T Result;
	if (NT_SUCCESS(MmCopyVirtualMemory(SourceProcess, SourceAddress, TargetProcess, TargetAddress, Size, KernelMode, &Result)))
		return STATUS_SUCCESS;
	else
		return STATUS_ACCESS_DENIED;
}

// Win10 記憶體寫入函數
BOOLEAN WIN10_WriteProcessMemory(HANDLE Pid, PVOID Address, SIZE_T BYTE_size, PVOID VirtualAddress)
{
	PVOID buff1;
	VOID *buff2;
	int MemoryNumerical = 0;
	KAPC_STATE KAPC = { 0 };

	PEPROCESS Process;
	PsLookupProcessByProcessId((HANDLE)Pid, &Process);

	__try
	{
		//分配記憶體
		buff1 = ExAllocatePoolWithTag((POOL_TYPE)0, BYTE_size, 1997);
		buff2 = buff1;
		*(int*)buff1 = 1;
		if (MmIsAddressValid((PVOID)VirtualAddress))
		{
			// 複製記憶體
			memcpy(buff2, VirtualAddress, BYTE_size);
		}
		else
		{
			return FALSE;
		}

		// 附加到要讀寫的程序
		KeStackAttachProcess((PRKPROCESS)Process, &KAPC);
		if (MmIsAddressValid((PVOID)Address))
		{
			// 判斷地址是否可寫
			ProbeForWrite(Address, BYTE_size, 1);
			// 複製記憶體
			memcpy(Address, buff2, BYTE_size);
		}
		else
		{
			return FALSE;
		}
		// 剝離附加的程序
		KeUnstackDetachProcess(&KAPC);
		ExFreePoolWithTag(buff2, 1997);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		return FALSE;
	}
	return FALSE;
}

// 寫入整數記憶體多級偏移
INT64 WIN10_WriteDeviationIntMemory(HANDLE Pid, LONG Base, DWORD offset[32], DWORD len, INT64 SetValue)
{
	INT64 Value = 0;
	LPCVOID pbase = (LPCVOID)Base;
	LPVOID rbuffer = (LPVOID)&Value;

	PEPROCESS Process;
	PsLookupProcessByProcessId((HANDLE)Pid, &Process);

	for (int x = len - 1; x >= 0; x--)
	{
		__try
		{
			KeReadProcessMemory(Process, pbase, rbuffer, 4);
			pbase = (LPCVOID)(Value + offset[x]);
		}
		__except (EXCEPTION_EXECUTE_HANDLER)
		{
			return 0;
		}
	}

	__try
	{
		KeReadProcessMemory(Process, pbase, rbuffer, 4);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		return 0;
	}

	// 使用原函數寫入
	BOOLEAN ref = WIN10_WriteProcessMemory(Pid, (void *)pbase, 4, &SetValue);
	if (ref == TRUE)
	{
		DbgPrint("[核心寫成功] # 寫入地址: %x \n", pbase);
		return 1;
	}
	return 0;
}

// 驅動解除安裝例程
VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint("Uninstall Driver \n");
}

// 驅動入口地址
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark \n");

	DWORD PID = 4884;
	LONG PBase = 0x6566e0;
	LONG Size = 4;
	INT64 SetValue = 100;

	DWORD Offset[32] = { 0 };

	Offset[0] = 0x18;
	Offset[1] = 0x0;
	Offset[2] = 0x14;
	Offset[3] = 0x0c;

	// 寫出記憶體資料
	INT64 write = WIN10_WriteDeviationIntMemory(PID, PBase, Offset, Size, SetValue);

	DbgPrint("PID: %d 基址: %p 偏移長度: %d \n", PID, PBase, Size);
	DbgPrint("[+] 1級偏移: %x \n", Offset[0]);
	DbgPrint("[+] 2級偏移: %x \n", Offset[1]);
	DbgPrint("[+] 3級偏移: %x \n", Offset[2]);
	DbgPrint("[+] 4級偏移: %x \n", Offset[3]);

	DbgPrint("[WriteMemory] 寫出偏移資料: %d \n", SetValue);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

執行如上程式碼將在0x6566e0所在的基址上,將資料替換為100,實現效果圖如下所示;

那麼如何實現讀寫記憶體浮點數,位元組集等多級偏移呢?

其實我們可以封裝一個WIN10_ReadDeviationMemory函數,讓其只計算得出偏移地址,而所需要寫出的型別則根據自己的實際需求配合不同的寫入函數完成,也就是將兩者分離開,如下則是一段實現計算偏移的程式碼片段,該程式碼同樣來自於本人的LyMemory驅動讀寫專案;

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

// 普通Ke記憶體讀取
NTSTATUS KeReadProcessMemory(PEPROCESS Process, PVOID SourceAddress, PVOID TargetAddress, SIZE_T Size)
{
	PEPROCESS SourceProcess = Process;
	PEPROCESS TargetProcess = PsGetCurrentProcess();
	SIZE_T Result;
	if (NT_SUCCESS(MmCopyVirtualMemory(SourceProcess, SourceAddress, TargetProcess, TargetAddress, Size, KernelMode, &Result)))
		return STATUS_SUCCESS;
	else
		return STATUS_ACCESS_DENIED;
}

// 讀取多級偏移記憶體動態地址
DWORD64 WIN10_ReadDeviationMemory(HANDLE Pid, LONG Base, DWORD offset[32], DWORD len)
{
	INT64 Value = 0;
	LPCVOID pbase = (LPCVOID)Base;
	LPVOID rbuffer = (LPVOID)&Value;

	PEPROCESS Process;
	PsLookupProcessByProcessId((HANDLE)Pid, &Process);

	for (int x = len - 1; x >= 0; x--)
	{
		__try
		{
			KeReadProcessMemory(Process, pbase, rbuffer, 4);
			pbase = (LPCVOID)(Value + offset[x]);
		}
		__except (EXCEPTION_EXECUTE_HANDLER)
		{
			return 0;
		}
	}

	return pbase;
}

// 驅動解除安裝例程
VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint("Uninstall Driver \n");
}

// 驅動入口地址
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark \n");

	DWORD PID = 4884;
	LONG PBase = 0x6566e0;
	LONG Size = 4;

	DWORD Offset[32] = { 0 };

	Offset[0] = 0x18;
	Offset[1] = 0x0;
	Offset[2] = 0x14;
	Offset[3] = 0x0c;

	// 寫出記憶體資料
	DWORD64 offsets = WIN10_ReadDeviationMemory(PID, PBase, Offset, Size);

	DbgPrint("PID: %d 基址: %p 偏移長度: %d \n", PID, PBase, Size);
	DbgPrint("[+] 1級偏移: %x \n", Offset[0]);
	DbgPrint("[+] 2級偏移: %x \n", Offset[1]);
	DbgPrint("[+] 3級偏移: %x \n", Offset[2]);
	DbgPrint("[+] 4級偏移: %x \n", Offset[3]);

	DbgPrint("[CheckMemory] 計算偏移地址: %x \n", offsets);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

執行如上程式碼將動態計算出目前偏移地址的pbase實際地址,實現效果圖如下所示;