在上一篇博文《驱动开发:内核通过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. 鸿蒙内核源码分析(内存映射篇) | 虚拟内存虚在哪里 | 百篇博客分析OpenHarmony源码 | v15.03

    百篇博客系列篇.本篇为: v15.xx 鸿蒙内核源码分析(内存映射篇) | 虚拟内存虚在哪里 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分 ...

  2. Windows驱动开发-内核常用内存函数

    搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool

  3. 驱动开发:内核中实现Dump进程转储

    多数ARK反内核工具中都存在驱动级别的内存转存功能,该功能可以将应用层中运行进程的内存镜像转存到特定目录下,内存转存功能在应对加壳程序的分析尤为重要,当进程在内存中解码后,我们可以很容易的将内存镜像导 ...

  4. 鸿蒙内核源码分析(内存汇编篇) | 谁是虚拟内存实现的基础 | 百篇博客分析OpenHarmony源码 | v14.14

    百篇博客系列篇.本篇为: v14.xx 鸿蒙内核源码分析(内存汇编篇) | 谁是虚拟内存实现的基础 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有 ...

  5. 《Linux Device Drivers》第十五章 内存映射和DMA——note

    简单介绍 很多类型的驱动程序编程都须要了解一些虚拟内存子系统怎样工作的知识 当遇到更为复杂.性能要求更为苛刻的子系统时,本章所讨论的内容迟早都要用到 本章的内容分成三个部分 讲述mmap系统调用的实现 ...

  6. LDD3 第15章 内存映射和DMA

    本章内容分为三个部分: 第一部分讲述了mmap系统调用的实现过程.将设备内存直接映射到用户进程的地址空间,尽管不是所有设备都需要,但是能显著的提高设备性能. 如何跨越边界直接访问用户空间的内存页,一些 ...

  7. 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 百篇博客分析OpenHarmony源码 | v16.02

    百篇博客系列篇.本篇为: v16.xx 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 51.c.h .o 内存管理相关篇为: v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪 ...

  8. 内存映射文件(Memory-Mapped File)

    Java Memory-Mapped File所使用的内存分配在物理内存而不是JVM堆内存,且分配在OS内核. 1: 内存映射文件及其应用 - 实现一个简单的消息队列 / 计算机程序的思维逻辑 在一般 ...

  9. mmap内存映射

    http://blog.csdn.net/kongdefei5000/article/details/70183119 内存映射是个很有用,也很有意思的思想.我们都知道操作系统分为用户态和内核态,用户 ...

随机推荐

  1. linux 编译式安装nginx

    ./configure --prefix=/usr/local/nginx --sbin-path=/usr/local/nginx/sbin/nginx --conf-path=/usr/local ...

  2. 你的工具包已到货「GitHub 热点速览 v.22.31」

    如果你经常用 shell 记得看看本周特推里的 gum,它能给你的 shell 增加新趣味.除了这个 shell kit,我们还有 dashboard kit--tabler,功能技能 kit eng ...

  3. 美女 Committer 手把手教你使用海豚调度

    还在为选哪个调度发愁么?还在为查使用手册愁眉不展么?来来来,先瞧一眼海豚调度的 Slogan:调度选的好,下班回家早.调度用的对,半夜安心睡.为充分贯彻这一宗旨,海豚调度一条龙服务来了,特地邀请海豚社 ...

  4. 杀死 Windows 某个端口

    进入终端命令行,输入netstat -aon|findstr 3000查找端口号所对应的 PID: 输入指令taskkill /pid 20348 /f:

  5. FWT快速沃尔什变换——基于朴素数学原理的卷积算法

    这是我的第一篇学习笔记,如有差错,请海涵... 目录 引子 卷积形式 算法流程 OR卷积 AND卷积 XOR卷积 模板 引子 首先,考虑这是兔子 数一数,会发现你有一只兔子,现在,我再给你一只兔子 再 ...

  6. laravel框架中验证后在页面提示错误信息

    {{-- 显示错误信息 判断:如果有错误则进行显示,--}} {{-- 通过$errors->any() 获取是否有错误,如果有则返回布尔值true,没有返回布尔值false--}} @if($ ...

  7. Java-随机数据生成器(造数据)

    概述 简单易用的随机数据生成器.一般用于开发和测试阶段的数据填充.模拟.仿真研究.演示等场景.可以集成到各种类型的java项目中使用. 优点 非常轻量级(不到1M),容易集成,无需过多第三方依赖 简单 ...

  8. CodeForces - 1625C

    Problem - 1625C - Codeforces 题意: 一条马路,有n个限速牌,表示的是从这个限速牌开始到下一个限速牌或者到马路尾的这段距离的速度,你可以拆除其中k个限速牌,问最少的时间是多 ...

  9. FR801xH开发

    一.空间分配 二.代码流程 1)user_custom_parameters 函数 __jump_table 结构体中保存了一些配置信息: void user_custom_parameters(vo ...

  10. 如何使用Postman快速简单的调用快递物流平台快递鸟API接口

    前沿 快递鸟是一家聚合类的第三方快递物流平台,目前该平台提供的产品主要以API为主.由于API不能直观的看到产品效果,需要进行API对接联调成功后才能真实的看到产品的实际效果.但是如果一上来就写代码进 ...