3.3 Windows驱动开发:内核MDL读写进程内存

MDL内存读写是一种通过创建MDL结构体来实现跨进程内存读写的方式。在Windows操作系统中,每个进程都有自己独立的虚拟地址空间,不同进程之间的内存空间是隔离的。因此,要在一个进程中读取或写入另一个进程的内存数据,需要先将目标进程的物理内存映射到当前进程的虚拟地址空间中,然后才能进行内存读写操作。

MDL结构体是Windows内核中专门用于描述物理内存的数据结构,它包含了一系列的数据元素,包括物理地址、长度、内存映射的虚拟地址等信息。通过创建MDL结构体并调用系统函数将其映射到当前进程的虚拟地址空间中,即可实现跨进程内存读写的操作。

相比于CR3切换方式,MDL内存读写更加稳定、安全,且不会受到寄存器的影响。同时,使用MDL内存读写方式还可以充分利用Windows操作系统的内存管理机制,从而实现更为高效的内存读写操作。因此,MDL内存读写是Windows操作系统中最为常用和推荐的一种跨进程内存读写方式。

3.1.1 MDL读取内存步骤
  • 1.调用PsLookupProcessByProcessId得到进程Process结构,这个函数是用于根据进程ID查找对应的进程对象的函数,通过传入的参数 data->pid 获取到对应的进程ID,然后通过调用 PsLookupProcessByProcessId 函数获取对应的 PEPROCESS 结构。如果获取失败则返回 FALSE。

  • 2.调用KeStackAttachProcess附加到对端进程内,在内核模式下,读取其他进程的内存时需要先附加到对应进程的上下文中,才能读取该进程的内存。因此,这里调用 KeStackAttachProcess 函数将当前线程切换到目标进程的上下文中。同时,为了在后面可以正确地从目标进程的上下文中返回,还需要保存当前进程的上下文状态。

  • 3.调用ProbeForRead检查内存是否可读写,在内核模式下,需要保证访问其他进程的内存是合法的,因此需要先调用 ProbeForRead 函数检查读取的内存空间是否可读写。如果该空间不可读写,则会触发异常,这里通过异常处理机制来处理这种情况。

  • 4.拷贝内存空间中的数据到自己的缓冲区内,在完成对内存空间的检查后,使用 RtlCopyMemory 函数将目标进程的内存数据拷贝到自己的缓冲区中。这里需要注意的是,由于内存空间可能很大,因此可能需要多次进行拷贝操作。

  • 5.调用KeUnstackDetachProcess接触绑定,在读取完内存数据后,需要将当前线程从目标进程的上下文中解除绑定,以便返回到原来的上下文中。这里调用 KeUnstackDetachProcess 函数完成解绑操作,同时恢复之前保存的当前进程的上下文状态。

  • 6.调用ObDereferenceObject使对象引用数减1,由于在第一步中调用了 PsLookupProcessByProcessId 函数获取了对应进程的 PEPROCESS 结构,因此需要调用 ObDereferenceObject 函数将其引用计数减1,以便释放对该对象的引用。

有了上述具体实现方法,那么我们就可以封装MDLReadMemory()内存读函数了,代码如下,该函数用于在 Windows 内核模式下读取指定进程的内存数据。下面是对这个函数的详细步骤分析:

  • 1.通过进程 ID 找到对应的进程对象:PsLookupProcessByProcessId 用于通过进程 ID 查找对应的进程对象。如果找不到该进程对象,则直接返回 FALSE。
PsLookupProcessByProcessId(data->pid, &process);
  • 2.在内核模式下,必须使用内核提供的函数来分配内存。这里使用的是 ExAllocatePool 函数,用于在内核堆中分配指定大小的内存缓冲区。如果分配失败,则返回 FALSE。
BYTE* GetData;
__try
{
    GetData = ExAllocatePool(PagedPool, data->size);
}
__except (1)
{
    return FALSE;
}
  • 3.在内核模式下,访问其他进程的内存必须先将当前进程的上下文切换到目标进程的上下文。这里使用的是 KeStackAttachProcess 函数,将当前进程的上下文切换到目标进程的上下文。同时,为了在后面可以正确地从目标进程的上下文中返回,还需要保存当前进程的上下文状态。
KAPC_STATE stack = { 0 };
KeStackAttachProcess(process, &stack);
  • 4.读取目标进程的内存数据,这段代码使用 ProbeForRead 函数检查要读取的内存区域是否合法,并且将目标进程的内存数据读取到之前分配的内存缓冲区中。如果读取过程中出现异常,则返回 FALSE。
__try
{
   ProbeForRead(data->address, data->size, 1);
   RtlCopyMemory(GetData, data->address, data->size);
}
__except (1)
{
   bRet = FALSE;
}
  • 5.恢复当前进程的上下文,这里使用的是 ObDereferenceObject 函数和 KeUnstackDetachProcess 函数,用于恢复之前保存的当前进程的上下文状态,同时解除对目标进程的引用计数。
