驱动开发:内核读写内存浮点数

打印 上一主题 下一主题

主题 895|帖子 895|积分 2685

如前所述,在前几章内容中笔者简单介绍了内存读写的基本实现方式,这其中包括了CR3切换读写,MDL映射读写,内存拷贝读写,本章将在如前所述的读写函数进一步封装,并以此来实现驱动读写内存浮点数的目的。内存浮点数的读写依赖于读写内存字节的实现,因为浮点数本质上也可以看作是一个字节集,对于单精度浮点数来说这个字节集列表是4字节,而对于双精度浮点数,此列表长度则为8字节。
如下代码片段摘取自本人的LyMemory驱动读写项目,函数ReadProcessMemoryByte用于读取内存特定字节类型的数据,函数WriteProcessMemoryByte则用于写入字节类型数据,完整代码如下所示;
这段代码中依然采用了《驱动开发:内核MDL读写进程内存》中所示的读写方法,通过MDL附加到进程并RtlCopyMemory拷贝数据,至于如何读写字节集只需要循环读写即可实现;
  1. // 署名权
  2. // right to sign one's name on a piece of work
  3. // PowerBy: LyShark
  4. // Email: me@lyshark.com
  5. #include <ntifs.h>
  6. #include <windef.h>
  7. // 读取内存字节
  8. BYTE ReadProcessMemoryByte(HANDLE Pid, ULONG64 Address, DWORD Size)
  9. {
  10.         KAPC_STATE state = { 0 };
  11.         BYTE OpCode;
  12.         PEPROCESS Process;
  13.         PsLookupProcessByProcessId((HANDLE)Pid, &Process);
  14.         // 绑定进程对象,进入进程地址空间
  15.         KeStackAttachProcess(Process, &state);
  16.         __try
  17.         {
  18.                 // ProbeForRead 检查内存地址是否有效, RtlCopyMemory 读取内存
  19.                 ProbeForRead((HANDLE)Address, Size, 1);
  20.                 RtlCopyMemory(&OpCode, (BYTE *)Address, Size);
  21.         }
  22.         __except (EXCEPTION_EXECUTE_HANDLER)
  23.         {
  24.                 // 调用KeUnstackDetachProcess解除与进程的绑定,退出进程地址空间
  25.                 KeUnstackDetachProcess(&state);
  26.                 // 让内核对象引用数减1
  27.                 ObDereferenceObject(Process);
  28.                 // DbgPrint("读取进程 %d 的地址 %x 出错", ptr->Pid, ptr->Address);
  29.                 return FALSE;
  30.         }
  31.         // 解除绑定
  32.         KeUnstackDetachProcess(&state);
  33.         // 让内核对象引用数减1
  34.         ObDereferenceObject(Process);
  35.         DbgPrint("[内核读字节] # 读取地址: 0x%x 读取数据: %x \n", Address, OpCode);
  36.         return OpCode;
  37. }
  38. // 写入内存字节
  39. BOOLEAN WriteProcessMemoryByte(HANDLE Pid, ULONG64 Address, DWORD Size, BYTE *OpCode)
  40. {
  41.         KAPC_STATE state = { 0 };
  42.         PEPROCESS Process;
  43.         PsLookupProcessByProcessId((HANDLE)Pid, &Process);
  44.         // 绑定进程,进入进程的地址空间
  45.         KeStackAttachProcess(Process, &state);
  46.         // 创建MDL地址描述符
  47.         PMDL mdl = IoAllocateMdl((HANDLE)Address, Size, 0, 0, NULL);
  48.         if (mdl == NULL)
  49.         {
  50.                 return FALSE;
  51.         }
  52.         //使MDL与驱动进行绑定
  53.         MmBuildMdlForNonPagedPool(mdl);
  54.         BYTE* ChangeData = NULL;
  55.         __try
  56.         {
  57.                 // 将MDL映射到我们驱动里的一个变量,对该变量读写就是对MDL对应的物理内存读写
  58.                 ChangeData = (BYTE *)MmMapLockedPages(mdl, KernelMode);
  59.         }
  60.         __except (EXCEPTION_EXECUTE_HANDLER)
  61.         {
  62.                 // DbgPrint("映射内存失败");
  63.                 IoFreeMdl(mdl);
  64.                 // 解除映射
  65.                 KeUnstackDetachProcess(&state);
  66.                 // 让内核对象引用数减1
  67.                 ObDereferenceObject(Process);
  68.                 return FALSE;
  69.         }
  70.         // 写入数据到指定位置
  71.         RtlCopyMemory(ChangeData, OpCode, Size);
  72.         DbgPrint("[内核写字节] # 写入地址: 0x%x 写入数据: %x \n", Address, OpCode);
  73.         // 让内核对象引用数减1
  74.         ObDereferenceObject(Process);
  75.         MmUnmapLockedPages(ChangeData, mdl);
  76.         KeUnstackDetachProcess(&state);
  77.         return TRUE;
  78. }
