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

来源: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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...