驅動開發:核心讀寫記憶體浮點數

2023-05-30 12:01:22

如前所述,在前幾章內容中筆者簡單介紹了記憶體讀寫的基本實現方式,這其中包括了CR3切換讀寫,MDL對映讀寫,記憶體拷貝讀寫,本章將在如前所述的讀寫函數進一步封裝,並以此來實現驅動讀寫記憶體浮點數的目的。記憶體浮點數的讀寫依賴於讀寫記憶體位元組的實現,因為浮點數本質上也可以看作是一個位元組集,對於單精度浮點數來說這個位元組集列表是4位元組,而對於雙精度浮點數,此列表長度則為8位元組。

如下程式碼片段摘取自本人的LyMemory驅動讀寫專案,函數ReadProcessMemoryByte用於讀取記憶體特定位元組型別的資料,函數WriteProcessMemoryByte則用於寫入位元組型別資料,完整程式碼如下所示;

這段程式碼中依然採用了《驅動開發:核心MDL讀寫程序記憶體》中所示的讀寫方法,通過MDL附加到程序並RtlCopyMemory拷貝資料,至於如何讀寫位元組集只需要迴圈讀寫即可實現;

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

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

// 讀取記憶體位元組
BYTE ReadProcessMemoryByte(HANDLE Pid, ULONG64 Address, DWORD Size)
{
	KAPC_STATE state = { 0 };
	BYTE OpCode;

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

	// 繫結程序物件,進入程序地址空間
	KeStackAttachProcess(Process, &state);

	__try
	{
		// ProbeForRead 檢查記憶體地址是否有效, RtlCopyMemory 讀取記憶體
		ProbeForRead((HANDLE)Address, Size, 1);
		RtlCopyMemory(&OpCode, (BYTE *)Address, Size);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		// 呼叫KeUnstackDetachProcess解除與程序的繫結,退出程序地址空間
		KeUnstackDetachProcess(&state);

		// 讓核心物件參照數減1
		ObDereferenceObject(Process);
		// DbgPrint("讀取程序 %d 的地址 %x 出錯", ptr->Pid, ptr->Address);
		return FALSE;
	}

	// 解除繫結
	KeUnstackDetachProcess(&state);
	// 讓核心物件參照數減1
	ObDereferenceObject(Process);
	DbgPrint("[核心讀位元組] # 讀取地址: 0x%x 讀取資料: %x \n", Address, OpCode);

	return OpCode;
}

// 寫入記憶體位元組
BOOLEAN WriteProcessMemoryByte(HANDLE Pid, ULONG64 Address, DWORD Size, BYTE *OpCode)
{
	KAPC_STATE state = { 0 };

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

	// 繫結程序,進入程序的地址空間
	KeStackAttachProcess(Process, &state);

	// 建立MDL地址描述符
	PMDL mdl = IoAllocateMdl((HANDLE)Address, Size, 0, 0, NULL);
	if (mdl == NULL)
	{
		return FALSE;
	}

	//使MDL與驅動進行繫結
	MmBuildMdlForNonPagedPool(mdl);
	BYTE* ChangeData = NULL;

	__try
	{
		// 將MDL對映到我們驅動裡的一個變數,對該變數讀寫就是對MDL對應的實體記憶體讀寫
		ChangeData = (BYTE *)MmMapLockedPages(mdl, KernelMode);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		// DbgPrint("對映記憶體失敗");
		IoFreeMdl(mdl);

		// 解除對映
		KeUnstackDetachProcess(&state);
		// 讓核心物件參照數減1
		ObDereferenceObject(Process);
		return FALSE;
	}

	// 寫入資料到指定位置
	RtlCopyMemory(ChangeData, OpCode, Size);
	DbgPrint("[核心寫位元組] # 寫入地址: 0x%x 寫入資料: %x \n", Address, OpCode);

	// 讓核心物件參照數減1
	ObDereferenceObject(Process);
	MmUnmapLockedPages(ChangeData, mdl);
	KeUnstackDetachProcess(&state);
	return TRUE;
}

實現讀取記憶體位元組集並將讀入的資料放入到LySharkReadByte位元組列表中,這段程式碼如下所示,通過呼叫ReadProcessMemoryByte都記憶體位元組並每次0x401000 + i在基址上面增加變數i以此來實現位元組集讀取;

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

	// 讀記憶體位元組集
	BYTE LySharkReadByte[8] = { 0 };

	for (size_t i = 0; i < 8; i++)
	{
		LySharkReadByte[i] = ReadProcessMemoryByte(4884, 0x401000 + i, 1);
	}

	// 輸出讀取的記憶體位元組
	for (size_t i = 0; i < 8; i++)
	{
		DbgPrint("[+] 列印資料: %x \n", LySharkReadByte[i]);
	}

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

執行如上程式碼片段,你會看到如下圖所示的讀取效果;

那麼如何實現寫記憶體位元組集呢?其實寫入記憶體位元組集與讀取基本類似,通過填充LySharkWriteByte位元組集列表,並呼叫WriteProcessMemoryByte函數依次迴圈位元組集列表即可實現寫出位元組集的目的;

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

	// 記憶體寫位元組集
	BYTE LySharkWriteByte[8] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };

	for (size_t i = 0; i < 8; i++)
	{
		BOOLEAN ref = WriteProcessMemoryByte(4884, 0x401000 + i, 1, LySharkWriteByte[i]);
		DbgPrint("[*] 寫出狀態: %d \n", ref);
	}

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

