450 lines
13 KiB
C
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;
|
|
}
|