驱动开发:内核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 内存映射是个很有用,也很有意思的思想.我们都知道操作系统分为用户态和内核态,用户 ...
随机推荐
- node.js操作数据库
var sys = require('sys'); var Client = require('mysql').Client; var client = new Client(); client.us ...
- Python花式读取大文件(10g/50g/1t)遇到的性能问题(面试向)
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_97 最近无论是面试还是笔试,有一个高频问题始终阴魂不散,那就是给一个大文件,至少超过10g,在内存有限的情况下(低于2g),该以什 ...
- Prometheus完整安装
官方组件: prometheus node_exporter blackbox_exporter alertmanager VictoriaMetrics 第三方开源软件: ConsulManager ...
- Spring源码 05 IOC 注解方式
参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...
- SvelteUI:运用svelte3构建的网页版UI组件库(升级版)
距离上次分享的svelte-ui 1.0已经一月有余,这次带来全新升级完整版svelte-ui 2.0. 这次优化并新增15+个组件.在开发之初借鉴了element-ui组件库,所以在组件结构及语法上 ...
- JavaScript 异步编程(二):Promise
PromiseState Promise 有一个 [[PromiseState]] 属性,表示当前的状态,状态有 pending 和 fulfill 以及 reject. 从第一个 Promise 开 ...
- q 短引用标签
<q/>标签可以使一段文本作为引用. <p>他说:<q>明天要下雨</q>.</p> 注意,源代码中并没有为这段文字添加引用符号,而是添加了 ...
- 若依3.6.0使用Mybatis-plus分页失效以及完美替换Pagehelper
一.前言 小编最近在经历后端框架的迁移,虽然不是小编来做,但是有个分页的情况让小编和一个同事去搞. 说一下小编这边的需求: 原来框架使用Mybatis-plus进行分页,要更换的新框架若依是使用Pag ...
- zkw线段树——简单易懂好写好调的线段树
0.简介 zkw线段树是一种非递归线段树,与普通线段树不同的是,它是棵标准的满二叉树,所以遍历过程可全程使用位运算,常数一般比线段树小得多. 1.结构/建树 前面说了,zkw线段树是满二叉树,可是原数 ...
- KingbaseES不同字符类型比较转换规则
Postgresql 常用的字符数据类型的有char.varchar和text,其中 char 固定长度类型, varchar 和 text 是可变长度类型.这三种类型在进行比较时,会进行隐含的类型转 ...