驱动开发:内核R3与R0内存映射拷贝
在上一篇博文《驱动开发:内核通过PEB得到进程参数》中我们通过使用KeStackAttachProcess附加进程的方式得到了该进程的PEB结构信息,本篇文章同样需要使用进程附加功能,但这次我们将实现一个更加有趣的功能,在某些情况下应用层与内核层需要共享一片内存区域通过这片区域可打通内核与应用层的隔离,此类功能的实现依附于MDL内存映射机制实现。
应用层(R3)数据映射到内核层(R0)
先来实现将R3内存数据拷贝到R0中,功能实现所调用的API如下:
- IoAllocateMdl 该函数用于创建
MDL(类似初始化) - MmProbeAndLockPages 用于锁定创建的地址其中
UserMode代表用户层,IoReadAccess以读取的方式锁定 - MmGetSystemAddressForMdlSafe 用于从
MDL中得到映射内存地址 - RtlCopyMemory 用于内存拷贝,将
DstAddr应用层中的数据拷贝到pMappedSrc中 - MmUnlockPages 拷贝结束后解锁
pSrcMdl - IoFreeMdl 释放
MDL
内存拷贝SafeCopyMemory_R3_to_R0函数封装代码如下:
#include <ntifs.h>
#include <windef.h>
// 分配内存
void* RtlAllocateMemory(BOOLEAN InZeroMemory, SIZE_T InSize)
{
void* Result = ExAllocatePoolWithTag(NonPagedPool, InSize, 'lysh');
if (InZeroMemory && (Result != NULL))
RtlZeroMemory(Result, InSize);
return Result;
}
// 释放内存
void RtlFreeMemory(void* InPointer)
{
ExFreePool(InPointer);
}
/*
将应用层中的内存复制到内核变量中
SrcAddr r3地址要复制
DstAddr R0申请的地址
Size 拷贝长度
*/
NTSTATUS SafeCopyMemory_R3_to_R0(ULONG_PTR SrcAddr, ULONG_PTR DstAddr, ULONG Size)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
ULONG nRemainSize = PAGE_SIZE - (SrcAddr & 0xFFF);
ULONG nCopyedSize = 0;
if (!SrcAddr || !DstAddr || !Size)
{
return status;
}
while (nCopyedSize < Size)
{
PMDL pSrcMdl = NULL;
PVOID pMappedSrc = NULL;
if (Size - nCopyedSize < nRemainSize)
{
nRemainSize = Size - nCopyedSize;
}
// 创建MDL
pSrcMdl = IoAllocateMdl((PVOID)(SrcAddr & 0xFFFFFFFFFFFFF000), PAGE_SIZE, FALSE, FALSE, NULL);
if (pSrcMdl)
{
__try
{
// 锁定内存页面(UserMode代表应用层)
MmProbeAndLockPages(pSrcMdl, UserMode, IoReadAccess);
// 从MDL中得到映射内存地址
pMappedSrc = MmGetSystemAddressForMdlSafe(pSrcMdl, NormalPagePriority);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
}
if (pMappedSrc)
{
__try
{
// 将MDL中的映射拷贝到pMappedSrc内存中
RtlCopyMemory((PVOID)DstAddr, (PVOID)((ULONG_PTR)pMappedSrc + (SrcAddr & 0xFFF)), nRemainSize);
}
__except (1)
{
// 拷贝内存异常
}
// 释放锁
MmUnlockPages(pSrcMdl);
}
if (pSrcMdl)
{
// 释放MDL
IoFreeMdl(pSrcMdl);
}
if (nCopyedSize)
{
nRemainSize = PAGE_SIZE;
}
nCopyedSize += nRemainSize;
SrcAddr += nRemainSize;
DstAddr += nRemainSize;
}
status = STATUS_SUCCESS;
return status;
}
调用该函数实现拷贝,如下代码中首先PsLookupProcessByProcessId得到进程EProcess结构,并KeStackAttachProcess附加进程,声明pTempBuffer指针用于存储RtlAllocateMemory开辟的内存空间,nSize则代表读取应用层进程数据长度,ModuleBase则是读入进程基址,调用SafeCopyMemory_R3_to_R0即可将应用层数据拷贝到内核空间,并最终BYTE* data转为BYTE字节的方式输出。
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
}
// lyshark.com
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
NTSTATUS status = STATUS_UNSUCCESSFUL;
PEPROCESS eproc = NULL;
KAPC_STATE kpc = { 0 };
__try
{
// HANDLE 进程PID
status = PsLookupProcessByProcessId((HANDLE)4556, &eproc);
if (NT_SUCCESS(status))
{
// 附加进程
KeStackAttachProcess(eproc, &kpc);
// -------------------------------------------------------------------
// 开始映射
// -------------------------------------------------------------------
// 将用户空间内存映射到内核空间
PVOID pTempBuffer = NULL;
ULONG nSize = 0x1024;
ULONG_PTR ModuleBase = 0x0000000140001000;
// 分配内存
pTempBuffer = RtlAllocateMemory(TRUE, nSize);
if (pTempBuffer)
{
// 拷贝数据到R0
status = SafeCopyMemory_R3_to_R0(ModuleBase, (ULONG_PTR)pTempBuffer, nSize);
if (NT_SUCCESS(status))
{
DbgPrint("[*] 拷贝应用层数据到内核里 \n");
}
// 转成BYTE方便读取
BYTE* data = pTempBuffer;
for (size_t i = 0; i < 10; i++)
{
DbgPrint("%02X \n", data[i]);
}
}
// 释放空间
RtlFreeMemory(pTempBuffer);
// 脱离进程
KeUnstackDetachProcess(&kpc);
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
代码运行后即可将进程中0x0000000140001000处的数据读入内核空间并输出:

内核层(R0)数据映射到应用层(R3)
与上方功能实现相反SafeCopyMemory_R0_to_R3函数则用于将一个内核层中的缓冲区写出到应用层中,写出过程:
- IoAllocateMdl 分别调用MDL分配,源地址
SrcAddr目标地址DstAddr均创建 - MmBuildMdlForNonPagedPool 该 MDL 指定非分页虚拟内存缓冲区,并对其进行更新以描述基础物理页
- MmGetSystemAddressForMdlSafe 调用两次得到源地址,分别获取
pSrcMdl,pDstMdl两个MDL的 - MmProbeAndLockPages 以写入方式锁定用户层中
pDstMdl的地址
内存拷贝SafeCopyMemory_R0_to_R3函数封装代码如下:
// 分配内存
void* RtlAllocateMemory(BOOLEAN InZeroMemory, SIZE_T InSize)
{
void* Result = ExAllocatePoolWithTag(NonPagedPool, InSize, 'lysh');
if (InZeroMemory && (Result != NULL))
RtlZeroMemory(Result, InSize);
return Result;
}
// 释放内存
void RtlFreeMemory(void* InPointer)
{
ExFreePool(InPointer);
}
/*
将内存中的数据复制到R3中
SrcAddr R0要复制的地址
DstAddr 返回R3的地址
Size 拷贝长度
*/
NTSTATUS SafeCopyMemory_R0_to_R3(PVOID SrcAddr, PVOID DstAddr, ULONG Size)
{
PMDL pSrcMdl = NULL, pDstMdl = NULL;
PUCHAR pSrcAddress = NULL, pDstAddress = NULL;
NTSTATUS st = STATUS_UNSUCCESSFUL;
// 分配MDL 源地址
pSrcMdl = IoAllocateMdl(SrcAddr, Size, FALSE, FALSE, NULL);
if (!pSrcMdl)
{
return st;
}
// 该 MDL 指定非分页虚拟内存缓冲区,并对其进行更新以描述基础物理页。
MmBuildMdlForNonPagedPool(pSrcMdl);
// 获取源地址MDL地址
pSrcAddress = MmGetSystemAddressForMdlSafe(pSrcMdl, NormalPagePriority);
if (!pSrcAddress)
{
IoFreeMdl(pSrcMdl);
return st;
}
// 分配MDL 目标地址
pDstMdl = IoAllocateMdl(DstAddr, Size, FALSE, FALSE, NULL);
if (!pDstMdl)
{
IoFreeMdl(pSrcMdl);
return st;
}
__try
{
// 以写入的方式锁定目标MDL
MmProbeAndLockPages(pDstMdl, UserMode, IoWriteAccess);
// 获取目标地址MDL地址
pDstAddress = MmGetSystemAddressForMdlSafe(pDstMdl, NormalPagePriority);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
if (pDstAddress)
{
__try
{
// 将源地址拷贝到目标地址
RtlCopyMemory(pDstAddress, pSrcAddress, Size);
}
__except (1)
{
// 拷贝内存异常
}
MmUnlockPages(pDstMdl);
st = STATUS_SUCCESS;
}
IoFreeMdl(pDstMdl);
IoFreeMdl(pSrcMdl);
return st;
}
调用该函数实现拷贝,此处除去附加进程以外,在拷贝之前调用了ZwAllocateVirtualMemory将内存属性设置为PAGE_EXECUTE_READWRITE可读可写可执行状态,然后在向该内存中写出pTempBuffer变量中的内容,此变量中的数据是0x90填充的区域。
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
}
// lyshark.com
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
NTSTATUS status = STATUS_UNSUCCESSFUL;
PEPROCESS eproc = NULL;
KAPC_STATE kpc = { 0 };
__try
{
// HANDLE 进程PID
status = PsLookupProcessByProcessId((HANDLE)4556, &eproc);
if (NT_SUCCESS(status))
{
// 附加进程
KeStackAttachProcess(eproc, &kpc);
// -------------------------------------------------------------------
// 开始映射
// -------------------------------------------------------------------
// 将用户空间内存映射到内核空间
PVOID pTempBuffer = NULL;
ULONG nSize = 0x1024;
PVOID ModuleBase = 0x0000000140001000;
// 分配内存
pTempBuffer = RtlAllocateMemory(TRUE, nSize);
if (pTempBuffer)
{
memset(pTempBuffer, 0x90, nSize);
// 设置内存属性 PAGE_EXECUTE_READWRITE
ZwAllocateVirtualMemory(NtCurrentProcess(), &ModuleBase, 0, &nSize, MEM_RESERVE, PAGE_EXECUTE_READWRITE);
ZwAllocateVirtualMemory(NtCurrentProcess(), &ModuleBase, 0, &nSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// 将数据拷贝到R3中
status = SafeCopyMemory_R0_to_R3(pTempBuffer, &ModuleBase, nSize);
if (NT_SUCCESS(status))
{
DbgPrint("[*] 拷贝内核数据到应用层 \n");
}
}
// 释放空间
RtlFreeMemory(pTempBuffer);
// 脱离进程
KeUnstackDetachProcess(&kpc);
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
拷贝成功后,应用层进程内将会被填充为Nop指令。

驱动开发:内核R3与R0内存映射拷贝的更多相关文章
- 鸿蒙内核源码分析(内存映射篇) | 虚拟内存虚在哪里 | 百篇博客分析OpenHarmony源码 | v15.03
百篇博客系列篇.本篇为: v15.xx 鸿蒙内核源码分析(内存映射篇) | 虚拟内存虚在哪里 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分 ...
- Windows驱动开发-内核常用内存函数
搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool
- 驱动开发:内核中实现Dump进程转储
多数ARK反内核工具中都存在驱动级别的内存转存功能,该功能可以将应用层中运行进程的内存镜像转存到特定目录下,内存转存功能在应对加壳程序的分析尤为重要,当进程在内存中解码后,我们可以很容易的将内存镜像导 ...
- 鸿蒙内核源码分析(内存汇编篇) | 谁是虚拟内存实现的基础 | 百篇博客分析OpenHarmony源码 | v14.14
百篇博客系列篇.本篇为: v14.xx 鸿蒙内核源码分析(内存汇编篇) | 谁是虚拟内存实现的基础 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有 ...
- 《Linux Device Drivers》第十五章 内存映射和DMA——note
简单介绍 很多类型的驱动程序编程都须要了解一些虚拟内存子系统怎样工作的知识 当遇到更为复杂.性能要求更为苛刻的子系统时,本章所讨论的内容迟早都要用到 本章的内容分成三个部分 讲述mmap系统调用的实现 ...
- LDD3 第15章 内存映射和DMA
本章内容分为三个部分: 第一部分讲述了mmap系统调用的实现过程.将设备内存直接映射到用户进程的地址空间,尽管不是所有设备都需要,但是能显著的提高设备性能. 如何跨越边界直接访问用户空间的内存页,一些 ...
- 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 百篇博客分析OpenHarmony源码 | v16.02
百篇博客系列篇.本篇为: v16.xx 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪 ...
- 内存映射文件(Memory-Mapped File)
Java Memory-Mapped File所使用的内存分配在物理内存而不是JVM堆内存,且分配在OS内核. 1: 内存映射文件及其应用 - 实现一个简单的消息队列 / 计算机程序的思维逻辑 在一般 ...
- mmap内存映射
http://blog.csdn.net/kongdefei5000/article/details/70183119 内存映射是个很有用,也很有意思的思想.我们都知道操作系统分为用户态和内核态,用户 ...
随机推荐
- GIt后悔药:还原提交的操作(谨慎操作)
一.背景: 偶尔会遇到git的版本分支的文件被误改的情况,需要还原,此篇文章可能会帮助到你. PS: 来理解下 Git 工作区.暂存区和版本库概念,可以更好的理解以下的还原操作. * 工作区:就是你在 ...
- PHP小工具
防SQL注入 function clean($input) { if (is_array($input)) { foreach ($input as $key => $val) { $outpu ...
- 上穷碧落下凡尘:Win10系统下基于Docker配置Elasticsearch7配合Python3进行全文检索交互
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_166 基于文档式的全文检索引擎大家都不陌生,之前一篇文章:使用Redisearch实现的全文检索功能服务,曾经使用Rediseac ...
- [b01lers2020]Welcome to Earth-1
1.打开之后界面如下,查看源代码信息,发现chase文件,结果如下: 2.访问chase文件会一直跳转到die界面,那就只能抓包进行查看,发现leftt文件,结果如下: 3.访问leftt文件并查看源 ...
- react实战 系列 —— React 的数据流和生命周期
其他章节请看: react实战 系列 数据流和生命周期 如何处理 React 中的数据,组件之间如何通信,数据在 React 中如何流动? 常用的 React 生命周期方法以及开源项目 spug 中使 ...
- 总结-一本通提高篇&算竞进阶记录
当一个人看见星空,就再无法忍受黑暗 为了点亮渐渐沉寂的星空 不想就这样退役 一定不会鸽の坑 . 一本通提高篇 . 算竞进阶 . CDQ & 整体二分 . 平衡树 . LCT . 字符串 . 随 ...
- 简单理解 Flutter 中 StatelessWidget 和 StatefulWidget
Widget 分为了两种类型,分别为 StatelessWidget 和 StatefulWidget. 顾名思义,StatelessWidget 就是无状态的组件,它只是作为一个不发生任何更新状态的 ...
- linux tcp 在timewait 状态下的报文处理
最近处理一个问题,我们nginx服务器作为透明代理,将核心网过来的用户上网请求代理到我们的cache服务器,如果cache服务器没有命中内容,则需要我们 作为客户端往源站请求内容,但用户对此一无所知, ...
- 面向对象06---static关键字
public class Preson { { System.out.println("匿名代码块");// 2 } static { System.out.println(&qu ...
- 如何在 Windows 和 Linux 上确定系统使用的是 MBR 分区还是 GPT 分区详细步骤!!!
在 Windows 上检查系统使用的是 MBR 分区还是 GPT 分区 点击放大镜搜索输入disk 点击打开 进入之后,右键点击你想要检查分区方案的磁盘,在右键菜单里选择属性! 在属性窗口,切换到卷, ...