草庐IT

驱动开发:内核R3与R0内存映射拷贝

LyShark 2023-04-15 原文

在上一篇博文《驱动开发:内核通过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内存映射拷贝的更多相关文章

  1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  2. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  3. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  4. ruby-on-rails - Ruby 中的内存模型 - 2

    ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序

  5. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  6. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  7. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  8. ruby-on-rails - 如何在 Ruby on Rails 中实现由 JSF 2.0 (Primefaces) 驱动的 UI 魔法 - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭10年前。问题1)我想知道ruby​​onrails是否有功能类似于primefaces的gem。我问的原因是如果您使用primefaces(http://www.primefaces.org/showcase-labs/ui/home.jsf),开发人员无需担心javascript或jquery的东西。据我所知,JSF是一个规范,基于规范的各种可用实现,prim

  9. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  10. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

随机推荐