驱动开发:内核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 内存映射是个很有用,也很有意思的思想.我们都知道操作系统分为用户态和内核态,用户 ... 
随机推荐
- python sphinx(文档生成器)入门
			简介 Sphinx 是一个 文档生成器 ,您也可以把它看成一种工具,它可以将一组纯文本源文件转换成各种输出格式,并且自动生成交叉引用.索引等.也就是说,如果您的目录包含一堆 reStructuredT ... 
- MySQL通配符与正则表达式
			通配符 通配符必须全文匹配时才为真,使用LIKE关键字 字符 示例 含义 _ "a_b" 任意一个字符"axb",其中x可以使任意字符,包括汉字 % " ... 
- Dynamic CRM使用FetchXML在js中查询与调用传递编码问题
			在页面交互脚本js中实现窗体交互逻辑是很常见的crm场景,一般情况下使用拓展工具RESTBuilder编辑器,可以很方便的进行操作,增删改查均能实现,但在某些较为特殊的场景下,需要根据条件去拼接查询过 ... 
- Vue3中defineEmits、defineProps 是怎么做到不用引入就能直接用的
			最近正在将一个使用单文件组件的 Options API 的 Vue2 JavaScript 项目升级为 Vue3 typescript,并利用 Composition API 的优势. 比如,下面这种 ... 
- 批处理(bat)命令修改xml模板数据
			给定一个模板,然后通过bat修改某个节点中的值 模板如下: <?xml version="1.0" encoding="UTF-16"?> < ... 
- 用Python实现广度优先搜索
			图是一种善于处理关系型数据的数据结构,使用它可以很轻松地表示数据之间是如何关联的 图的实现形式有很多,最简单的方法之一就是用散列表 背景 图有两种经典的遍历方式:广度优先搜索和深度优先搜索.两者是相似 ... 
- KingbaseES 创建只读(read_only)用户
			数据库版本: prod=> select version(); version --------------------------------------------------------- ... 
- K8S Pod及其控制器
			Pod K8S里能够运行的最小逻辑单元,1个Pod可以运行多个容器 Pod 控制器 Pod控制器是Pod启动的一种模版,用来保证在K8S中启动的Pod始终按照人们的预期运行(副本数,生命周期.健康状态 ... 
- 【读书笔记】C#高级编程 第十二章 动态语言扩展
			(一)DLR C#4的动态功能是Dynamic Language Runtime(动态语言运行时,DLR)的一部分.DLR是添加到CLR的一系列服务. (二)dynamic类型 dynamic类型允许 ... 
- R语言-tidyr和dplyr
			一.安装和加载 1.安装并加载tidyr和dplyr包 install.packages("tidyr") library(tidyr) install.packages(&quo ... 
