/* * 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 . */ #include 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; }