在上一篇博文《驱动开发:内核通过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);
// -------------------------------------------------------------------
// 开始映射
// ---------------------------------------------------