執行如上程式碼片段,即可將LySharkWriteByte[8]中的位元組集寫出到記憶體0x401000 + i的位置處,輸出效果圖如下所示;

接下來不如本章的重點內容,首先如何實現讀記憶體單精度與雙精度浮點數的目的,實現原理是通過讀取BYTE型別的前4或者8位元組的資料,並通過*((FLOAT*)buffpyr)將其轉換為浮點數,通過此方法即可實現位元組集到浮點數的轉換,而決定是單精度還是雙精度則只是一個位元組集長度問題,這段讀寫程式碼實現原理如下所示;

// 讀記憶體單精度浮點數
FLOAT ReadProcessFloat(DWORD Pid, ULONG64 Address)
{
	BYTE buff[4] = { 0 };
	BYTE* buffpyr = buff;

	for (DWORD x = 0; x < 4; x++)
	{
		BYTE item = ReadProcessMemoryByte(Pid, Address + x, 1);
		buff[x] = item;
	}

	return *((FLOAT*)buffpyr);
}

// 讀記憶體雙精度浮點數
DOUBLE ReadProcessMemoryDouble(DWORD Pid, ULONG64 Address)
{
	BYTE buff[8] = { 0 };
	BYTE* buffpyr = buff;

	for (DWORD x = 0; x < 8; x++)
	{
		BYTE item = ReadProcessMemoryByte(Pid, Address + x, 1);
		buff[x] = item;
	}

	return *((DOUBLE*)buffpyr);
}

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

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

	// 讀取單精度
	FLOAT fl = ReadProcessFloat(4884, 0x401000);
	DbgPrint("[讀取單精度] = %d \n", fl);

	// 讀取雙精度浮點數
	DOUBLE fl = ReadProcessMemoryDouble(4884, 0x401000);
	DbgPrint("[讀取雙精度] = %d \n", fl);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

如上程式碼就是實現浮點數讀寫的關鍵所在,這段程式碼中的浮點數傳值如果在核心中會提示無法解析的外部符號 _fltused此處只用於演示核心原理,如果想要實現不報錯,該程式碼中的傳值操作應在應用層進行,而傳入引數也應改為位元組型別即可。

同理,對於寫記憶體浮點數而言依舊如此,只是在接收到使用者層傳遞引數後應對其dtoc雙精度浮點數轉為CHAR或者ftoc單精度浮點數轉為CHAR型別,再寫出即可;

// 將DOUBLE適配為合適的Char型別
VOID dtoc(double dvalue, unsigned char* arr)
{
	unsigned char* pf;
	unsigned char* px;
	unsigned char i;

	// unsigned char型指標取得浮點數的首地址
	pf = (unsigned char*)&dvalue;

	// 字元陣列arr準備儲存浮點數的四個位元組,px指標指向位元組陣列arr
	px = arr;

	for (i = 0; i < 8; i++)
	{
		// 使用unsigned char型指標從低地址一個位元組一個位元組取出
		*(px + i) = *(pf + i);
	}
}

// 將Float適配為合適的Char型別
VOID ftoc(float fvalue, unsigned char* arr)
{
	unsigned char* pf;
	unsigned char* px;
	unsigned char i;

	// unsigned char型指標取得浮點數的首地址
	pf = (unsigned char*)&fvalue;

	// 字元陣列arr準備儲存浮點數的四個位元組,px指標指向位元組陣列arr
	px = arr;

	for (i = 0; i < 4; i++)
	{
		// 使用unsigned char型指標從低地址一個位元組一個位元組取出
		*(px + i) = *(pf + i);
	}
}

// 寫記憶體單精度浮點數
BOOL WriteProcessMemoryFloat(DWORD Pid, ULONG64 Address, FLOAT write)
{
	BYTE buff[4] = { 0 };
	ftoc(write, buff);

	for (DWORD x = 0; x < 4; x++)
	{
		BYTE item = WriteProcessMemoryByte(Pid, Address + x, buff[x], 1);
		buff[x] = item;
	}

	return TRUE;
}

// 寫記憶體雙精度浮點數
BOOL WriteProcessMemoryDouble(DWORD Pid, ULONG64 Address, DOUBLE write)
{
	BYTE buff[8] = { 0 };
	dtoc(write, buff);

	for (DWORD x = 0; x < 8; x++)
	{
		BYTE item = WriteProcessMemoryByte(Pid, Address + x, buff[x], 1);
		buff[x] = item;
	}

	return TRUE;
}

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

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

	// 寫單精度
	FLOAT LySharkFloat1 = 12.5;
	INT fl = WriteProcessMemoryFloat(4884, 0x401000, LySharkFloat1);
	DbgPrint("[寫單精度] = %d \n", fl);

	// 讀取雙精度浮點數
	DOUBLE LySharkFloat2 = 12.5;
	INT d1 = WriteProcessMemoryDouble(4884, 0x401000, LySharkFloat2);
	DbgPrint("[寫雙精度] = %d \n", d1);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}