复制代码
实现读取内存字节集并将读入的数据放入到LySharkReadByte字节列表中,这段代码如下所示,通过调用ReadProcessMemoryByte都内存字节并每次0x401000 + i在基址上面增加变量i以此来实现字节集读取;
  1. // 驱动入口地址
  2. NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
  3. {
  4.         DbgPrint("Hello LyShark \n");
  5.         // 读内存字节集
  6.         BYTE LySharkReadByte[8] = { 0 };
  7.         for (size_t i = 0; i < 8; i++)
  8.         {
  9.                 LySharkReadByte[i] = ReadProcessMemoryByte(4884, 0x401000 + i, 1);
  10.         }
  11.         // 输出读取的内存字节
  12.         for (size_t i = 0; i < 8; i++)
  13.         {
  14.                 DbgPrint("[+] 打印数据: %x \n", LySharkReadByte[i]);
  15.         }
  16.         Driver->DriverUnload = UnDriver;
  17.         return STATUS_SUCCESS;
  18. }
复制代码
运行如上代码片段,你会看到如下图所示的读取效果;

那么如何实现写内存字节集呢?其实写入内存字节集与读取基本类似,通过填充LySharkWriteByte字节集列表,并调用WriteProcessMemoryByte函数依次循环字节集列表即可实现写出字节集的目的;
  1. // 驱动入口地址
  2. NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
  3. {
  4.         DbgPrint("Hello LyShark \n");
  5.         // 内存写字节集
  6.         BYTE LySharkWriteByte[8] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
  7.         for (size_t i = 0; i < 8; i++)
  8.         {
  9.                 BOOLEAN ref = WriteProcessMemoryByte(4884, 0x401000 + i, 1, LySharkWriteByte[i]);
  10.                 DbgPrint("[*] 写出状态: %d \n", ref);
  11.         }
  12.         Driver->DriverUnload = UnDriver;
  13.         return STATUS_SUCCESS;
  14. }
复制代码
运行如上代码片段,即可将LySharkWriteByte[8]中的字节集写出到内存0x401000 + i的位置处,输出效果图如下所示;

接下来不如本章的重点内容,首先如何实现读内存单精度与双精度浮点数的目的,实现原理是通过读取BYTE类型的前4或者8字节的数据,并通过*((FLOAT*)buffpyr)将其转换为浮点数,通过此方法即可实现字节集到浮点数的转换,而决定是单精度还是双精度则只是一个字节集长度问题,这段读写代码实现原理如下所示;
  1. // 读内存单精度浮点数
  2. FLOAT ReadProcessFloat(DWORD Pid, ULONG64 Address)
  3. {
  4.         BYTE buff[4] = { 0 };
  5.         BYTE* buffpyr = buff;
  6.         for (DWORD x = 0; x < 4; x++)
  7.         {
  8.                 BYTE item = ReadProcessMemoryByte(Pid, Address + x, 1);
  9.                 buff[x] = item;
  10.         }
  11.         return *((FLOAT*)buffpyr);
  12. }
  13. // 读内存双精度浮点数
  14. DOUBLE ReadProcessMemoryDouble(DWORD Pid, ULONG64 Address)
  15. {
  16.         BYTE buff[8] = { 0 };
  17.         BYTE* buffpyr = buff;
  18.         for (DWORD x = 0; x < 8; x++)
  19.         {
  20.                 BYTE item = ReadProcessMemoryByte(Pid, Address + x, 1);
  21.                 buff[x] = item;
  22.         }
  23.         return *((DOUBLE*)buffpyr);
  24. }
  25. // 驱动卸载例程
  26. VOID UnDriver(PDRIVER_OBJECT driver)
  27. {
  28.         DbgPrint("Uninstall Driver \n");
  29. }
  30. // 驱动入口地址
  31. NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
  32. {
  33.         DbgPrint("Hello LyShark \n");
  34.         // 读取单精度
  35.         FLOAT fl = ReadProcessFloat(4884, 0x401000);
  36.         DbgPrint("[读取单精度] = %d \n", fl);
  37.         // 读取双精度浮点数
  38.         DOUBLE fl = ReadProcessMemoryDouble(4884, 0x401000);
  39.         DbgPrint("[读取双精度] = %d \n", fl);
  40.         Driver->DriverUnload = UnDriver;
  41.         return STATUS_SUCCESS;
  42. }
