HyperDbg/hyperdbg/hyperkd/code/debugger/user-level/ThreadHolder.c

658 lines
20 KiB
C

/**
* @file ThreadHolder.c
* @author Sina Karvandi (sina@hyperdbg.org)
* @brief User debugger's thread holder
* @details
*
* @version 0.1
* @date 2022-01-28
*
* @copyright This project is released under the GNU Public License v3.
*
*/
#include "pch.h"
/**
* @brief Pre allocate buffer for thread holder
*
* @return VOID
*/
VOID
ThreadHolderAllocateThreadHoldingBuffers()
{
//
// Request to allocate two buffer for holder of threads
//
PoolManagerRequestAllocation(sizeof(USERMODE_DEBUGGING_THREAD_HOLDER), 2, PROCESS_THREAD_HOLDER);
//
// As it might be called from an attaching request and never find a
// a chance to allocate it, we allocate it here as it's safe at PASSIVE_LEVEL
//
PoolManagerCheckAndPerformAllocationAndDeallocation();
}
/**
* @brief Assign a thread holder to process debugging details
*
* @param ProcessDebuggingDetail
* @return BOOLEAN
*/
BOOLEAN
ThreadHolderAssignThreadHolderToProcessDebuggingDetails(PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail)
{
PUSERMODE_DEBUGGING_THREAD_HOLDER ThreadHolder;
//
// Initialize the list entry of threads
//
InitializeListHead(&ProcessDebuggingDetail->ThreadsListHead);
//
// Add a thread holder here
//
ThreadHolder = (USERMODE_DEBUGGING_THREAD_HOLDER *)
PoolManagerRequestPool(PROCESS_THREAD_HOLDER, TRUE, sizeof(USERMODE_DEBUGGING_THREAD_HOLDER));
if (!ThreadHolder)
{
return FALSE;
}
//
// Add it to thread holder of the structure
//
InsertHeadList(&ProcessDebuggingDetail->ThreadsListHead, &(ThreadHolder->ThreadHolderList));
return TRUE;
}
/**
* @brief Check if there is any thread paused by checking process debugging details
*
* @param ProcessDebuggingDetail
* @return BOOLEAN
*/
BOOLEAN
ThreadHolderIsAnyPausedThreadInProcess(PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail)
{
PLIST_ENTRY TempList = 0;
TempList = &ProcessDebuggingDetail->ThreadsListHead;
while (&ProcessDebuggingDetail->ThreadsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PUSERMODE_DEBUGGING_THREAD_HOLDER ThreadHolder =
CONTAINING_RECORD(TempList, USERMODE_DEBUGGING_THREAD_HOLDER, ThreadHolderList);
for (SIZE_T i = 0; i < MAX_THREADS_IN_A_PROCESS_HOLDER; i++)
{
if (ThreadHolder->Threads[i].ThreadId != NULL_ZERO && ThreadHolder->Threads[i].IsPaused)
{
return TRUE;
}
}
}
return FALSE;
}
/**
* @brief Unpause all threads in the process debugging details
*
* @param ProcessDebuggingDetail
* @return BOOLEAN
*/
BOOLEAN
ThreadHolderUnpauseAllThreadsInProcess(PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail)
{
PLIST_ENTRY TempList = 0;
BOOLEAN AtLeastOneThreadUnpaused = FALSE;
TempList = &ProcessDebuggingDetail->ThreadsListHead;
while (&ProcessDebuggingDetail->ThreadsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PUSERMODE_DEBUGGING_THREAD_HOLDER ThreadHolder =
CONTAINING_RECORD(TempList, USERMODE_DEBUGGING_THREAD_HOLDER, ThreadHolderList);
for (SIZE_T i = 0; i < MAX_THREADS_IN_A_PROCESS_HOLDER; i++)
{
if (ThreadHolder->Threads[i].ThreadId != NULL_ZERO && ThreadHolder->Threads[i].IsPaused)
{
//
// At least one thread is paused, let's unpause it
//
ThreadHolder->Threads[i].IsPaused = FALSE;
AtLeastOneThreadUnpaused = TRUE;
}
}
}
return AtLeastOneThreadUnpaused;
}
/**
* @brief Find the active threads of the process from process id
*
* @param ProcessId
* @param ThreadId
* @return PUSERMODE_DEBUGGING_THREAD_DETAILS
*/
PUSERMODE_DEBUGGING_THREAD_DETAILS
ThreadHolderGetProcessThreadDetailsByProcessIdAndThreadId(UINT32 ProcessId, UINT32 ThreadId)
{
PLIST_ENTRY TempList = 0;
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail;
//
// First, find the process details
//
ProcessDebuggingDetail = AttachingFindProcessDebuggingDetailsByProcessId(ProcessId);
if (ProcessDebuggingDetail == NULL)
{
return NULL;
}
TempList = &ProcessDebuggingDetail->ThreadsListHead;
while (&ProcessDebuggingDetail->ThreadsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PUSERMODE_DEBUGGING_THREAD_HOLDER ThreadHolder =
CONTAINING_RECORD(TempList, USERMODE_DEBUGGING_THREAD_HOLDER, ThreadHolderList);
for (SIZE_T i = 0; i < MAX_THREADS_IN_A_PROCESS_HOLDER; i++)
{
if (ThreadHolder->Threads[i].ThreadId == ThreadId)
{
//
// The active thread's structure is found
//
return &ThreadHolder->Threads[i];
}
}
}
//
// Active thread not found
//
return NULL;
}
/**
* @brief Find the first active threads of the process from process id
*
* @param ProcessId
* @return PUSERMODE_DEBUGGING_THREAD_DETAILS
*/
PUSERMODE_DEBUGGING_THREAD_DETAILS
ThreadHolderGetProcessFirstThreadDetailsByProcessId(UINT32 ProcessId)
{
PLIST_ENTRY TempList = 0;
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail;
//
// First, find the process details
//
ProcessDebuggingDetail = AttachingFindProcessDebuggingDetailsByProcessId(ProcessId);
if (ProcessDebuggingDetail == NULL)
{
return NULL;
}
TempList = &ProcessDebuggingDetail->ThreadsListHead;
while (&ProcessDebuggingDetail->ThreadsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PUSERMODE_DEBUGGING_THREAD_HOLDER ThreadHolder =
CONTAINING_RECORD(TempList, USERMODE_DEBUGGING_THREAD_HOLDER, ThreadHolderList);
for (SIZE_T i = 0; i < MAX_THREADS_IN_A_PROCESS_HOLDER; i++)
{
if (ThreadHolder->Threads[i].ThreadId != NULL_ZERO)
{
//
// The active thread's structure is found
//
return &ThreadHolder->Threads[i];
}
}
}
//
// Active thread not found
//
return NULL;
}
/**
* @brief Find the active process debugging detail from the thread id
*
* @param ThreadId
* @return PUSERMODE_DEBUGGING_PROCESS_DETAILS
*/
PUSERMODE_DEBUGGING_PROCESS_DETAILS
ThreadHolderGetProcessDebuggingDetailsByThreadId(UINT32 ThreadId)
{
PLIST_ENTRY TempList = 0;
PLIST_ENTRY TempList2 = 0;
TempList = &g_ProcessDebuggingDetailsListHead;
while (&g_ProcessDebuggingDetailsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetails =
CONTAINING_RECORD(TempList, USERMODE_DEBUGGING_PROCESS_DETAILS, AttachedProcessList);
//
// Search through all the active threads of this process
//
TempList2 = &ProcessDebuggingDetails->ThreadsListHead;
while (&ProcessDebuggingDetails->ThreadsListHead != TempList2->Flink)
{
TempList2 = TempList2->Flink;
PUSERMODE_DEBUGGING_THREAD_HOLDER ThreadHolder =
CONTAINING_RECORD(TempList2, USERMODE_DEBUGGING_THREAD_HOLDER, ThreadHolderList);
for (SIZE_T i = 0; i < MAX_THREADS_IN_A_PROCESS_HOLDER; i++)
{
if (ThreadHolder->Threads[i].ThreadId == ThreadId)
{
//
// The target thread is found, not let's return the process debugging
// details of this process
//
return ProcessDebuggingDetails;
}
}
}
}
//
// Active thread not found
//
return NULL;
}
/**
* @brief Find or create user-mode debugging details for threads
*
* @param ThreadId
* @param ProcessDebuggingDetail
* @return PUSERMODE_DEBUGGING_THREAD_DETAILS
*/
PUSERMODE_DEBUGGING_THREAD_DETAILS
ThreadHolderFindOrCreateThreadDebuggingDetail(UINT32 ThreadId, PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail)
{
PLIST_ENTRY TempList = 0;
TempList = &ProcessDebuggingDetail->ThreadsListHead;
//
// Let's see if we can find the thread
//
while (&ProcessDebuggingDetail->ThreadsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PUSERMODE_DEBUGGING_THREAD_HOLDER ThreadHolder =
CONTAINING_RECORD(TempList, USERMODE_DEBUGGING_THREAD_HOLDER, ThreadHolderList);
for (SIZE_T i = 0; i < MAX_THREADS_IN_A_PROCESS_HOLDER; i++)
{
if (ThreadHolder->Threads[i].ThreadId == ThreadId)
{
//
// We find a thread, let's return it's structure
//
return &ThreadHolder->Threads[i];
}
}
}
//
// *** We're here, the thread is not found, let's create an entry for it ***
//
//
// Two threads should not simultaneously reach here
//
SpinlockLock(&VmxRootThreadHoldingLock);
TempList = &ProcessDebuggingDetail->ThreadsListHead;
//
// Let's see if we can find the thread
//
while (&ProcessDebuggingDetail->ThreadsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PUSERMODE_DEBUGGING_THREAD_HOLDER ThreadHolder =
CONTAINING_RECORD(TempList, USERMODE_DEBUGGING_THREAD_HOLDER, ThreadHolderList);
for (SIZE_T i = 0; i < MAX_THREADS_IN_A_PROCESS_HOLDER; i++)
{
if (ThreadHolder->Threads[i].ThreadId == NULL_ZERO)
{
//
// We find a null thread place, let's return it's structure
//
ThreadHolder->Threads[i].ThreadId = ThreadId;
SpinlockUnlock(&VmxRootThreadHoldingLock);
return &ThreadHolder->Threads[i];
}
}
}
//
// We didn't find an empty entry,
// let's use another structure and link it to the thread holder
//
PUSERMODE_DEBUGGING_THREAD_HOLDER NewThreadHolder = (USERMODE_DEBUGGING_THREAD_HOLDER *)
PoolManagerRequestPool(PROCESS_THREAD_HOLDER, TRUE, sizeof(USERMODE_DEBUGGING_THREAD_HOLDER));
if (NewThreadHolder == NULL)
{
LogError("Err, enable to find a place to save the threads data, "
"please use 'prealloc' command to allocate more pre-allocated "
"buffer for the thread holder");
SpinlockUnlock(&VmxRootThreadHoldingLock);
return NULL;
}
//
// Add the current thread as the first entry of the holder
//
NewThreadHolder->Threads[0].ThreadId = ThreadId;
//
// Link to the thread holding structure
//
InsertHeadList(&ProcessDebuggingDetail->ThreadsListHead, &(NewThreadHolder->ThreadHolderList));
//
// Other threads are now allowed to use the thread listing mechanism
//
SpinlockUnlock(&VmxRootThreadHoldingLock);
return &NewThreadHolder->Threads[0];
}
/**
* @brief Apply the action of the user debugger to a specific thread or
* all threads
*
* @param ProcessDebuggingDetails
* @param ActionRequest
*
* @return BOOLEAN Shows whether the command is applied or not
*/
BOOLEAN
ThreadHolderApplyActionToPausedThreads(PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetails,
PDEBUGGER_UD_COMMAND_PACKET ActionRequest)
{
PUSERMODE_DEBUGGING_THREAD_DETAILS ThreadDebuggingDetails;
BOOLEAN CommandApplied = FALSE;
if (!ActionRequest->ApplyToAllPausedThreads)
{
//
// *** only the active thread ***
//
ThreadDebuggingDetails = ThreadHolderGetProcessThreadDetailsByProcessIdAndThreadId(ProcessDebuggingDetails->ProcessId,
ActionRequest->TargetThreadId);
//
// Apply the command
//
for (SIZE_T i = 0; i < MAX_USER_ACTIONS_FOR_THREADS; i++)
{
if (ThreadDebuggingDetails->UdAction[i].ActionType == DEBUGGER_UD_COMMAND_ACTION_TYPE_NONE)
{
//
// Set the action
//
ThreadDebuggingDetails->UdAction[i].OptionalParam1 = ActionRequest->UdAction.OptionalParam1;
ThreadDebuggingDetails->UdAction[i].OptionalParam2 = ActionRequest->UdAction.OptionalParam2;
ThreadDebuggingDetails->UdAction[i].OptionalParam3 = ActionRequest->UdAction.OptionalParam3;
ThreadDebuggingDetails->UdAction[i].OptionalParam4 = ActionRequest->UdAction.OptionalParam4;
//
// At last we set the action type to make it valid
//
ThreadDebuggingDetails->UdAction[i].ActionType = ActionRequest->UdAction.ActionType;
//
// Command is applied
//
return TRUE;
}
}
}
else
{
//
// *** apply to all paused threads ***
//
PLIST_ENTRY TempList = 0;
TempList = &ProcessDebuggingDetails->ThreadsListHead;
while (&ProcessDebuggingDetails->ThreadsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PUSERMODE_DEBUGGING_THREAD_HOLDER ThreadHolder =
CONTAINING_RECORD(TempList, USERMODE_DEBUGGING_THREAD_HOLDER, ThreadHolderList);
for (SIZE_T i = 0; i < MAX_THREADS_IN_A_PROCESS_HOLDER; i++)
{
if (ThreadHolder->Threads[i].ThreadId != NULL_ZERO &&
ThreadHolder->Threads[i].IsPaused)
{
for (SIZE_T j = 0; j < MAX_USER_ACTIONS_FOR_THREADS; j++)
{
if (ThreadHolder->Threads[i].UdAction[j].ActionType == DEBUGGER_UD_COMMAND_ACTION_TYPE_NONE)
{
//
// Set the action
//
ThreadHolder->Threads[i].UdAction[j].OptionalParam1 = ActionRequest->UdAction.OptionalParam1;
ThreadHolder->Threads[i].UdAction[j].OptionalParam2 = ActionRequest->UdAction.OptionalParam2;
ThreadHolder->Threads[i].UdAction[j].OptionalParam3 = ActionRequest->UdAction.OptionalParam3;
ThreadHolder->Threads[i].UdAction[j].OptionalParam4 = ActionRequest->UdAction.OptionalParam4;
//
// At last we set the action type to make it valid
//
ThreadHolder->Threads[i].UdAction[j].ActionType = ActionRequest->UdAction.ActionType;
CommandApplied = TRUE;
break;
}
}
}
}
}
}
//
// Return the result of applying the command
//
return CommandApplied;
}
/**
* @brief Free all of thread holder structures
*
* @param ProcessDebuggingDetail
* @return VOID
*/
VOID
ThreadHolderFreeHoldingStructures(PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail)
{
PLIST_ENTRY TempList = 0;
TempList = &ProcessDebuggingDetail->ThreadsListHead;
while (&ProcessDebuggingDetail->ThreadsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PUSERMODE_DEBUGGING_THREAD_HOLDER ThreadHolder =
CONTAINING_RECORD(TempList, USERMODE_DEBUGGING_THREAD_HOLDER, ThreadHolderList);
//
// The thread is allocated from the pool management, so we'll
// free it from there
//
PoolManagerFreePool((UINT64)ThreadHolder);
}
}
/**
* @brief Query count of active debugging threads and processes
*
* @return UINT32
*/
UINT32
ThreadHolderQueryCountOfActiveDebuggingThreadsAndProcesses()
{
PLIST_ENTRY TempList = 0;
PLIST_ENTRY TempList2 = 0;
UINT32 CountOfThreadsAndProcesses = 0;
TempList = &g_ProcessDebuggingDetailsListHead;
while (&g_ProcessDebuggingDetailsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetails =
CONTAINING_RECORD(TempList, USERMODE_DEBUGGING_PROCESS_DETAILS, AttachedProcessList);
//
// Each process is also counted (no matter if it has active paused thread or not)
//
CountOfThreadsAndProcesses++;
//
// Search through all the active threads of this process
//
TempList2 = &ProcessDebuggingDetails->ThreadsListHead;
while (&ProcessDebuggingDetails->ThreadsListHead != TempList2->Flink)
{
TempList2 = TempList2->Flink;
PUSERMODE_DEBUGGING_THREAD_HOLDER ThreadHolder =
CONTAINING_RECORD(TempList2, USERMODE_DEBUGGING_THREAD_HOLDER, ThreadHolderList);
for (SIZE_T i = 0; i < MAX_THREADS_IN_A_PROCESS_HOLDER; i++)
{
if (ThreadHolder->Threads[i].IsPaused)
{
//
// A paused thread should be counted
//
CountOfThreadsAndProcesses++;
}
}
}
}
//
// Return count of active threads and processes
//
return CountOfThreadsAndProcesses;
}
/**
* @brief Query details of active debugging threads and processes
*
* @param BufferToStoreDetails
* @param MaxCount
*
* @return VOID
*/
VOID
ThreadHolderQueryDetailsOfActiveDebuggingThreadsAndProcesses(
USERMODE_DEBUGGING_THREAD_OR_PROCESS_STATE_DETAILS * BufferToStoreDetails,
UINT32 MaxCount)
{
PLIST_ENTRY TempList = 0;
PLIST_ENTRY TempList2 = 0;
UINT32 CurrentIndex = 0;
if (MaxCount == 0)
{
//
// Invalid query, storage is empty
//
return;
}
TempList = &g_ProcessDebuggingDetailsListHead;
while (&g_ProcessDebuggingDetailsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetails =
CONTAINING_RECORD(TempList, USERMODE_DEBUGGING_PROCESS_DETAILS, AttachedProcessList);
//
// Each process is also save (no matter if it has active paused thread or not)
//
BufferToStoreDetails[CurrentIndex].IsProcess = TRUE;
BufferToStoreDetails[CurrentIndex].ProcessId = ProcessDebuggingDetails->ProcessId;
CurrentIndex++;
if (MaxCount == CurrentIndex)
{
//
// Storage is full
//
return;
}
//
// Search through all the active threads of this process
//
TempList2 = &ProcessDebuggingDetails->ThreadsListHead;
while (&ProcessDebuggingDetails->ThreadsListHead != TempList2->Flink)
{
TempList2 = TempList2->Flink;
PUSERMODE_DEBUGGING_THREAD_HOLDER ThreadHolder =
CONTAINING_RECORD(TempList2, USERMODE_DEBUGGING_THREAD_HOLDER, ThreadHolderList);
for (SIZE_T i = 0; i < MAX_THREADS_IN_A_PROCESS_HOLDER; i++)
{
if (ThreadHolder->Threads[i].IsPaused)
{
//
// A paused thread should be saved
//
BufferToStoreDetails[CurrentIndex].IsProcess = FALSE;
BufferToStoreDetails[CurrentIndex].ProcessId = ProcessDebuggingDetails->ProcessId;
BufferToStoreDetails[CurrentIndex].ThreadId = ThreadHolder->Threads[i].ThreadId;
BufferToStoreDetails[CurrentIndex].NumberOfBlockedContextSwitches = ThreadHolder->Threads[i].NumberOfBlockedContextSwitches;
CurrentIndex++;
if (MaxCount == CurrentIndex)
{
//
// Storage is full
//
return;
}
}
}
}
}
}