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