复制代码
如上代码就是实现浮点数读写的关键所在,这段代码中的浮点数传值如果在内核中会提示无法解析的外部符号 _fltused此处只用于演示核心原理,如果想要实现不报错,该代码中的传值操作应在应用层进行,而传入参数也应改为字节类型即可。
同理,对于写内存浮点数而言依旧如此,只是在接收到用户层传递参数后应对其dtoc双精度浮点数转为CHAR或者ftoc单精度浮点数转为CHAR类型,再写出即可;
  1. // 将DOUBLE适配为合适的Char类型
  2. VOID dtoc(double dvalue, unsigned char* arr)
  3. {
  4.         unsigned char* pf;
  5.         unsigned char* px;
  6.         unsigned char i;
  7.         // unsigned char型指针取得浮点数的首地址
  8.         pf = (unsigned char*)&dvalue;
  9.         // 字符数组arr准备存储浮点数的四个字节,px指针指向字节数组arr
  10.         px = arr;
  11.         for (i = 0; i < 8; i++)
  12.         {
  13.                 // 使用unsigned char型指针从低地址一个字节一个字节取出
  14.                 *(px + i) = *(pf + i);
  15.         }
  16. }
  17. // 将Float适配为合适的Char类型
  18. VOID ftoc(float fvalue, unsigned char* arr)
  19. {
  20.         unsigned char* pf;
  21.         unsigned char* px;
  22.         unsigned char i;
  23.         // unsigned char型指针取得浮点数的首地址
  24.         pf = (unsigned char*)&fvalue;
  25.         // 字符数组arr准备存储浮点数的四个字节,px指针指向字节数组arr
  26.         px = arr;
  27.         for (i = 0; i < 4; i++)
  28.         {
  29.                 // 使用unsigned char型指针从低地址一个字节一个字节取出
  30.                 *(px + i) = *(pf + i);
  31.         }
  32. }
  33. // 写内存单精度浮点数
  34. BOOL WriteProcessMemoryFloat(DWORD Pid, ULONG64 Address, FLOAT write)
  35. {
  36.         BYTE buff[4] = { 0 };
  37.         ftoc(write, buff);
  38.         for (DWORD x = 0; x < 4; x++)
  39.         {
  40.                 BYTE item = WriteProcessMemoryByte(Pid, Address + x, buff[x], 1);
  41.                 buff[x] = item;
  42.         }
  43.         return TRUE;
  44. }
  45. // 写内存双精度浮点数
  46. BOOL WriteProcessMemoryDouble(DWORD Pid, ULONG64 Address, DOUBLE write)
  47. {
  48.         BYTE buff[8] = { 0 };
  49.         dtoc(write, buff);
  50.         for (DWORD x = 0; x < 8; x++)
  51.         {
  52.                 BYTE item = WriteProcessMemoryByte(Pid, Address + x, buff[x], 1);
  53.                 buff[x] = item;
  54.         }
  55.         return TRUE;
  56. }
  57. // 驱动卸载例程
  58. VOID UnDriver(PDRIVER_OBJECT driver)
  59. {
  60.         DbgPrint("Uninstall Driver \n");
  61. }
  62. // 驱动入口地址
  63. NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
  64. {
  65.         DbgPrint("Hello LyShark \n");
  66.         // 写单精度
  67.         FLOAT LySharkFloat1 = 12.5;
  68.         INT fl = WriteProcessMemoryFloat(4884, 0x401000, LySharkFloat1);
  69.         DbgPrint("[写单精度] = %d \n", fl);
  70.         // 读取双精度浮点数
  71.         DOUBLE LySharkFloat2 = 12.5;
  72.         INT d1 = WriteProcessMemoryDouble(4884, 0x401000, LySharkFloat2);
  73.         DbgPrint("[写双精度] = %d \n", d1);
  74.         Driver->DriverUnload = UnDriver;
  75.         return STATUS_SUCCESS;
  76. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

飞不高

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表