ObDereferenceObject(process);
KeUnstackDetachProcess(&stack);
  • 6.将读取的数据拷贝到输出参数中,将读取到的数据拷贝到输出参数中,并释放之前分配的内存缓冲区。
RtlCopyMemory(data->data, GetData, data->size);

将如上代码片段整合起来即可得到一个完整的内存读数据案例,读者可传入一个结构体实现对特定进程特定内存的动态读取功能,完整代码如下所示;

#include <ntifs.h>
#include <windef.h>

typedef struct
{
    DWORD pid;                // 要读写的进程ID
    DWORD64 address;          // 要读写的地址
    DWORD size;               // 读写长度
    BYTE* data;               // 要读写的数据
}ReadMemoryStruct;

// MDL读内存
BOOL MDLReadMemory(ReadMemoryStruct* data)
{
    BOOL bRet = TRUE;
    PEPROCESS process = NULL;

    PsLookupProcessByProcessId(data->pid, &process);

    if (process == NULL)
    {
        return FALSE;
    }

    BYTE* GetData;
    __try
    {
        GetData = ExAllocatePool(PagedPool, data->size);
    }
    __except (1)
    {
        return FALSE;
    }

    KAPC_STATE stack = { 0 };
    KeStackAttachProcess(process, &stack);

    __try
    {
        ProbeForRead(data->address, data->size, 1);
        RtlCopyMemory(GetData, data->address, data->size);
    }
    __except (1)
    {
        bRet = FALSE;
    }

    ObDereferenceObject(process);
    KeUnstackDetachProcess(&stack);
    RtlCopyMemory(data->data, GetData, data->size);
    ExFreePool(GetData);
    return bRet;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    DbgPrint(("Uninstall Driver Is OK \n"));
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint(("hello lyshark \n"));

    ReadMemoryStruct ptr;

    ptr.pid = 6672;
    ptr.address = 0x402c00;
    ptr.size = 100;

    // 分配空间接收数据
    ptr.data = ExAllocatePool(PagedPool, ptr.size);

    // 读内存
    MDLReadMemory(&ptr);

    // 输出数据
    for (size_t i = 0; i < 100; i++)
    {
        DbgPrint("%x \n", ptr.data[i]);
    }

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

读取内存地址0x402c00效果如下所示:

3.1.2 MDL写入内存步骤
  • 1.首先需要通过调用PsLookupProcessByProcessId函数获取目标进程的进程结构,该函数将根据传递的进程ID返回对应进程的PEPROCESS结构体,该结构体中包含了进程的各种信息。

  • 2.接下来使用KeStackAttachProcess函数附加到目标进程的上下文环境中,以便可以读取和写入该进程的内存空间。该函数将当前线程的上下文环境切换到目标进程的上下文环境中,使得该线程可以访问和修改目标进程的内存。

  • 3.在进行内存写入操作之前,需要调用ProbeForRead函数来检查要写入的内存空间是否可读写。这个步骤是为了确保要写入的内存空间没有被保护或被其他进程占用,以避免对系统造成不良影响。

  • 4.如果检查通过,接下来需要将目标进程的内存空间中的数据拷贝到当前进程的缓冲区中,以便进行修改操作。

  • 5.接下来需要调用MmMapLockedPages函数来锁定当前内存页面,以便可以对其进行修改。该函数将返回一个指向系统虚拟地址的指针,该地址是由系统自动分配的。在写入完成后,需要使用MmUnmapLockedPages函数来释放锁定的内存页面。

  • 6.然后,使用RtlCopyMemory函数完成内存拷贝操作,将缓冲区中的数据写入到锁定的内存页面中。

  • 7.写入操作完成后,需要调用IoFreeMdl函数来释放MDL锁。MDL锁用于锁定MDL描述的内存页面,以便可以对其进行操作。

  • 8.最后使用KeUnstackDetachProcess函数解除当前进程与目标进程之间的绑定,使得当前线程的上下文环境恢复到原始的状态。

此外在完成MDL写入内存操作后,还需要调用ObDereferenceObject函数将MDL对象的引用计数减1,以便在不再需要该对象时释放它所占用的系统资源。

从如上分析来看写入时与读取基本类似,只是多了锁定页面和解锁操作,这段MDL写内存完整实现代码如下所示;

#include <ntifs.h>
#include <windef.h>

typedef struct
{
    DWORD pid;                // 要读写的进程ID
    DWORD64 address;          // 要读写的地址
    DWORD size;               // 读写长度
    BYTE* data;               // 要读写的数据
}WriteMemoryStruct;

// MDL写内存
BOOL MDLWriteMemory(WriteMemoryStruct* data)
{
    BOOL bRet = TRUE;
    PEPROCESS process = NULL;

    PsLookupProcessByProcessId(data->pid, &process);
    if (process == NULL)
    {
        return FALSE;
    }

    BYTE* GetData;
    __try
    {
        GetData = ExAllocatePool(PagedPool, data->size);
    }
    __except (1)
    {
        return FALSE;
    }

    for (int i = 0; i < data->size; i++)
    {
        GetData[i] = data->data[i];
    }

    KAPC_STATE stack = { 0 };
    KeStackAttachProcess(process, &stack);

    PMDL mdl = IoAllocateMdl(data->address, data->size, 0, 0, NULL);
    if (mdl == NULL)
    {
        return FALSE;
    }

    MmBuildMdlForNonPagedPool(mdl);

    BYTE* ChangeData = NULL;

    __try
    {
        ChangeData = MmMapLockedPages(mdl, KernelMode);
        RtlCopyMemory(ChangeData, GetData, data->size);
    }
    __except (1)
    {
        bRet = FALSE;
        goto END;
    }

END:
    IoFreeMdl(mdl);
    ExFreePool(GetData);
    KeUnstackDetachProcess(&stack);
    ObDereferenceObject(process);

    return bRet;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    DbgPrint(("Uninstall Driver Is OK \n"));
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint(("hello lyshark \n"));

    WriteMemoryStruct ptr;

    ptr.pid = 6672;
    ptr.address = 0x402c00;
    ptr.size = 5;

    // 需要写入的数据
    ptr.data = ExAllocatePool(PagedPool, ptr.size);

    // 循环设置
    for (size_t i = 0; i < 5; i++)
    {
        ptr.data[i] = 0x90;
    }

    // 写内存
    MDLWriteMemory(&ptr);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

写出效果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/151671.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于SSM的校园服务平台管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

C++初阶,详解类和对象(2)

详解类和对象&#xff08;2&#xff09; 一&#xff0c;前言二&#xff0c;构造函数2.1构造函数概念2.2构造函数特性 三&#xff0c;析构函数3.1析构函数概念3.2析构函数特性 一&#xff0c;前言 上一篇我们讲了类的大体框架&#xff0c;这篇内容我们要重点来说一说类的几个默…

【Vue】内置指令真的很常用!

内置指令 v-text v-text用于将一个变量的值渲染为元素的文本内容 注意v-text只能接受字符串类型的值,对于对象类型,会调用toString()方法 与插值表达式区别就是它会替换标签中的值&#xff0c;只显示它绑定的&#xff08;还是插值语法用的多~&#xff09; 语法 <元素 …

普源DS1052E固件升级【附所有升级固件及工具】

折腾了两天&#xff0c;总算是弄好了。 升级的目的是啥&#xff1f;DS1052E的带宽是50M&#xff0c;示波器的时基最小可以调到5ns。固件升级后示波器的时基最小可以调到2ns&#xff0c;理论上说明此时示波器的带宽是100M。 网上的方法能找到很多&#xff0c;我总结一下大概的流…

jeesite 按部门过滤数据权限(保姆级图文教程)

文章目录 前言一、数据库表添加机构字段二、修改实体3.修改服务层总结前言 在项目开发过程中,数据需要按照部门、公司进行权限过滤,本篇文章记录下如何修改按部门进行权限过滤的详细图文教程。 一、数据库表添加机构字段 要进行权限过滤的表中添加机构字段 二、修改实体 添…

Java 算法篇-链表的经典算法:根据值删除节点、删除倒数第 n 个节点

&#x1f525;博客主页&#xff1a; 小扳_-CSDN博客 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 链表的创建 2.0 链表的经典算法 - 根据值来删除节点 2.1 根据值来删除节点 - 遍历链表来实现 2.2 根据值来删除节点 - 递归实现 3.0 链表的经典算法 - 删除倒数第 n…

Godot4.1 GDExtension 配置VisualStudio方法梳理以及快捷配置工具

写在最前 本篇教程基于之前教程&#xff0c;并且默认为Windows10&#xff0c;64位&#xff0c;Godot版本4.1.3如果遇到任何问题&#xff0c;欢迎及时提出&#xff0c;如果配置成功了请点个赞&#xff0c;球球啦。 之前教程 https://blog.csdn.net/qq_31805591/article/detai…

Java学习day12:static关键字,字符串声明,字符串常量池

声明&#xff1a;该专栏本人重新过一遍java知识点时候的笔记汇总&#xff0c;主要是每天的知识点题解&#xff0c;算是让自己巩固复习&#xff0c;也希望能给初学的朋友们一点帮助&#xff0c;大佬们不喜勿喷(抱拳了老铁&#xff01;) 往期回顾&#xff1a; Java学习day11&…

错误:CUDA error: device-side assert triggered CUDA kernel errors

对llama扩充中文词表后直接增量预训练&#xff0c;忘记设置--modules_to_save embed_tokens,lm_head,所以导致向量维度不一致&#xff0c;出现下面的错误。 1. 错误 2. 原因 出现这个错误的原因可能是因为维度或标签不一致。可以仔细排查一下。

【k8s集群搭建(二):基于虚拟机的linux的k8s集群搭建_超详细_可视化界面Dashboard安装_记录全过程踩坑记录及解决方法】

在 master 执行 # 根据 在线配置文件 创建资源 kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml设置访问端口 # 修改配置文件 找到 type&#xff0c;将 ClusterIP 改成 NodePort kubectl edit svc kubernetes-…

虾皮之家数据分析插件:知虾数据分析工具提升销量的利器

在当今的电商市场中&#xff0c;虾皮Shopee成为了许多商家的首选平台。然而&#xff0c;随着竞争的加剧&#xff0c;店铺运营变得越来越具有挑战性。如何提升销量&#xff0c;优化标题和图片&#xff0c;合理设置SKU&#xff0c;并准确跟踪店铺活动数据和竞品数据&#xff0c;已…

为什么网安人才缺口那么大,就业率却上不去?

为什么网安相关行业人才缺口还有三百多万&#xff0c;但现在却还有很多程序员找不到工作&#xff0c;难道我们又被所谓大数据骗了吗&#xff1f; 其实啊&#xff0c;造成如此现象的有以下几点原因&#xff1a;首先&#xff0c;教学青黄不接&#xff0c;因为网安属于近几年新开…

SCons

什么是构建工具&#xff08;系统&#xff09; 构建工具&#xff08;software construction tool&#xff09;是一种软件&#xff0c;它可以**根据一定的规则或指令&#xff0c;将源代码编译成可执行的二进制程序。**这是构建工具最基本也最重要的功能。 实际上构建工具的功能…

03.智慧商城——路由配置

01. 路由配置 - 一级路由 但凡是单个页面&#xff0c;独立展示的&#xff0c;都是一级路由 路由设计&#xff1a; 登录页首页架子 首页 - 二级分类页 - 二级购物车 - 二级我的 - 二级 搜索页搜索列表页商品详情页结算支付页我的订单页 router/index.js 配置一级路由&#x…

基于springboot实现一起来约苗管理系统项目【项目源码】

基于springboot实现一起来约苗平台管理系统演示 Java技术 Java是由Sun公司推出的一门跨平台的面向对象的程序设计语言。因为Java 技术具有卓越的通用性、高效性、健壮的安全性和平台移植性的特点&#xff0c;而且Java是开源的&#xff0c;拥有全世界最大的开发者专业社群&…

Hack_Kid

Hack_Kid 靶机地址&#xff1a;https://download.vulnhub.com/hackerkid/Hacker_Kid-v1.0.1.ova 一、主机发现 发现靶机IP为192.168.80.135 二、端口扫描 发现靶机开启了80、53、9999端口 三、信息收集 1.访问80端口 2.访问9999端口 根据靶场作者的提示&#xff0c;不…

未来服务器操作系统的趋势与展望

摘要&#xff1a; 随着云计算、大数据和人工智能不断的发展&#xff0c;服务器操作系统也需要随之进行新一轮的升级。本文通过分析当前服务器操作系统的现状&#xff0c;探讨了未来服务器操作系统的趋势和展望&#xff0c;并针对一些关键问题提出了解决方案。 一、引言 服务器…

老哥们平日是怎么排查线上问题的?

1、做好监控告警 如果线上出现了问题&#xff0c;我们更多的是希望由监控告警发现我们出了线上问题&#xff0c;而不是等到业务侧反馈。所以&#xff0c;我们需要对核心接口做好监控告警的功能。 2、定位报警层面 如果是业务代码层面的监控报警&#xff0c;那我们应该是可以…

PVE Win平台虚拟机下如何安装恢复自定义备份Win系统镜像ISO文件(已成功实现)

环境: Virtual Environment 7.3-3 Win s2019 UltraISO9.7 USM6.0 NTLite_v2.1.1.7917 问题描述: PVE Win平台虚拟机下如何安装恢复自定义备份Win系统镜像ISO文件 本次目标 主要是对虚拟机里面Win系统备份做成可安装ISO文件恢复至别的虚拟机或者实体机上 解决方案: …

海康Visionmaster-环境配置:MFC 二次开发环境配置方法

1 新建 MFC 工程&#xff0c;拷贝 DLL:VM\VisionMaster4.0.0\Development\V4.0.0 \ComControl\bin\x64 下的所有拷贝到项目工程输出目录下&#xff0c;如下图所示&#xff0c;项目的输出路径是 Dll 文件夹。 2 通过配置 C目录和链接器的方式配置 VM 环境 2.1 C目录下添加附加…