3.3 Windows驱动开发:内核MDL读写进程内存

mdl内存读写是通过创建mdl结构体实现跨进程内存读写的一种方法。在windows操作系统中,每个进程拥有独立的虚拟地址空间,不同进程的内存空间是隔离的。因此,要在一个进程中读取或写入另一个进程的内存数据,首先需要将目标进程的物理内存映射到当前进程的虚拟地址空间中,然后才能进行内存读写操作。

MDL结构体是windows内核中专门用于描述物理内存的数据结构,包含了物理地址、长度、内存映射的虚拟地址等信息。通过创建MDL结构体并调用系统函数将其映射到当前进程的虚拟地址空间中,可以实现跨进程内存读写操作。

与CR3切换方式相比,MDL内存读写更稳定、安全,且不受寄存器影响。同时,MDL内存读写方式还能充分利用Windows操作系统的内存管理机制,实现更高效的内存读写操作。因此,MDL内存读写是Windows操作系统中最常用和推荐的跨进程内存读写方式。

3.1.1 MDL读取内存步骤

  1. 调用PsLookupProcessByProcessId获取进程Process结构:该函数根据进程ID查找对应的进程对象,通过data->pid获取进程ID,然后调用PsLookupProcessByProcessId获取PEPROCESS结构。如果获取失败,则返回FALSE。
  2. 调用KeStackAttachProcess附加到目标进程:在内核模式下,读取其他进程的内存需要先附加到对应进程的上下文中。调用KeStackAttachProcess函数将当前线程切换到目标进程的上下文,同时保存当前进程的上下文状态。
  3. 调用ProbeForRead检查内存是否可读写:在内核模式下,访问其他进程的内存需要保证访问合法,因此调用ProbeForRead函数检查读取的内存空间是否可读写。如果不可读写,则会触发异常,通过异常处理机制处理这种情况。
  4. 将内存空间中的数据拷贝到自己的缓冲区:完成内存空间检查后,使用RtlCopyMemory函数将目标进程的内存数据拷贝到自己的缓冲区中。由于内存空间可能很大,可能需要多次拷贝操作。
  5. 调用KeUnstackDetachProcess解除绑定:读取完内存数据后,需要将当前线程从目标进程的上下文中解除绑定,返回到原来的上下文中。调用KeUnstackDetachProcess函数完成解绑操作,同时恢复之前保存的当前进程的上下文状态。
  6. 调用ObDereferenceObject减少对象引用数:由于第一步中调用了PsLookupProcessByProcessId函数获取了对应进程的PEPROCESS结构,因此需要调用ObDereferenceObject函数将其引用计数减1,以便释放对该对象的引用。

通过上述步骤,我们可以封装MDLReadMemory()内存读函数,代码如下,该函数用于在Windows内核模式下读取指定进程的内存数据:

#include <ntifs.h> #include <windef.h> <p>typedef struct { DWORD pid;                // 要读写的进程ID DWORD64 address;          // 要读写的地址 DWORD size;               // 读写长度 BYTE* data;               // 要读写的数据 } ReadMemoryStruct;</p><p>// MDL读内存 BOOL MDLReadMemory(ReadMemoryStruct<em> data) { BOOL bRet = TRUE; PEPROCESS process = NULL; PsLookupProcessByProcessId(data->pid, &process); if (process == NULL) { return FALSE; } BYTE</em> GetData; <strong>try { GetData = ExAllocatePool(PagedPool, data->size); } </strong>except (1) { return FALSE; } KAPC_STATE stack = { 0 }; KeStackAttachProcess(process, &stack); <strong>try { ProbeForRead(data->address, data->size, 1); RtlCopyMemory(GetData, data->address, data->size); } </strong>except (1) { bRet = FALSE; } ObDereferenceObject(process); KeUnstackDetachProcess(&stack); RtlCopyMemory(data->data, GetData, data->size); ExFreePool(GetData); return bRet; }</p><p>VOID UnDriver(PDRIVER_OBJECT driver) { DbgPrint(("Uninstall Driver Is OK n")); }</p><p>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 < ptr.size; i++) { DbgPrint(("%02x ", ptr.data[i])); } DbgPrint(("n")); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }

读取内存地址0x402c00效果如下所示:

3.3 Windows驱动开发:内核MDL读写进程内存

3.1.2 MDL写入内存步骤

  1. 调用PsLookupProcessByProcessId获取目标进程的进程结构:该函数根据传递的进程ID返回对应进程的PEPROCESS结构体,包含了进程的各种信息。
  2. 使用KeStackAttachProcess附加到目标进程的上下文环境:该函数将当前线程的上下文环境切换到目标进程的上下文环境,使得该线程可以访问和修改目标进程的内存。
  3. 调用ProbeForRead检查要写入的内存空间是否可读写:这个步骤是为了确保要写入的内存空间没有被保护或被其他进程占用,以避免对系统造成不良影响。
  4. 将目标进程的内存空间中的数据拷贝到当前进程的缓冲区:便于进行修改操作。
  5. 调用MmMapLockedPages锁定当前内存页面:该函数将返回一个指向系统虚拟地址的指针,该地址是由系统自动分配的。在写入完成后,需要使用MmUnmapLockedPages函数来释放锁定的内存页面。
  6. 使用RtlCopyMemory完成内存拷贝操作:将缓冲区中的数据写入到锁定的内存页面中。
  7. 调用IoFreeMdl释放MDL锁:MDL锁用于锁定MDL描述的内存页面,以便可以对其进行操作。
  8. 使用KeUnstackDetachProcess解除当前进程与目标进程之间的绑定:使得当前线程的上下文环境恢复到原始的状态。
  9. 调用ObDereferenceObject将MDL对象的引用计数减1:便于在不再需要该对象时释放它所占用的系统资源。

从上述分析来看,写入操作与读取操作基本类似,只是多了锁定页面和解锁操作。MDL写内存的完整实现代码如下所示:

#include <ntifs.h></p><h1>include <windef.h></h1><p>typedef struct { DWORD pid;                // 要读写的进程ID DWORD64 address;          // 要读写的地址 DWORD size;               // 读写长度 BYTE* data;               // 要读写的数据 } WriteMemoryStruct;</p><p>// MDL写内存 BOOL MDLWriteMemory(WriteMemoryStruct<em> data) { BOOL bRet = TRUE; PEPROCESS process = NULL; PsLookupProcessByProcessId(data->pid, &process); if (process == NULL) { return FALSE; } BYTE</em> GetData; <strong>try { GetData = ExAllocatePool(PagedPool, data->size); } </strong>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; <strong>try { ChangeData = MmMapLockedPages(mdl, KernelMode); RtlCopyMemory(ChangeData, GetData, data->size); } </strong>except (1) { bRet = FALSE; goto END; } END: IoFreeMdl(mdl); ExFreePool(GetData); KeUnstackDetachProcess(&stack); ObDereferenceObject(process); return bRet; }</p><p>VOID UnDriver(PDRIVER_OBJECT driver) { DbgPrint(("Uninstall Driver Is OK n")); }</p><p>NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) { DbgPrint(("hello lyshark n")); WriteMemoryStruct ptr; ptr.pid = 6672; ptr.address = 0x402c00; ptr.size = 5; // 需要写入的数据 ptr.data = ExAllocatePool(PagedPool, ptr.size); // 循环设置 for (size_t i = 0; i < ptr.size; i++) { ptr.data[i] = 0x90; } // 写内存 MDLWriteMemory(&ptr); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }

写出效果如下:

3.3 Windows驱动开发:内核MDL读写进程内存

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享