语言文字建设网站/武汉seo工厂
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; // 要读写的进程IDDWORD64 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; // 要读写的进程IDDWORD64 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;
}
写出效果如下:
相关文章:

3.3 Windows驱动开发:内核MDL读写进程内存
MDL内存读写是一种通过创建MDL结构体来实现跨进程内存读写的方式。在Windows操作系统中,每个进程都有自己独立的虚拟地址空间,不同进程之间的内存空间是隔离的。因此,要在一个进程中读取或写入另一个进程的内存数据,需要先将目标进…...

开源与闭源:驾驭大模型未来的关键决断
在数字化的时代洪流中,开源与闭源的选择不断成为技术界的重要分水岭。随着特斯拉CEO埃隆马斯克的言论及其决策,公开支持开源,并糅合商业理念与技术革新,使得这场辩论再次成为公众关注的焦点。那么,在这场关乎技术发展脉…...

面向对象成员之属性
属性:通过方法改造出来 # 1.编写时 # 方法上方写property # 方法参数:只有一个self # 2.使用时:无需加括号 对象.方法 # 3.应用场景:对于简单的方法,当无需传参且有返回值时,可以使用 property class Foo(object):def _init_(self):...propertydef start(self):return 1pr…...

第六十二周周报
学习目标: 一、实验 二、论文 学习时间: 2023.11.11-2023.11.17 学习产出: 实验 1、CB模块实验效果出来了,加上去效果不太行,后续实验考虑是否将CB模块换到其他地方 2、CiFAR100实验已完成,效果比Vi…...

【机器学习】 特征工程:特征预处理,归一化、标准化、处理缺失值
特征预处理采用的是特定的统计方法(数学方法)将数据转化为算法要求的数字 1. 数值型数据 归一化,将原始数据变换到[0,1]之间 标准化,数据转化到均值为0,方差为1的范围内 缺失值,缺失值处理成均值、中…...

【深度学习实验】网络优化与正则化(七):超参数优化方法——网格搜索、随机搜索、贝叶斯优化、动态资源分配、神经架构搜索
文章目录 一、实验介绍二、实验环境1. 配置虚拟环境2. 库版本介绍 三、优化算法0. 导入必要的库1. 随机梯度下降SGD算法a. PyTorch中的SGD优化器b. 使用SGD优化器的前馈神经网络 2.随机梯度下降的改进方法a. 学习率调整b. 梯度估计修正 3. 梯度估计修正:动量法Momen…...

简单漂亮的首页
效果图 说明 这个首页我也是构思了很久,才想出这个界面,大家喜欢的话,可以拿走去使用 技术的话,采用的就是vue的语法,但是不影响,很多样式我都是直接手敲出来的 代码实现 标语 <!-- 标语 start-->&…...

SSM项目初始化流程与操作概念解释-SpringBoot简化版
文章目录 1.引入概念2.导入依赖3.项目配置4.依照SpringMVC框架构建项目 1.引入概念 例如某一个XX系统,该系统存在前台页面(给用户直观看或使用),和后台页面(给管理人员调整数据和权限)。 这二个页面都通过…...

Angular 路由无缝导航的实现与应用(六)
Angular 是一种流行的前端开发框架,它提供了强大的路由功能,用于构建单页应用程序(SPA)。本文将介绍 Angular 路由的基本概念和使用方法,并通过具体的代码实例演示如何利用路由实现无缝的页面导航。 什么是 Angular 路…...

quickapp_快应用_tabBar
tabBar 配置项中配置tabBar(版本兼容)使用tabs组件配置tabBar语法示例问题-切换tab没有反应问题-数据渲染问题解决优化 问题-tab的动态配置 第三方组件tabbar 一般首页都会显示几个tab用于进行页面切换,以下是几种tab配置方式。 配置项中配置tabBar(版本兼容) 在m…...

PCL_点云分割_基于法线微分分割
一、概述 PCL_点云分割_基于法线微分分割_点云法向量微分-CSDN博客 利用不同的半径(大的半径、小半径)来计算同一个点的法向量差值P。判断P的范围,从而进行分割。 看图理解: 二、计算流程 1、计算P点小半径的法向量Ns 2、计…...

计算机毕业论文内容参考|基于深度学习的交通标识智能识别系统的设计与维护
文章目录 导文摘要前言绪论1课题背景2国内外现状与趋势3课题内容相关技术与方法介绍系统分析总结与展望导文 基于深度学习的交通标识智能识别系统是一种利用深度学习模型对交通标识进行识别和解析的系统。它可以帮助驾驶员更好地理解交通规则和安全提示,同时也可以提高道路交通…...

SELinux零知识学习十六、SELinux策略语言之类型强制(1)
接前一篇文章:SELinux零知识学习十五、SELinux策略语言之客体类别和许可(9) 二、SELinux策略语言之类型强制 SELinux策略大部分内容都是由多条类型强制规则构成的,这些规则控制被允许的使用权,大多数默认转换标志、审…...

轻量封装WebGPU渲染系统示例<34>-数据驱动之Json构建场景
场景和数据之间的互通: 场景数据化或者数据化场景,是当前的主流场景数据构成方式。方便传输方便交换甚至是交互。 内置数据互通机制更有利于用户在各种应用场合下实现具体的3D相关的应用需求。用户只需要关心标准的或者约定好的数据定义及操作方式就能方…...

全局异常拦截和Spring Security认证异常的拦截的顺序
📑前言 本文主要全局异常拦截和Spring Security认证异常的顺序,如果有什么需要改进的地方还请大佬指出⛺️ 🎬作者简介:大家好,我是青衿🥇 ☁️博客首页:CSDN主页放风讲故事 🌄每日…...

Hive Lateral View explode列为空时导致数据异常丢失
一、问题描述 日常工作中我们经常会遇到一些非结构化数据,因此常常会将Lateral View 结合explode使用,达到将非结构化数据转化成结构化数据的目的,但是该方法对应explode的内容是有非null限制的,否则就有可能造成数据缺失。 SE…...

音频类型转换工具-可执行文件exe/dmg制作
朋友车载音乐需要MP3格式,想要个批量转换工具 准备工作 brew install ffmpeg --HEAD或者官网下载安装ffmpeg并配置环境conda install ffmpeg 或者pip install ffmpeg-python 音频类型转换程序.py文件 exe文件在windows下打包,dmg在macos下打包&#…...

【Proteus仿真】【51单片机】公交车报站系统
文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器,使用LCD12864显示模块、DS18B20温度传感器、DS1302时钟模块、按键、LED蜂鸣器、ULN2003、28BYJ48步进电机模块等。 主要功能: 系统运行后&…...

C++--STL总结
参考教程:黑马程序员匠心之作|C教程从0到1入门编程,学习编程不再难_哔哩哔哩_bilibili 软件界一直希望建立一种可重复利用的东西,C的面向对象和泛型编程思想,目的就是复用性的提升。 大多情况下,数据结构和算法都未能有一套标准,…...

Python----图像的手绘效果
图像的数组表示 图像是有规则的二维数据,可以用numpy 库将图像转换成数组对象 : from PIL import Image import numpy as np imnp.array(Image.open("D://np.jpg")) print(im.shape,im.dtype)结果: 图像转换对应的ndarray 类型是3 维数据&am…...

Android13集成paho.mqtt.android启动异常
项目中原依赖是: implementation(org.eclipse.paho:org.eclipse.paho.android.service:1.1.1) {exclude module: support-v4transitive true } implementation org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5在Android10系统运行正常,能够连接…...

STM框架之按键扫描新思路
STM框架之按键扫描新思路 引入代码展示思路分析 我们学习了定时器实现毫秒级/秒级任务框架,这期我们基于任务框架学习按键扫描新思路。 引入 在按键扫描的过程中,最重要的一步就是按键消抖,解决的方法最简单粗暴的就是先扫描一次按键状态&am…...

Linux服务器挂载另一台服务器的文件夹(mount)
我们实际应用中,会常遇到多个Linux服务器之间需要频繁共享文件,或者是一台服务器需要使用另一台服务器的闲置磁盘空间。最方便的方法就是挂载另一台linux文件夹(文件服务器),通俗理解为:当前服务器远程连接…...

剑指offer --- 用两个栈实现队列的先进先出特性
目录 前言 一、读懂题目 二、思路分析 三、代码呈现 总结 前言 当我们需要实现队列的先进先出特性时,可以使用栈来模拟队列的行为。本文将介绍如何使用两个栈来实现队列,并给出具体的思路和代码实现。 一、读懂题目 题目:用两个栈实现一…...

流媒体协议
◆ RTP(Real-time Transport Protocol),实时传输协议。 ◆ RTCP(Real-time Transport Control Protocol),实时传输控制协议。 ◆ RTSP(Real Time Streaming Protocol),实时流协议。 ◆ RTMP(Real Time Messaging Protocol),实时…...

ClickHouse的分片和副本
1.副本 副本的目的主要是保障数据的高可用性,即使一台ClickHouse节点宕机,那么也可以从其他服务器获得相同的数据。 Data Replication | ClickHouse Docs 1.1 副本写入流程 1.2 配置步骤 (1)启动zookeeper集群 (2&…...

C语言编程陷阱(五)
陷阱21:不要使用逗号运算符代替分号 C语言中,我们可以使用分号来结束一个语句,比如a = b;,这样可以让编译器知道语句的边界,以及执行的顺序。但是,如果我们想要在一个语句中执行多个表达式,就可以使用逗号运算符,比如a = (b = c, c + 1);,这样可以让编译器按照从左到右…...

chardet检测文件编码,使用生成器逐行读取文件
detect_encoding 函数使用 chardet 来检测文件的编码。然后,在 process_large_file 函数中,根据检测到的编码方式打开文件。这样,你就能够更准确地处理不同编码的文件。 import chardetdef detect_encoding(file_path):with open(file_path,…...

html所有标签和DOCTYPE的总结
一、DOCTYPE 1. 意义 DOCTYPE是一种标准通用标记语言的文档类型声明,告诉标准通用标记语言解析器它应该使用什么样的文档类型定义来解析文档。 2. 应用 现在,我们需要告诉标准通用标记语言解析器,我们接下去要用html来编写代码了。 <…...

2023年11月15号期中测验判断题(Java)
1-1 局部变量可以与成员变量重名。 正确答案:T 解释: 局部变量可以和成员变量重名,通常,为了区分局部变量和成员变量,会使用this关键字(C称this指针,python是self关键字)来特别声…...