2025-05-13 19:45:22 +03:00

450 lines
13 KiB
C

/*
* KProcessHacker
*
* Copyright (C) 2010-2016 wj32
*
* This file is part of Process Hacker.
*
* Process Hacker is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Process Hacker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
*/
#include <kph.h>
ULONG KphpGetCopyExceptionInfo(
__in PEXCEPTION_POINTERS ExceptionInfo,
__out PBOOLEAN HaveBadAddress,
__out PULONG_PTR BadAddress
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, KphCopyVirtualMemory)
#pragma alloc_text(PAGE, KpiReadVirtualMemoryUnsafe)
#endif
#define KPH_STACK_COPY_BYTES 0x200
#define KPH_POOL_COPY_BYTES 0x10000
#define KPH_MAPPED_COPY_PAGES 14
#define KPH_POOL_COPY_THRESHOLD 0x3ff
ULONG KphpGetCopyExceptionInfo(
__in PEXCEPTION_POINTERS ExceptionInfo,
__out PBOOLEAN HaveBadAddress,
__out PULONG_PTR BadAddress
)
{
PEXCEPTION_RECORD exceptionRecord;
*HaveBadAddress = FALSE;
exceptionRecord = ExceptionInfo->ExceptionRecord;
if ((exceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) ||
(exceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) ||
(exceptionRecord->ExceptionCode == STATUS_IN_PAGE_ERROR))
{
if (exceptionRecord->NumberParameters > 1)
{
/* We have the address. */
*HaveBadAddress = TRUE;
*BadAddress = exceptionRecord->ExceptionInformation[1];
}
}
return EXCEPTION_EXECUTE_HANDLER;
}
/**
* Copies memory from one process to another.
*
* \param FromProcess The source process.
* \param FromAddress The source address.
* \param ToProcess The target process.
* \param ToAddress The target address.
* \param BufferLength The number of bytes to copy.
* \param AccessMode The mode in which to perform access checks.
* \param ReturnLength A variable which receives the number of bytes copied.
*/
NTSTATUS KphCopyVirtualMemory(
__in PEPROCESS FromProcess,
__in PVOID FromAddress,
__in PEPROCESS ToProcess,
__in PVOID ToAddress,
__in SIZE_T BufferLength,
__in KPROCESSOR_MODE AccessMode,
__out PSIZE_T ReturnLength
)
{
UCHAR stackBuffer[KPH_STACK_COPY_BYTES];
PVOID buffer;
PFN_NUMBER mdlBuffer[(sizeof(MDL) / sizeof(PFN_NUMBER)) + KPH_MAPPED_COPY_PAGES + 1];
PMDL mdl = (PMDL)mdlBuffer;
PVOID mappedAddress;
SIZE_T mappedTotalSize;
SIZE_T blockSize;
SIZE_T stillToCopy;
KAPC_STATE apcState;
PVOID sourceAddress;
PVOID targetAddress;
BOOLEAN doMappedCopy;
BOOLEAN pagesLocked;
BOOLEAN copyingToTarget = FALSE;
BOOLEAN probing = FALSE;
BOOLEAN mapping = FALSE;
BOOLEAN haveBadAddress;
ULONG_PTR badAddress;
PAGED_CODE();
sourceAddress = FromAddress;
targetAddress = ToAddress;
// We don't check if buffer == NULL when freeing. If buffer doesn't need to be freed, set to
// stackBuffer, not NULL.
buffer = stackBuffer;
mappedTotalSize = (KPH_MAPPED_COPY_PAGES - 2) * PAGE_SIZE;
if (mappedTotalSize > BufferLength)
mappedTotalSize = BufferLength;
stillToCopy = BufferLength;
blockSize = mappedTotalSize;
while (stillToCopy)
{
// If we're at the last copy block, copy the remaining bytes instead of the whole block
// size.
if (blockSize > stillToCopy)
blockSize = stillToCopy;
// Choose the best method based on the number of bytes left to copy.
if (blockSize > KPH_POOL_COPY_THRESHOLD)
{
doMappedCopy = TRUE;
}
else
{
doMappedCopy = FALSE;
if (blockSize <= KPH_STACK_COPY_BYTES)
{
if (buffer != stackBuffer)
ExFreePoolWithTag(buffer, 'ChpK');
buffer = stackBuffer;
}
else
{
// Don't allocate the buffer if we've done so already. Note that the block size
// never increases, so this allocation will always be OK.
if (buffer == stackBuffer)
{
// Keep trying to allocate a buffer.
while (TRUE)
{
buffer = ExAllocatePoolWithTag(NonPagedPool, blockSize, 'ChpK');
// Stop trying if we got a buffer.
if (buffer)
break;
blockSize /= 2;
// Use the stack buffer if we can.
if (blockSize <= KPH_STACK_COPY_BYTES)
{
buffer = stackBuffer;
break;
}
}
}
}
}
// Reset state.
mappedAddress = NULL;
pagesLocked = FALSE;
copyingToTarget = FALSE;
KeStackAttachProcess(FromProcess, &apcState);
__try
{
// Probe only if this is the first time.
if (sourceAddress == FromAddress && AccessMode != KernelMode)
{
probing = TRUE;
ProbeForRead(sourceAddress, BufferLength, sizeof(UCHAR));
probing = FALSE;
}
if (doMappedCopy)
{
// Initialize the MDL.
MmInitializeMdl(mdl, sourceAddress, blockSize);
MmProbeAndLockPages(mdl, AccessMode, IoReadAccess);
pagesLocked = TRUE;
// Map the pages.
mappedAddress = MmMapLockedPagesSpecifyCache(
mdl,
KernelMode,
MmCached,
NULL,
FALSE,
HighPagePriority
);
if (!mappedAddress)
{
// Insufficient resources; exit.
mapping = TRUE;
ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
}
}
else
{
memcpy(buffer, sourceAddress, blockSize);
}
KeUnstackDetachProcess(&apcState);
// Attach to the target process and copy the contents out.
KeStackAttachProcess(ToProcess, &apcState);
// Probe only if this is the first time.
if (targetAddress == ToAddress && AccessMode != KernelMode)
{
probing = TRUE;
ProbeForWrite(targetAddress, BufferLength, sizeof(UCHAR));
probing = FALSE;
}
// Copy the data.
copyingToTarget = TRUE;
if (doMappedCopy)
memcpy(targetAddress, mappedAddress, blockSize);
else
memcpy(targetAddress, buffer, blockSize);
}
__except (KphpGetCopyExceptionInfo(
GetExceptionInformation(),
&haveBadAddress,
&badAddress
))
{
KeUnstackDetachProcess(&apcState);
// If we mapped the pages, unmap them.
if (mappedAddress)
MmUnmapLockedPages(mappedAddress, mdl);
// If we locked the pages, unlock them.
if (pagesLocked)
MmUnlockPages(mdl);
// If we allocated pool storage, free it.
if (buffer != stackBuffer)
ExFreePoolWithTag(buffer, 'ChpK');
// If we failed when probing or mapping, return the error status.
if (probing || mapping)
return GetExceptionCode();
// Determine which copy failed.
if (copyingToTarget && haveBadAddress)
{
*ReturnLength = (ULONG)(badAddress - (ULONG_PTR)sourceAddress);
}
else
{
*ReturnLength = BufferLength - stillToCopy;
}
return STATUS_PARTIAL_COPY;
}
KeUnstackDetachProcess(&apcState);
if (doMappedCopy)
{
MmUnmapLockedPages(mappedAddress, mdl);
MmUnlockPages(mdl);
}
stillToCopy -= blockSize;
sourceAddress = (PVOID)((ULONG_PTR)sourceAddress + blockSize);
targetAddress = (PVOID)((ULONG_PTR)targetAddress + blockSize);
}
if (buffer != stackBuffer)
ExFreePoolWithTag(buffer, 'ChpK');
*ReturnLength = BufferLength;
return STATUS_SUCCESS;
}
/**
* Copies process or kernel memory into the current process.
*
* \param ProcessHandle A handle to a process. The handle must have PROCESS_VM_READ access. This
* parameter may be NULL if \a BaseAddress lies above the user-mode range.
* \param BaseAddress The address from which memory is to be copied.
* \param Buffer A buffer which receives the copied memory.
* \param BufferSize The number of bytes to copy.
* \param NumberOfBytesRead A variable which receives the number of bytes copied to the buffer.
* \param Key An access key. If no valid L2 key is provided, the function fails.
* \param Client The client that initiated the request.
* \param AccessMode The mode in which to perform access checks.
*/
NTSTATUS KpiReadVirtualMemoryUnsafe(
__in_opt HANDLE ProcessHandle,
__in PVOID BaseAddress,
__out_bcount(BufferSize) PVOID Buffer,
__in SIZE_T BufferSize,
__out_opt PSIZE_T NumberOfBytesRead,
__in_opt KPH_KEY Key,
__in PKPH_CLIENT Client,
__in KPROCESSOR_MODE AccessMode
)
{
NTSTATUS status;
PEPROCESS process;
SIZE_T numberOfBytesRead;
PAGED_CODE();
if (!NT_SUCCESS(status = KphValidateKey(KphKeyLevel2, Key, Client, AccessMode)))
return status;
if (AccessMode != KernelMode)
{
if (
(ULONG_PTR)BaseAddress + BufferSize < (ULONG_PTR)BaseAddress ||
(ULONG_PTR)Buffer + BufferSize < (ULONG_PTR)Buffer ||
(ULONG_PTR)Buffer + BufferSize > (ULONG_PTR)MmHighestUserAddress
)
{
return STATUS_ACCESS_VIOLATION;
}
if (NumberOfBytesRead)
{
__try
{
ProbeForWrite(NumberOfBytesRead, sizeof(SIZE_T), sizeof(SIZE_T));
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return GetExceptionCode();
}
}
}
if (BufferSize != 0)
{
// Select the appropriate copy method.
if ((ULONG_PTR)BaseAddress + BufferSize > (ULONG_PTR)MmHighestUserAddress)
{
ULONG_PTR page;
ULONG_PTR pageEnd;
status = KphValidateAddressForSystemModules(BaseAddress, BufferSize);
if (!NT_SUCCESS(status))
return status;
// Kernel memory copy (unsafe)
page = (ULONG_PTR)BaseAddress & ~(PAGE_SIZE - 1);
pageEnd = ((ULONG_PTR)BaseAddress + BufferSize - 1) & ~(PAGE_SIZE - 1);
__try
{
// This will obviously fail if any of the pages aren't resident.
for (; page <= pageEnd; page += PAGE_SIZE)
{
if (!MmIsAddressValid((PVOID)page))
ExRaiseStatus(STATUS_ACCESS_VIOLATION);
}
memcpy(Buffer, BaseAddress, BufferSize);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return GetExceptionCode();
}
numberOfBytesRead = BufferSize;
status = STATUS_SUCCESS;
}
else
{
// User memory copy (safe)
status = ObReferenceObjectByHandle(
ProcessHandle,
PROCESS_VM_READ,
*PsProcessType,
AccessMode,
&process,
NULL
);
if (NT_SUCCESS(status))
{
status = KphCopyVirtualMemory(
process,
BaseAddress,
PsGetCurrentProcess(),
Buffer,
BufferSize,
AccessMode,
&numberOfBytesRead
);
ObDereferenceObject(process);
}
}
}
else
{
numberOfBytesRead = 0;
status = STATUS_SUCCESS;
}
if (NumberOfBytesRead)
{
if (AccessMode != KernelMode)
{
__try
{
*NumberOfBytesRead = numberOfBytesRead;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
// Don't mess with the status.
NOTHING;
}
}
else
{
*NumberOfBytesRead = numberOfBytesRead;
}
}
return status;
}