/** * @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 : // 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 // *(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; } }