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

简介: 如前所述,在前几章内容中笔者简单介绍了`内存读写`的基本实现方式,这其中包括了`CR3切换`读写,`MDL映射`读写,`内存拷贝`读写,本章将在如前所述的读写函数进一步封装,并以此来实现驱动读写内存浮点数的目的。内存`浮点数`的读写依赖于`读写内存字节`的实现,因为浮点数本质上也可以看作是一个字节集,对于`单精度浮点数`来说这个字节集列表是4字节,而对于`双精度浮点数`,此列表长度则为8字节。

如前所述,在前几章内容中笔者简单介绍了内存读写的基本实现方式,这其中包括了CR3切换读写,MDL映射读写,内存拷贝读写,本章将在如前所述的读写函数进一步封装,并以此来实现驱动读写内存浮点数的目的。内存浮点数的读写依赖于读写内存字节的实现,因为浮点数本质上也可以看作是一个字节集,对于单精度浮点数来说这个字节集列表是4字节,而对于双精度浮点数,此列表长度则为8字节。

如下代码片段摘取自本人的LyMemory驱动读写项目,函数ReadProcessMemoryByte用于读取内存特定字节类型的数据,函数WriteProcessMemoryByte则用于写入字节类型数据,完整代码如下所示;

这段代码中依然采用了《驱动开发:内核MDL读写进程内存》中所示的读写方法,通过MDL附加到进程并RtlCopyMemory拷贝数据,至于如何读写字节集只需要循环读写即可实现;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#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;
}

运行如上代码片段,你会看到如下图所示的读取效果;

image.png

那么如何实现写内存字节集呢?其实写入内存字节集与读取基本类似,通过填充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的位置处,输出效果图如下所示;

image.png

接下来不如本章的重点内容,首先如何实现读内存单精度与双精度浮点数的目的,实现原理是通过读取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;
}
相关文章
|
19天前
|
算法 Linux 开发者
深入探究Linux内核中的内存管理机制
本文旨在对Linux操作系统的内存管理机制进行深入分析,探讨其如何通过高效的内存分配和回收策略来优化系统性能。文章将详细介绍Linux内核中内存管理的关键技术点,包括物理内存与虚拟内存的映射、页面置换算法、以及内存碎片的处理方法等。通过对这些技术点的解析,本文旨在为读者提供一个清晰的Linux内存管理框架,帮助理解其在现代计算环境中的重要性和应用。
|
1月前
|
存储 C++
看完就等于拿捏浮点数在内存中的储存了
看完就等于拿捏浮点数在内存中的储存了
47 2
看完就等于拿捏浮点数在内存中的储存了
|
22天前
|
缓存 算法 Linux
Linux内核中的内存管理机制深度剖析####
【10月更文挑战第28天】 本文深入探讨了Linux操作系统的心脏——内核,聚焦其内存管理机制的奥秘。不同于传统摘要的概述方式,本文将以一次虚拟的内存分配请求为引子,逐步揭开Linux如何高效、安全地管理着从微小嵌入式设备到庞大数据中心数以千计程序的内存需求。通过这段旅程,读者将直观感受到Linux内存管理的精妙设计与强大能力,以及它是如何在复杂多变的环境中保持系统稳定与性能优化的。 ####
25 0
|
1月前
|
存储 算法 C语言
MacOS环境-手写操作系统-15-内核管理 检测可用内存
MacOS环境-手写操作系统-15-内核管理 检测可用内存
37 0
|
2月前
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
|
3月前
|
Linux Windows
反射内存卡驱动的安装
【8月更文挑战第28天】以下是反射内存卡驱动安装的一般步骤:首先确认内存卡型号及操作系统版本,并从制造商官网下载兼容的驱动程序。安装时,运行安装包,按提示接受许可协议,选择安装路径,连接内存卡,并完成安装,可能需重启计算机。最后,通过设备管理器验证安装是否成功,如遇问题可查阅相关文档或求助技术支持。
|
3月前
|
算法 安全 UED
探索操作系统的内核空间:虚拟内存管理
【7月更文挑战第50天】 在现代操作系统中,虚拟内存管理是核心功能之一,它允许操作系统高效地使用物理内存,并为应用程序提供独立的地址空间。本文将深入探讨操作系统虚拟内存管理的机制,包括分页、分段以及内存交换等关键技术,并分析它们如何共同作用以实现内存的有效管理和保护。通过理解这些原理,读者可以更好地把握操作系统的内部工作原理及其对应用程序性能的影响。
|
3月前
|
easyexcel Java 关系型数据库
阿里巴巴-EasyExcel 基于Java的简单、省内存的读写Excel
该文章主要介绍了在Java应用中如何使用EasyExcel技术完成对Excel文件的导入和导出操作,包括环境搭建、基本概念、快速入门、进阶操作和综合应用等内容,并提供了相关代码示例和注意事项。
 阿里巴巴-EasyExcel 基于Java的简单、省内存的读写Excel
|
3月前
|
Swift iOS开发
iOS开发-属性的内存管理
【8月更文挑战第12天】在iOS开发中,属性的内存管理至关重要,直接影响应用性能与稳定性。主要策略包括:`strong`(强引用),不维持对象生命期,可用于解除循环引用;`assign`(赋值),适用于基本数据类型及非指针对象属性;`copy`,复制对象而非引用,确保对象不变性。iOS采用引用计数管理内存,ARC(自动引用计数)自动处理引用增减,简化开发。为避免循环引用,可利用弱引用或Swift中的`[weak self]`。最佳实践包括:选择恰当的内存管理策略、减少不必要的强引用、及时释放不再使用的对象、注意block内存管理,并使用Xcode工具进行内存分析。
|
3月前
|
Java 开发工具 Android开发
Android经典面试题之开发中常见的内存泄漏,以及如何避免和防范
本文介绍Android开发中内存泄漏的概念及其危害,并列举了四种常见泄漏原因:静态变量持有Context、非静态内部类、资源未释放及监听器未注销。提供了具体代码示例和防范措施,如使用ApplicationContext、弱引用、适时释放资源及利用工具检测泄漏。通过遵循这些建议,开发者可以有效提高应用稳定性和性能。
55 0