/*++ Copyright (c) 1989 Microsoft Corporation Module Name: readwrt.c Abstract: This module contains the routines which implement the capability to read and write the virtual memory of a target process. Author: Lou Perazzoli (loup) 22-May-1989 Landy Wang (landyw) 02-June-1997 Revision History: --*/ #include "mi.h" // // The maximum amount to try to Probe and Lock is 14 pages, this // way it always fits in a 16 page allocation. // #define MAX_LOCK_SIZE ((ULONG)(14 * PAGE_SIZE)) // // The maximum to move in a single block is 64k bytes. // #define MAX_MOVE_SIZE (LONG)0x10000 // // The minimum to move is a single block is 128 bytes. // #define MINIMUM_ALLOCATION (LONG)128 // // Define the pool move threshold value. // #define POOL_MOVE_THRESHOLD 511 // // Define forward referenced procedure prototypes. // ULONG MiGetExceptionInfo ( IN PEXCEPTION_POINTERS ExceptionPointers, IN PLOGICAL ExceptionAddressConfirmed, IN PULONG_PTR BadVa ); 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 ); 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 ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE,MiGetExceptionInfo) #pragma alloc_text(PAGE,NtReadVirtualMemory) #pragma alloc_text(PAGE,NtWriteVirtualMemory) #pragma alloc_text(PAGE,MiDoMappedCopy) #pragma alloc_text(PAGE,MiDoPoolCopy) #pragma alloc_text(PAGE,MmCopyVirtualMemory) #endif #define COPY_STACK_SIZE 64 NTSTATUS NtReadVirtualMemory ( IN HANDLE ProcessHandle, IN PVOID BaseAddress, OUT PVOID Buffer, IN SIZE_T BufferSize, OUT PSIZE_T NumberOfBytesRead OPTIONAL ) /*++ 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. NumberOfBytesRead - Receives the actual number of bytes transferred into the specified buffer. 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(NumberOfBytesRead)) { try { ProbeForWriteUlong_ptr (NumberOfBytesRead); } except(EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } } } // // If the buffer size is not zero, then attempt to read data from the // specified process address space into the current process address // space. // BytesCopied = 0; Status = STATUS_SUCCESS; if (BufferSize != 0) { // // Reference the target process. // Status = ObReferenceObjectByHandle(ProcessHandle, PROCESS_VM_READ, PsProcessType, PreviousMode, (PVOID *)&Process, NULL); // // If the process was successfully referenced, then attempt to // read the specified memory either by direct mapping or copying // through nonpaged pool. // if (Status == STATUS_SUCCESS) { Status = MmCopyVirtualMemory (Process, BaseAddress, PsGetCurrentProcessByThread(CurrentThread), Buffer, BufferSize, PreviousMode, &BytesCopied); // // Dereference the target process. // ObDereferenceObject(Process); } } // // If requested, return the number of bytes read. // if (ARGUMENT_PRESENT(NumberOfBytesRead)) { try { *NumberOfBytesRead = BytesCopied; } except(EXCEPTION_EXECUTE_HANDLER) { NOTHING; } } return Status; } NTSTATUS NtWriteVirtualMemory( IN HANDLE ProcessHandle, OUT PVOID BaseAddress, IN CONST VOID *Buffer, IN SIZE_T BufferSize, OUT PSIZE_T NumberOfBytesWritten OPTIONAL ) /*++ 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; } 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. // 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. // 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; } ULONG MiGetExceptionInfo ( IN PEXCEPTION_POINTERS ExceptionPointers, IN OUT PLOGICAL ExceptionAddressConfirmed, IN OUT PULONG_PTR BadVa ) /*++ Routine Description: This routine examines a exception record and extracts the virtual address of an access violation, guard page violation, or in-page error. Arguments: ExceptionPointers - Supplies a pointer to the exception record. ExceptionAddressConfirmed - Receives TRUE if the exception address was reliably detected, FALSE if not. BadVa - Receives the virtual address which caused the access violation. Return Value: EXECUTE_EXCEPTION_HANDLER --*/ { PEXCEPTION_RECORD ExceptionRecord; PAGED_CODE(); // // If the exception code is an access violation, guard page violation, // or an in-page read error, then return the faulting address. Otherwise. // return a special address value. // *ExceptionAddressConfirmed = FALSE; ExceptionRecord = ExceptionPointers->ExceptionRecord; if ((ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) || (ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) || (ExceptionRecord->ExceptionCode == STATUS_IN_PAGE_ERROR)) { // // The virtual address which caused the exception is the 2nd // parameter in the exception information array. // // The number of parameters will be zero if an exception handler // above us (like the one in MmProbeAndLockPages) caught the // original exception and subsequently just raised status. // This means the number of bytes copied is zero. // if (ExceptionRecord->NumberParameters > 1) { *ExceptionAddressConfirmed = TRUE; *BadVa = ExceptionRecord->ExceptionInformation[1]; } } return EXCEPTION_EXECUTE_HANDLER; } 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; #if 0 // // It is unfortunate that Windows 2000 and all the releases of NT always // inadvertently returned from this routine detached, as we must maintain // this behavior even now. // KeDetachProcess(); #endif 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); MmProbeAndLockPages (Mdl, PreviousMode, IoReadAccess); LockedMdlPages = TRUE; 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; } 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; #if 0 // // It is unfortunate that Windows 2000 and all the releases of NT always // inadvertently returned from this routine detached, as we must maintain // this behavior even now. // KeDetachProcess(); #endif 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; }