MDL(Memory Descriptor List)是Windows操作系统中的一个数据结构,用于描述和管理内存页的信息。该数据结构由一系列描述内存页的元素组成,每个元素称为一个MDL条目(MDL Entry)。每个MDL条目记录了一个内存页的物理地址和相关信息,多个MDL条目可以组成一个链表,以便描述一段连续的或非连续的内存区域。
内存读写是最常用的一种读写模式,通常需要附加到指定进程空间内然后调用内存拷贝得到对端内存中的数据,在调用结束后再将其空间释放掉,通过这种方式实现内存读写操作,此种模式的读写操作也是最推荐使用的相比于CR3切换来说,此方式更稳定并不会受寄存器的影响。
MDL读取内存步骤
在使用MDL进行内存读取时,可以通过MmProbeAndLockPages
函数将目标内存页面锁定到物理内存,并将其映射到驱动程序的虚拟地址空间中。然后,使用MmMapLockedPagesSpecifyCache
函数将锁定的页面映射到驱动程序的虚拟地址空间中,以便进行读取操作。完成后,您可以使用MmUnmapLockedPages
函数取消映射,并使用MmUnlockPages
函数解锁页面。
下面是对每个步骤的进一步解释:
-
调用
PsLookupProcessByProcessId
:用于根据进程ID获取对应的进程结构体指针。通过进程ID可以找到目标进程的EPROCESS结构,从而可以获取有关进程的各种信息。 -
调用
KeStackAttachProcess
:用于将当前线程附加到目标进程的上下文中,使得当前线程在执行时具有目标进程的上下文环境。这样可以访问目标进程的虚拟地址空间,包括其内存页。 -
调用
ProbeForRead
:用于检查指定的内存区域是否可读取。在访问其他进程的内存之前,需要先验证目标内存区域是否有效和可访问,以防止访问非法内存。 -
拷贝内存空间中的数据:在经过前面的验证后,可以使用内存拷贝操作(如memcpy或RtlCopyMemory)将目标进程的内存数据复制到驱动程序的缓冲区中。这样驱动程序就可以读取或处理目标进程的内存数据。
-
调用
KeUnstackDetachProcess
:函数用于解除当前线程与目标进程的绑定,恢复当前线程的上下文环境。在完成对目标进程的操作后,需要解除绑定,以确保当前线程的执行环境恢复到原始状态。 -
最后调用
ObDereferenceObject
:这个函数用于减少对象的引用计数。在使用完目标进程的EPROCESS结构后,需要减少对该对象的引用计数,以便系统在不再使用时可以正确地释放内存资源。
代码总结起来应该是如下样子,用户传入一个结构体,输出对应长度的字节数据:
#include <ntifs.h>
#include <windef.h>
typedef struct
{
DWORD pid; // 要读写的进程ID
DWORD64 address; // 要读写的地址
DWORD size; // 读写长度
BYTE* data; // 要读写的数据
}ReadMemoryStruct;
// MDL读内存
BOOL MDLReadMemory(ReadMemoryStruct* data)
{
BOOL bRet = TRUE;
PEPROCESS process = NULL;
PsLookupProcessByProcessId(data->pid, &process);
if (process == NULL)
{
return FALSE;
}
BYTE* GetData;
__try
{
GetData = ExAllocatePool(PagedPool, data->size);
}
__except (1)
{
return FALSE;
}
KAPC_STATE stack = { 0 };
KeStackAttachProcess(process, &stack);
__try
{
ProbeForRead(data->address, data->size, 1);
RtlCopyMemory(GetData, data->address, data->size);
}
__except (1)
{
bRet = FALSE;
}
ObDereferenceObject(process);
KeUnstackDetachProcess(&stack);
RtlCopyMemory(data->data, GetData, data->size);
ExFreePool(GetData);
return bRet;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint(("hello lyshark \n"));
ReadMemoryStruct ptr;
ptr.pid = 6672;
ptr.address = 0x402c00;
ptr.size = 100;
// 分配空间接收数据
ptr.data = ExAllocatePool(PagedPool, ptr.size);
// 读内存
MDLReadMemory(&ptr);
// 输出数据
for (size_t i = 0; i < 100; i++)
{
DbgPrint("%x \n", ptr.data[i]);
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
读取内存地址0x402c00
效果如下所示:
MDL写入内存步骤
在使用MDL进行内存写入时,可以使用MmProbeAndLockPages
函数将目标内存页面锁定到物理内存,并将其映射到驱动程序的虚拟地址空间中。然后,您可以在映射的地址上执行写入操作。完成后使用MmUnmapLockedPages
函数取消映射,并使用MmUnlockPages
函数解锁页面。
MDL写入数据预读取基本保持一致,只是在第五步增加了三个函数,其他的与读取保持一致。
-
调用
MmMapLockedPages
:这个函数用于将MDL锁定的内存页面映射到系统空间中的虚拟地址,以便可以进行写入操作。通过映射页面,可以获取指向内存数据的指针,从而进行修改或写入操作。 -
调用
RtlCopyMemory
:这个函数用于在驱动程序的缓冲区和映射的内存页面之间进行内存拷贝操作。通过这个操作,将数据从驱动程序的缓冲区复制到映射的内存页面中,实现数据的写入。 -
调用
IoFreeMdl
:这个函数用于释放先前创建的MDL(内存描述符列表)。在完成对映射页面的写入操作后,需要释放相应的MDL,以便系统可以正确管理内存资源。
写入时与读取类似,只是多了锁定页面和解锁操作。
#include <ntifs.h>
#include <windef.h>
typedef struct
{
DWORD pid; // 要读写的进程ID
DWORD64 address; // 要读写的地址
DWORD size; // 读写长度
BYTE* data; // 要读写的数据
}ReadMemoryStruct;
// MDL写内存
BOOL MDLWriteMemory(ReadMemoryStruct* data)
{
BOOL bRet = TRUE;
PEPROCESS process = NULL;
PsLookupProcessByProcessId(data->pid, &process);
if (process == NULL)
{
return FALSE;
}
BYTE* GetData;
__try
{
GetData = ExAllocatePool(PagedPool, data->size);
}
__except (1)
{
return FALSE;
}
for (int i = 0; i < data->size; i++)
{
GetData[i] = data->data[i];
}
KAPC_STATE stack = { 0 };
KeStackAttachProcess(process, &stack);
PMDL mdl = IoAllocateMdl(data->address, data->size, 0, 0, NULL);
if (mdl == NULL)
{
return FALSE;
}
MmBuildMdlForNonPagedPool(mdl);
BYTE* ChangeData = NULL;
__try
{
ChangeData = MmMapLockedPages(mdl, KernelMode);
RtlCopyMemory(ChangeData, GetData, data->size);
}
__except (1)
{
bRet = FALSE;
goto END;
}
END:
IoFreeMdl(mdl);
ExFreePool(GetData);
KeUnstackDetachProcess(&stack);
ObDereferenceObject(process);
return bRet;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint(("hello lyshark \n"));
ReadMemoryStruct ptr;
ptr.pid = 6672;
ptr.address = 0x402c00;
ptr.size = 5;
// 需要写入的数据
ptr.data = ExAllocatePool(PagedPool, ptr.size);
// 循环设置
for (size_t i = 0; i < 5; i++)
{
ptr.data[i] = 0x90;
}
// 写内存
MDLWriteMemory(&ptr);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
写出效果如下: