云购网站做的起来吗,大连模板建站哪家好,深圳自定义网站开发,备案 手机网站MDL内存读写是一种通过创建MDL结构体来实现跨进程内存读写的方式。在Windows操作系统中#xff0c;每个进程都有自己独立的虚拟地址空间#xff0c;不同进程之间的内存空间是隔离的。因此#xff0c;要在一个进程中读取或写入另一个进程的内存数据#xff0c;需要先将目标进…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使对象引用数减1由于在第一步中调用了 PsLookupProcessByProcessId 函数获取了对应进程的 PEPROCESS 结构因此需要调用 ObDereferenceObject 函数将其引用计数减1以便释放对该对象的引用。
有了上述具体实现方法那么我们就可以封装MDLReadMemory()内存读函数了代码如下该函数用于在 Windows 内核模式下读取指定进程的内存数据。下面是对这个函数的详细步骤分析
1.通过进程 ID 找到对应的进程对象PsLookupProcessByProcessId 用于通过进程 ID 查找对应的进程对象。如果找不到该进程对象则直接返回 FALSE。
PsLookupProcessByProcessId(data-pid, process);2.在内核模式下必须使用内核提供的函数来分配内存。这里使用的是 ExAllocatePool 函数用于在内核堆中分配指定大小的内存缓冲区。如果分配失败则返回 FALSE。
BYTE* GetData;
__try
{GetData ExAllocatePool(PagedPool, data-size);
}
__except (1)
{return FALSE;
}3.在内核模式下访问其他进程的内存必须先将当前进程的上下文切换到目标进程的上下文。这里使用的是 KeStackAttachProcess 函数将当前进程的上下文切换到目标进程的上下文。同时为了在后面可以正确地从目标进程的上下文中返回还需要保存当前进程的上下文状态。
KAPC_STATE stack { 0 };
KeStackAttachProcess(process, stack);4.读取目标进程的内存数据这段代码使用 ProbeForRead 函数检查要读取的内存区域是否合法并且将目标进程的内存数据读取到之前分配的内存缓冲区中。如果读取过程中出现异常则返回 FALSE。
__try
{ProbeForRead(data-address, data-size, 1);RtlCopyMemory(GetData, data-address, data-size);
}
__except (1)
{bRet FALSE;
}5.恢复当前进程的上下文这里使用的是 ObDereferenceObject 函数和 KeUnstackDetachProcess 函数用于恢复之前保存的当前进程的上下文状态同时解除对目标进程的引用计数。
ObDereferenceObject(process);
KeUnstackDetachProcess(stack);6.将读取的数据拷贝到输出参数中将读取到的数据拷贝到输出参数中并释放之前分配的内存缓冲区。
RtlCopyMemory(data-data, GetData, data-size);将如上代码片段整合起来即可得到一个完整的内存读数据案例读者可传入一个结构体实现对特定进程特定内存的动态读取功能完整代码如下所示
#include ntifs.h
#include windef.htypedef struct
{DWORD pid; // 要读写的进程IDDWORD64 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效果如下所示 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函数解除当前进程与目标进程之间的绑定使得当前线程的上下文环境恢复到原始的状态。
此外在完成MDL写入内存操作后还需要调用ObDereferenceObject函数将MDL对象的引用计数减1以便在不再需要该对象时释放它所占用的系统资源。
从如上分析来看写入时与读取基本类似只是多了锁定页面和解锁操作这段MDL写内存完整实现代码如下所示
#include ntifs.h
#include windef.htypedef struct
{DWORD pid; // 要读写的进程IDDWORD64 address; // 要读写的地址DWORD size; // 读写长度BYTE* data; // 要读写的数据
}WriteMemoryStruct;// MDL写内存
BOOL MDLWriteMemory(WriteMemoryStruct* 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));WriteMemoryStruct 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;
}写出效果如下