Windows2000/private/ntos/mm/readwrt.c
2020-09-30 17:12:32 +02:00

654 lines
22 KiB
C

/*++
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
--*/
#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))
#define MAX_MOVE_SIZE (LONG)0x10000// The maximum to move in a single block is 64k bytes.
#define MINIMUM_ALLOCATION (LONG)128// The minimum to move is a single block is 128 bytes.
#define POOL_MOVE_THRESHOLD 511// Define the pool move threshold value.
// Define forward referenced procedure prototypes.
NTSTATUS MiValidateUserTransfer(IN PVOID BaseAddress, IN PVOID Buffer, IN ULONG BufferSize);
ULONG MiGetExceptionInfo(IN PEXCEPTION_POINTERS ExceptionPointers, IN PULONG_PTR BadVa1, IN PULONG_PTR BadVa2);
NTSTATUS MiDoMappedCopy(IN PEPROCESS FromProcess,
IN PVOID FromAddress,
IN PEPROCESS ToProcess,
OUT PVOID ToAddress,
IN ULONG BufferSize,
IN KPROCESSOR_MODE PreviousMode,
OUT PULONG NumberOfBytesRead);
NTSTATUS MiDoPoolCopy(IN PEPROCESS FromProcess,
IN PVOID FromAddress,
IN PEPROCESS ToProcess,
OUT PVOID ToAddress,
IN ULONG BufferSize,
IN KPROCESSOR_MODE PreviousMode,
OUT PULONG 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,MiValidateUserTransfer)
#endif
#define COPY_STACK_SIZE 64
NTSTATUS NtReadVirtualMemory(IN HANDLE ProcessHandle, IN PVOID BaseAddress, OUT PVOID Buffer, IN ULONG BufferSize, OUT PULONG 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:
TBS
--*/
{
ULONG BytesCopied;
KPROCESSOR_MODE PreviousMode;
PEPROCESS Process;
NTSTATUS Status;
PAGED_CODE();
// Get the previous mode and probe output argument if necessary.
PreviousMode = KeGetPreviousMode();
if (PreviousMode != KernelMode) {
Status = MiValidateUserTransfer(BaseAddress, Buffer, BufferSize);
if (Status != STATUS_SUCCESS) {
return Status;
}
if (ARGUMENT_PRESENT(NumberOfBytesRead)) {
try {
ProbeForWriteUlong(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, PsGetCurrentProcess(), Buffer, BufferSize, PreviousMode, &BytesCopied);
ObDereferenceObject(Process);// Dereference the target 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 PVOID Buffer,
IN ULONG BufferSize,
OUT PULONG 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:
TBS
--*/
{
ULONG BytesCopied;
KPROCESSOR_MODE PreviousMode;
PEPROCESS Process;
NTSTATUS Status;
PAGED_CODE();
// Get the previous mode and probe output argument if necessary.
PreviousMode = KeGetPreviousMode();
if (PreviousMode != KernelMode) {
Status = MiValidateUserTransfer(BaseAddress, Buffer, BufferSize);
if (Status != STATUS_SUCCESS) {
return Status;
}
if (ARGUMENT_PRESENT(NumberOfBytesWritten)) {
try {
ProbeForWriteUlong(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(PsGetCurrentProcess(), Buffer, Process, BaseAddress, BufferSize, PreviousMode, &BytesCopied);
ObDereferenceObject(Process);// Dereference the target 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 PVOID FromAddress,
IN PEPROCESS ToProcess,
OUT PVOID ToAddress,
IN ULONG BufferSize,
IN KPROCESSOR_MODE PreviousMode,
OUT PULONG NumberOfBytesCopied
)
{
NTSTATUS Status;
KIRQL OldIrql;
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.
MiLockSystemSpace(OldIrql);
if (ProcessToLock->AddressSpaceDeleted != 0) {
MiUnlockSystemSpace(OldIrql);
return STATUS_PROCESS_IS_TERMINATING;
}
ProcessToLock->VmOperation += 1;
MiUnlockSystemSpace(OldIrql);
// 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.
MiLockSystemSpace(OldIrql);
ProcessToLock->VmOperation -= 1;
if ((ProcessToLock->VmOperation == 0) &&
(ProcessToLock->VmOperationEvent != NULL)) {
KeSetEvent(ProcessToLock->VmOperationEvent, 0, FALSE);
}
MiUnlockSystemSpace(OldIrql);
return Status;
}
ULONG MiGetExceptionInfo(IN PEXCEPTION_POINTERS ExceptionPointers, IN PULONG_PTR BadVa1, IN PULONG_PTR BadVa2)
/*++
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.
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.
*BadVa2 = 0;
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.
*BadVa1 = ExceptionRecord->ExceptionInformation[1];
if (ExceptionRecord->NumberParameters == 3) {
*BadVa2 = ExceptionRecord->ExceptionInformation[2];
}
} else {// Unexpected exception - set the number of bytes copied to zero.
*BadVa2 = 0xFFFFFFFF;
}
return EXCEPTION_EXECUTE_HANDLER;
}
NTSTATUS
MiDoMappedCopy(
IN PEPROCESS FromProcess,
IN PVOID FromAddress,
IN PEPROCESS ToProcess,
OUT PVOID ToAddress,
IN ULONG BufferSize,
IN KPROCESSOR_MODE PreviousMode,
OUT PULONG 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:
TBS
--*/
{
ULONG AmountToMove;
ULONG_PTR BadVa1;
ULONG_PTR BadVa2;
PEPROCESS CurrentProcess;
LOGICAL Moving;
LOGICAL Probing;
BOOLEAN LockedMdlPages;
PVOID InVa;
ULONG LeftToMove;
PULONG MappedAddress;
ULONG MaximumMoved;
PMDL Mdl;
PFN_NUMBER MdlHack[(sizeof(MDL) / sizeof(PFN_NUMBER)) + (MAX_LOCK_SIZE >> PAGE_SHIFT) + 1];
PVOID OutVa;
LOGICAL MappingFailed;
PAGED_CODE();
MappingFailed = FALSE;
// Get the address of the current process object and initialize copy parameters.
CurrentProcess = PsGetCurrentProcess();
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;
while (LeftToMove > 0) {
if (LeftToMove < AmountToMove) {
AmountToMove = LeftToMove;// Set to move the remaining bytes.
}
KeDetachProcess();
KeAttachProcess(&FromProcess->Pcb);
// 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.
MappedAddress = NULL;
LockedMdlPages = FALSE;
if ((InVa == FromAddress) && (PreviousMode != KernelMode)) {
Probing = TRUE;
ProbeForRead(FromAddress, BufferSize, sizeof(CHAR));
}
Probing = FALSE;
MmInitializeMdl(Mdl, (PVOID)InVa, AmountToMove);// Initialize MDL for request.
Moving = TRUE;
MmProbeAndLockPages(Mdl, PreviousMode, IoReadAccess);
Moving = FALSE;
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.
KeDetachProcess();
KeAttachProcess(&ToProcess->Pcb);
// Now operating in the context of the ToProcess.
if ((InVa == FromAddress) && (PreviousMode != KernelMode)) {
Probing = TRUE;
ProbeForWrite(ToAddress, BufferSize, sizeof(CHAR));
}
Probing = FALSE;
RtlCopyMemory(OutVa, MappedAddress, AmountToMove);
} except(MiGetExceptionInfo(GetExceptionInformation(), &BadVa1, &BadVa2))
{
// If an exception occurs during the move operation or probe, return the exception code as the status value.
KeDetachProcess();
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();
} else {
// The failure occurred during the move operation, determine
// which move failed, and calculate the number of bytes actually moved.
if (Moving == TRUE) {
if (BadVa1 != 0xFFFFFFFF) {
*NumberOfBytesRead = (ULONG)((ULONG_PTR)BadVa2 - (ULONG_PTR)FromAddress);
}
} else {
*NumberOfBytesRead = BufferSize - LeftToMove;
}
}
return STATUS_PARTIAL_COPY;
}
MmUnmapLockedPages(MappedAddress, Mdl);
MmUnlockPages(Mdl);
LeftToMove -= AmountToMove;
InVa = (PVOID)((ULONG_PTR)InVa + AmountToMove);
OutVa = (PVOID)((ULONG_PTR)OutVa + AmountToMove);
}
KeDetachProcess();
*NumberOfBytesRead = BufferSize;// Set number of bytes moved.
return STATUS_SUCCESS;
}
NTSTATUS
MiDoPoolCopy(
IN PEPROCESS FromProcess,
IN PVOID FromAddress,
IN PEPROCESS ToProcess,
OUT PVOID ToAddress,
IN ULONG BufferSize,
IN KPROCESSOR_MODE PreviousMode,
OUT PULONG 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:
TBS
--*/
{
ULONG AmountToMove;
ULONG_PTR BadVa1;
ULONG_PTR BadVa2;
PEPROCESS CurrentProcess;
LOGICAL Moving;
LOGICAL Probing;
PVOID InVa;
ULONG LeftToMove;
ULONG 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 = (PULONG)&StackArray[0];
} else {
do {
PoolArea = ExAllocatePoolWithTag(NonPagedPool, MaximumMoved, 'wRmM');
if (PoolArea != NULL) {
FreePool = TRUE;
break;
}
MaximumMoved = MaximumMoved >> 1;
if (MaximumMoved <= sizeof(StackArray)) {
PoolArea = (PULONG)&StackArray[0];
break;
}
} while (TRUE);
}
// Copy the data into pool, then copy back into the ToProcess.
LeftToMove = BufferSize;
AmountToMove = MaximumMoved;
while (LeftToMove > 0) {
if (LeftToMove < AmountToMove) {
AmountToMove = LeftToMove;// Set to move the remaining bytes.
}
KeDetachProcess();
KeAttachProcess(&FromProcess->Pcb);
// 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;
Moving = TRUE;
RtlCopyMemory(PoolArea, InVa, AmountToMove);
Moving = FALSE;
KeDetachProcess();
KeAttachProcess(&ToProcess->Pcb);
// Now operating in the context of the ToProcess.
if ((InVa == FromAddress) && (PreviousMode != KernelMode)) {
Probing = TRUE;
ProbeForWrite(ToAddress, BufferSize, sizeof(CHAR));
}
Probing = FALSE;
RtlCopyMemory(OutVa, PoolArea, AmountToMove);
} except(MiGetExceptionInfo(GetExceptionInformation(), &BadVa1, &BadVa2))
{
// If an exception occurs during the move operation or probe,
// return the exception code as the status value.
KeDetachProcess();
if (FreePool) {
ExFreePool(PoolArea);
}
if (Probing == TRUE) {
return GetExceptionCode();
} else {
// The failure occurred during the move operation, determine
// which move failed, and calculate the number of bytes actually moved.
if (Moving == TRUE) {// The failure occurred getting the data.
if (BadVa1 != 0xFFFFFFFF) {
*NumberOfBytesRead = (ULONG)((ULONG_PTR)(BadVa2 - (ULONG_PTR)FromAddress));
}
} else {// The failure occurred writing the data.
*NumberOfBytesRead = BufferSize - LeftToMove;
}
}
return STATUS_PARTIAL_COPY;
}
LeftToMove -= AmountToMove;
InVa = (PVOID)((ULONG_PTR)InVa + AmountToMove);
OutVa = (PVOID)((ULONG_PTR)OutVa + AmountToMove);
}
if (FreePool) {
ExFreePool(PoolArea);
}
KeDetachProcess();
*NumberOfBytesRead = BufferSize;// Set number of bytes moved.
return STATUS_SUCCESS;
}
NTSTATUS MiValidateUserTransfer(IN PVOID BaseAddressPointer, IN PVOID BufferPointer, IN ULONG BufferSize)
/*++
Routine Description:
This function checks whether the parameter source and destination address ranges lie within the user virtual address space.
This function does NOT guarantee that the addresses are in fact mapped or that faults will not occur - it just validates the address range.
Arguments:
BaseAddress - Supplies the base address within a process to be read.
Buffer - Supplies the address of a buffer which sends or receives the contents from the specified process address space.
BufferSize - Supplies the requested number of bytes to read or write from the specified process.
Return Value:
STATUS_SUCCESS if valid user ranges, error otherwise.
Environment:
None.
--*/
{
ULONG_PTR BaseAddress;
ULONG_PTR Buffer;
BaseAddress = (ULONG_PTR)BaseAddressPointer;
Buffer = (ULONG_PTR)BufferPointer;
PAGED_CODE();
// check that both the base address and the target buffer qualify
// as valid user address ranges. Wrapping is not allowed.
if (BaseAddress + BufferSize < BaseAddress) {
return STATUS_ACCESS_VIOLATION;
}
if (((PVOID)BaseAddress > MM_HIGHEST_USER_ADDRESS) || ((PVOID)(BaseAddress + BufferSize) > MM_HIGHEST_USER_ADDRESS)) {
return STATUS_ACCESS_VIOLATION;
}
if (Buffer + BufferSize < Buffer) {
return STATUS_ACCESS_VIOLATION;
}
if (((PVOID)Buffer > MM_HIGHEST_USER_ADDRESS) || ((PVOID)(Buffer + BufferSize) > MM_HIGHEST_USER_ADDRESS)) {
return STATUS_ACCESS_VIOLATION;
}
return STATUS_SUCCESS;
}