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

来源:https://www.cnblogs.com/LyShark/archive/2023/05/30/17182633.html
-Advertisement-
Play Games

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


如前所述,在前幾章內容中筆者簡單介紹了記憶體讀寫的基本實現方式,這其中包括了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;
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [官方文檔](https://numpy.org/doc/stable/reference/generated/numpy.bincount.html#numpy-bincount) `out = np.bincount(x[, weights, minlength])` **該函數用於統計輸入數組 ...
  • 面試題==知識點,這裡所記錄的面試題並不針對於面試者,而是將這些面試題作為技能知識點來看待。不以刷題進大廠為目的,而是以學習為目的。這裡的知識點會持續更新,目錄也會隨時進行調整。 ...
  • # Python 實現 m3u8 視頻下載 m3u8 是一種**基於文本的媒體播放列表文件格式**,通常用於指定流媒體播放器播放線上媒體流。它是一個簡單的文本文件,其中包含多個由 URI 引用的媒體資源文件的 URL。m3u8 文件通常包含多個 ts 文件的鏈接,這些 ts 文件是實際的視頻和音頻數 ...
  • 摘要:在併發場景中,Java SDK中提供了ReadWriteLock來滿足讀多寫少的場景。 本文分享自華為雲社區《【高併發】基於ReadWriteLock開了個一款高性能緩存》,作者:冰 河。 寫在前面 在實際工作中,有一種非常普遍的併發場景:那就是讀多寫少的場景。在這種場景下,為了優化程式的性能 ...
  • [toc] # 一、背景介紹 您好,我是[@馬哥python說](https://mp.weixin.qq.com/s/EuOKLq6ZSgQGnijreylSiA) ,一枚10年程式猿。 自從2023.3月以來,"淄博燒烤"現象持續占領熱搜流量,體現了後疫情時代眾多網友對人間煙火氣的美好嚮往,本現 ...
  • 242. 有效的字母異位詞 ```cpp class Solution { public: bool isAnagram(string s, string t) { if(s.size()!=t.size()) return false; int ans[26]={0}; for(auto& ch: ...
  • 前言 咱換工作啦! 新工作這邊需要用到的開發語言是 Haxe,最近大概會寫幾篇筆記。Haxe 的介紹就不寫了,打算記錄點有用的學習內容,先從搭建開發環境開始吧! 當前適用版本: VSCode:Current Latest Version Haxe 版本:4.3.1 文章最近更新日期:2023.05. ...
  • 在最近的互聯網項目開發中,需要獲取用戶的訪問ip信息,併進行後續統計分析。 這些ip信息是在第三方的服務中分組存放的,且每個分組都都是分頁(1頁10條)存放的,如果一次性訪問大量的數據,API很有可能會報錯。 怎樣通過HTTP的方式去獲取到信息,並且模擬瀏覽器每頁每頁獲取10條的信息,且持久到資料庫... ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...