怎么看网站用的什么程序做的,网站定制开发微信运营,国外可以做自媒体的网站,做qq空间的网站在上一篇博文《内核通过PEB得到进程参数》中我们通过使用KeStackAttachProcess附加进程的方式得到了该进程的PEB结构信息#xff0c;本篇文章同样需要使用进程附加功能#xff0c;但这次我们将实现一个更加有趣的功能#xff0c;在某些情况下应用层与内核层需要共享一片内存…在上一篇博文《内核通过PEB得到进程参数》中我们通过使用KeStackAttachProcess附加进程的方式得到了该进程的PEB结构信息本篇文章同样需要使用进程附加功能但这次我们将实现一个更加有趣的功能在某些情况下应用层与内核层需要共享一片内存区域通过这片区域可打通内核与应用层的隔离此类功能的实现依附于MDL内存映射机制实现。
3.5.1 应用层映射到内核层
先来实现将R3内存数据拷贝到R0中功能实现所调用的API如下:
调用IoAllocateMdl创建一个MDL结构体。这个结构体描述了一个要锁定的内存页的位置和大小。调用MmProbeAndLockPages用于锁定创建的地址其中UserMode代表用户层,IoReadAccess以读取的方式锁定调用MmGetSystemAddressForMdlSafe用于从MDL中得到映射内存地址调用RtlCopyMemory用于内存拷贝,将DstAddr应用层中的数据拷贝到pMappedSrc中调用MmUnlockPages拷贝结束后解锁pSrcMdl调用IoFreeMdl释放之前创建的MDL结构体。
如上则是应用层数据映射到内核中的流程我们将该流程封装成SafeCopyMemory_R3_to_R0方便后期的使用函数对数据的复制进行了分块操作因此可以处理更大的内存块。
下面是对该函数的分析 1.首先进行一些参数的检查如果有任何一个参数为0那么函数就会返回 STATUS_UNSUCCESSFUL。 2.使用一个 while 循环来分块复制数据每个块的大小为 PAGE_SIZE (通常是4KB)。这个循环在整个内存范围内循环每次复制一个内存页的大小直到复制完整个内存范围。 3.在循环内部首先根据起始地址和当前要复制的大小来确定本次要复制的大小。如果剩余的内存不足一页大小则只复制剩余的内存。 4.调用 IoAllocateMdl 创建一个 MDL表示要锁定和复制的内存页。这里使用了 (PVOID)(SrcAddr 0xFFFFFFFFFFFFF000) 来确定页的起始地址。因为页的大小为 0x1000因此在计算页的起始地址时将 SrcAddr 向下舍入到最接近的 0x1000 的倍数。 5.如果 IoAllocateMdl 成功则调用 MmProbeAndLockPages 来锁定页面。这个函数将页面锁定到物理内存中并返回一个虚拟地址该虚拟地址指向已锁定页面的内核地址。 6.使用 MmGetSystemAddressForMdlSafe 函数获取一个映射到内核空间的地址该地址可以直接访问锁定的内存页。 6.如果获取到了映射地址则使用 RtlCopyMemory 函数将要复制的数据从应用层内存拷贝到映射到内核空间的地址。在复制结束后使用 MmUnlockPages 函数解锁内存页释放对页面的访问权限。
最后释放 MDL 并更新 SrcAddr 和 DstAddr 以复制下一个内存块。如果复制过程中发生任何异常函数将返回 STATUS_UNSUCCESSFUL。
总的来说这个函数是一个很好的实现它遵循了内核驱动程序中的最佳实践包括对内存的安全处理、分块复制、错误处理等。
#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;}// 创建MDLpSrcMdl 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){// 释放MDLIoFreeMdl(pSrcMdl);}if (nCopyedSize){nRemainSize PAGE_SIZE;}nCopyedSize nRemainSize;SrcAddr nRemainSize;DstAddr nRemainSize;}status STATUS_SUCCESS;return status;
}有了封装好的SafeCopyMemory_R3_to_R0函数那么接下来就是使用该函数实现应用层到内核层中的拷贝为了能实现拷贝我们需要做以下几个准备工作
1.使用PsLookupProcessByProcessId函数通过进程ID查找到对应的EProcess结构体以获取该进程在内核中的信息。2.使用KeStackAttachProcess函数将当前进程的执行上下文切换到指定进程的上下文以便能够访问该进程的内存。3.使用RtlAllocateMemory函数在当前进程的内存空间中分配一块缓冲区用于存储从指定进程中读取的数据。4.调用SafeCopyMemory_R3_to_R0函数将指定进程的内存数据拷贝到分配的缓冲区中。5.将缓冲区中的数据转换为BYTE类型的指针并将其输出。
PsLookupProcessByProcessId函数用于通过进程ID查找到对应的EProcess结构体这个结构体是Windows操作系统内核中用于表示一个进程的数据结构。
NTSTATUS status PsLookupProcessByProcessId(ProcessId, ProcessObject);
if (!NT_SUCCESS(status)) {return status;
}KeStackAttachProcess函数将当前进程的执行上下文切换到指定进程的上下文以便能够访问该进程的内存。这个函数也只能在内核态中调用。
KeStackAttachProcess(ProcessObject, ApcState);RtlAllocateMemory函数在当前进程的内存空间中分配一块缓冲区用于存储从指定进程中读取的数据。这个函数是Windows操作系统内核中用于动态分配内存的函数其中第一个参数TRUE表示允许操作系统在分配内存时进行页面合并以减少内存碎片的产生。第二个参数nSize表示需要分配的内存空间的大小。如果分配失败就需要将之前的操作撤销并返回错误状态。
PVOID pTempBuffer RtlAllocateMemory(TRUE, nSize);
if (pTempBuffer NULL) {KeUnstackDetachProcess(ApcState);ObDereferenceObject(ProcessObject);return STATUS_NO_MEMORY;
}SafeCopyMemory_R3_to_R0函数将指定进程的内存数据拷贝到分配的缓冲区中。
if (!SafeCopyMemory_R3_to_R0(ModuleBase, pTempBuffer, nSize)) {RtlFreeMemory(pTempBuffer);KeUnstackDetachProcess(ApcState);ObDereferenceObject(ProcessObject);return STATUS_UNSUCCESSFUL;
}最后将缓冲区中的数据转换为BYTE类型的指针并将其输出。需要注意的是在返回之前需要先将当前进程的执行上下文切换回原先的上下文。
BYTE* data (BYTE*)pTempBuffer;
KeUnstackDetachProcess(ApcState);
ObDereferenceObject(ProcessObject);
return data;如上实现细节用一段话总结首先PsLookupProcessByProcessId得到进程EProcess结构并KeStackAttachProcess附加进程声明pTempBuffer指针用于存储RtlAllocateMemory开辟的内存空间nSize则代表读取应用层进程数据长度ModuleBase则是读入进程基址调用SafeCopyMemory_R3_to_R0即可将应用层数据拷贝到内核空间并最终BYTE* data转为BYTE字节的方式输出。这样就完成了将指定进程的内存数据拷贝到当前进程中的操作。
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 进程PIDstatus 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){// 拷贝数据到R0status 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处的数据读入内核空间并输出 3.5.2 内核层映射到应用层
与上方功能实现相反SafeCopyMemory_R0_to_R3函数则用于将一个内核层中的缓冲区写出到应用层中SafeCopyMemory_R0_to_R3函数接收源地址SrcAddr、要复制的数据长度Length以及目标地址DstAddr作为参数其写出流程可总结为如下步骤: 1.使用IoAllocateMdl函数分别为源地址SrcAddr和目标地址DstAddr创建两个内存描述列表MDL。 2.使用MmBuildMdlForNonPagedPool函数将源地址的MDL更新为描述非分页池的虚拟内存缓冲区并更新MDL以描述底层物理页。 3.通过两次调用MmGetSystemAddressForMdlSafe函数分别获取源地址和目标地址的指针即pSrcMdl和pDstMdl。 4.使用MmProbeAndLockPages函数以写入方式锁定用户空间中pDstMdl指向的地址并将它的虚拟地址映射到物理内存页从而确保该内存页在复制期间不会被交换出去或被释放掉。 5.然后使用MmMapLockedPagesSpecifyCache函数将锁定的用户空间内存页映射到内核空间并返回内核空间中的虚拟地址。 6.最后使用RtlCopyMemory函数将源地址的数据复制到目标地址。 7.使用MmUnlockPages函数解除用户空间内存页的锁定并使用MmUnmapLockedPages函数取消内核空间与用户空间之间的内存映射。 8.最后释放源地址和目标地址的MDL使用IoFreeMdl函数进行释放。
内存拷贝SafeCopyMemory_R0_to_R3函数函数首先分配源地址和目标地址的MDL结构然后获取它们的虚拟地址并以写入方式锁定目标地址的MDL最后使用RtlCopyMemory函数将源地址的内存数据拷贝到目标地址。拷贝完成后函数解锁目标地址的MDL并返回操作状态。
封装代码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{// 以写入的方式锁定目标MDLMmProbeAndLockPages(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填充的区域。
此处的ZwAllocateVirtualMemory函数用于在进程的虚拟地址空间中分配一块连续的内存区域以供进程使用。它属于Windows内核API的一种与用户态的VirtualAlloc函数相似但是它运行于内核态可以分配不受用户空间地址限制的虚拟内存并且可以用于在驱动程序中为自己或其他进程分配内存。
函数的原型为
NTSYSAPI NTSTATUS NTAPI ZwAllocateVirtualMemory(_In_ HANDLE ProcessHandle,_Inout_ PVOID *BaseAddress,_In_ ULONG_PTR ZeroBits,_Inout_ PSIZE_T RegionSize,_In_ ULONG AllocationType,_In_ ULONG Protect
);其中ProcessHandle参数是进程句柄BaseAddress是分配到的内存区域的起始地址ZeroBits指定保留的高位RegionSize是分配内存大小AllocationType和Protect分别表示内存分配类型和内存保护属性。
ZwAllocateVirtualMemory函数成功返回NT_SUCCESS返回值为0否则返回相应的错误代码。如果函数成功调用会将BaseAddress参数指向分配到的内存区域的起始地址同时将RegionSize指向的值修改为实际分配到的内存大小。
当内存属性被设置为PAGE_EXECUTE_READWRITE之后则下一步直接调用SafeCopyMemory_R0_to_R3进行映射即可其调用完整案例如下所示
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 进程PIDstatus 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_READWRITEZwAllocateVirtualMemory(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指令。