mirror of
https://github.com/HyperDbg/HyperDbg.git
synced 2026-07-13 02:59:30 +00:00
1492 lines
43 KiB
C
1492 lines
43 KiB
C
/**
|
|
* @file Attaching.c
|
|
* @author Sina Karvandi (sina@hyperdbg.org)
|
|
* @brief Attaching and detaching for debugging user-mode processes
|
|
* @details
|
|
*
|
|
* @version 0.1
|
|
* @date 2021-12-28
|
|
*
|
|
* @copyright This project is released under the GNU Public License v3.
|
|
*
|
|
*/
|
|
#include "pch.h"
|
|
|
|
/**
|
|
* @brief Initialize the attaching mechanism
|
|
* @details as we use the functionalities for these functions, we initialize
|
|
* them from the debugger at start up of debugger (not initialization of user-debugger)
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingInitialize()
|
|
{
|
|
UNICODE_STRING FunctionName;
|
|
|
|
//
|
|
// Find address of PsGetProcessPeb
|
|
//
|
|
if (g_PsGetProcessPeb == NULL)
|
|
{
|
|
RtlInitUnicodeString(&FunctionName, L"PsGetProcessPeb");
|
|
g_PsGetProcessPeb = (PsGetProcessPeb)MmGetSystemRoutineAddress(&FunctionName);
|
|
|
|
if (g_PsGetProcessPeb == NULL)
|
|
{
|
|
LogError("Err, cannot resolve PsGetProcessPeb");
|
|
|
|
//
|
|
// Won't fail the entire debugger for not finding this
|
|
//
|
|
// return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Find address of PsGetProcessWow64Process
|
|
//
|
|
if (g_PsGetProcessWow64Process == NULL)
|
|
{
|
|
RtlInitUnicodeString(&FunctionName, L"PsGetProcessWow64Process");
|
|
g_PsGetProcessWow64Process = (PsGetProcessWow64Process)MmGetSystemRoutineAddress(&FunctionName);
|
|
|
|
if (g_PsGetProcessWow64Process == NULL)
|
|
{
|
|
LogError("Err, cannot resolve PsGetProcessPeb");
|
|
|
|
//
|
|
// Won't fail the entire debugger for not finding this
|
|
//
|
|
// return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Find address of ZwQueryInformationProcess
|
|
//
|
|
if (g_ZwQueryInformationProcess == NULL)
|
|
{
|
|
UNICODE_STRING RoutineName;
|
|
|
|
RtlInitUnicodeString(&RoutineName, L"ZwQueryInformationProcess");
|
|
|
|
g_ZwQueryInformationProcess = (ZwQueryInformationProcess)MmGetSystemRoutineAddress(&RoutineName);
|
|
|
|
if (g_ZwQueryInformationProcess == NULL)
|
|
{
|
|
LogError("Err, cannot resolve ZwQueryInformationProcess");
|
|
|
|
//
|
|
// Won't fail the entire debugger for not finding this
|
|
//
|
|
// return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Create user-mode debugging details for threads
|
|
*
|
|
* @param ProcessId
|
|
* @param Is32Bit
|
|
* @param Eprocess
|
|
* @param PebAddressToMonitor
|
|
* @return UINT64 returns the unique token
|
|
*/
|
|
UINT64
|
|
AttachingCreateProcessDebuggingDetails(UINT32 ProcessId,
|
|
BOOLEAN Enabled,
|
|
BOOLEAN Is32Bit,
|
|
BOOLEAN CheckCallbackAtFirstInstruction,
|
|
PEPROCESS Eprocess,
|
|
UINT64 PebAddressToMonitor)
|
|
{
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail;
|
|
|
|
//
|
|
// Allocate the buffer
|
|
//
|
|
ProcessDebuggingDetail = (USERMODE_DEBUGGING_PROCESS_DETAILS *)
|
|
PlatformMemAllocateZeroedNonPagedPool(sizeof(USERMODE_DEBUGGING_PROCESS_DETAILS));
|
|
|
|
if (!ProcessDebuggingDetail)
|
|
{
|
|
return (UINT64)NULL;
|
|
}
|
|
|
|
//
|
|
// Set the unique tag and increment it
|
|
//
|
|
ProcessDebuggingDetail->Token = g_SeedOfUserDebuggingDetails++;
|
|
|
|
//
|
|
// Set the details of the created buffer
|
|
//
|
|
ProcessDebuggingDetail->ProcessId = ProcessId;
|
|
ProcessDebuggingDetail->Enabled = Enabled;
|
|
ProcessDebuggingDetail->Is32Bit = Is32Bit;
|
|
ProcessDebuggingDetail->CheckCallBackForInterceptingFirstInstruction = CheckCallbackAtFirstInstruction;
|
|
ProcessDebuggingDetail->Eprocess = Eprocess;
|
|
ProcessDebuggingDetail->PebAddressToMonitor = (PVOID)PebAddressToMonitor;
|
|
|
|
//
|
|
// Allocate a thread holder buffer for this process
|
|
//
|
|
if (!ThreadHolderAssignThreadHolderToProcessDebuggingDetails(ProcessDebuggingDetail))
|
|
{
|
|
PlatformMemFreePool(ProcessDebuggingDetail);
|
|
return (UINT64)NULL;
|
|
}
|
|
|
|
//
|
|
// Attach it to the list of active thread (LIST_ENTRY)
|
|
//
|
|
InsertHeadList(&g_ProcessDebuggingDetailsListHead, &(ProcessDebuggingDetail->AttachedProcessList));
|
|
|
|
//
|
|
// return the token
|
|
//
|
|
return ProcessDebuggingDetail->Token;
|
|
}
|
|
|
|
/**
|
|
* @brief Find user-mode debugging details for threads by token
|
|
*
|
|
* @param Token
|
|
* @return PUSERMODE_DEBUGGING_PROCESS_DETAILS
|
|
*/
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS
|
|
AttachingFindProcessDebuggingDetailsByToken(UINT64 Token)
|
|
{
|
|
LIST_FOR_EACH_LINK(g_ProcessDebuggingDetailsListHead, USERMODE_DEBUGGING_PROCESS_DETAILS, AttachedProcessList, ProcessDebuggingDetails)
|
|
{
|
|
//
|
|
// Check if we found the target thread and if it's enabled
|
|
//
|
|
if (ProcessDebuggingDetails->Token == Token && ProcessDebuggingDetails->Enabled)
|
|
{
|
|
return ProcessDebuggingDetails;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Find user-mode debugging details for threads by process Id
|
|
*
|
|
* @param ProcessId
|
|
* @return PUSERMODE_DEBUGGING_PROCESS_DETAILS
|
|
*/
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS
|
|
AttachingFindProcessDebuggingDetailsByProcessId(UINT32 ProcessId)
|
|
{
|
|
LIST_FOR_EACH_LINK(g_ProcessDebuggingDetailsListHead, USERMODE_DEBUGGING_PROCESS_DETAILS, AttachedProcessList, ProcessDebuggingDetails)
|
|
{
|
|
//
|
|
// Check if we found the target thread and if it's enabled
|
|
//
|
|
if (ProcessDebuggingDetails->ProcessId == ProcessId && ProcessDebuggingDetails->Enabled)
|
|
{
|
|
return ProcessDebuggingDetails;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Find user-mode debugging details for threads that is in
|
|
* the start-up phase
|
|
*
|
|
* @return PUSERMODE_DEBUGGING_PROCESS_DETAILS
|
|
*/
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS
|
|
AttachingFindProcessDebuggingDetailsInStartingPhase()
|
|
{
|
|
LIST_FOR_EACH_LINK(g_ProcessDebuggingDetailsListHead, USERMODE_DEBUGGING_PROCESS_DETAILS, AttachedProcessList, ProcessDebuggingDetails)
|
|
{
|
|
if (ProcessDebuggingDetails->IsOnTheStartingPhase)
|
|
{
|
|
return ProcessDebuggingDetails;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Remove and deallocate all thread debuggig details
|
|
*
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
AttachingRemoveAndFreeAllProcessDebuggingDetails()
|
|
{
|
|
LIST_FOR_EACH_LINK(g_ProcessDebuggingDetailsListHead, USERMODE_DEBUGGING_PROCESS_DETAILS, AttachedProcessList, ProcessDebuggingDetails)
|
|
{
|
|
//
|
|
// Free the thread holding structure(s)
|
|
//
|
|
ThreadHolderFreeHoldingStructures(ProcessDebuggingDetails);
|
|
|
|
//
|
|
// Remove thread debugging detail from the list active threads
|
|
//
|
|
RemoveEntryList(&ProcessDebuggingDetails->AttachedProcessList);
|
|
|
|
//
|
|
// Unallocate the pool
|
|
//
|
|
PlatformMemFreePool(ProcessDebuggingDetails);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Remove user-mode debugging details for threads by its token
|
|
*
|
|
* @param Token
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingRemoveProcessDebuggingDetailsByToken(UINT64 Token)
|
|
{
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetails;
|
|
|
|
//
|
|
// Find the entry
|
|
//
|
|
ProcessDebuggingDetails = AttachingFindProcessDebuggingDetailsByToken(Token);
|
|
|
|
if (!ProcessDebuggingDetails)
|
|
{
|
|
//
|
|
// Token not found!
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Free the thread holding structure(s)
|
|
//
|
|
ThreadHolderFreeHoldingStructures(ProcessDebuggingDetails);
|
|
|
|
//
|
|
// Remove thread debugging detail from the list active threads
|
|
//
|
|
RemoveEntryList(&ProcessDebuggingDetails->AttachedProcessList);
|
|
|
|
//
|
|
// Unallocate the pool
|
|
//
|
|
PlatformMemFreePool(ProcessDebuggingDetails);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the start up phase of a debugging thread buffer by its token
|
|
*
|
|
* @param Set
|
|
* @param Token
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingSetStartingPhaseOfProcessDebuggingDetailsByToken(BOOLEAN Set, UINT64 Token)
|
|
{
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetails;
|
|
|
|
//
|
|
// If it's set to TRUE, then we check to only put 1 thread at the time
|
|
// to the starting phase
|
|
//
|
|
ProcessDebuggingDetails = AttachingFindProcessDebuggingDetailsInStartingPhase();
|
|
|
|
if (Set && ProcessDebuggingDetails != NULL)
|
|
{
|
|
//
|
|
// There is another thread in starting phase!
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Find the entry
|
|
//
|
|
ProcessDebuggingDetails = AttachingFindProcessDebuggingDetailsByToken(Token);
|
|
|
|
if (!ProcessDebuggingDetails)
|
|
{
|
|
//
|
|
// Token not found!
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Set the starting phase
|
|
//
|
|
ProcessDebuggingDetails->IsOnTheStartingPhase = Set;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Handle cases where we reached to the valid loaded module
|
|
* The main module should be loaded once we reach to this function
|
|
*
|
|
* @param DbgState The state of the debugger on the current core
|
|
* @param ProcessDebuggingDetail
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingReachedToValidLoadedModule(PROCESSOR_DEBUGGING_STATE * DbgState,
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail)
|
|
{
|
|
DEBUGGEE_BP_PACKET BpRequest = {0};
|
|
|
|
//
|
|
// Double check to make sure the main module is loaded
|
|
//
|
|
if (!CheckAccessValidityAndSafety(ProcessDebuggingDetail->EntrypointOfMainModule, sizeof(CHAR)))
|
|
{
|
|
LogError("Err, the main module is not loaded or the main entrypoint is not valid");
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Setting a breakpoint to the main entrypoint of the module
|
|
//
|
|
BpRequest.Address = ProcessDebuggingDetail->EntrypointOfMainModule;
|
|
BpRequest.Core = DEBUGGEE_BP_APPLY_TO_ALL_CORES;
|
|
BpRequest.Pid = DEBUGGEE_BP_APPLY_TO_ALL_PROCESSES;
|
|
BpRequest.Tid = DEBUGGEE_BP_APPLY_TO_ALL_THREADS;
|
|
|
|
//
|
|
// Remove the breakpoint after hit
|
|
//
|
|
BpRequest.RemoveAfterHit = TRUE;
|
|
|
|
//
|
|
// Check if the process needs to check for callbacks for the very first instruction
|
|
//
|
|
if (ProcessDebuggingDetail->CheckCallBackForInterceptingFirstInstruction)
|
|
{
|
|
//
|
|
// This breakpoint should check for callbacks
|
|
//
|
|
BpRequest.CheckForCallbacks = TRUE;
|
|
}
|
|
|
|
//
|
|
// Register the breakpoint
|
|
// Note that, even though it's a user-mode breakpoint, we are currently
|
|
// in the memory layout of the target process, thus, no switching memory
|
|
// layout is needed
|
|
//
|
|
if (!BreakpointAddNew(&BpRequest, FALSE))
|
|
{
|
|
LogError("Err, unable to set breakpoint on the entrypoint module");
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Finish the starting point of the thread
|
|
//
|
|
AttachingSetStartingPhaseOfProcessDebuggingDetailsByToken(FALSE, ProcessDebuggingDetail->Token);
|
|
|
|
//
|
|
// Check if we're connect to the kHyperDbg or uHyperDbg
|
|
//
|
|
if (g_KernelDebuggerState)
|
|
{
|
|
//
|
|
// Handling state through the kernel-mode debugger
|
|
//
|
|
KdHandleBreakpointAndDebugBreakpoints(DbgState,
|
|
DEBUGGEE_PAUSING_REASON_DEBUGGEE_STARTING_MODULE_LOADED,
|
|
NULL);
|
|
}
|
|
else if (g_UserDebuggerState)
|
|
{
|
|
//
|
|
// Handling state through the user-mode debugger
|
|
//
|
|
UdHandleInstantBreak(DbgState,
|
|
DEBUGGEE_PAUSING_REASON_DEBUGGEE_STARTING_MODULE_LOADED,
|
|
ProcessDebuggingDetail);
|
|
}
|
|
else
|
|
{
|
|
LogError("Err, no debugger is attached to handle the entrypoint interception");
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Handled successfully
|
|
//
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Handle the interception of finding the entrypoint on
|
|
* attaching to user-mode process
|
|
*
|
|
* @param DbgState The state of the debugger on the current core
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
AttachingHandleEntrypointInterception(PROCESSOR_DEBUGGING_STATE * DbgState)
|
|
{
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail = NULL;
|
|
PAGE_FAULT_EXCEPTION PageFaultErrorCode = {0};
|
|
|
|
//
|
|
// Not increment the RIP register as no instruction is intended to go
|
|
//
|
|
VmFuncSuppressRipIncrement(DbgState->CoreId);
|
|
|
|
ProcessDebuggingDetail = AttachingFindProcessDebuggingDetailsByProcessId(HANDLE_TO_UINT32(PsGetCurrentProcessId()));
|
|
|
|
//
|
|
// Check to only break on the target process id and thread id and when
|
|
// the entrypoint is not called, if the thread debugging detail is found
|
|
//
|
|
if (ProcessDebuggingDetail != NULL)
|
|
{
|
|
if (g_IsWaitingForUserModeProcessEntryToBeCalled)
|
|
{
|
|
//
|
|
// Show a message that we reached to the entrypoint
|
|
//
|
|
// Log("Reached to the main module entrypoint (%016llx)\n", VmFuncGetLastVmexitRip(DbgState->CoreId));
|
|
|
|
//
|
|
// Not waiting for these event anymore
|
|
//
|
|
g_IsWaitingForUserModeProcessEntryToBeCalled = FALSE;
|
|
|
|
//
|
|
// Whenever Windows calls the start entrypoint of the target PE, initially,
|
|
// the module is not loaded in the memory, thus a page-fault will happen and
|
|
// the page-fault handler will bring the module content to the memory and after
|
|
// that, the start entrypoint of PE is called again
|
|
// It's not possible for us to handle everything here without checking for the
|
|
// possible page-fault, it is because the first instruction is not available to be
|
|
// read from the memory, thus, it shows a wrong instruction and the user might not
|
|
// use the instrumentation step-in from the start up of the entrypoint.
|
|
// Because if the user uses the instrumentation step-in command then it should go
|
|
// the page-fault handler routine in the kernel and it's not what the user expects
|
|
// we inject a #PF here and let the Windows handle the page-fault. Meanwhile, we
|
|
// set the hardware debug breakpoint again at the start of the entrypoint, so, the
|
|
// next time that the instruction is about to be fetched, a vm-exit happens as a
|
|
// result of intercepting #DBs
|
|
// In some cases, there is no need to inject #PF to the guest. One example is when
|
|
// the target page is already available in the memory like the same process is also
|
|
// open in the debuggee. In these cases, we handle the break directly without injecting
|
|
// any page-fault
|
|
//
|
|
|
|
if (!CheckAccessValidityAndSafety(ProcessDebuggingDetail->EntrypointOfMainModule, sizeof(CHAR)))
|
|
{
|
|
// LogInfo("Injecting #PF for entrypoint at : %llx", ProcessDebuggingDetail->EntrypointOfMainModule);
|
|
|
|
//
|
|
// We're waiting for this pointer to be called again after handling page-fault
|
|
//
|
|
g_IsWaitingForReturnAndRunFromPageFault = TRUE;
|
|
|
|
//
|
|
// Create page-fault error code (user, fetch #PF)
|
|
//
|
|
PageFaultErrorCode.AsUInt = 0x14;
|
|
|
|
//
|
|
// Inject the page-fault
|
|
//
|
|
VmFuncEventInjectPageFaultWithCr2(DbgState->CoreId,
|
|
ProcessDebuggingDetail->EntrypointOfMainModule,
|
|
PageFaultErrorCode.AsUInt);
|
|
|
|
//
|
|
// Also, set the RFLAGS.TF to intercept the process (thread) again after inject #PF
|
|
//
|
|
VmFuncSetRflagTrapFlag(TRUE);
|
|
|
|
//
|
|
// Indicate that we should set the trap flag to the FALSE next time on
|
|
// the same process/thread
|
|
//
|
|
if (!BreakpointRestoreTheTrapFlagOnceTriggered(HANDLE_TO_UINT32(PsGetCurrentProcessId()), HANDLE_TO_UINT32(PsGetCurrentThreadId())))
|
|
{
|
|
LogWarning("Warning, it is currently not possible to add the current process/thread to the list of processes "
|
|
"where the trap flag should be masked. Please ensure that you manually unset the trap flag");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Address is valid, probably the module is previously loaded
|
|
// or another process with same image is currently running
|
|
// Thus, there is no need to inject #PF, we'll handle it in debugger
|
|
//
|
|
AttachingReachedToValidLoadedModule(DbgState, ProcessDebuggingDetail);
|
|
}
|
|
}
|
|
else if (g_IsWaitingForReturnAndRunFromPageFault)
|
|
{
|
|
//
|
|
// not waiting for a break after the page-fault anymore
|
|
//
|
|
g_IsWaitingForReturnAndRunFromPageFault = FALSE;
|
|
|
|
//
|
|
// We reached here as a result of setting the trap flag after
|
|
// injecting a page-fault
|
|
//
|
|
AttachingReachedToValidLoadedModule(DbgState, ProcessDebuggingDetail);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Allocate a nop-sled buffer
|
|
* @param ReservedBuffAddress
|
|
* @param ProcessId
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingAdjustNopSledBuffer(UINT64 ReservedBuffAddress, UINT32 ProcessId)
|
|
{
|
|
PEPROCESS SourceProcess;
|
|
KAPC_STATE State = {0};
|
|
|
|
if (PsLookupProcessByProcessId((HANDLE)ProcessId, &SourceProcess) != STATUS_SUCCESS)
|
|
{
|
|
//
|
|
// if the process not found
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
__try
|
|
{
|
|
KeStackAttachProcess(SourceProcess, &State);
|
|
|
|
//
|
|
// Fill the memory with nops
|
|
//
|
|
memset((PVOID)ReservedBuffAddress, 0x90, PAGE_SIZE);
|
|
|
|
//
|
|
// Set jmps to form a loop (little endians)
|
|
//
|
|
// Disassembly of section .text:
|
|
// 0000000000000000 <NopLoop>:
|
|
// 0: 90 nop
|
|
// 1: 90 nop
|
|
// 2: 90 nop
|
|
// 3: 90 nop
|
|
// 4: 90 nop
|
|
// 5: 90 nop
|
|
// 6: 90 nop
|
|
// 7: 90 nop
|
|
// 8: 0f a2 cpuid
|
|
// a: eb f4 jmp 0 <NopLoop>
|
|
//
|
|
*(UINT16 *)(ReservedBuffAddress + PAGE_SIZE - 4) = 0xa20f;
|
|
*(UINT16 *)(ReservedBuffAddress + PAGE_SIZE - 2) = 0xf4eb;
|
|
|
|
KeUnstackDetachProcess(&State);
|
|
|
|
ObDereferenceObject(SourceProcess);
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
KeUnstackDetachProcess(&State);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Check thread interceptions with user-mode debugger
|
|
*
|
|
* @param CoreId
|
|
*
|
|
* @return BOOLEAN if TRUE show that the thread interceptin is handled otherwise FALSE
|
|
*/
|
|
BOOLEAN
|
|
AttachingCheckThreadInterceptionWithUserDebugger(UINT32 CoreId)
|
|
{
|
|
PROCESSOR_DEBUGGING_STATE * DbgState = &g_DbgState[CoreId];
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail;
|
|
|
|
//
|
|
// Check whether user-debugger is initialized or not
|
|
//
|
|
if (!g_UserDebuggerState)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
ProcessDebuggingDetail = AttachingFindProcessDebuggingDetailsByProcessId(HANDLE_TO_UINT32(PsGetCurrentProcessId()));
|
|
|
|
//
|
|
// Check if the process debugging detail is found
|
|
//
|
|
if (!ProcessDebuggingDetail)
|
|
{
|
|
//
|
|
// not related to user debugger
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check if thread is in the intercepting phase
|
|
//
|
|
if (ProcessDebuggingDetail->IsOnThreadInterceptingPhase)
|
|
{
|
|
//
|
|
// Handling state through the user-mode debugger
|
|
//
|
|
if (UdCheckAndHandleBreakpointsAndDebugBreaks(DbgState,
|
|
DEBUGGEE_PAUSING_REASON_DEBUGGEE_GENERAL_THREAD_INTERCEPTED,
|
|
NULL))
|
|
{
|
|
//
|
|
// Check for possible commands that need to be executed
|
|
//
|
|
UdCheckForCommand(DbgState, ProcessDebuggingDetail);
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// this thread is not in intercepting phase
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* @brief Enable or disable the thread intercepting phase
|
|
* @details this function should be called in vmx non-root
|
|
*
|
|
* @param ProcessDebuggingToken
|
|
* @param Enable
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingConfigureInterceptingThreads(UINT64 ProcessDebuggingToken, BOOLEAN Enable)
|
|
{
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail;
|
|
|
|
//
|
|
// Get the current process debugging detail
|
|
//
|
|
ProcessDebuggingDetail = AttachingFindProcessDebuggingDetailsByToken(ProcessDebuggingToken);
|
|
|
|
if (!ProcessDebuggingDetail)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If the thread is already in intercepting phase, we should return,
|
|
//
|
|
if (Enable && ProcessDebuggingDetail->IsOnThreadInterceptingPhase)
|
|
{
|
|
LogError("Err, thread is already in intercepting phase");
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// We're or we're not in thread intercepting phase now
|
|
//
|
|
ProcessDebuggingDetail->IsOnThreadInterceptingPhase = Enable;
|
|
|
|
//
|
|
// Intercepting threads is enabled
|
|
//
|
|
if (Enable)
|
|
{
|
|
//
|
|
// Add the process to the watching list to be able to intercept the threads
|
|
//
|
|
ConfigureExecTrapAddProcessToWatchingList(ProcessDebuggingDetail->ProcessId);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Unpause the threads of the target process
|
|
//
|
|
ThreadHolderUnpauseAllThreadsInProcess(ProcessDebuggingDetail);
|
|
|
|
//
|
|
// Remove the process from the watching list
|
|
//
|
|
ConfigureExecTrapRemoveProcessFromWatchingList(ProcessDebuggingDetail->ProcessId);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief This function checks whether any special initialization is needed while
|
|
* attaching to a process that requests a callback
|
|
*
|
|
* @details this function should not be called in vmx-root
|
|
*
|
|
* @param AttachRequest
|
|
* @param ProcessDebuggingToken
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingCheckForSafeCallbackRequestedInitializations(PDEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS AttachRequest,
|
|
UINT64 ProcessDebuggingToken)
|
|
{
|
|
UNREFERENCED_PARAMETER(AttachRequest);
|
|
UNREFERENCED_PARAMETER(ProcessDebuggingToken);
|
|
|
|
//
|
|
// Enable the memory access logging
|
|
//
|
|
ConfigureInitializeExecTrapOnAllProcessors();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Attach to the target process
|
|
* @details this function should not be called in vmx-root
|
|
*
|
|
* @param AttachRequest
|
|
* @param IsAttachingToEntrypoint
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingPerformAttachToProcess(PDEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS AttachRequest, BOOLEAN IsAttachingToEntrypoint)
|
|
{
|
|
PEPROCESS SourceProcess;
|
|
UINT64 ProcessDebuggingToken;
|
|
UINT64 PebAddressToMonitor;
|
|
BOOLEAN ResultOfApplyingEvent;
|
|
BOOLEAN Is32Bit;
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS TempProcessDebuggingDetail;
|
|
EPT_HOOKS_ADDRESS_DETAILS_FOR_MEMORY_MONITOR EptHookDetails = {0};
|
|
|
|
if (g_PsGetProcessWow64Process == NULL || g_PsGetProcessPeb == NULL)
|
|
{
|
|
AttachRequest->Result = DEBUGGER_ERROR_FUNCTIONS_FOR_INITIALIZING_PEB_ADDRESSES_ARE_NOT_INITIALIZED;
|
|
return FALSE;
|
|
}
|
|
|
|
if (PsLookupProcessByProcessId((HANDLE)AttachRequest->ProcessId, &SourceProcess) != STATUS_SUCCESS)
|
|
{
|
|
//
|
|
// if the process not found
|
|
//
|
|
AttachRequest->Result = DEBUGGER_ERROR_UNABLE_TO_ATTACH_TO_TARGET_USER_MODE_PROCESS;
|
|
return FALSE;
|
|
}
|
|
|
|
ObDereferenceObject(SourceProcess);
|
|
|
|
//
|
|
// Check to avoid double attaching to a process
|
|
//
|
|
TempProcessDebuggingDetail = AttachingFindProcessDebuggingDetailsByProcessId(AttachRequest->ProcessId);
|
|
|
|
if (TempProcessDebuggingDetail != NULL && TempProcessDebuggingDetail->Eprocess == SourceProcess)
|
|
{
|
|
//
|
|
// The user already attached to this process
|
|
//
|
|
AttachRequest->Result = DEBUGGER_ERROR_UNABLE_TO_ATTACH_TO_AN_ALREADY_ATTACHED_PROCESS;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// check whether the target process is 32-bit or 64-bit
|
|
//
|
|
if (!UserAccessIsWow64Process((HANDLE)AttachRequest->ProcessId, &Is32Bit))
|
|
{
|
|
//
|
|
// Unable to detect whether it's 32-bit or 64-bit
|
|
//
|
|
AttachRequest->Result = DEBUGGER_ERROR_UNABLE_TO_DETECT_32_BIT_OR_64_BIT_PROCESS;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Set the Is32Bit flag of attach request
|
|
//
|
|
AttachRequest->Is32Bit = Is32Bit;
|
|
|
|
//
|
|
// Get 32-bit or 64-bit PEB
|
|
//
|
|
if (Is32Bit)
|
|
{
|
|
PebAddressToMonitor = (UINT64)((PPEB32)g_PsGetProcessWow64Process(SourceProcess));
|
|
}
|
|
else
|
|
{
|
|
PebAddressToMonitor = (UINT64)((PPEB)g_PsGetProcessPeb(SourceProcess));
|
|
}
|
|
|
|
if (PebAddressToMonitor == (UINT64)NULL)
|
|
{
|
|
AttachRequest->Result = DEBUGGER_ERROR_UNABLE_TO_ATTACH_TO_TARGET_USER_MODE_PROCESS;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Create the debugging detail for process
|
|
//
|
|
ProcessDebuggingToken = AttachingCreateProcessDebuggingDetails(AttachRequest->ProcessId,
|
|
TRUE,
|
|
Is32Bit,
|
|
AttachRequest->CheckCallbackAtFirstInstruction,
|
|
SourceProcess,
|
|
PebAddressToMonitor);
|
|
|
|
//
|
|
// Check if we successfully get the token
|
|
//
|
|
if (ProcessDebuggingToken == (UINT64)NULL)
|
|
{
|
|
AttachRequest->Result = DEBUGGER_ERROR_UNABLE_TO_ATTACH_TO_TARGET_USER_MODE_PROCESS;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check if it's attaching at entrypoint or attaching to a previously running process
|
|
//
|
|
if (IsAttachingToEntrypoint)
|
|
{
|
|
//
|
|
// *** attaching to the entrypoint of the process ***
|
|
//
|
|
|
|
//
|
|
// Check whether any special initialization for thread safe features
|
|
// is needed or not
|
|
//
|
|
if (AttachRequest->CheckCallbackAtFirstInstruction)
|
|
{
|
|
AttachingCheckForSafeCallbackRequestedInitializations(AttachRequest, ProcessDebuggingToken);
|
|
}
|
|
|
|
//
|
|
// Waiting for #DB to be triggered
|
|
//
|
|
g_IsWaitingForUserModeProcessEntryToBeCalled = TRUE;
|
|
|
|
//
|
|
// Set the starting point of the thread
|
|
//
|
|
if (!AttachingSetStartingPhaseOfProcessDebuggingDetailsByToken(TRUE, ProcessDebuggingToken))
|
|
{
|
|
//
|
|
// Remove the created thread debugging detail
|
|
//
|
|
AttachingRemoveProcessDebuggingDetailsByToken(ProcessDebuggingToken);
|
|
|
|
g_IsWaitingForUserModeProcessEntryToBeCalled = FALSE;
|
|
AttachRequest->Result = DEBUGGER_ERROR_UNABLE_TO_ATTACH_TO_TARGET_USER_MODE_PROCESS;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Apply monitor memory range to the PEB address
|
|
//
|
|
|
|
EptHookDetails.StartAddress = PebAddressToMonitor;
|
|
|
|
//
|
|
// both start and the end are at the same address
|
|
// because it hooks an entire page anyway
|
|
//
|
|
EptHookDetails.EndAddress = PebAddressToMonitor;
|
|
EptHookDetails.SetHookForRead = TRUE;
|
|
EptHookDetails.SetHookForWrite = TRUE;
|
|
EptHookDetails.SetHookForExec = FALSE;
|
|
|
|
ResultOfApplyingEvent = DebuggerEventEnableMonitorReadWriteExec(
|
|
&EptHookDetails,
|
|
AttachRequest->ProcessId,
|
|
FALSE // Applied from VMX non-root mode
|
|
);
|
|
|
|
if (!ResultOfApplyingEvent)
|
|
{
|
|
//
|
|
// Remove the created thread debugging detail
|
|
//
|
|
AttachingRemoveProcessDebuggingDetailsByToken(ProcessDebuggingToken);
|
|
|
|
g_IsWaitingForUserModeProcessEntryToBeCalled = FALSE;
|
|
AttachRequest->Result = DEBUGGER_ERROR_UNABLE_TO_ATTACH_TO_TARGET_USER_MODE_PROCESS;
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// *** attaching to previously running process ***
|
|
//
|
|
if (!AttachingConfigureInterceptingThreads(ProcessDebuggingToken, TRUE))
|
|
{
|
|
//
|
|
// Remove the created thread debugging details
|
|
//
|
|
AttachingRemoveProcessDebuggingDetailsByToken(ProcessDebuggingToken);
|
|
|
|
AttachRequest->Result = DEBUGGER_ERROR_UNABLE_TO_ATTACH_TO_TARGET_USER_MODE_PROCESS;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Operation was successful
|
|
//
|
|
AttachRequest->Token = ProcessDebuggingToken;
|
|
AttachRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief handling unhandled EPT violations
|
|
* @param CoreId
|
|
* @param ViolationQualification
|
|
* @param GuestPhysicalAddr
|
|
*
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingCheckUnhandledEptViolation(UINT32 CoreId,
|
|
UINT64 ViolationQualification,
|
|
UINT64 GuestPhysicalAddr)
|
|
{
|
|
UNREFERENCED_PARAMETER(CoreId);
|
|
UNREFERENCED_PARAMETER(ViolationQualification);
|
|
UNREFERENCED_PARAMETER(GuestPhysicalAddr);
|
|
|
|
//
|
|
// Not handled here
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* @brief Clearing hooks after resuming the process
|
|
* @details this function should not be called in vmx-root
|
|
*
|
|
* @param AttachRequest
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingRemoveHooks(PDEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS AttachRequest)
|
|
{
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetails;
|
|
|
|
//
|
|
// Get the thread debugging detail
|
|
//
|
|
ProcessDebuggingDetails = AttachingFindProcessDebuggingDetailsByToken(AttachRequest->Token);
|
|
|
|
//
|
|
// Check if token is valid or not
|
|
//
|
|
if (!ProcessDebuggingDetails)
|
|
{
|
|
AttachRequest->Result = DEBUGGER_ERROR_INVALID_THREAD_DEBUGGING_TOKEN;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check if the entrypoint is reached or not,
|
|
// if it's not reached then we won't remove the hooks
|
|
//
|
|
if (!g_IsWaitingForUserModeProcessEntryToBeCalled)
|
|
{
|
|
//
|
|
// The entrypoint is called, we should remove the hook
|
|
//
|
|
if (!ConfigureEptHookUnHookSingleAddress((UINT64)ProcessDebuggingDetails->PebAddressToMonitor,
|
|
(UINT64)NULL,
|
|
ProcessDebuggingDetails->ProcessId))
|
|
{
|
|
AttachRequest->Result = DEBUGGER_ERROR_UNABLE_TO_REMOVE_HOOKS;
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The unhooking operation was successful
|
|
//
|
|
AttachRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The entrypoint is not called, we shouldn't remove the hook
|
|
//
|
|
AttachRequest->Result = DEBUGGER_ERROR_UNABLE_TO_REMOVE_HOOKS_ENTRYPOINT_NOT_REACHED;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Pauses the target process
|
|
* @details this function should not be called in vmx-root
|
|
*
|
|
* @param PauseRequest
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingPauseProcess(PDEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS PauseRequest)
|
|
{
|
|
if (AttachingConfigureInterceptingThreads(PauseRequest->Token, TRUE))
|
|
{
|
|
//
|
|
// The pausing operation was successful
|
|
//
|
|
PauseRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
PauseRequest->Result = DEBUGGER_ERROR_UNABLE_TO_PAUSE_THE_PROCESS_THREADS;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Continues the target process
|
|
* @details this function should not be called in vmx-root
|
|
*
|
|
* @param ContinueRequest
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingContinueProcess(PDEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS ContinueRequest)
|
|
{
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetails;
|
|
|
|
//
|
|
// Get the thread debugging detail
|
|
//
|
|
ProcessDebuggingDetails = AttachingFindProcessDebuggingDetailsByToken(ContinueRequest->Token);
|
|
|
|
//
|
|
// Check if token is valid or not
|
|
//
|
|
if (!ProcessDebuggingDetails)
|
|
{
|
|
ContinueRequest->Result = DEBUGGER_ERROR_INVALID_THREAD_DEBUGGING_TOKEN;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Configure the intercepting threads to be disabled
|
|
//
|
|
if (AttachingConfigureInterceptingThreads(ContinueRequest->Token, FALSE))
|
|
{
|
|
//
|
|
// The continuing operation was successful
|
|
//
|
|
ContinueRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
ContinueRequest->Result = DEBUGGER_ERROR_UNABLE_TO_PAUSE_THE_PROCESS_THREADS;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Kill the target process from kernel-mode
|
|
* @details this function should not be called in vmx-root
|
|
*
|
|
* @param KillRequest
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingKillProcess(PDEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS KillRequest)
|
|
{
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail;
|
|
BOOLEAN WasKilled = FALSE;
|
|
|
|
ProcessDebuggingDetail = AttachingFindProcessDebuggingDetailsByProcessId(KillRequest->ProcessId);
|
|
|
|
//
|
|
// Check if process is valid in process debugging details
|
|
//
|
|
if (!ProcessDebuggingDetail)
|
|
{
|
|
//
|
|
// not found
|
|
//
|
|
KillRequest->Result = DEBUGGER_ERROR_THE_USER_DEBUGGER_NOT_ATTACHED_TO_THE_PROCESS;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check if process exists or not
|
|
//
|
|
if (!CommonIsProcessExist(ProcessDebuggingDetail->ProcessId))
|
|
{
|
|
//
|
|
// Process was killed before killing it here (not exists)
|
|
//
|
|
|
|
//
|
|
// Remove the entry from the process debugging details list
|
|
//
|
|
AttachingRemoveProcessDebuggingDetailsByToken(ProcessDebuggingDetail->Token);
|
|
|
|
KillRequest->Result = DEBUGGER_ERROR_UNABLE_TO_KILL_THE_PROCESS_DOES_NOT_EXISTS;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check if we can kill it using the first method
|
|
//
|
|
WasKilled = CommonKillProcess(KillRequest->ProcessId, PROCESS_KILL_METHOD_1);
|
|
|
|
if (WasKilled)
|
|
{
|
|
goto Success;
|
|
}
|
|
|
|
//
|
|
// Check if we can kill it using the second method
|
|
//
|
|
WasKilled = CommonKillProcess(KillRequest->ProcessId, PROCESS_KILL_METHOD_2);
|
|
|
|
if (WasKilled)
|
|
{
|
|
goto Success;
|
|
}
|
|
|
|
//
|
|
// Check if we can kill it using the third method
|
|
//
|
|
WasKilled = CommonKillProcess(KillRequest->ProcessId, PROCESS_KILL_METHOD_3);
|
|
|
|
if (WasKilled)
|
|
{
|
|
goto Success;
|
|
}
|
|
|
|
//
|
|
// No way we can kill the shit :(
|
|
//
|
|
KillRequest->Result = DEBUGGER_ERROR_UNABLE_TO_KILL_THE_PROCESS;
|
|
return FALSE;
|
|
|
|
Success:
|
|
|
|
//
|
|
// Remove the entry from the process debugging details list
|
|
//
|
|
AttachingRemoveProcessDebuggingDetailsByToken(ProcessDebuggingDetail->Token);
|
|
|
|
KillRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Clearing hooks after resuming the process
|
|
* @details this function should not be called in vmx-root
|
|
*
|
|
* @param DetachRequest
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingPerformDetach(PDEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS DetachRequest)
|
|
{
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail;
|
|
|
|
ProcessDebuggingDetail = AttachingFindProcessDebuggingDetailsByProcessId(DetachRequest->ProcessId);
|
|
|
|
//
|
|
// Check if process is valid in process debugging details
|
|
//
|
|
if (!ProcessDebuggingDetail)
|
|
{
|
|
//
|
|
// not found
|
|
//
|
|
DetachRequest->Result = DEBUGGER_ERROR_THE_USER_DEBUGGER_NOT_ATTACHED_TO_THE_PROCESS;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// If the threads are paused, we can't detach from the target process
|
|
// before sending this request, the debugger should continued all the
|
|
// threads by sending a continue packet
|
|
//
|
|
if (ThreadHolderIsAnyPausedThreadInProcess(ProcessDebuggingDetail))
|
|
{
|
|
//
|
|
// We found a thread that is still pause
|
|
//
|
|
DetachRequest->Result = DEBUGGER_ERROR_UNABLE_TO_DETACH_AS_THERE_ARE_PAUSED_THREADS;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Remove the process from interception mechanisms
|
|
//
|
|
AttachingConfigureInterceptingThreads(ProcessDebuggingDetail->Token, FALSE);
|
|
|
|
//
|
|
// Remove the entry from the process debugging details list
|
|
//
|
|
AttachingRemoveProcessDebuggingDetailsByToken(ProcessDebuggingDetail->Token);
|
|
|
|
DetachRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Switch to the target thread
|
|
*
|
|
* @param SwitchRequest
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingSwitchProcess(PDEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS SwitchRequest)
|
|
{
|
|
PUSERMODE_DEBUGGING_PROCESS_DETAILS ProcessDebuggingDetail;
|
|
PUSERMODE_DEBUGGING_THREAD_DETAILS ThreadDebuggingDetail;
|
|
|
|
if (SwitchRequest->ProcessId != NULL_ZERO)
|
|
{
|
|
//
|
|
// Switch by process id
|
|
//
|
|
ProcessDebuggingDetail = AttachingFindProcessDebuggingDetailsByProcessId(SwitchRequest->ProcessId);
|
|
}
|
|
else if (SwitchRequest->ThreadId != NULL_ZERO)
|
|
{
|
|
//
|
|
// Switch by thread id
|
|
//
|
|
ProcessDebuggingDetail = ThreadHolderGetProcessDebuggingDetailsByThreadId(SwitchRequest->ThreadId);
|
|
}
|
|
else
|
|
{
|
|
SwitchRequest->Result = DEBUGGER_ERROR_UNABLE_TO_SWITCH_PROCESS_ID_OR_THREAD_ID_IS_INVALID;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Check if process is valid in process debugging details
|
|
//
|
|
if (!ProcessDebuggingDetail)
|
|
{
|
|
//
|
|
// not found
|
|
//
|
|
SwitchRequest->Result = DEBUGGER_ERROR_UNABLE_TO_SWITCH_PROCESS_ID_OR_THREAD_ID_IS_INVALID;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Set the IsPaused field
|
|
//
|
|
if (SwitchRequest->ThreadId != NULL_ZERO)
|
|
{
|
|
//
|
|
// Find the thread's state
|
|
//
|
|
ThreadDebuggingDetail = ThreadHolderGetProcessThreadDetailsByProcessIdAndThreadId(ProcessDebuggingDetail->ProcessId,
|
|
SwitchRequest->ThreadId);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Find the first thread in the process
|
|
//
|
|
ThreadDebuggingDetail = ThreadHolderGetProcessFirstThreadDetailsByProcessId(ProcessDebuggingDetail->ProcessId);
|
|
}
|
|
|
|
//
|
|
// Check if we find the target thread
|
|
//
|
|
if (!ThreadDebuggingDetail)
|
|
{
|
|
//
|
|
// not found
|
|
//
|
|
SwitchRequest->Result = DEBUGGER_ERROR_UNABLE_TO_SWITCH_THERE_IS_NO_THREAD_ON_THE_PROCESS;
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Fill the needed details by user mode
|
|
//
|
|
SwitchRequest->Token = ProcessDebuggingDetail->Token;
|
|
SwitchRequest->ProcessId = ProcessDebuggingDetail->ProcessId;
|
|
SwitchRequest->ThreadId = ThreadDebuggingDetail->ThreadId;
|
|
SwitchRequest->Is32Bit = ProcessDebuggingDetail->Is32Bit;
|
|
SwitchRequest->Rip = ThreadDebuggingDetail->ThreadRip;
|
|
SwitchRequest->IsPaused = ThreadDebuggingDetail->IsPaused;
|
|
SwitchRequest->SizeOfInstruction = ThreadDebuggingDetail->SizeOfInstruction;
|
|
|
|
memcpy(&SwitchRequest->InstructionBytesOnRip[0],
|
|
&ThreadDebuggingDetail->InstructionBytesOnRip[0],
|
|
ThreadDebuggingDetail->SizeOfInstruction);
|
|
|
|
SwitchRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Query count of active debugging threads
|
|
*
|
|
* @param QueryCountOfDebugThreadsRequest
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingQueryCountOfActiveDebuggingThreadsAndProcesses(PDEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS QueryCountOfDebugThreadsRequest)
|
|
{
|
|
UINT32 CountOfProcessesAndThreads;
|
|
|
|
//
|
|
// Count the result
|
|
//
|
|
CountOfProcessesAndThreads = ThreadHolderQueryCountOfActiveDebuggingThreadsAndProcesses();
|
|
|
|
//
|
|
// Set the results
|
|
//
|
|
QueryCountOfDebugThreadsRequest->CountOfActiveDebuggingThreadsAndProcesses = CountOfProcessesAndThreads;
|
|
QueryCountOfDebugThreadsRequest->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Query details of active debugging threads
|
|
*
|
|
* @param BufferToStoreDetails
|
|
* @param BufferSize
|
|
* @return BOOLEAN
|
|
*/
|
|
BOOLEAN
|
|
AttachingQueryDetailsOfActiveDebuggingThreadsAndProcesses(PVOID BufferToStoreDetails, UINT32 BufferSize)
|
|
{
|
|
UINT32 CountOfProcessesAndThreadsToStore;
|
|
|
|
CountOfProcessesAndThreadsToStore = BufferSize / SIZEOF_USERMODE_DEBUGGING_THREAD_OR_PROCESS_STATE_DETAILS;
|
|
|
|
if (CountOfProcessesAndThreadsToStore == 0)
|
|
{
|
|
//
|
|
// No active thread or process
|
|
//
|
|
return FALSE;
|
|
}
|
|
|
|
// LogInfo("Count of active process and threads : %x", CountOfProcessesAndThreadsToStore);
|
|
|
|
//
|
|
// Get the results
|
|
//
|
|
ThreadHolderQueryDetailsOfActiveDebuggingThreadsAndProcesses(BufferToStoreDetails, CountOfProcessesAndThreadsToStore);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @brief Dispatch and perform attaching tasks
|
|
* @details this function should not be called in vmx-root
|
|
*
|
|
* @param AttachRequest
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
AttachingTargetProcess(PDEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS Request)
|
|
{
|
|
//
|
|
// As we're here, we need to initialize the user-mode debugger
|
|
//
|
|
if (!UdInitializeUserDebugger())
|
|
{
|
|
Request->Result = DEBUGGER_ERROR_DEBUGGER_NOT_INITIALIZED;
|
|
return;
|
|
}
|
|
|
|
switch (Request->Action)
|
|
{
|
|
case DEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS_ACTION_ATTACH:
|
|
|
|
AttachingPerformAttachToProcess(Request, Request->IsStartingNewProcess);
|
|
|
|
break;
|
|
|
|
case DEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS_ACTION_DETACH:
|
|
|
|
AttachingPerformDetach(Request);
|
|
|
|
break;
|
|
|
|
case DEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS_ACTION_REMOVE_HOOKS:
|
|
|
|
AttachingRemoveHooks(Request);
|
|
|
|
break;
|
|
|
|
case DEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS_ACTION_KILL_PROCESS:
|
|
|
|
AttachingKillProcess(Request);
|
|
|
|
break;
|
|
|
|
case DEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS_ACTION_CONTINUE_PROCESS:
|
|
|
|
AttachingContinueProcess(Request);
|
|
|
|
break;
|
|
|
|
case DEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS_ACTION_PAUSE_PROCESS:
|
|
|
|
AttachingPauseProcess(Request);
|
|
|
|
break;
|
|
|
|
case DEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS_ACTION_SWITCH_BY_PROCESS_OR_THREAD:
|
|
|
|
AttachingSwitchProcess(Request);
|
|
|
|
break;
|
|
|
|
case DEBUGGER_ATTACH_DETACH_USER_MODE_PROCESS_ACTION_QUERY_COUNT_OF_ACTIVE_DEBUGGING_THREADS:
|
|
|
|
AttachingQueryCountOfActiveDebuggingThreadsAndProcesses(Request);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
Request->Result = DEBUGGER_ERROR_INVALID_ACTION_TYPE;
|
|
|
|
break;
|
|
}
|
|
}
|