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