访问其他进程内存
在Ring3 是提供了两个API函数,WriteProcessMemory和ReadProcessMemory来读取其他进程的内存
BOOL WINAPI WriteProcessMemory( __in HANDLE hProcess, __in LPVOID lpBaseAddress, __in LPCVOID lpBuffer, __in SIZE_T nSize, __out SIZE_T *lpNumberOfBytesWritten );
BOOL WINAPI ReadProcessMemory( __in HANDLE hProcess, __in LPCVOID lpBaseAddress, __out LPVOID lpBuffer, __in SIZE_T nSize, __out SIZE_T *lpNumberOfBytesRead );
而在ring0也是有相应的接口函数,NtWriteVirtualMemory和NtReadVirtualMemory
NTSTATUS NtWriteVirtualMemory( __in HANDLE ProcessHandle, __in_opt PVOID BaseAddress, __in_bcount(BufferSize) CONST VOID *Buffer, __in SIZE_T BufferSize, __out_opt PSIZE_T NumberOfBytesWritten )
NTSTATUS NtWriteVirtualMemory( __in HANDLE ProcessHandle, __in_opt PVOID BaseAddress, __in_bcount(BufferSize) CONST VOID *Buffer, __in SIZE_T BufferSize, __out_opt PSIZE_T NumberOfBytesWritten ) /*++ Routine Description: This function copies the specified address range from the current process into the specified address range of the specified process. Arguments: ProcessHandle - Supplies an open handle to a process object. BaseAddress - Supplies the base address to be written to in the specified process. Buffer - Supplies the address of a buffer which contains the contents to be written into the specified process address space. BufferSize - Supplies the requested number of bytes to write into the specified process. NumberOfBytesWritten - Receives the actual number of bytes transferred into the specified address space. Return Value: NTSTATUS. --*/ { SIZE_T BytesCopied; KPROCESSOR_MODE PreviousMode; PEPROCESS Process; NTSTATUS Status; PETHREAD CurrentThread; PAGED_CODE(); // // Get the previous mode and probe output argument if necessary. // CurrentThread = PsGetCurrentThread (); PreviousMode = KeGetPreviousModeByThread(&CurrentThread->Tcb); if (PreviousMode != KernelMode) { if (((PCHAR)BaseAddress + BufferSize < (PCHAR)BaseAddress) || ((PCHAR)Buffer + BufferSize < (PCHAR)Buffer) || ((PVOID)((PCHAR)BaseAddress + BufferSize) > MM_HIGHEST_USER_ADDRESS) || ((PVOID)((PCHAR)Buffer + BufferSize) > MM_HIGHEST_USER_ADDRESS)) { return STATUS_ACCESS_VIOLATION; } if (ARGUMENT_PRESENT(NumberOfBytesWritten)) { try { ProbeForWriteUlong_ptr(NumberOfBytesWritten); } except(EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } } } // // If the buffer size is not zero, then attempt to write data from the // current process address space into the target process address space. // BytesCopied = 0; Status = STATUS_SUCCESS; if (BufferSize != 0) { // // Reference the target process. // Status = ObReferenceObjectByHandle(ProcessHandle, PROCESS_VM_WRITE, PsProcessType, PreviousMode, (PVOID *)&Process, NULL); // // If the process was successfully referenced, then attempt to // write the specified memory either by direct mapping or copying // through nonpaged pool. // if (Status == STATUS_SUCCESS) { Status = MmCopyVirtualMemory (PsGetCurrentProcessByThread(CurrentThread), Buffer, Process, BaseAddress, BufferSize, PreviousMode, &BytesCopied); // // Dereference the target process. // ObDereferenceObject(Process); } } // // If requested, return the number of bytes read. // if (ARGUMENT_PRESENT(NumberOfBytesWritten)) { try { *NumberOfBytesWritten = BytesCopied; } except(EXCEPTION_EXECUTE_HANDLER) { NOTHING; } } return Status; }
NtWriteVirutalMemory中只是进行了一些参数的校验和对进程对象的引用,然后就调用了MmCopyVirtualMemory函数,我们继续跟入:
NTSTATUS MmCopyVirtualMemory( IN PEPROCESS FromProcess, IN CONST VOID *FromAddress, IN PEPROCESS ToProcess, OUT PVOID ToAddress, IN SIZE_T BufferSize, IN KPROCESSOR_MODE PreviousMode, OUT PSIZE_T NumberOfBytesCopied ) { NTSTATUS Status; PEPROCESS ProcessToLock; if (BufferSize == 0) { ASSERT (FALSE); // No one should call with a zero size. return STATUS_SUCCESS; } ProcessToLock = FromProcess; if (FromProcess == PsGetCurrentProcess()) { ProcessToLock = ToProcess; } // // Make sure the process still has an address space. // //EProcess上的保护位,避免操作进程时进程突然关闭 if (ExAcquireRundownProtection (&ProcessToLock->RundownProtect) == FALSE) { return STATUS_PROCESS_IS_TERMINATING; } // // If the buffer size is greater than the pool move threshold, // then attempt to write the memory via direct mapping. // //#define POOL_MOVE_THRESHOLD 511 if (BufferSize > POOL_MOVE_THRESHOLD) { Status = MiDoMappedCopy(FromProcess, FromAddress, ToProcess, ToAddress, BufferSize, PreviousMode, NumberOfBytesCopied); // // If the completion status is not a working quota problem, // then finish the service. Otherwise, attempt to write the // memory through nonpaged pool. // if (Status != STATUS_WORKING_SET_QUOTA) { goto CompleteService; } *NumberOfBytesCopied = 0; } // // There was not enough working set quota to write the memory via // direct mapping or the size of the write was below the pool move // threshold. Attempt to write the specified memory through nonpaged // pool. // Status = MiDoPoolCopy(FromProcess, FromAddress, ToProcess, ToAddress, BufferSize, PreviousMode, NumberOfBytesCopied); // // Dereference the target process. // CompleteService: // // Indicate that the vm operation is complete. // ExReleaseRundownProtection (&ProcessToLock->RundownProtect); return Status; }
我们可以看到当需要操作的缓冲区大小大于POOL_MOVE_THRESHOLD(511)字节时,就调用MiDoMappedCopy来拷贝内存,反之则调用MiDoPoolCopy。
NTSTATUS MiDoMappedCopy ( IN PEPROCESS FromProcess, IN CONST VOID *FromAddress, IN PEPROCESS ToProcess, OUT PVOID ToAddress, IN SIZE_T BufferSize, IN KPROCESSOR_MODE PreviousMode, OUT PSIZE_T NumberOfBytesRead ) /*++ Routine Description: This function copies the specified address range from the specified process into the specified address range of the current process. Arguments: FromProcess - Supplies an open handle to a process object. FromAddress - Supplies the base address in the specified process to be read. ToProcess - Supplies an open handle to a process object. ToAddress - Supplies the address of a buffer which receives the contents from the specified process address space. BufferSize - Supplies the requested number of bytes to read from the specified process. PreviousMode - Supplies the previous processor mode. NumberOfBytesRead - Receives the actual number of bytes transferred into the specified buffer. Return Value: NTSTATUS. --*/ { KAPC_STATE ApcState; SIZE_T AmountToMove; ULONG_PTR BadVa; LOGICAL Moving; LOGICAL Probing; LOGICAL LockedMdlPages; CONST VOID *InVa; SIZE_T LeftToMove; PSIZE_T MappedAddress; SIZE_T MaximumMoved; PMDL Mdl; PFN_NUMBER MdlHack[(sizeof(MDL)/sizeof(PFN_NUMBER)) + (MAX_LOCK_SIZE >> PAGE_SHIFT) + 1]; PVOID OutVa; LOGICAL MappingFailed; LOGICAL ExceptionAddressConfirmed; PAGED_CODE(); MappingFailed = FALSE; InVa = FromAddress; OutVa = ToAddress; MaximumMoved = MAX_LOCK_SIZE; if (BufferSize <= MAX_LOCK_SIZE) { MaximumMoved = BufferSize; } Mdl = (PMDL)&MdlHack[0]; // // Map the data into the system part of the address space, then copy it. // LeftToMove = BufferSize; AmountToMove = MaximumMoved; Probing = FALSE; // // Initializing BadVa & ExceptionAddressConfirmed is not needed for // correctness but without it the compiler cannot compile this code // W4 to check for use of uninitialized variables. // BadVa = 0; ExceptionAddressConfirmed = FALSE; while (LeftToMove > 0) { if (LeftToMove < AmountToMove) { // // Set to move the remaining bytes. // AmountToMove = LeftToMove; } KeStackAttachProcess (&FromProcess->Pcb, &ApcState); MappedAddress = NULL; LockedMdlPages = FALSE; Moving = FALSE; ASSERT (Probing == FALSE); // // We may be touching a user's memory which could be invalid, // declare an exception handler. // try { // // Probe to make sure that the specified buffer is accessible in // the target process. // if ((InVa == FromAddress) && (PreviousMode != KernelMode)){ Probing = TRUE; ProbeForRead (FromAddress, BufferSize, sizeof(CHAR)); Probing = FALSE; } // // Initialize MDL for request. // MmInitializeMdl (Mdl, (PVOID)InVa, AmountToMove);//初始化MDL MmProbeAndLockPages (Mdl, PreviousMode, IoReadAccess); //锁定物理页 LockedMdlPages = TRUE; //将MDL中存储的物理地址映射到虚拟地址 MappedAddress = MmMapLockedPagesSpecifyCache (Mdl, KernelMode, MmCached, NULL, FALSE, HighPagePriority); if (MappedAddress == NULL) { MappingFailed = TRUE; ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES); } // // Deattach from the FromProcess and attach to the ToProcess. // KeUnstackDetachProcess (&ApcState); KeStackAttachProcess (&ToProcess->Pcb, &ApcState); // // Now operating in the context of the ToProcess. // if ((InVa == FromAddress) && (PreviousMode != KernelMode)){ Probing = TRUE; ProbeForWrite (ToAddress, BufferSize, sizeof(CHAR)); Probing = FALSE; } Moving = TRUE; RtlCopyMemory (OutVa, MappedAddress, AmountToMove); } except (MiGetExceptionInfo (GetExceptionInformation(), &ExceptionAddressConfirmed, &BadVa)) { // // If an exception occurs during the move operation or probe, // return the exception code as the status value. // KeUnstackDetachProcess (&ApcState); if (MappedAddress != NULL) { MmUnmapLockedPages (MappedAddress, Mdl); } if (LockedMdlPages == TRUE) { MmUnlockPages (Mdl); } if (GetExceptionCode() == STATUS_WORKING_SET_QUOTA) { return STATUS_WORKING_SET_QUOTA; } if ((Probing == TRUE) || (MappingFailed == TRUE)) { return GetExceptionCode(); } // // If the failure occurred during the move operation, determine // which move failed, and calculate the number of bytes // actually moved. // *NumberOfBytesRead = BufferSize - LeftToMove; if (Moving == TRUE) { if (ExceptionAddressConfirmed == TRUE) { *NumberOfBytesRead = (SIZE_T)((ULONG_PTR)BadVa - (ULONG_PTR)FromAddress); } } return STATUS_PARTIAL_COPY; } KeUnstackDetachProcess (&ApcState); MmUnmapLockedPages (MappedAddress, Mdl); MmUnlockPages (Mdl); LeftToMove -= AmountToMove; InVa = (PVOID)((ULONG_PTR)InVa + AmountToMove); OutVa = (PVOID)((ULONG_PTR)OutVa + AmountToMove); } // // Set number of bytes moved. // *NumberOfBytesRead = BufferSize; return STATUS_SUCCESS; }
阅读MiDoMappedCopy函数可以发现是利用MDL把源进程的FromAddr对应物理内存映射到系统空间来,然后靠挂到目标进程空间再写入,
NTSTATUS MiDoPoolCopy ( IN PEPROCESS FromProcess, IN CONST VOID *FromAddress, IN PEPROCESS ToProcess, OUT PVOID ToAddress, IN SIZE_T BufferSize, IN KPROCESSOR_MODE PreviousMode, OUT PSIZE_T NumberOfBytesRead ) /*++ Routine Description: This function copies the specified address range from the specified process into the specified address range of the current process. Arguments: ProcessHandle - Supplies an open handle to a process object. BaseAddress - Supplies the base address in the specified process to be read. Buffer - Supplies the address of a buffer which receives the contents from the specified process address space. BufferSize - Supplies the requested number of bytes to read from the specified process. PreviousMode - Supplies the previous processor mode. NumberOfBytesRead - Receives the actual number of bytes transferred into the specified buffer. Return Value: NTSTATUS. --*/ { KAPC_STATE ApcState; SIZE_T AmountToMove; LOGICAL ExceptionAddressConfirmed; ULONG_PTR BadVa; PEPROCESS CurrentProcess; LOGICAL Moving; LOGICAL Probing; CONST VOID *InVa; SIZE_T LeftToMove; SIZE_T MaximumMoved; PVOID OutVa; PVOID PoolArea; LONGLONG StackArray[COPY_STACK_SIZE]; ULONG FreePool; PAGED_CODE(); ASSERT (BufferSize != 0); // // Get the address of the current process object and initialize copy // parameters. // CurrentProcess = PsGetCurrentProcess(); InVa = FromAddress; OutVa = ToAddress; // // Allocate non-paged memory to copy in and out of. // MaximumMoved = MAX_MOVE_SIZE; if (BufferSize <= MAX_MOVE_SIZE) { MaximumMoved = BufferSize; } FreePool = FALSE; if (BufferSize <= sizeof(StackArray)) { PoolArea = (PVOID)&StackArray[0]; } else { do { PoolArea = ExAllocatePoolWithTag (NonPagedPool, MaximumMoved, 'wRmM'); if (PoolArea != NULL) { FreePool = TRUE; break; } MaximumMoved = MaximumMoved >> 1; if (MaximumMoved <= sizeof(StackArray)) { PoolArea = (PVOID)&StackArray[0]; break; } } while (TRUE); } // // Initializing BadVa & ExceptionAddressConfirmed is not needed for // correctness but without it the compiler cannot compile this code // W4 to check for use of uninitialized variables. // BadVa = 0; ExceptionAddressConfirmed = FALSE; // // Copy the data into pool, then copy back into the ToProcess. // LeftToMove = BufferSize; AmountToMove = MaximumMoved; Probing = FALSE; while (LeftToMove > 0) { if (LeftToMove < AmountToMove) { // // Set to move the remaining bytes. // AmountToMove = LeftToMove; } KeStackAttachProcess (&FromProcess->Pcb, &ApcState); Moving = FALSE; ASSERT (Probing == FALSE); // // We may be touching a user's memory which could be invalid, // declare an exception handler. // try { // // Probe to make sure that the specified buffer is accessible in // the target process. // if ((InVa == FromAddress) && (PreviousMode != KernelMode)){ Probing = TRUE; ProbeForRead (FromAddress, BufferSize, sizeof(CHAR)); Probing = FALSE; } RtlCopyMemory (PoolArea, InVa, AmountToMove); KeUnstackDetachProcess (&ApcState); KeStackAttachProcess (&ToProcess->Pcb, &ApcState); // // Now operating in the context of the ToProcess. // if ((InVa == FromAddress) && (PreviousMode != KernelMode)){ Probing = TRUE; ProbeForWrite (ToAddress, BufferSize, sizeof(CHAR)); Probing = FALSE; } Moving = TRUE; RtlCopyMemory (OutVa, PoolArea, AmountToMove); } except (MiGetExceptionInfo (GetExceptionInformation(), &ExceptionAddressConfirmed, &BadVa)) { // // If an exception occurs during the move operation or probe, // return the exception code as the status value. // KeUnstackDetachProcess (&ApcState); if (FreePool) { ExFreePool (PoolArea); } if (Probing == TRUE) { return GetExceptionCode(); } // // If the failure occurred during the move operation, determine // which move failed, and calculate the number of bytes // actually moved. // *NumberOfBytesRead = BufferSize - LeftToMove; if (Moving == TRUE) { // // The failure occurred writing the data. // if (ExceptionAddressConfirmed == TRUE) { *NumberOfBytesRead = (SIZE_T)((ULONG_PTR)(BadVa - (ULONG_PTR)FromAddress)); } } return STATUS_PARTIAL_COPY; } KeUnstackDetachProcess (&ApcState); LeftToMove -= AmountToMove; InVa = (PVOID)((ULONG_PTR)InVa + AmountToMove); OutVa = (PVOID)((ULONG_PTR)OutVa + AmountToMove); } if (FreePool) { ExFreePool (PoolArea); } // // Set number of bytes moved. // *NumberOfBytesRead = BufferSize; return STATUS_SUCCESS; }
很明显MiDoPoolCopy是在ring0申请一块缓冲区,然后靠挂到源进程的空间,然后把要写入的内容拷贝到缓冲区,在靠挂到目标进程,将缓冲区的内存再拷贝到目标地址。
这里就出现了进程“靠挂”的问题,《Windows内核情景分析》上介绍的进程靠挂:在Windows的内核中,一个线程可以暂时 “挂靠(Attach)”到另一个进程的地址空间。比方说,线程T本来是属于进程A的,当这个线程在内核中运行时,如果其活动与用户空间有关(APC就是与用户空间有关),那么当时的用户空间应该就是进程A的用户空间。但是Windows内核允许一些跨进程的操作(例如将ntdll.dll的映像装入新创进程B的用户空间并对其进行操作),所以有时候需要把当时的用户空间切换到别的进程(例如B) 的用户空间,这就称为“挂靠(Attach)”。
简言之就是内核线程可以访问任意进程的应用层空间,而内核线程可以访问的进程空间是由什么决定呢?要知道一个内核线程怎么知道它应该访问哪个应用层的进程空间和映射的物理内存,就是线程“属于”哪个进程的问题,在应用层创建的线程属于创建这个线程的进程,当然,也可以利用远程线程为其他进程创建线程,那内核线程呢?“当前进程”是哪一个?
这就是根据EThread中的ApcState来判断的
kd> dt _ethread
.......
+0x040 ApcState : _KAPC_STATE
+0x170 SavedApcState : _KAPC_STATE
......
kd> dt _KAPC_STATE nt!_KAPC_STATE +0x000 ApcListHead : [2] _LIST_ENTRY +0x010 Process : Ptr32 _KPROCESS +0x014 KernelApcInProgress : UChar +0x015 KernelApcPending : UChar +0x016 UserApcPending : UChar
这里的Process就是指示的"当前进程",而一个进程的虚拟内存映射的物理页就是通过页面映射表(页目录--->页表--->页偏移)来转化的,而每当调度一个进程运行时,就将其映射表的物理地址装入CPU的控制寄存器Cr3,因为MMU是通过物理地址访问页面映射表。那按照这样的思路,如果我们把目标进程的映射表的基地址放入Cr3寄存器,那当前CPU访问的不就是目标进程的物理页,也可以达到对进程的内存的操作。
kd> dt _KPROCESS
nt!_KPROCESS
+0x000 Header : _DISPATCHER_HEADER
+0x010 ProfileListHead : _LIST_ENTRY
+0x018 DirectoryTableBase : Uint4B
DirectoryTableBase就是页目录的基址,将其中的值写入Cr3寄存器,当前内核线程访问的就是目标进程的物理内存。
也就是说,目前了解的访问目标进程的内存的方法有进程靠挂,MDL,和利用Cr3寄存器。
但是进程靠挂修改目标进程的内存还需要要考虑到一个问题,就是内存的保护属性,先尝试直接写入内存,如果触发异常,则需要调用NtProtectVirtualMemory来修改内存的保护属性为PAGE_READWRITE再进行写入,完成后再恢复。
NTSTATUS MyWriteProcessMemory(PVOID Addr,ULONG_PTR ulPid,PVOID pData,ULONG_PTR ulSize) { NTSTATUS Status = STATUS_UNSUCCESSFUL; PEPROCESS EProcess; BOOLEAN bAttach = FALSE; KAPC_STATE ApcState; PVOID Buffer = NULL; ULONG_PTR ulOldProtect = 0; PETHREAD EThread; CHAR PreMode; PVOID ulStart = Addr; ULONG_PTR ulRegionSize = 0x1000; HANDLE hProcess = NULL; if (ulPid) { Status = PsLookupProcessByProcessId((HANDLE)ulPid,&EProcess); if (NT_SUCCESS(Status)&&EProcess &&IsRealProcess(EProcess)) { ObfDereferenceObject(EProcess); Buffer = ExAllocatePool(PagedPool,ulSize); if (Buffer==NULL) { return STATUS_UNSUCCESSFUL; } memcpy(Buffer,pData,ulSize); //先将数据拷贝到ring 0内存 __try { KeStackAttachProcess(EProcess, &ApcState); bAttach = TRUE; __try { memcpy((PVOID)Addr,(PVOID)Buffer,ulSize); //尝试直接写入,如果触发异常再修改页面保护属性 if (bAttach) { KeUnstackDetachProcess(&ApcState); bAttach = FALSE; } if (Buffer!=NULL) { ExFreePool(Buffer); } return STATUS_SUCCESS; } __except(EXCEPTION_EXECUTE_HANDLER) { if (bAttach) { KeUnstackDetachProcess(&ApcState); bAttach = FALSE; } EThread = PsGetCurrentThread(); PreMode = ChangePreMode(EThread); Status = ObOpenObjectByPointer(EProcess, OBJ_KERNEL_HANDLE, NULL, GENERIC_ALL, *PsProcessType, KernelMode, &hProcess); if (!NT_SUCCESS(Status)) { RecoverPreMode(EThread, PreMode); if (Buffer!=NULL) { ExFreePool(Buffer); } return STATUS_UNSUCCESSFUL; } //触发异常,修改页面保护属性为 PAGE_READWRITE 再写入 Status = NtProtectVirtualMemory(hProcess,&ulStart, &ulRegionSize,PAGE_READWRITE,&ulOldProtect); if (!NT_SUCCESS(Status)) { ZwClose(hProcess); RecoverPreMode(EThread, PreMode); if (Buffer!=NULL) { ExFreePool(Buffer); } return STATUS_UNSUCCESSFUL; } RecoverPreMode(EThread, PreMode); __try { KeStackAttachProcess(EProcess, &ApcState); bAttach = TRUE; //写入 memcpy((PVOID)Addr,(PVOID)Buffer,ulSize); if (bAttach) { KeUnstackDetachProcess(&ApcState); bAttach = FALSE; } EThread = PsGetCurrentThread(); PreMode = ChangePreMode(EThread); //恢复原来的保护属性 Status = NtProtectVirtualMemory(hProcess,&ulStart, &ulRegionSize,ulOldProtect,NULL); if (!NT_SUCCESS(Status)) { RecoverPreMode(EThread, PreMode); ZwClose(hProcess); if (Buffer!=NULL) { ExFreePool(Buffer); } return STATUS_UNSUCCESSFUL; } RecoverPreMode(EThread, PreMode); ZwClose(hProcess); if (Buffer!=NULL) { ExFreePool(Buffer); } return STATUS_SUCCESS; } __except(EXCEPTION_EXECUTE_HANDLER) { if (bAttach) { KeUnstackDetachProcess(&ApcState); bAttach = FALSE; } if (Buffer!=NULL) { ExFreePool(Buffer); } return STATUS_UNSUCCESSFUL; } } } __except(EXCEPTION_EXECUTE_HANDLER) { if (bAttach) { KeUnstackDetachProcess(&ApcState); bAttach = FALSE; } if (Buffer!=NULL) { ExFreePool(Buffer); } return STATUS_UNSUCCESSFUL; } } } return STATUS_UNSUCCESSFUL; }
通过MDL就比较简单,而且也是比较官方的做法,
通过MDL来对目标进程的某个地址进行清零的操作。
BOOLEAN MyZeroOfMemory(PEPROCESS TargetEProcess,PVOID BaseAddress, ULONG_PTR ulLength, KPROCESSOR_MODE AccessMode) { ULONG ulLast = 0; PMDL Mdl = NULL; PVOID MappedAddress = NULL; KIRQL OldIrql; BOOLEAN bRet = FALSE; BOOLEAN bAttach = FALSE; KAPC_STATE ApcState; KeStackAttachProcess(TargetEProcess, &ApcState); //切换到目标进程空间中 bAttach = TRUE; ulLast = (ULONG_PTR)BaseAddress & 0xFFF; Mdl = IoAllocateMdl(BaseAddress ,ulLast + ulLength,FALSE,FALSE,NULL); if (Mdl) { MmBuildMdlForNonPagedPool(Mdl);//结构体 虚拟地址 Mdl->MdlFlags |= MDL_MAPPED_TO_SYSTEM_VA; __try { MappedAddress = MmMapLockedPagesSpecifyCache(Mdl,UserMode,MmCached,NULL,0,NormalPagePriority); if (MappedAddress) { OldIrql = KeRaiseIrqlToDpcLevel(); memset(MappedAddress, 0, ulLength); KeLowerIrql(OldIrql); MmUnmapLockedPages(MappedAddress,Mdl); bRet = TRUE; } } __except(1) { bRet = FALSE; } IoFreeMdl(Mdl); } if (bAttach) { KeUnstackDetachProcess(&ApcState); bAttach = FALSE; } return bRet; }
我们对于在ring0的两块内存也可以利用MDL来达到更为安全的操作,利用MDL实现的拷贝内存
NTSTATUS SafeCopyMemory(PVOID SrcAddr, PVOID DstAddr, ULONG SrcSize) { PMDL SrcMdl, DstMdl; PUCHAR SrcAddress, DstAddress; NTSTATUS Status = STATUS_UNSUCCESSFUL; ULONG_PTR r; //IoAllocateMdl() 分配一个系统空间虚拟地址空间, //并将其记录在一个“内存描述符表(MDL)”结构中备用, //到实际需要时再为之建立临时映射 SrcMdl = IoAllocateMdl(SrcAddr, SrcSize, FALSE, FALSE, NULL); if (MmIsAddressValid(SrcMdl)) { //更新MDL 中的物理页的描述 MmBuildMdlForNonPagedPool(SrcMdl); //物理页在系统空间的虚拟地址 SrcAddress = MmGetSystemAddressForMdlSafe(SrcMdl, NormalPagePriority); //系统为我们创建内存 if (MmIsAddressValid(SrcAddress)) { //为拷贝目标创建一个MDL DstMdl = IoAllocateMdl(DstAddr,SrcSize,FALSE,FALSE, NULL); if (MmIsAddressValid(DstMdl)) { __try { MmProbeAndLockPages(DstMdl, KernelMode, IoWriteAccess); DstAddress = MmGetSystemAddressForMdlSafe(DstMdl, NormalPagePriority); if (MmIsAddressValid(DstAddress)) { RtlZeroMemory(DstAddress,SrcSize); RtlCopyMemory(DstAddress, SrcAddress, SrcSize); Status = STATUS_SUCCESS; } MmUnlockPages(DstMdl); } __except(EXCEPTION_EXECUTE_HANDLER) { if (DstMdl) { MmUnlockPages(DstMdl); } if (DstMdl) { IoFreeMdl(DstMdl); } if (SrcMdl) { IoFreeMdl(SrcMdl); } return GetExceptionCode(); } IoFreeMdl(DstMdl); } } IoFreeMdl(SrcMdl); } return Status; }
下面是通过Cr3寄存器来访问
void MyReadProcessMemory(IN PEPROCESS Process, IN PVOID Address, IN ULONG_PTR Length, OUT PVOID Buffer) { ULONG_PTR pDTB=0,OldCr3=0,vAddr=0; //DTB pDTB=*((ULONG_PTR*)(ULONG_PTR)Process + DIRECTORY_TABLE_BASE); if(pDTB==0) { DbgPrint("Can not get PDT"); return; } //操作cr3寄存器 _disable(); OldCr3=__readcr3(); __writecr3(pDTB); _enable(); //读内存 if(MmIsAddressValid(Address)) { RtlCopyMemory(Buffer,Address,Length); } //恢复cr3 _disable(); __writecr3(OldCr3); _enable(); } void MyWriteProcessMemory(IN PEPROCESS Process, IN PVOID Address, IN ULONG_PTR Length, IN PVOID Buffer) { ULONG_PTR pDTB=0,OldCr3=0,vAddr=0; pDTB=*((ULONG_PTR*)(ULONG_PTR)Process + DIRECTORY_TABLE_BASE); if(pDTB==0) { return; } _disable(); OldCr3=__readcr3(); __writecr3(pDTB); _enable(); if(MmIsAddressValid(Address)) { RtlCopyMemory(Address,Buffer,Length); } _disable(); __writecr3(OldCr3); _enable(); }