/* * 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 #include typedef struct _CAPTURE_BACKTRACE_THREAD_CONTEXT { BOOLEAN Local; KAPC Apc; KEVENT CompletedEvent; ULONG FramesToSkip; ULONG FramesToCapture; PVOID *BackTrace; ULONG CapturedFrames; ULONG BackTraceHash; } CAPTURE_BACKTRACE_THREAD_CONTEXT, *PCAPTURE_BACKTRACE_THREAD_CONTEXT; KKERNEL_ROUTINE KphpCaptureStackBackTraceThreadSpecialApc; VOID KphpCaptureStackBackTraceThreadSpecialApc( __in PRKAPC Apc, __inout PKNORMAL_ROUTINE *NormalRoutine, __inout PVOID *NormalContext, __inout PVOID *SystemArgument1, __inout PVOID *SystemArgument2 ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, KpiOpenThread) #pragma alloc_text(PAGE, KpiOpenThreadProcess) #pragma alloc_text(PAGE, KphCaptureStackBackTraceThread) #pragma alloc_text(PAGE, KphpCaptureStackBackTraceThreadSpecialApc) #pragma alloc_text(PAGE, KpiCaptureStackBackTraceThread) #pragma alloc_text(PAGE, KpiQueryInformationThread) #pragma alloc_text(PAGE, KpiSetInformationThread) #endif /** * Opens a thread. * * \param ThreadHandle A variable which receives the thread handle. * \param DesiredAccess The desired access to the thread. * \param ClientId The identifier of a thread. \a UniqueThread must be present. If \a UniqueProcess * is present, the process of the referenced thread will be checked against this identifier. * \param Key An access key. * \li If a L2 key is provided, no access checks are performed. * \li If a L1 key is provided, only read access is permitted but no additional access checks are * performed. * \li If no valid 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 KpiOpenThread( __out PHANDLE ThreadHandle, __in ACCESS_MASK DesiredAccess, __in PCLIENT_ID ClientId, __in_opt KPH_KEY Key, __in PKPH_CLIENT Client, __in KPROCESSOR_MODE AccessMode ) { NTSTATUS status; CLIENT_ID clientId; PETHREAD thread; KPH_KEY_LEVEL requiredKeyLevel; HANDLE threadHandle; PAGED_CODE(); if (AccessMode != KernelMode) { __try { ProbeForWrite(ThreadHandle, sizeof(HANDLE), sizeof(HANDLE)); ProbeForRead(ClientId, sizeof(CLIENT_ID), sizeof(ULONG)); clientId = *ClientId; } __except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } } else { clientId = *ClientId; } // Use the process ID if it was specified. if (clientId.UniqueProcess) { status = PsLookupProcessThreadByCid(&clientId, NULL, &thread); } else { status = PsLookupThreadByThreadId(clientId.UniqueThread, &thread); } if (!NT_SUCCESS(status)) return status; requiredKeyLevel = KphKeyLevel1; if ((DesiredAccess & KPH_THREAD_READ_ACCESS) != DesiredAccess) requiredKeyLevel = KphKeyLevel2; if (NT_SUCCESS(status = KphValidateKey(requiredKeyLevel, Key, Client, AccessMode))) { // Always open in KernelMode to skip access checks. status = ObOpenObjectByPointer( thread, 0, NULL, DesiredAccess, *PsThreadType, KernelMode, &threadHandle ); } ObDereferenceObject(thread); if (NT_SUCCESS(status)) { if (AccessMode != KernelMode) { __try { *ThreadHandle = threadHandle; } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); } } else { *ThreadHandle = threadHandle; } } return status; } /** * Opens the process of a thread. * * \param ThreadHandle A handle to a thread. * \param DesiredAccess The desired access to the process. * \param ProcessHandle A variable which receives the process handle. * \param AccessMode The mode in which to perform access checks. */ NTSTATUS KpiOpenThreadProcess( __in HANDLE ThreadHandle, __in ACCESS_MASK DesiredAccess, __out PHANDLE ProcessHandle, __in KPROCESSOR_MODE AccessMode ) { NTSTATUS status; PETHREAD thread; PEPROCESS process; HANDLE processHandle; PAGED_CODE(); if (AccessMode != KernelMode) { __try { ProbeForWrite(ProcessHandle, sizeof(HANDLE), sizeof(HANDLE)); } __except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } } status = ObReferenceObjectByHandle( ThreadHandle, 0, *PsThreadType, AccessMode, &thread, NULL ); if (!NT_SUCCESS(status)) return status; process = IoThreadToProcess(thread); status = ObOpenObjectByPointer( process, 0, NULL, DesiredAccess, *PsProcessType, AccessMode, &processHandle ); ObDereferenceObject(thread); if (NT_SUCCESS(status)) { if (AccessMode != KernelMode) { __try { *ProcessHandle = processHandle; } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); } } else { *ProcessHandle = processHandle; } } return status; } /** * Captures a stack trace of the current thread. * * \param FramesToSkip The number of frames to skip from the bottom of the stack. * \param FramesToCapture The number of frames to capture. * \param Flags A combination of the following: * \li \c RTL_WALK_USER_MODE_STACK The user-mode stack will be retrieved instead of the kernel-mode * stack. * \param BackTrace An array in which the stack trace will be stored. * \param BackTraceHash A variable which receives a hash of the stack trace. * * \return The number of frames captured. */ ULONG KphCaptureStackBackTrace( __in ULONG FramesToSkip, __in ULONG FramesToCapture, __in_opt ULONG Flags, __out_ecount(FramesToCapture) PVOID *BackTrace, __out_opt PULONG BackTraceHash ) { PVOID backTrace[MAX_STACK_DEPTH]; ULONG framesFound; ULONG hash; ULONG i; // Skip the current frame (for this function). FramesToSkip++; // Ensure that we won't overrun the buffer. if (FramesToCapture + FramesToSkip > MAX_STACK_DEPTH) return 0; // Validate the flags. if ((Flags & RTL_WALK_VALID_FLAGS) != Flags) return 0; // Walk the stack. framesFound = RtlWalkFrameChain( backTrace, FramesToCapture + FramesToSkip, Flags ); // Return nothing if we found fewer frames than we wanted to skip. if (framesFound <= FramesToSkip) return 0; // Copy over the stack trace. At the same time we calculate the stack trace hash by summing the // addresses. for (i = 0, hash = 0; i < FramesToCapture; i++) { if (FramesToSkip + i >= framesFound) break; BackTrace[i] = backTrace[FramesToSkip + i]; hash += PtrToUlong(BackTrace[i]); } if (BackTraceHash) *BackTraceHash = hash; return i; } /** * Captures the stack trace of a thread. * * \param Thread The thread to capture the stack trace of. * \param FramesToSkip The number of frames to skip from the bottom of the stack. * \param FramesToCapture The number of frames to capture. * \param BackTrace An array in which the stack trace will be stored. * \param CapturedFrames A variable which receives the number of frames captured. * \param BackTraceHash A variable which receives a hash of the stack trace. * \param AccessMode The mode in which to perform access checks. * * \return The number of frames captured. */ NTSTATUS KphCaptureStackBackTraceThread( __in PETHREAD Thread, __in ULONG FramesToSkip, __in ULONG FramesToCapture, __out_ecount(FramesToCapture) PVOID *BackTrace, __out_opt PULONG CapturedFrames, __out_opt PULONG BackTraceHash, __in KPROCESSOR_MODE AccessMode ) { NTSTATUS status = STATUS_SUCCESS; CAPTURE_BACKTRACE_THREAD_CONTEXT context; ULONG backTraceSize; PVOID *backTrace; PAGED_CODE(); // Make sure the caller didn't request too many frames. This also restricts the amount of memory // we will try to allocate later. if (FramesToCapture > MAX_STACK_DEPTH) return STATUS_INVALID_PARAMETER_3; backTraceSize = FramesToCapture * sizeof(PVOID); if (AccessMode != KernelMode) { __try { ProbeForWrite(BackTrace, backTraceSize, sizeof(PVOID)); if (CapturedFrames) ProbeForWrite(CapturedFrames, sizeof(ULONG), sizeof(ULONG)); if (BackTraceHash) ProbeForWrite(BackTraceHash, sizeof(ULONG), sizeof(ULONG)); } __except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } } // If the caller doesn't want to capture anything, return immediately. if (backTraceSize == 0) { if (AccessMode != KernelMode) { __try { if (CapturedFrames) *CapturedFrames = 0; if (BackTraceHash) *BackTraceHash = 0; } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); } } else { if (CapturedFrames) *CapturedFrames = 0; if (BackTraceHash) *BackTraceHash = 0; } return status; } // Allocate storage for the stack trace. backTrace = ExAllocatePoolWithTag(NonPagedPool, backTraceSize, 'bhpK'); if (!backTrace) return STATUS_INSUFFICIENT_RESOURCES; // Initialize the context structure. context.FramesToSkip = FramesToSkip; context.FramesToCapture = FramesToCapture; context.BackTrace = backTrace; // Check if we're trying to get a stack trace of the current thread. // If so, we don't need to insert an APC. if (Thread == PsGetCurrentThread()) { PCAPTURE_BACKTRACE_THREAD_CONTEXT contextPtr = &context; PVOID dummy = NULL; KIRQL oldIrql; // Raise the IRQL to APC_LEVEL to simulate an APC environment, // and call the APC routine directly. context.Local = TRUE; KeRaiseIrql(APC_LEVEL, &oldIrql); KphpCaptureStackBackTraceThreadSpecialApc( &context.Apc, NULL, NULL, &contextPtr, &dummy ); KeLowerIrql(oldIrql); } else { context.Local = FALSE; KeInitializeEvent(&context.CompletedEvent, NotificationEvent, FALSE); KeInitializeApc( &context.Apc, (PKTHREAD)Thread, OriginalApcEnvironment, KphpCaptureStackBackTraceThreadSpecialApc, NULL, NULL, KernelMode, NULL ); if (KeInsertQueueApc(&context.Apc, &context, NULL, 2)) { // Wait for the APC to complete. status = KeWaitForSingleObject( &context.CompletedEvent, Executive, KernelMode, FALSE, NULL ); } else { status = STATUS_UNSUCCESSFUL; } } if (NT_SUCCESS(status)) { ASSERT(context.CapturedFrames <= FramesToCapture); if (AccessMode != KernelMode) { __try { memcpy(BackTrace, backTrace, context.CapturedFrames * sizeof(PVOID)); if (CapturedFrames) *CapturedFrames = context.CapturedFrames; if (BackTraceHash) *BackTraceHash = context.BackTraceHash; } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); } } else { memcpy(BackTrace, backTrace, context.CapturedFrames * sizeof(PVOID)); if (CapturedFrames) *CapturedFrames = context.CapturedFrames; if (BackTraceHash) *BackTraceHash = context.BackTraceHash; } } ExFreePoolWithTag(backTrace, 'bhpK'); return status; } VOID KphpCaptureStackBackTraceThreadSpecialApc( __in PRKAPC Apc, __inout PKNORMAL_ROUTINE *NormalRoutine, __inout PVOID *NormalContext, __inout PVOID *SystemArgument1, __inout PVOID *SystemArgument2 ) { PCAPTURE_BACKTRACE_THREAD_CONTEXT context = *SystemArgument1; PAGED_CODE(); context->CapturedFrames = KphCaptureStackBackTrace( context->FramesToSkip, context->FramesToCapture, 0, context->BackTrace, &context->BackTraceHash ); if (!context->Local) { // Notify the originating thread that we have completed. KeSetEvent(&context->CompletedEvent, 0, FALSE); } } /** * Captures the stack trace of a thread. * * \param ThreadHandle A handle to the thread to capture the stack trace of. * \param FramesToSkip The number of frames to skip from the bottom of the stack. * \param FramesToCapture The number of frames to capture. * \param BackTrace An array in which the stack trace will be stored. * \param CapturedFrames A variable which receives the number of frames captured. * \param BackTraceHash A variable which receives a hash of the stack trace. * \param AccessMode The mode in which to perform access checks. * * \return The number of frames captured. */ NTSTATUS KpiCaptureStackBackTraceThread( __in HANDLE ThreadHandle, __in ULONG FramesToSkip, __in ULONG FramesToCapture, __out_ecount(FramesToCapture) PVOID *BackTrace, __out_opt PULONG CapturedFrames, __out_opt PULONG BackTraceHash, __in KPROCESSOR_MODE AccessMode ) { NTSTATUS status = STATUS_SUCCESS; PETHREAD thread; PAGED_CODE(); status = ObReferenceObjectByHandle( ThreadHandle, 0, *PsThreadType, AccessMode, &thread, NULL ); if (!NT_SUCCESS(status)) return status; status = KphCaptureStackBackTraceThread( thread, FramesToSkip, FramesToCapture, BackTrace, CapturedFrames, BackTraceHash, AccessMode ); ObDereferenceObject(thread); return status; } /** * Queries thread information. * * \param ThreadHandle A handle to a thread. * \param ThreadInformationClass The type of information to query. * \param ThreadInformation The buffer in which the information will be stored. * \param ThreadInformationLength The number of bytes available in \a ThreadInformation. * \param ReturnLength A variable which receives the number of bytes required to be available in * \a ThreadInformation. * \param AccessMode The mode in which to perform access checks. */ NTSTATUS KpiQueryInformationThread( __in HANDLE ThreadHandle, __in KPH_THREAD_INFORMATION_CLASS ThreadInformationClass, __out_bcount(ProcessInformationLength) PVOID ThreadInformation, __in ULONG ThreadInformationLength, __out_opt PULONG ReturnLength, __in KPROCESSOR_MODE AccessMode ) { NTSTATUS status; PETHREAD thread; ULONG returnLength; PAGED_CODE(); if (AccessMode != KernelMode) { __try { ProbeForWrite(ThreadInformation, ThreadInformationLength, sizeof(ULONG)); if (ReturnLength) ProbeForWrite(ReturnLength, sizeof(ULONG), sizeof(ULONG)); } __except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } } status = ObReferenceObjectByHandle( ThreadHandle, THREAD_QUERY_INFORMATION, *PsThreadType, AccessMode, &thread, NULL ); if (!NT_SUCCESS(status)) return status; switch (ThreadInformationClass) { default: status = STATUS_INVALID_INFO_CLASS; returnLength = 0; break; } ObDereferenceObject(thread); if (ReturnLength) { if (AccessMode != KernelMode) { __try { *ReturnLength = returnLength; } __except (EXCEPTION_EXECUTE_HANDLER) { NOTHING; } } else { *ReturnLength = returnLength; } } return status; } /** * Sets thread information. * * \param ThreadHandle A handle to a thread. * \param ThreadInformationClass The type of information to set. * \param ThreadInformation A buffer which contains the information to set. * \param ThreadInformationLength The number of bytes present in \a ThreadInformation. * \param AccessMode The mode in which to perform access checks. */ NTSTATUS KpiSetInformationThread( __in HANDLE ThreadHandle, __in KPH_THREAD_INFORMATION_CLASS ThreadInformationClass, __in_bcount(ThreadInformationLength) PVOID ThreadInformation, __in ULONG ThreadInformationLength, __in KPROCESSOR_MODE AccessMode ) { NTSTATUS status; PETHREAD thread; PAGED_CODE(); if (AccessMode != KernelMode) { __try { ProbeForRead(ThreadInformation, ThreadInformationLength, sizeof(ULONG)); } __except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } } status = ObReferenceObjectByHandle( ThreadHandle, THREAD_SET_INFORMATION, *PsThreadType, AccessMode, &thread, NULL ); if (!NT_SUCCESS(status)) return status; switch (ThreadInformationClass) { default: status = STATUS_INVALID_INFO_CLASS; break; } ObDereferenceObject(thread); return status; }