Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

971 lines
24 KiB

/*++
Copyright (c) 1989 Microsoft Corporation
Copyright (c) 1992 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
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 foreward referenced procedure prototypes.
//
ULONG
MiGetExceptionInfo (
IN PEXCEPTION_POINTERS ExceptionPointers,
IN PULONG BadVa
);
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,
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)
#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) {
#ifdef MIPS
//
// Handle the PCR case for mips.
//
if (((ULONG)BaseAddress >= KSEG0_BASE) ||
(((ULONG)BaseAddress + BufferSize) > (ULONG)KSEG0_BASE) ||
(((ULONG)BaseAddress + BufferSize) < (ULONG)BaseAddress)) {
return STATUS_ACCESS_VIOLATION;
}
if (((ULONG)Buffer >= KSEG0_BASE) ||
(((ULONG)Buffer + BufferSize) > (ULONG)KSEG0_BASE) ||
(((ULONG)Buffer + BufferSize) < (ULONG)Buffer)) {
return STATUS_ACCESS_VIOLATION;
}
#elif defined(_PPC_)
//
// Handle the PCR case for PPC.
//
if (((ULONG)BaseAddress >= KIPCR) &&
((ULONG)BaseAddress < (KIPCR2 + PAGE_SIZE)) &&
(((ULONG)BaseAddress + BufferSize) < (KIPCR2 + PAGE_SIZE)) &&
(((ULONG)BaseAddress + BufferSize) >= (ULONG)BaseAddress)) {
;
} else if (BaseAddress > MM_HIGHEST_USER_ADDRESS) {
return STATUS_ACCESS_VIOLATION;
}
if (Buffer > MM_HIGHEST_USER_ADDRESS) {
return STATUS_ACCESS_VIOLATION;
}
#else
if ((BaseAddress > MM_HIGHEST_USER_ADDRESS) ||
(Buffer > MM_HIGHEST_USER_ADDRESS)) {
return STATUS_ACCESS_VIOLATION;
}
#endif
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);
//
// 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 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) {
if ((BaseAddress > MM_HIGHEST_USER_ADDRESS) ||
(Buffer > MM_HIGHEST_USER_ADDRESS)) {
return STATUS_ACCESS_VIOLATION;
}
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);
//
// 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 PVOID FromAddress,
IN PEPROCESS ToProcess,
OUT PVOID ToAddress,
IN ULONG BufferSize,
IN KPROCESSOR_MODE PreviousMode,
OUT PULONG NumberOfBytesCopied
)
{
NTSTATUS Status;
KIRQL OldIrql;
PEPROCESS ProcessToLock;
ProcessToLock = FromProcess;
if (FromProcess == PsGetCurrentProcess()) {
ProcessToLock = ToProcess;
}
//
// Make sure the process still has an address space.
//
ExAcquireSpinLock (&MmSystemSpaceLock, &OldIrql);
if (ProcessToLock->AddressSpaceDeleted != 0) {
ExReleaseSpinLock ( &MmSystemSpaceLock, OldIrql );
return STATUS_PROCESS_IS_TERMINATING;
}
ProcessToLock->VmOperation += 1;
ExReleaseSpinLock ( &MmSystemSpaceLock, 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,
NumberOfBytesCopied);
//
// Dereference the target process.
//
CompleteService:
//
// Indicate that the vm operation is complete.
//
ExAcquireSpinLock (&MmSystemSpaceLock, &OldIrql);
ProcessToLock->VmOperation -= 1;
if ((ProcessToLock->VmOperation == 0) &&
(ProcessToLock->VmOperationEvent != NULL)) {
KeSetEvent (ProcessToLock->VmOperationEvent, 0, FALSE);
}
ExReleaseSpinLock ( &MmSystemSpaceLock, OldIrql );
return Status;
}
ULONG
MiGetExceptionInfo (
IN PEXCEPTION_POINTERS ExceptionPointers,
IN PULONG 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.
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.
//
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.
//
*BadVa = ExceptionRecord->ExceptionInformation[1];
} else {
//
// Unexpected exception - set the number of bytes copied to zero.
//
*BadVa = 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 BadVa;
PEPROCESS CurrentProcess;
BOOLEAN FailedMove;
BOOLEAN FailedProbe;
PULONG InVa;
ULONG LeftToMove;
PULONG MappedAddress;
ULONG MaximumMoved;
PMDL Mdl;
ULONG MdlHack[(sizeof(MDL)/4) + (MAX_LOCK_SIZE >> PAGE_SHIFT) + 1];
PULONG OutVa;
PAGED_CODE();
//
// 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) {
//
// Set to move the remaining bytes.
//
AmountToMove = LeftToMove;
}
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 accessable in
// the target process.
//
MappedAddress = NULL;
if (((PVOID)InVa == FromAddress) &&
((PVOID)InVa <= MM_HIGHEST_USER_ADDRESS)) {
FailedProbe = TRUE;
ProbeForRead (FromAddress, BufferSize, sizeof(CHAR));
}
//
// Initialize MDL for request.
//
MmInitializeMdl(Mdl,
InVa,
AmountToMove);
FailedMove = TRUE;
MmProbeAndLockPages (Mdl, PreviousMode, IoReadAccess);
FailedMove = FALSE;
MappedAddress = MmMapLockedPages (Mdl, KernelMode);
//
// Deattach from the FromProcess and attach to the ToProcess.
//
KeDetachProcess();
KeAttachProcess (&ToProcess->Pcb);
//
// Now operating in the context of the ToProcess.
//
if (((PVOID)InVa == FromAddress)
&& (ToAddress <= MM_HIGHEST_USER_ADDRESS)) {
ProbeForWrite (ToAddress, BufferSize, sizeof(CHAR));
FailedProbe = FALSE;
}
RtlCopyMemory (OutVa, MappedAddress, AmountToMove);
} except (MiGetExceptionInfo (GetExceptionInformation(), &BadVa)) {
//
// 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);
MmUnlockPages (Mdl);
}
if (GetExceptionCode() == STATUS_WORKING_SET_QUOTA) {
return STATUS_WORKING_SET_QUOTA;
}
if (FailedProbe) {
return GetExceptionCode();
} else {
//
// The failure occurred during the move operation, determine
// which move failed, and calculate the number of bytes
// actually moved.
//
if (FailedMove) {
if (BadVa != 0xFFFFFFFF) {
*NumberOfBytesRead = BadVa - (ULONG)FromAddress;
}
} else {
*NumberOfBytesRead = BadVa - (ULONG)ToAddress;
}
}
return STATUS_PARTIAL_COPY;
}
MmUnmapLockedPages (MappedAddress, Mdl);
MmUnlockPages (Mdl);
LeftToMove -= AmountToMove;
InVa = (PVOID)((ULONG)InVa + AmountToMove);
OutVa = (PVOID)((ULONG)OutVa + AmountToMove);
}
KeDetachProcess();
//
// Set number of bytes moved.
//
*NumberOfBytesRead = BufferSize;
return STATUS_SUCCESS;
}
NTSTATUS
MiDoPoolCopy (
IN PEPROCESS FromProcess,
IN PVOID FromAddress,
IN PEPROCESS ToProcess,
OUT PVOID ToAddress,
IN ULONG BufferSize,
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.
NumberOfBytesRead - Receives the actual number of bytes
transferred into the specified buffer.
Return Value:
TBS
--*/
{
ULONG AmountToMove;
ULONG BadVa;
PEPROCESS CurrentProcess;
BOOLEAN FailedMove;
BOOLEAN FailedProbe;
PULONG InVa;
ULONG LeftToMove;
ULONG MaximumMoved;
PULONG OutVa;
PULONG PoolArea;
LONGLONG StackArray[COPY_STACK_SIZE];
ULONG FreePool;
PAGED_CODE();
//
// 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;
}
if (BufferSize <= (COPY_STACK_SIZE * sizeof(LONGLONG))) {
PoolArea = (PULONG)&StackArray[0];
FreePool = FALSE;
} else {
PoolArea = ExAllocatePoolWithTag (NonPagedPool, MaximumMoved, 'wRmM');
while (PoolArea == NULL) {
if (MaximumMoved <= MINIMUM_ALLOCATION) {
PoolArea = ExAllocatePoolWithTag (NonPagedPoolMustSucceed,
MaximumMoved, 'wRmM');
} else {
MaximumMoved = MaximumMoved >> 1;
PoolArea = ExAllocatePoolWithTag (NonPagedPool, MaximumMoved, 'wRmM');
}
}
FreePool = TRUE;
}
//
// Copy the data into pool, then copy back into the ToProcess.
//
LeftToMove = BufferSize;
AmountToMove = MaximumMoved;
while (LeftToMove > 0) {
if (LeftToMove < AmountToMove) {
//
// Set to move the remaining bytes.
//
AmountToMove = LeftToMove;
}
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 accessable in
// the target process.
//
if (((PVOID)InVa == FromAddress) &&
((PVOID)InVa <= MM_HIGHEST_USER_ADDRESS)) {
FailedProbe = TRUE;
ProbeForRead (FromAddress, BufferSize, sizeof(CHAR));
}
FailedMove = TRUE;
RtlCopyMemory (PoolArea, InVa, AmountToMove);
FailedMove = FALSE;
KeDetachProcess();
KeAttachProcess (&ToProcess->Pcb);
//
// Now operating in the context of the ToProcess.
//
if (((PVOID)InVa == FromAddress)
&& (ToAddress <= MM_HIGHEST_USER_ADDRESS)) {
ProbeForWrite (ToAddress, BufferSize, sizeof(CHAR));
FailedProbe = FALSE;
}
RtlCopyMemory (OutVa, PoolArea, AmountToMove);
} except (MiGetExceptionInfo (GetExceptionInformation(), &BadVa)) {
//
// If an exception occurs during the move operation or probe,
// return the exception code as the status value.
//
KeDetachProcess();
if (FreePool) {
ExFreePool (PoolArea);
}
if (FailedProbe) {
return GetExceptionCode();
} else {
//
// The failure occurred during the move operation, determine
// which move failed, and calculate the number of bytes
// actually moved.
//
if (FailedMove) {
//
// The failure occurred getting the data.
//
if (BadVa != 0xFFFFFFFF) {
*NumberOfBytesRead = BadVa - (ULONG)FromAddress;
}
} else {
//
// The failure occurred writing the data.
//
*NumberOfBytesRead = BadVa - (ULONG)ToAddress;
}
}
return STATUS_PARTIAL_COPY;
}
LeftToMove -= AmountToMove;
InVa = (PVOID)((ULONG)InVa + AmountToMove);
OutVa = (PVOID)((ULONG)OutVa + AmountToMove);
}
if (FreePool) {
ExFreePool (PoolArea);
}
KeDetachProcess();
//
// Set number of bytes moved.
//
*NumberOfBytesRead = BufferSize;
return STATUS_SUCCESS;
}