如前所述,在前几章内容中笔者简单介绍了内存读写的基本实现方式,这其中包括了CR3切换读写,MDL映射读写,内存拷贝读写,本章将在如前所述的读写函数进一步封装,并以此来实现驱动读写内存浮点数的目的。
内存浮点数的读写依赖于读写内存字节
的实现,因为浮点数本质上也可以看作是一个字节集,对于单精度浮点数
来说这个字节集列表是4字节,而对于双精度浮点数
,此列表长度则为8字节。
如下代码片段摘取自本人的LyMemory
驱动读写项目,函数ReadProcessMemoryByte
用于读取内存特定字节类型的数据,函数WriteProcessMemoryByte
则用于写入字节类型数据,完整代码如下所示;
这段代码中依然采用了《内核MDL读写进程内存》
中所示的读写方法,通过MDL附加到进程并RtlCopyMemory
拷贝数据,至于如何读写字节集只需要循环读写即可实现;
#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((HANDLE)Address, Size, 1); RtlCopyMemory(&OpCode, (BYTE *)Address, Size); } __except (EXCEPTION_EXECUTE_HANDLER) { KeUnstackDetachProcess(&state);
ObDereferenceObject(Process); return FALSE; }
KeUnstackDetachProcess(&state); 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);
PMDL mdl = IoAllocateMdl((HANDLE)Address, Size, 0, 0, NULL); if (mdl == NULL) { return FALSE; }
MmBuildMdlForNonPagedPool(mdl); BYTE* ChangeData = NULL;
__try { ChangeData = (BYTE *)MmMapLockedPages(mdl, KernelMode); } __except (EXCEPTION_EXECUTE_HANDLER) { IoFreeMdl(mdl);
KeUnstackDetachProcess(&state); ObDereferenceObject(Process); return FALSE; }
RtlCopyMemory(ChangeData, OpCode, Size); DbgPrint("[内核写字节] # 写入地址: 0x%x 写入数据: %x \n", Address, OpCode);
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类型,再写出即可;
VOID dtoc(double dvalue, unsigned char* arr) { unsigned char* pf; unsigned char* px; unsigned char i;
pf = (unsigned char*)&dvalue;
px = arr;
for (i = 0; i < 8; i++) { *(px + i) = *(pf + i); } }
VOID ftoc(float fvalue, unsigned char* arr) { unsigned char* pf; unsigned char* px; unsigned char i;
pf = (unsigned char*)&fvalue;
px = arr;
for (i = 0; i < 4; i++) { *(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; }
|