HyperDbg/hyperdbg/hyperkd/code/debugger/commands/BreakpointCommands.c
2026-06-09 01:37:48 +02:00

1283 lines
40 KiB
C

/**
* @file BreakpointCommands.c
* @author Sina Karvandi (sina@hyperdbg.org)
* @brief Routines for breakpoint commands
* @details
* @version 0.1
* @date 2021-03-12
*
* @copyright This project is released under the GNU Public License v3.
*
*/
#include "pch.h"
/**
* @brief Check and perform actions on RFLAGS.TF
* @param ProcessId
* @param ThreadId
* @param TrapSetByDebugger
*
* @return BOOLEAN Shows whether the #DB should be handled by the debugger or re-injected
*/
BOOLEAN
BreakpointCheckAndPerformActionsOnTrapFlags(UINT32 ProcessId, UINT32 ThreadId, BOOLEAN * TrapSetByDebugger)
{
UINT32 Index;
DEBUGGER_PROCESS_THREAD_INFORMATION ProcThrdInfo = {0};
BOOLEAN Result;
BOOLEAN ResultToReturn;
RFLAGS Rflags = {0};
//
// Read the RFLAGS
//
Rflags.AsUInt = VmFuncGetRflags();
//
// Form the process id and thread id into a 64-bit value
//
ProcThrdInfo.Fields.ProcessId = ProcessId;
ProcThrdInfo.Fields.ThreadId = ThreadId;
//
// Make sure, nobody is in the middle of modifying the list
//
SpinlockLock(&BreakpointCommandTrapListLock);
//
// *** Search the list of processes/threads for the current process's trap flag state ***
//
Result = BinarySearchPerformSearchItem((UINT64 *)&g_TrapFlagState.ThreadInformation[0],
g_TrapFlagState.NumberOfItems,
&Index,
ProcThrdInfo.AsUInt);
//
// Indicate whether the trap flag is set by the debugger or not
//
*TrapSetByDebugger = Result;
//
// We check the trap flag after the results because we might set the trap flag
// for the thread but the thread might run 'popfq' removing our trap flag
// so, we both check whether thread is expected to have trap flag, if not
// we check whether the trap flag is available or not
//
if (!Result && !Rflags.TrapFlag)
{
//
// It's not related to a TRAP FLAG, and we didn't previously set trap flag for this thread
// So, probably other events like setting hardware debug breakpoints caused this #DB
// which means that it should be handled by the debugger
//
ResultToReturn = TRUE;
goto Return;
}
else if (!Result && Rflags.TrapFlag)
{
//
// As it's not set by the debugger (not found in our list), it means the program or
// a debugger already set the trap flag, we'll return FALSE
//
// LogInfo("Caution: The process (pid:%x, tid:%x, name:%s) is utilizing a trap flag, "
// "which was not previously adjusted by HyperDbg. This occurrence could indicate "
// "the employment of an anti-debugging technique by the process or the involvement "
// "of another debugger. By default, HyperDbg automatically manages these #DB events "
// "and halt the debugger; however, if you wish to redirect them to the debugger, "
// "you can utilize 'test trap off'. Alternatively, you can use the transparent-mode "
// "to mitigate these situations",
// PsGetCurrentProcessId(),
// PsGetCurrentThreadId(),
// CommonGetProcessNameFromProcessControlBlock(PsGetCurrentProcess()));
//
// Returning false means that it should be re-injected into the debuggee
//
ResultToReturn = FALSE;
goto Return;
}
else
{
//
// *** being here means the thread is found in the list of threads that we set TRAP FLAG on it ***
//
//
// Uset or set the TRAP flag
//
VmFuncSetRflagTrapFlag(FALSE);
//
// Remove the thread/process from the list
// We're sure the Result is TRUE
//
InsertionSortDeleteItem((UINT64 *)&g_TrapFlagState.ThreadInformation[0],
&g_TrapFlagState.NumberOfItems,
Index);
//
// Handled #DB by debugger
//
ResultToReturn = TRUE;
goto Return;
}
Return:
//
// Unlock the list modification lock
//
SpinlockUnlock(&BreakpointCommandTrapListLock);
//
// By default, #DBs are managed by HyperDbg
//
return ResultToReturn;
}
/**
* @brief Trigger callback for breakpoint hit
*
* @param DbgState The state of the debugger on the current core
* @param ProcessId
* @param ThreadId
*
* @return BOOLEAN If true, it won't halt the debugger, but if false will halt the debugger
*/
BOOLEAN
BreakpointTriggerCallbacks(PROCESSOR_DEBUGGING_STATE * DbgState, UINT32 ProcessId, UINT32 ThreadId)
{
UNREFERENCED_PARAMETER(DbgState);
UNREFERENCED_PARAMETER(ProcessId);
UNREFERENCED_PARAMETER(ThreadId);
//
// Add the process/thread to the watching list
//
// LogInfo("Adding to watch list: Process Id: %x, Thread Id: %x", ProcessId, ThreadId);
//
// By default return FALSE to set handling the breakpoint to the user to the debugger
//
return FALSE;
}
/**
* @brief This function makes sure to unset the RFLAGS.TF on next trigger of #DB
* on the target process/thread
* @param ProcessId
* @param ThreadId
*
* @return BOOLEAN
*/
BOOLEAN
BreakpointRestoreTheTrapFlagOnceTriggered(UINT32 ProcessId, UINT32 ThreadId)
{
UINT32 Index;
BOOLEAN Result;
BOOLEAN SuccessfullyStored;
DEBUGGER_PROCESS_THREAD_INFORMATION ProcThrdInfo = {0};
//
// Form the process id and thread id into a 64-bit value
//
ProcThrdInfo.Fields.ProcessId = ProcessId;
ProcThrdInfo.Fields.ThreadId = ThreadId;
//
// Make sure, nobody is in the middle of modifying the list
//
SpinlockLock(&BreakpointCommandTrapListLock);
//
// *** Search the list of processes/threads for the current process's trap flag state ***
//
Result = BinarySearchPerformSearchItem((UINT64 *)&g_TrapFlagState.ThreadInformation[0],
g_TrapFlagState.NumberOfItems,
&Index,
ProcThrdInfo.AsUInt);
if (Result)
{
//
// It means that we already find this entry in the stored list
// so, just imply that the addition was successful (no need for extra addition)
//
SuccessfullyStored = TRUE;
goto Return;
}
else
{
//
// Insert the thread into the list as the item is not already present
//
SuccessfullyStored = InsertionSortInsertItem((UINT64 *)&g_TrapFlagState.ThreadInformation[0],
&g_TrapFlagState.NumberOfItems,
MAXIMUM_NUMBER_OF_THREAD_INFORMATION_FOR_TRAPS,
&Index, // not used
ProcThrdInfo.AsUInt);
goto Return;
}
Return:
//
// Unlock the list modification lock
//
SpinlockUnlock(&BreakpointCommandTrapListLock);
return SuccessfullyStored;
}
/**
* @brief Check and handle debug breakpoint exceptions
*
* @param CoreId
*
* @return BOOLEAN
*/
BOOLEAN
BreakpointCheckAndHandleDebugBreakpoint(UINT32 CoreId)
{
BOOLEAN TrapSetByDebugger;
PROCESSOR_DEBUGGING_STATE * DbgState = &g_DbgState[CoreId];
BOOLEAN HandledByDebuggerRoutines = TRUE;
//
// *** Check whether anything should be changed with trap-flags
// and also it indicates whether the debugger itself set this trap
// flag or it's not supposed to be set by the debugger ***
//
if (BreakpointCheckAndPerformActionsOnTrapFlags(HANDLE_TO_UINT32(PsGetCurrentProcessId()),
HANDLE_TO_UINT32(PsGetCurrentThreadId()),
&TrapSetByDebugger))
{
if (DbgState->ThreadOrProcessTracingDetails.DebugRegisterInterceptionState)
{
//
// This check was to show whether it is because of thread change detection or not
//
// This way of handling has a problem, if the user set to change
// the thread and instead of using 'g', it pressed the 'p' to
// set or a trap happens somewhere then will be ignored
// it because we don't know the origin of this debug breakpoint
// and it only happens on '.thread2' command, the correct way
// to handle it is to find the exact hw debug register that caused
// this vm-exit, but it's a really rare case, so we left it without
// handling this case
//
ThreadHandleThreadChange(DbgState);
}
else if (g_UserDebuggerState == TRUE &&
(g_IsWaitingForUserModeProcessEntryToBeCalled || g_IsWaitingForReturnAndRunFromPageFault))
{
//
// Handle for user-mode attaching mechanism
//
AttachingHandleEntrypointInterception(DbgState);
}
else if (g_KernelDebuggerState == TRUE)
{
//
// Here we added the handler for the kernel because we want
// stepping routines to work, even if the debugger masks the
// traps by using 'test trap off', so stepping still works
//
//
// Handle debug events (breakpoint, traps, hardware debug register when kernel
// debugger is attached)
//
KdHandleDebugEventsWhenKernelDebuggerIsAttached(DbgState, TrapSetByDebugger);
}
else if (g_UserDebuggerState == TRUE &&
UdHandleDebugEventsWhenUserDebuggerIsAttached(DbgState, TrapSetByDebugger))
{
//
// if the above function returns true, no need for further action
// it's handled in the user debugger
//
}
else
{
//
// Here it means that the trap is supposed to be handled by
// HyperDbg but, we couldn't find any routines that gonna
// handle it (it's probably an error)
//
HandledByDebuggerRoutines = FALSE;
LogError("Err, trap is supposed to be handled by the debugger, but none of routines handled it");
}
}
else
{
//
// *** it's not supposed to be handled by the debugger routines, the guest
// or the target debuggee throws a debug break (#DB) ***
//
//
// It means that it's not handled by the debugger routines
// By default HyperDbg intercepts all #DBs and break the debugger if
// it's attached to the debugger, otherwise injects to the guest VM
//
if (g_InterceptDebugBreaks)
{
//
// The user explicitly told the debugger not to intercept any
// traps (e.g., by using 'test trap off')
//
HandledByDebuggerRoutines = FALSE;
}
else if (g_KernelDebuggerState == TRUE)
{
//
// Handle debug events (breakpoint, traps, hardware debug register when kernel
// debugger is attached)
//
KdHandleDebugEventsWhenKernelDebuggerIsAttached(DbgState, TrapSetByDebugger);
}
else if (g_UserDebuggerState == TRUE &&
UdHandleDebugEventsWhenUserDebuggerIsAttached(DbgState, TrapSetByDebugger))
{
//
// if the above function returns true, no need for further action
// it's handled in the user debugger
//
}
else
{
//
// Inject to back to the guest as it's not either handled by the kernel debugger
// routines or the user debugger
//
HandledByDebuggerRoutines = FALSE;
}
}
return HandledByDebuggerRoutines;
}
/**
* @brief clears the 0xcc and removes the breakpoint
* @detail this function won't remove the descriptor from the list
* @param BreakpointDescriptor
*
* @return BOOLEAN
*/
BOOLEAN
BreakpointClear(PDEBUGGEE_BP_DESCRIPTOR BreakpointDescriptor)
{
BYTE TargetMem = NULL_ZERO;
//
// Check if address is safe (only one byte for 0xcc)
//
if (!CheckAccessValidityAndSafety(BreakpointDescriptor->Address, sizeof(BYTE)))
{
//
// Double check if we can access it by physical address
//
MemoryMapperReadMemorySafeByPhysicalAddress(BreakpointDescriptor->PhysAddress,
(UINT64)&TargetMem,
sizeof(BYTE));
if (TargetMem != 0xcc)
{
return FALSE;
}
}
//
// Apply the previous byte
//
MemoryMapperWriteMemorySafeByPhysicalAddress(BreakpointDescriptor->PhysAddress,
(UINT64)&BreakpointDescriptor->PreviousByte,
sizeof(BYTE));
//
// Set breakpoint to disabled
//
BreakpointDescriptor->Enabled = FALSE;
BreakpointDescriptor->AvoidReApplyBreakpoint = TRUE;
return TRUE;
}
/**
* @brief Clears the breakpoint and remove the entry from the breakpoint list
* @param
*
* @return VOID
*/
VOID
BreakpointClearAndDeallocateMemory(PDEBUGGEE_BP_DESCRIPTOR BreakpointDesc)
{
//
// Clear the breakpoint
//
BreakpointClear(BreakpointDesc);
//
// Remove breakpoint from the list of breakpoints
//
RemoveEntryList(&BreakpointDesc->BreakpointsList);
//
// Uninitialize the breakpoint descriptor (safely)
//
PoolManagerFreePool((UINT64)BreakpointDesc);
}
/**
* @brief Check and reapply breakpoint
*
* @param DbgState The state of the debugger on the current core
*
* @return BOOLEAN
*/
BOOLEAN
BreakpointCheckAndHandleReApplyingBreakpoint(PROCESSOR_DEBUGGING_STATE * DbgState)
{
BOOLEAN Result = FALSE;
if (DbgState->SoftwareBreakpointState != NULL)
{
BYTE BreakpointByte = 0xcc;
//
// MTF is handled
//
Result = TRUE;
//
// Restore previous breakpoint byte
//
MemoryMapperWriteMemorySafeByPhysicalAddress(
DbgState->SoftwareBreakpointState->PhysAddress,
(UINT64)&BreakpointByte,
sizeof(BYTE));
DbgState->SoftwareBreakpointState = NULL;
}
return Result;
}
/**
* @brief Check if the breakpoint vm-exit relates to 'bp' command or not
*
* @param DbgState The state of the debugger on the current core
* @param GuestRip
* @param Reason
* @param ChangeMtfState
*
* @return BOOLEAN
*/
BOOLEAN
BreakpointCheckAndHandleDebuggerDefinedBreakpoints(PROCESSOR_DEBUGGING_STATE * DbgState,
UINT64 GuestRip,
DEBUGGEE_PAUSING_REASON Reason,
BOOLEAN ChangeMtfState)
{
CR3_TYPE GuestCr3 = {0};
BOOLEAN IsHandledByBpRoutines = FALSE;
PLIST_ENTRY TempList = 0;
UINT64 GuestRipPhysical = (UINT64)NULL;
DEBUGGER_TRIGGERED_EVENT_DETAILS TargetContext = {0};
BOOLEAN AvoidUnsetMtf = FALSE;
BOOLEAN IgnoreUserHandling = FALSE;
//
// ***** Check breakpoint for 'bp' command *****
//
//
// Find the current process cr3
//
GuestCr3.Flags = LayoutGetCurrentProcessCr3().Flags;
//
// Convert breakpoint to physical address
//
GuestRipPhysical = VirtualAddressToPhysicalAddressByProcessCr3((PVOID)GuestRip, GuestCr3);
//
// Iterate through the list of breakpoints
//
TempList = &g_BreakpointsListHead;
while (&g_BreakpointsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PDEBUGGEE_BP_DESCRIPTOR CurrentBreakpointDesc = CONTAINING_RECORD(TempList, DEBUGGEE_BP_DESCRIPTOR, BreakpointsList);
if (CurrentBreakpointDesc->PhysAddress == GuestRipPhysical)
{
//
// It's a breakpoint by 'bp' command
//
IsHandledByBpRoutines = TRUE;
//
// First, we remove the breakpoint
//
MemoryMapperWriteMemorySafeByPhysicalAddress(GuestRipPhysical,
(UINT64)&CurrentBreakpointDesc->PreviousByte,
sizeof(BYTE));
//
// Now, halt the debuggee
//
TargetContext.Context = (PVOID)VmFuncGetLastVmexitRip(DbgState->CoreId);
//
// In breakpoints tag is breakpoint id, not event tag
//
if (Reason == DEBUGGEE_PAUSING_REASON_DEBUGGEE_SOFTWARE_BREAKPOINT_HIT)
{
TargetContext.Tag = CurrentBreakpointDesc->BreakpointId;
}
//
// Hint the debuggee about the length
//
DbgState->InstructionLengthHint = CurrentBreakpointDesc->InstructionLength;
//
// Check constraints
//
if ((CurrentBreakpointDesc->Pid == DEBUGGEE_BP_APPLY_TO_ALL_PROCESSES || CurrentBreakpointDesc->Pid == HANDLE_TO_UINT32(PsGetCurrentProcessId())) &&
(CurrentBreakpointDesc->Tid == DEBUGGEE_BP_APPLY_TO_ALL_THREADS || CurrentBreakpointDesc->Tid == HANDLE_TO_UINT32(PsGetCurrentThreadId())) &&
(CurrentBreakpointDesc->Core == DEBUGGEE_BP_APPLY_TO_ALL_CORES || CurrentBreakpointDesc->Core == DbgState->CoreId))
{
//
// Check if breakpoint should be removed after this hit or not
//
if (CurrentBreakpointDesc->RemoveAfterHit)
{
//
// One hit, we have to remove it
//
BreakpointClearAndDeallocateMemory(CurrentBreakpointDesc);
}
//
// Check if it needs to check for callbacks or not
//
if (CurrentBreakpointDesc->CheckForCallbacks)
{
//
// check callbacks
//
IgnoreUserHandling = BreakpointTriggerCallbacks(DbgState, HANDLE_TO_UINT32(PsGetCurrentProcessId()), HANDLE_TO_UINT32(PsGetCurrentThreadId()));
}
//
// Check if we need to handle the breakpoint by user or just ignore handling it
//
if (!IgnoreUserHandling && !g_InterceptBreakpoints && !g_InterceptBreakpointsAndEventsForCommandsInRemoteComputer)
{
//
// *** It's not safe to access CurrentBreakpointDesc anymore as the
// breakpoint might be removed ***
//
if (g_KernelDebuggerState)
{
KdHandleBreakpointAndDebugBreakpoints(DbgState,
Reason,
&TargetContext);
}
else if (g_UserDebuggerState)
{
UdHandleInstantBreak(DbgState, Reason, NULL);
}
else
{
LogInfo("Err, no debugger is attached to handle the breakpoint");
}
}
}
//
// Reset hint to instruction length
//
DbgState->InstructionLengthHint = 0;
//
// Check if we should re-apply the breakpoint after this instruction
// or not (in other words, is breakpoint still valid)
//
if (!CurrentBreakpointDesc->AvoidReApplyBreakpoint)
{
//
// We should re-apply the breakpoint on next mtf
//
DbgState->SoftwareBreakpointState = CurrentBreakpointDesc;
//
// As we want to continue debuggee, the MTF might arrive when the
// host finish executing it's time slice; thus, a clock interrupt
// or an IPI might be arrived and the next instruction is not what
// we expect. The following codes are added because we realized if the execution takes long then
// the execution might be switched to another routines, thus, MTF might conclude on
// another routine and we might (and will) trigger the same instruction soon
//
VmFuncEnableMtfAndChangeExternalInterruptState(DbgState->CoreId);
//
// Avoid unsetting MTF
//
AvoidUnsetMtf = TRUE;
}
//
// Do not increment rip
//
VmFuncSuppressRipIncrement(DbgState->CoreId);
//
// No need to iterate anymore
//
break;
}
}
if (IsHandledByBpRoutines && ChangeMtfState)
{
VmFuncChangeMtfUnsettingState(DbgState->CoreId, AvoidUnsetMtf);
}
return IsHandledByBpRoutines;
}
/**
* @brief Handle breakpoint vm-exits (#BP)
*
* @param CoreId
*
* @return BOOLEAN
*/
BOOLEAN
BreakpointHandleBreakpoints(UINT32 CoreId)
{
DEBUGGER_TRIGGERED_EVENT_DETAILS TargetContext = {0};
UINT64 GuestRip = 0;
PROCESSOR_DEBUGGING_STATE * DbgState = &g_DbgState[CoreId];
GuestRip = VmFuncGetRip();
//
// A breakpoint triggered and two things might be happened,
// first, a breakpoint is triggered randomly in the computer and
// we shouldn't do anything on it (won't change the instruction)
// second, the breakpoint is because of the 'bp' command, we should
// replace it with exact byte
//
//
// Check if the breakpoint is handled by the debugger routines
//
if (BreakpointCheckAndHandleDebuggerDefinedBreakpoints(DbgState,
GuestRip,
DEBUGGEE_PAUSING_REASON_DEBUGGEE_SOFTWARE_BREAKPOINT_HIT,
FALSE))
{
//
// The breakpoint is handled by the debugger routines
// so, we don't need to do anything else
//
return TRUE;
}
//
// re-inject #BP back to the guest if not handled by the hidden breakpoint
//
if (g_KernelDebuggerState)
{
//
// *** Kernel debugger is attached, let's halt everything ***
//
//
// To avoid the computer crash situation from the HyperDbg's breakpoint hitting while the interception is on
// we should always call BreakpointCheckAndHandleDebuggerDefinedBreakpoints first to handle the breakpoint
//
if (g_InterceptBreakpoints || g_InterceptBreakpointsAndEventsForCommandsInRemoteComputer)
{
//
// re-inject back to the guest as not handled if the interception is on and the breakpoint is not from the Hyperdbg's breakpoints
//
return FALSE;
}
//
// It's a random breakpoint byte
//
TargetContext.Context = (PVOID)GuestRip;
KdHandleBreakpointAndDebugBreakpoints(DbgState,
DEBUGGEE_PAUSING_REASON_DEBUGGEE_SOFTWARE_BREAKPOINT_HIT,
&TargetContext);
//
// Increment rip
//
VmFuncPerformRipIncrement(DbgState->CoreId);
//
// By default, we handle the random breakpoints if the kernel debugger is attached
//
return TRUE;
}
else if (g_UserDebuggerState)
{
//
// *** User debugger is attached, let's halt the process ***
//
//
// Check if it's a random breakpoint byte
//
if (UdHandleInstantBreak(DbgState,
DEBUGGEE_PAUSING_REASON_DEBUGGEE_SOFTWARE_BREAKPOINT_HIT,
NULL))
{
//
// if the above function returns true, it's handled in the user debugger
//
//
// Increment rip
//
VmFuncPerformRipIncrement(DbgState->CoreId);
return TRUE;
}
//
// By default, we won't handle the random (unrelated) breakpoints in the user debugger
//
return FALSE;
}
//
// *** re-inject back to the guest as not handled here ***
//
return FALSE;
}
/**
* @brief writes the 0xcc and applies the breakpoint
* @detail this function won't remove the descriptor from the list
*
* @param BreakpointDescriptor
* @param SwitchToTargetMemoryLayout If TRUE, it will switch to the target memory layout
*
* @return BOOLEAN
*/
BOOLEAN
BreakpointWrite(PDEBUGGEE_BP_DESCRIPTOR BreakpointDescriptor, BOOLEAN SwitchToTargetMemoryLayout)
{
BYTE PreviousByte = NULL_ZERO;
BYTE BreakpointByte = 0xcc; // int 3
//
// Check if address is safe (only one byte for 0xcc)
//
if (SwitchToTargetMemoryLayout)
{
if (!CheckAccessValidityAndSafetyByProcessId(BreakpointDescriptor->Address, sizeof(BYTE), BreakpointDescriptor->Pid))
{
return FALSE;
}
}
else
{
if (!CheckAccessValidityAndSafety(BreakpointDescriptor->Address, sizeof(BYTE)))
{
return FALSE;
}
}
//
// Read and save previous byte and save it to the descriptor
//
if (SwitchToTargetMemoryLayout)
{
MemoryMapperReadMemoryUnsafe(
BreakpointDescriptor->Address,
&PreviousByte,
sizeof(BYTE),
BreakpointDescriptor->Pid);
}
else
{
MemoryMapperReadMemorySafeOnTargetProcess(
BreakpointDescriptor->Address,
&PreviousByte,
sizeof(BYTE));
}
//
// Store the previous byte
//
BreakpointDescriptor->PreviousByte = PreviousByte;
//
// Set breakpoint to enabled
//
BreakpointDescriptor->Enabled = TRUE;
BreakpointDescriptor->AvoidReApplyBreakpoint = FALSE;
//
// Apply the breakpoint
//
if (SwitchToTargetMemoryLayout)
{
MemoryMapperWriteMemorySafeFromVmxNonRootyPhysicalAddress(BreakpointDescriptor->PhysAddress,
(PVOID)&BreakpointByte,
sizeof(BYTE));
}
else
{
MemoryMapperWriteMemorySafeByPhysicalAddress(BreakpointDescriptor->PhysAddress,
(UINT64)&BreakpointByte,
sizeof(BYTE));
}
return TRUE;
}
/**
* @brief Remove all the breakpoints if possible
*
* @return VOID
*/
VOID
BreakpointRemoveAllBreakpoints()
{
PLIST_ENTRY TempList = 0;
//
// Iterate through the list of breakpoints
//
TempList = &g_BreakpointsListHead;
while (&g_BreakpointsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PDEBUGGEE_BP_DESCRIPTOR CurrentBreakpointDesc = CONTAINING_RECORD(TempList, DEBUGGEE_BP_DESCRIPTOR, BreakpointsList);
//
// Clear and deallocate the breakpoint
//
BreakpointClearAndDeallocateMemory(CurrentBreakpointDesc);
}
}
/**
* @brief Find entry of breakpoint descriptor from list
* of breakpoints by breakpoint id
* @param BreakpointId
*
* @return PDEBUGGEE_BP_DESCRIPTOR
*/
PDEBUGGEE_BP_DESCRIPTOR
BreakpointGetEntryByBreakpointId(UINT64 BreakpointId)
{
PLIST_ENTRY TempList = 0;
TempList = &g_BreakpointsListHead;
while (&g_BreakpointsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PDEBUGGEE_BP_DESCRIPTOR CurrentBreakpointDesc = CONTAINING_RECORD(TempList, DEBUGGEE_BP_DESCRIPTOR, BreakpointsList);
if (CurrentBreakpointDesc->BreakpointId == BreakpointId)
{
return CurrentBreakpointDesc;
}
}
//
// We didn't find anything, so return null
//
return NULL;
}
/**
* @brief Find entry of breakpoint descriptor from list
* of breakpoints by address
* @param Address
*
* @return PDEBUGGEE_BP_DESCRIPTOR
*/
PDEBUGGEE_BP_DESCRIPTOR
BreakpointGetEntryByAddress(UINT64 Address)
{
PLIST_ENTRY TempList = 0;
TempList = &g_BreakpointsListHead;
while (&g_BreakpointsListHead != TempList->Flink)
{
TempList = TempList->Flink;
PDEBUGGEE_BP_DESCRIPTOR CurrentBreakpointDesc = CONTAINING_RECORD(TempList, DEBUGGEE_BP_DESCRIPTOR, BreakpointsList);
if (CurrentBreakpointDesc->Address == Address)
{
return CurrentBreakpointDesc;
}
}
//
// We didn't find anything, so return null
//
return NULL;
}
/**
* @brief Add new breakpoints
* @param BpDescriptor
* @param SwitchToTargetMemoryLayout
*
* @return BOOLEAN
*/
BOOLEAN
BreakpointAddNew(PDEBUGGEE_BP_PACKET BpDescriptorArg, BOOLEAN SwitchToTargetMemoryLayout)
{
CR3_TYPE GuestCr3 = {0};
PDEBUGGEE_BP_DESCRIPTOR BreakpointDescriptor = NULL;
BOOLEAN IsAddress32Bit = FALSE;
//
// Find the current process cr3
//
if (SwitchToTargetMemoryLayout)
{
//
// Check if the process id is valid or not
//
if (BpDescriptorArg->Pid != DEBUGGEE_BP_APPLY_TO_ALL_PROCESSES &&
!CommonIsProcessExist(BpDescriptorArg->Pid))
{
//
// Process id is invalid (Set the error)
//
BpDescriptorArg->Result = DEBUGGER_ERROR_INVALID_PROCESS_ID;
return FALSE;
}
GuestCr3.Flags = LayoutGetCr3ByProcessId(BpDescriptorArg->Pid).Flags;
}
else
{
GuestCr3.Flags = LayoutGetCurrentProcessCr3().Flags;
}
//
// *** Validate arguments ***
//
//
// Check if the core number is not invalid
//
if (BpDescriptorArg->Core != DEBUGGEE_BP_APPLY_TO_ALL_CORES &&
!CommonValidateCoreNumber(BpDescriptorArg->Core))
{
//
// Core is invalid (Set the error)
//
BpDescriptorArg->Result = DEBUGGER_ERROR_INVALID_CORE_ID;
return FALSE;
}
//
// Check if breakpoint already exists on list or not
//
if (BreakpointGetEntryByAddress(BpDescriptorArg->Address) != NULL)
{
//
// Address is already on the list (Set the error)
//
BpDescriptorArg->Result = DEBUGGER_ERROR_BREAKPOINT_ALREADY_EXISTS_ON_THE_ADDRESS;
return FALSE;
}
//
// Check if address is safe (only one byte for 0xcc)
//
if (SwitchToTargetMemoryLayout)
{
if (!CheckAccessValidityAndSafetyByProcessId(BpDescriptorArg->Address, sizeof(BYTE), BpDescriptorArg->Pid))
{
BpDescriptorArg->Result = DEBUGGER_ERROR_EDIT_MEMORY_STATUS_INVALID_ADDRESS_BASED_ON_CURRENT_PROCESS;
return FALSE;
}
}
else
{
if (!CheckAccessValidityAndSafety(BpDescriptorArg->Address, sizeof(BYTE)))
{
BpDescriptorArg->Result = DEBUGGER_ERROR_EDIT_MEMORY_STATUS_INVALID_ADDRESS_BASED_ON_CURRENT_PROCESS;
return FALSE;
}
}
//
// On the debugger mode, we won't check for process id and thread id, if these arguments are invalid
// then the HyperDbg simply ignores the breakpoints but it makes the computer slow
// it just won't be triggered
//
//
// When we reach here means that the arguments are valid and address is
// safe to access (put 0xcc)
//
//
// Get the pre-allocated buffer
//
BreakpointDescriptor = (DEBUGGEE_BP_DESCRIPTOR *)
PoolManagerRequestPool(BREAKPOINT_DEFINITION_STRUCTURE, TRUE, sizeof(DEBUGGEE_BP_DESCRIPTOR));
if (BreakpointDescriptor == NULL)
{
//
// No pool ! Probably the user set more than MAXIMUM_BREAKPOINTS_WITHOUT_CONTINUE
// pools without IOCTL (continue)
//
BpDescriptorArg->Result = DEBUGGER_ERROR_MAXIMUM_BREAKPOINT_WITHOUT_CONTINUE;
return FALSE;
}
//
// Copy details of breakpoint to the descriptor structure
//
g_MaximumBreakpointId++;
BreakpointDescriptor->BreakpointId = g_MaximumBreakpointId;
BreakpointDescriptor->Address = BpDescriptorArg->Address;
BreakpointDescriptor->PhysAddress = VirtualAddressToPhysicalAddressByProcessCr3((PVOID)BpDescriptorArg->Address,
GuestCr3);
BreakpointDescriptor->Core = BpDescriptorArg->Core;
BreakpointDescriptor->Pid = BpDescriptorArg->Pid;
BreakpointDescriptor->Tid = BpDescriptorArg->Tid;
BreakpointDescriptor->RemoveAfterHit = BpDescriptorArg->RemoveAfterHit;
BreakpointDescriptor->CheckForCallbacks = BpDescriptorArg->CheckForCallbacks;
//
// Check whether address is 32-bit or 64-bit
//
if (BpDescriptorArg->Address & 0xff00000000000000)
{
//
// This is a kernel-base address and as the kernel is 64-bit, we assume it's a 64-bit address
//
IsAddress32Bit = FALSE;
}
else
{
//
// The address is not a kernel address, thus, we check whether the debuggee is running on user-mode
// or not
//
if (SwitchToTargetMemoryLayout)
{
UserAccessIsWow64Process((HANDLE)BpDescriptorArg->Pid, &IsAddress32Bit);
}
else
{
IsAddress32Bit = KdIsGuestOnUsermode32Bit();
}
}
//
// Use length disassembler engine to get the instruction length
//
if (SwitchToTargetMemoryLayout)
{
BreakpointDescriptor->InstructionLength = (UINT16)DisassemblerLengthDisassembleEngineByProcessId(
(PVOID)BpDescriptorArg->Address,
IsAddress32Bit,
BpDescriptorArg->Pid);
}
else
{
BreakpointDescriptor->InstructionLength = (UINT16)DisassemblerLengthDisassembleEngineInVmxRootOnTargetProcess(
(PVOID)BpDescriptorArg->Address,
IsAddress32Bit);
}
//
// Breakpoints are enabled by default
//
BreakpointDescriptor->Enabled = TRUE;
//
// Now we should add the breakpoint to the list of breakpoints (LIST_ENTRY)
//
InsertHeadList(&g_BreakpointsListHead, &(BreakpointDescriptor->BreakpointsList));
//
// Apply the breakpoint
//
BreakpointWrite(BreakpointDescriptor, SwitchToTargetMemoryLayout);
//
// Show that operation was successful
//
BpDescriptorArg->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
return TRUE;
}
/**
* @brief List all breakpoints
*
* @return VOID
*/
VOID
BreakpointListAllBreakpoint()
{
BOOLEAN IsListEmpty = TRUE;
PLIST_ENTRY TempList = 0;
TempList = &g_BreakpointsListHead;
while (&g_BreakpointsListHead != TempList->Blink)
{
TempList = TempList->Blink;
PDEBUGGEE_BP_DESCRIPTOR CurrentBreakpointDesc = CONTAINING_RECORD(TempList, DEBUGGEE_BP_DESCRIPTOR, BreakpointsList);
if (IsListEmpty)
{
Log("Id Address Status\n");
Log("-- --------------- --------");
IsListEmpty = FALSE;
}
Log("\n%02x %016llx %s", CurrentBreakpointDesc->BreakpointId, CurrentBreakpointDesc->Address, CurrentBreakpointDesc->Enabled ? "enabled" : "disabled");
if (CurrentBreakpointDesc->Core != DEBUGGEE_BP_APPLY_TO_ALL_CORES)
{
Log(" core = %x ", CurrentBreakpointDesc->Core);
}
if (CurrentBreakpointDesc->Pid != DEBUGGEE_BP_APPLY_TO_ALL_PROCESSES)
{
Log(" pid = %x ", CurrentBreakpointDesc->Pid);
}
if (CurrentBreakpointDesc->Tid != DEBUGGEE_BP_APPLY_TO_ALL_THREADS)
{
Log(" tid = %x ", CurrentBreakpointDesc->Tid);
}
}
//
// Check if the list is empty or not
//
if (IsListEmpty)
{
Log("Breakpoints list is empty");
}
}
/**
* @brief List of modify breakpoints
* @param ListOrModifyBreakpoints
* @param SwitchToTargetMemoryLayout
*
* @return BOOLEAN
*/
BOOLEAN
BreakpointListOrModify(PDEBUGGEE_BP_LIST_OR_MODIFY_PACKET ListOrModifyBreakpoints, BOOLEAN SwitchToTargetMemoryLayout)
{
PDEBUGGEE_BP_DESCRIPTOR BreakpointDescriptor = NULL;
if (ListOrModifyBreakpoints->Request == DEBUGGEE_BREAKPOINT_MODIFICATION_REQUEST_LIST_BREAKPOINTS)
{
BreakpointListAllBreakpoint();
}
else if (ListOrModifyBreakpoints->Request == DEBUGGEE_BREAKPOINT_MODIFICATION_REQUEST_ENABLE)
{
BreakpointDescriptor = BreakpointGetEntryByBreakpointId(ListOrModifyBreakpoints->BreakpointId);
if (BreakpointDescriptor == NULL)
{
//
// Breakpoint id is invalid
//
ListOrModifyBreakpoints->Result = DEBUGGER_ERROR_BREAKPOINT_ID_NOT_FOUND;
return FALSE;
}
//
// Check to make sure that breakpoint is not already enabled
//
if (BreakpointDescriptor->Enabled)
{
ListOrModifyBreakpoints->Result = DEBUGGER_ERROR_BREAKPOINT_ALREADY_ENABLED;
return FALSE;
}
//
// Set the breakpoint (without removing from list)
//
BreakpointWrite(BreakpointDescriptor, SwitchToTargetMemoryLayout);
}
else if (ListOrModifyBreakpoints->Request == DEBUGGEE_BREAKPOINT_MODIFICATION_REQUEST_DISABLE)
{
BreakpointDescriptor = BreakpointGetEntryByBreakpointId(ListOrModifyBreakpoints->BreakpointId);
if (BreakpointDescriptor == NULL)
{
//
// Breakpoint id is invalid
//
ListOrModifyBreakpoints->Result = DEBUGGER_ERROR_BREAKPOINT_ID_NOT_FOUND;
return FALSE;
}
//
// Check to make sure that breakpoint is not already disabled
//
if (!BreakpointDescriptor->Enabled)
{
ListOrModifyBreakpoints->Result = DEBUGGER_ERROR_BREAKPOINT_ALREADY_DISABLED;
return FALSE;
}
//
// Unset the breakpoint (without removing from list)
//
BreakpointClear(BreakpointDescriptor);
}
else if (ListOrModifyBreakpoints->Request == DEBUGGEE_BREAKPOINT_MODIFICATION_REQUEST_CLEAR)
{
BreakpointDescriptor = BreakpointGetEntryByBreakpointId(ListOrModifyBreakpoints->BreakpointId);
if (BreakpointDescriptor == NULL)
{
//
// Breakpoint id is invalid
//
ListOrModifyBreakpoints->Result = DEBUGGER_ERROR_BREAKPOINT_ID_NOT_FOUND;
return FALSE;
}
//
// Clear and deallocate the breakpoint
//
BreakpointClearAndDeallocateMemory(BreakpointDescriptor);
}
//
// Operation was successful
//
ListOrModifyBreakpoints->Result = DEBUGGER_OPERATION_WAS_SUCCESSFUL;
return TRUE;
}