HyperDbg/hyperdbg/hyperkd/code/debugger/objects/Thread.c

726 lines
22 KiB
C

/**
* @file Thread.c
* @author Sina Karvandi (sina@hyperdbg.org)
* @brief Implementation of kernel debugger functions for threads
* @details
*
* @version 0.1
* @date 2021-11-23
*
* @copyright This project is released under the GNU Public License v3.
*
*/
#include "pch.h"
/**
* @brief handle thread changes
* @param DbgState The state of the debugger on the current core
*
* @return BOOLEAN
*/
BOOLEAN
ThreadHandleThreadChange(PROCESSOR_DEBUGGING_STATE * DbgState)
{
//
// Check if we reached to the target thread or not
//
if ((g_ThreadSwitch.ThreadId != NULL_ZERO && g_ThreadSwitch.ThreadId == HANDLE_TO_UINT32(PsGetCurrentThreadId())) ||
(g_ThreadSwitch.Thread != NULL64_ZERO && g_ThreadSwitch.Thread == PsGetCurrentThread()))
{
//
// Halt the debuggee, we have found the target thread
//
KdHandleBreakpointAndDebugBreakpoints(DbgState, DEBUGGEE_PAUSING_REASON_DEBUGGEE_THREAD_SWITCHED, NULL);
//
// Found
//
return TRUE;
}
//
// Not found
//
return FALSE;
}
/**
* @brief make evnvironment ready to change the thread
*
* @param DbgState The state of the debugger on the current core
* @param ThreadId
* @param EThread
* @param CheckByClockInterrupt
*
* @return BOOLEAN
*/
BOOLEAN
ThreadSwitch(PROCESSOR_DEBUGGING_STATE * DbgState,
UINT32 ThreadId,
PETHREAD EThread,
BOOLEAN CheckByClockInterrupt)
{
//
// Initialized with NULL
//
g_ThreadSwitch.Thread = NULL64_ZERO;
g_ThreadSwitch.ThreadId = NULL_ZERO;
//
// Check to avoid invalid switch
//
if (ThreadId == NULL_ZERO && EThread == NULL64_ZERO)
{
return FALSE;
}
//
// Set the target thread id, ethread to switch
//
if (EThread != NULL)
{
if (CheckAccessValidityAndSafety((UINT64)EThread, sizeof(BYTE)))
{
g_ThreadSwitch.Thread = EThread;
}
else
{
//
// An invalid address is specified by user
//
return FALSE;
}
}
else if (ThreadId != NULL_ZERO)
{
g_ThreadSwitch.ThreadId = ThreadId;
}
//
// Send request for the target task to the halted cores (synchronized)
//
HaltedCoreBroadcastTaskAllCores(DbgState,
DEBUGGER_HALTED_CORE_TASK_SET_THREAD_INTERCEPTION,
TRUE,
TRUE,
(PVOID)CheckByClockInterrupt);
return TRUE;
}
/**
* @brief shows the threads list
* @param ThreadListSymbolInfo
* @param QueryAction
* @param CountOfThreads
* @param ListSaveBuffer
* @param ListSaveBuffSize
*
* @return BOOLEAN
*/
BOOLEAN
ThreadShowList(PDEBUGGEE_THREAD_LIST_NEEDED_DETAILS ThreadListSymbolInfo,
DEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS_ACTIONS QueryAction,
UINT32 * CountOfThreads,
PVOID ListSaveBuffer,
UINT64 ListSaveBuffSize)
{
UINT64 ThreadListHead;
UINT32 EnumerationCount = 0;
UINT64 Thread = (UINT64)NULL;
LIST_ENTRY ThreadLinks = {0};
CLIENT_ID ThreadCid = {0};
UINT32 MaximumBufferCount = 0;
PDEBUGGEE_THREAD_LIST_DETAILS_ENTRY SavingEntries = ListSaveBuffer;
//
// validate parameters
//
if (QueryAction == DEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS_ACTION_QUERY_COUNT &&
CountOfThreads == NULL)
{
return FALSE;
}
if (QueryAction == DEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS_ACTION_QUERY_SAVE_DETAILS &&
(ListSaveBuffer == NULL || ListSaveBuffSize == 0))
{
return FALSE;
}
//
// compute size to avoid overflow
//
if (QueryAction == DEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS_ACTION_QUERY_SAVE_DETAILS)
{
MaximumBufferCount = (UINT32)(ListSaveBuffSize / sizeof(DEBUGGEE_THREAD_LIST_DETAILS_ENTRY));
}
UINT32 ThreadListHeadOffset = ThreadListSymbolInfo->ThreadListHeadOffset; // nt!_EPROCESS.ThreadListHead
UINT32 ThreadListEntryOffset = ThreadListSymbolInfo->ThreadListEntryOffset; // nt!_ETHREAD.ThreadListEntry
UINT32 CidOffset = ThreadListSymbolInfo->CidOffset; // nt!_ETHREAD.Cid
UINT32 ActiveProcessLinksOffset = ThreadListSymbolInfo->ActiveProcessLinksOffset; // nt!_EPROCESS.ActiveProcessLinks
UINT64 PsActiveProcessHeadAddress = ThreadListSymbolInfo->PsActiveProcessHead; // nt!PsActiveProcessHead
//
// Validate params
//
if (ThreadListHeadOffset == NULL_ZERO ||
ThreadListEntryOffset == NULL_ZERO ||
CidOffset == NULL_ZERO ||
ActiveProcessLinksOffset == NULL_ZERO ||
PsActiveProcessHeadAddress == NULL64_ZERO)
{
return FALSE;
}
//
// Set the target process
//
if (ThreadListSymbolInfo->Process == NULL64_ZERO)
{
//
// Means that it's for the current process
//
ThreadListSymbolInfo->Process = (UINT64)PsGetCurrentProcess();
ThreadListHead = (UINT64)PsGetCurrentProcess() + ThreadListHeadOffset;
}
else
{
//
// Means that the user specified a special process
//
ThreadListHead = (UINT64)ThreadListSymbolInfo->Process + ThreadListHeadOffset;
}
//
// Check if the process's thread list head is valid or not
//
if (!CheckAccessValidityAndSafety(ThreadListHead, sizeof(BYTE)))
{
return FALSE;
}
//
// Check if the nt!_EPROCESS is valid or not (available in the system or not)
//
if (!ProcessCheckIfEprocessIsValid(ThreadListSymbolInfo->Process,
PsActiveProcessHeadAddress,
ActiveProcessLinksOffset))
{
return FALSE;
}
if (QueryAction == DEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS_ACTION_SHOW_INSTANTLY)
{
//
// Show the message of show the process
//
Log("PROCESS\t%llx\tIMAGE\t%s\n",
ThreadListSymbolInfo->Process,
CommonGetProcessNameFromProcessControlBlock((PEPROCESS)ThreadListSymbolInfo->Process));
}
//
// Show thread list, we read everything from the view of system process
//
MemoryMapperReadMemorySafe(ThreadListHead, &ThreadLinks, sizeof(ThreadLinks));
//
// Find the top of ETHREAD from nt!_ETHREAD.ThreadListEntry
//
Thread = (UINT64)ThreadLinks.Flink - ThreadListEntryOffset;
do
{
//
// Show thread list, we read everything from the view of system process
//
MemoryMapperReadMemorySafe(Thread + CidOffset,
&ThreadCid,
sizeof(ThreadCid));
switch (QueryAction)
{
case DEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS_ACTION_SHOW_INSTANTLY:
//
// Show the list of process
//
Log("\tTHREAD\t%llx (%llx.%llx)\n", Thread, ThreadCid.UniqueProcess, ThreadCid.UniqueThread);
break;
case DEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS_ACTION_QUERY_COUNT:
EnumerationCount++;
break;
case DEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS_ACTION_QUERY_SAVE_DETAILS:
EnumerationCount++;
//
// Check to avoid overflow
//
if (EnumerationCount == MaximumBufferCount - 1)
{
//
// buffer is full
//
goto ReturnEnd;
}
//
// Save the details
//
SavingEntries[EnumerationCount - 1].Eprocess = ThreadListSymbolInfo->Process;
SavingEntries[EnumerationCount - 1].ProcessId = HANDLE_TO_UINT32(ThreadCid.UniqueProcess);
SavingEntries[EnumerationCount - 1].ThreadId = HANDLE_TO_UINT32(ThreadCid.UniqueThread);
SavingEntries[EnumerationCount - 1].Ethread = Thread;
MemoryMapperReadMemorySafe((UINT64)CommonGetProcessNameFromProcessControlBlock((PEPROCESS)ThreadListSymbolInfo->Process),
&SavingEntries[EnumerationCount - 1].ImageFileName,
15);
break;
default:
break;
}
MemoryMapperReadMemorySafe(Thread + ThreadListEntryOffset,
&ThreadLinks,
sizeof(ThreadLinks));
//
// Find the next process from the list of this process
//
Thread = (UINT64)ThreadLinks.Flink - ThreadListEntryOffset;
} while ((UINT64)ThreadLinks.Flink != ThreadListHead);
ReturnEnd:
//
// In case of query count of Threads, we'll set this parameter
//
if (QueryAction == DEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS_ACTION_QUERY_COUNT)
{
*CountOfThreads = EnumerationCount;
}
return TRUE;
}
/**
* @brief change the current thread
*
* @param DbgState The state of the debugger on the current core
* @param TidRequest
*
* @return BOOLEAN
*/
BOOLEAN
ThreadInterpretThread(PROCESSOR_DEBUGGING_STATE * DbgState,
PDEBUGGEE_DETAILS_AND_SWITCH_THREAD_PACKET TidRequest)
{
switch (TidRequest->ActionType)
{
case DEBUGGEE_DETAILS_AND_SWITCH_THREAD_GET_THREAD_DETAILS:
//
// Debugger wants to know current tid, nt!_ETHREAD and process name, etc.
//
TidRequest->ProcessId = HANDLE_TO_UINT32(PsGetCurrentProcessId());
TidRequest->ThreadId = HANDLE_TO_UINT32(PsGetCurrentThreadId());
TidRequest->Process = (UINT64)PsGetCurrentProcess();
TidRequest->Thread = (UINT64)PsGetCurrentThread();
MemoryMapperReadMemorySafe((UINT64)CommonGetProcessNameFromProcessControlBlock(PsGetCurrentProcess()), &TidRequest->ProcessName, 16);
//
// Operation was successful
//
TidRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
break;
case DEBUGGEE_DETAILS_AND_SWITCH_THREAD_PERFORM_SWITCH:
//
// Perform the thread switch
//
if (!ThreadSwitch(DbgState,
TidRequest->ThreadId,
(PETHREAD)TidRequest->Thread,
TidRequest->CheckByClockInterrupt))
{
TidRequest->Result = DEBUGGER_ERROR_DETAILS_OR_SWITCH_THREAD_INVALID_PARAMETER;
break;
}
//
// Operation was successful
//
TidRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
break;
case DEBUGGEE_DETAILS_AND_SWITCH_THREAD_GET_THREAD_LIST:
//
// Show the threads list
//
if (!ThreadShowList(&TidRequest->ThreadListSymDetails,
DEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS_ACTION_SHOW_INSTANTLY,
NULL,
NULL,
(UINT64)NULL))
{
TidRequest->Result = DEBUGGER_ERROR_DETAILS_OR_SWITCH_THREAD_INVALID_PARAMETER;
break;
}
//
// Operation was successful
//
TidRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
break;
default:
//
// Invalid type of action
//
TidRequest->Result = DEBUGGER_ERROR_DETAILS_OR_SWITCH_THREAD_INVALID_PARAMETER;
break;
}
//
// Check if the above operation contains error
//
if (TidRequest->Result == DEBUGGER_OPERATION_WAS_SUCCESSFUL)
{
return TRUE;
}
else
{
return FALSE;
}
}
/**
* @brief Enable or disable the thread change monitoring detection
* on the running core based on putting a HW breakpoint on the gs:[188]
* @details should be called on vmx root
*
* @param DbgState The state of the debugger on the current core
* @param Enable
* @return VOID
*/
VOID
ThreadDetectChangeByDebugRegisterOnGs(PROCESSOR_DEBUGGING_STATE * DbgState,
BOOLEAN Enable)
{
UINT64 MsrGsBase;
if (Enable)
{
//
// Enable Thread Change Detection
// *** Read the address of GS:188 to g_CurrentThreadLocation ***
//
//
// We are in kernel, so we should read MSR GS_BASE
// IA32_GS_BASE 0xC0000101
// IA32_KERNEL_GS_BASE 0xC0000102
// IA32_KERNEL_GS_BASE is currently user's gs as
// we are in the kernel-mode, if we need to intercept
// from user-mode then IA32_KERNEL_GS_BASE should be used
// but it's not for our case
//
MsrGsBase = CpuReadMsr(IA32_GS_BASE);
//
// Now, we have the gs base on MSR
// while in Windows gs:188 has the address
// of where the store current _ETHREAD, so
// we have to 188 to the gs base
//
MsrGsBase += 0x188;
//
// Set the global value for current thread of this processor
//
DbgState->ThreadOrProcessTracingDetails.CurrentThreadLocationOnGs = MsrGsBase;
//
// Set interception state
//
DbgState->ThreadOrProcessTracingDetails.DebugRegisterInterceptionState = TRUE;
//
// Enable load debug controls and save debug controls because we don't
// want dr7 and dr0 remove their configuration on vm-exits and also
// we'll be able to change the dr7 of the guest on VMCS
//
VmFuncSetLoadDebugControls(DbgState->CoreId, TRUE);
VmFuncSetSaveDebugControls(DbgState->CoreId, TRUE);
//
// Intercept #DBs by changing exception bitmap (one core)
//
VmFuncSetExceptionBitmap(DbgState->CoreId, EXCEPTION_VECTOR_DEBUG_BREAKPOINT);
//
// Note, this function is running as a DPC routines, means that
// we're currently on DISPATCH_LEVEL so after modifying debug
// registers and after disabling mov to dr (using vmcall),
// nothing is able to change debug registers so it's safe
//
//
// Set debug register to fire an exception in the case of
// read/write on the gs:188 as we intercept it on
// hypervisor side by exception bitmap on #DBs
// However, this call is somehow useless because I also set it
// on Mov 2 Debug regs handler (vm-exit), but we set from here
// to make sure that the vm-exit handler set this break on access
//
SetDebugRegisters(
DEBUGGER_DEBUG_REGISTER_FOR_THREAD_MANAGEMENT,
BREAK_ON_WRITE_ONLY,
TRUE,
DbgState->ThreadOrProcessTracingDetails.CurrentThreadLocationOnGs);
//
// Enables mov to debug registers exitings in primary cpu-based controls
// it is because I realized that some other routines in Windows like
// KiSaveProcessorControlState and KiRestoreProcessorControlState and
// other functions directly change the debug registers, probably
// because we should not modify debug registers directly, by the way, we
// are hypervisor and we can easily ignore mov to debug register (0 in
// this case), however we should somehow hide this process in the future
//
VmFuncSetMovDebugRegsExiting(DbgState->CoreId, TRUE);
}
else
{
//
// Disable Thread Change Detection
// *** Remove side changes ***
//
//
// We should not ignore debug registers change anymore
//
DbgState->ThreadOrProcessTracingDetails.DebugRegisterInterceptionState = FALSE;
//
// Disable mov to debug regs vm-exit
//
VmFuncSetMovDebugRegsExiting(DbgState->CoreId, FALSE);
//
// Disable load debug controls and save debug controls because
// no longer needed
//
VmFuncSetLoadDebugControls(DbgState->CoreId, FALSE);
VmFuncSetSaveDebugControls(DbgState->CoreId, FALSE);
//
// Disable intercepting #DBs
//
VmFuncUnsetExceptionBitmap(DbgState->CoreId, EXCEPTION_VECTOR_DEBUG_BREAKPOINT);
//
// No longer need to store such gs:188 value
//
DbgState->ThreadOrProcessTracingDetails.CurrentThreadLocationOnGs = (UINT64)NULL;
}
}
/**
* @brief Query for deubg register interception state
* @param CoreId
*
* @return BOOLEAN whether it's activated or not
*/
BOOLEAN
ThreadQueryDebugRegisterInterceptionStateByCoreId(UINT32 CoreId)
{
BOOLEAN Result = FALSE;
PROCESSOR_DEBUGGING_STATE * DbgState = &g_DbgState[CoreId];
Result = DbgState->ThreadOrProcessTracingDetails.DebugRegisterInterceptionState;
return Result;
}
/**
* @brief Enable or disable the thread change monitoring detection
* on the running core based on intercepting clock interrupts
* @details should be called on vmx root
*
* @param DbgState The state of the debugger on the current core
* @param Enable
* @return VOID
*/
VOID
ThreadDetectChangeByInterceptingClockInterrupts(PROCESSOR_DEBUGGING_STATE * DbgState,
BOOLEAN Enable)
{
if (Enable)
{
//
// We should get the clock interrupts
//
DbgState->ThreadOrProcessTracingDetails.InterceptClockInterruptsForThreadChange = TRUE;
//
// Intercept external interrupts (for monitoring clock interrupts)
//
VmFuncSetExternalInterruptExiting(DbgState->CoreId, TRUE);
}
else
{
//
// We should ignore intercepting any further clock interrupts
//
DbgState->ThreadOrProcessTracingDetails.InterceptClockInterruptsForThreadChange = FALSE;
//
// Undo intercepting external interrupts
//
VmFuncSetExternalInterruptExiting(DbgState->CoreId, FALSE);
}
}
/**
* @brief Enable or disable the thread change monitoring detection
* on the running core
* @details should be called on vmx root
*
* @param DbgState The state of the debugger on the current core
* @param Enable
* @param IsSwitchByClockIntrrupt
*
* @return VOID
*/
VOID
ThreadEnableOrDisableThreadChangeMonitor(PROCESSOR_DEBUGGING_STATE * DbgState,
BOOLEAN Enable,
BOOLEAN IsSwitchByClockIntrrupt)
{
if (Enable)
{
DbgState->ThreadOrProcessTracingDetails.InitialSetThreadChangeEvent = TRUE;
DbgState->ThreadOrProcessTracingDetails.InitialSetByClockInterrupt = IsSwitchByClockIntrrupt;
}
else
{
//
// Avoid future sets/unsets
//
DbgState->ThreadOrProcessTracingDetails.InitialSetThreadChangeEvent = FALSE;
DbgState->ThreadOrProcessTracingDetails.InitialSetByClockInterrupt = FALSE;
}
//
// Check if it's a HW breakpoint on gs:[188] or a clock interception
//
if (!IsSwitchByClockIntrrupt)
{
ThreadDetectChangeByDebugRegisterOnGs(DbgState, Enable);
}
else
{
ThreadDetectChangeByInterceptingClockInterrupts(DbgState, Enable);
}
}
/**
* @brief Query thread details (count)
*
* @param DebuggerUsermodeProcessOrThreadQueryRequest
*
* @return BOOLEAN
*/
BOOLEAN
ThreadQueryCount(PDEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS DebuggerUsermodeProcessOrThreadQueryRequest)
{
BOOLEAN Result = FALSE;
//
// Getting the count results
//
Result = ThreadShowList(&DebuggerUsermodeProcessOrThreadQueryRequest->ThreadListNeededDetails,
DEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS_ACTION_QUERY_COUNT,
&DebuggerUsermodeProcessOrThreadQueryRequest->Count,
NULL,
(UINT64)NULL);
if (Result && DebuggerUsermodeProcessOrThreadQueryRequest->Count != 0)
{
DebuggerUsermodeProcessOrThreadQueryRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
return TRUE;
}
DebuggerUsermodeProcessOrThreadQueryRequest->Result = DEBUGGER_ERROR_UNABLE_TO_QUERY_COUNT_OF_PROCESSES_OR_THREADS;
return FALSE;
}
/**
* @brief Query thread details (list)
*
* @param DebuggerUsermodeProcessOrThreadQueryRequest
* @param AddressToSaveDetail
* @param BufferSize
*
* @return BOOLEAN
*/
BOOLEAN
ThreadQueryList(PDEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS DebuggerUsermodeProcessOrThreadQueryRequest,
PVOID AddressToSaveDetail,
UINT32 BufferSize)
{
BOOLEAN Result = FALSE;
//
// Getting the list of threads
//
Result = ThreadShowList(&DebuggerUsermodeProcessOrThreadQueryRequest->ThreadListNeededDetails,
DEBUGGER_QUERY_ACTIVE_PROCESSES_OR_THREADS_ACTION_QUERY_SAVE_DETAILS,
NULL,
AddressToSaveDetail,
BufferSize);
return Result;
}
/**
* @brief Query thread details
*
* @param GetInformationThreadRequest
*
* @return BOOLEAN
*/
BOOLEAN
ThreadQueryDetails(PDEBUGGEE_DETAILS_AND_SWITCH_THREAD_PACKET GetInformationThreadRequest)
{
GetInformationThreadRequest->ProcessId = HANDLE_TO_UINT32(PsGetCurrentProcessId());
GetInformationThreadRequest->Process = (UINT64)PsGetCurrentProcess();
GetInformationThreadRequest->Thread = (UINT64)PsGetCurrentThread();
GetInformationThreadRequest->ThreadId = HANDLE_TO_UINT32(PsGetCurrentThreadId());
RtlCopyMemory(&GetInformationThreadRequest->ProcessName,
CommonGetProcessNameFromProcessControlBlock(PsGetCurrentProcess()),
15);
GetInformationThreadRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
return TRUE;
}