在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(); }