/** * @file events.cpp * @author Sina Karvandi (sina@hyperdbg.org) * @brief events commands * @details * @version 0.1 * @date 2020-07-24 * * @copyright This project is released under the GNU Public License v3. * */ #include "pch.h" // // Global Variables // extern LIST_ENTRY g_EventTrace; extern BOOLEAN g_EventTraceInitialized; extern BOOLEAN g_BreakPrintingOutput; extern BOOLEAN g_AutoFlush; extern BOOLEAN g_IsVmmModuleLoaded; extern BOOLEAN g_IsConnectedToRemoteDebuggee; extern BOOLEAN g_IsSerialConnectedToRemoteDebuggee; extern BOOLEAN g_IsSerialConnectedToRemoteDebugger; extern UINT64 g_EventTag; /** * @brief help of the events command * * @return VOID */ VOID CommandEventsHelp() { ShowMessages("events : shows active and disabled events\n"); ShowMessages("syntax : \tevents\n"); ShowMessages("syntax : \tevents [e|d|c all|EventNumber (hex)]\n"); ShowMessages("syntax : \tevents [sc State (on|off)]\n"); ShowMessages("e : enable\n"); ShowMessages("d : disable\n"); ShowMessages("c : clear\n"); ShowMessages("note : If you specify 'all' then e, d, or c will be applied to " "all of the events.\n\n"); ShowMessages("\n"); ShowMessages("\te.g : events \n"); ShowMessages("\te.g : events e 12\n"); ShowMessages("\te.g : events d 10\n"); ShowMessages("\te.g : events c 10\n"); ShowMessages("\te.g : events c all\n"); ShowMessages("\te.g : events sc on\n"); ShowMessages("\te.g : events sc off\n"); } /** * @brief events command handler * * @param CommandTokens * @param Command * * @return VOID */ VOID CommandEvents(vector CommandTokens, string Command) { DEBUGGER_MODIFY_EVENTS_TYPE RequestedAction; UINT64 RequestedTag; // // Validate the parameters (size) // if (CommandTokens.size() != 1 && CommandTokens.size() != 3) { ShowMessages("incorrect use of the '%s'\n\n", GetCaseSensitiveStringFromCommandToken(CommandTokens.at(0)).c_str()); CommandEventsHelp(); return; } if (CommandTokens.size() == 1) { if (!g_EventTraceInitialized) { ShowMessages("no active/disabled events \n"); return; } CommandEventsShowEvents(); // // No need to continue any further // return; } // // Validate second argument as it's not just a simple // events without any parameter // if (CompareLowerCaseStrings(CommandTokens.at(1), "e")) { RequestedAction = DEBUGGER_MODIFY_EVENTS_ENABLE; } else if (CompareLowerCaseStrings(CommandTokens.at(1), "d")) { RequestedAction = DEBUGGER_MODIFY_EVENTS_DISABLE; } else if (CompareLowerCaseStrings(CommandTokens.at(1), "c")) { RequestedAction = DEBUGGER_MODIFY_EVENTS_CLEAR; } else if (CompareLowerCaseStrings(CommandTokens.at(1), "sc")) { if (CompareLowerCaseStrings(CommandTokens.at(2), "on")) { KdSendShortCircuitingEventToDebuggee(TRUE); } else if (CompareLowerCaseStrings(CommandTokens.at(2), "off")) { KdSendShortCircuitingEventToDebuggee(FALSE); } else { ShowMessages( "please specify a correct 'on' or 'off' state for the short-circuiting state\n\n"); CommandEventsHelp(); return; } // // No need to further continue // return; } else { // // unknown second command // ShowMessages("incorrect use of the '%s'\n\n", GetCaseSensitiveStringFromCommandToken(CommandTokens.at(0)).c_str()); CommandEventsHelp(); return; } // // Validate third argument as it's not just a simple // events without any parameter // if (CompareLowerCaseStrings(CommandTokens.at(2), "all")) { RequestedTag = DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG; } else if (!ConvertTokenToUInt64(CommandTokens.at(2), &RequestedTag)) { ShowMessages( "please specify a correct hex value for tag id (event number)\n\n"); CommandEventsHelp(); return; } // // Send the request to the kernel, we add it to a constant // that's because we want start tags from that constant // if (RequestedTag != DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG) { RequestedTag = RequestedTag + DebuggerEventTagStartSeed; } // // Perform event related tasks // CommandEventsModifyAndQueryEvents(RequestedTag, RequestedAction); } /** * @brief Check the kernel whether the event is enabled or disabled * * @param Tag the tag of the target event * @return BOOLEAN if the event was enabled and false if event was * disabled */ BOOLEAN CommandEventQueryEventState(UINT64 Tag) { BOOLEAN IsEnabled; if (g_IsSerialConnectedToRemoteDebuggee) { // // It's a remote debugger in Debugger Mode // if (KdSendEventQueryAndModifyPacketToDebuggee( Tag, DEBUGGER_MODIFY_EVENTS_QUERY_STATE, &IsEnabled)) { return IsEnabled; } else { ShowMessages("err, unable to get the state of the event\n"); return FALSE; } } else { // // It's a local debugging in VMI Mode // return CommandEventsModifyAndQueryEvents( Tag, DEBUGGER_MODIFY_EVENTS_QUERY_STATE); } // // By default, disabled, even if there was an error // return FALSE; } /** * @brief print every active and disabled events * @details this function will not show cleared events * @return VOID */ VOID CommandEventsShowEvents() { // // It's an events without any argument so we have to show // all the currently active events // PLIST_ENTRY TempList = 0; BOOLEAN IsThereAnyEvents = FALSE; TempList = &g_EventTrace; while (&g_EventTrace != TempList->Blink) { TempList = TempList->Blink; PDEBUGGER_GENERAL_EVENT_DETAIL CommandDetail = CONTAINING_RECORD(TempList, DEBUGGER_GENERAL_EVENT_DETAIL, CommandsEventList); string CommandMessage((char *)CommandDetail->CommandStringBuffer); // // Do not show the \n(s) // ReplaceAll(CommandMessage, "\n", " "); // // Only show portion of message // if (CommandMessage.length() > 70) { CommandMessage = CommandMessage.substr(0, 70); CommandMessage += "..."; } ShowMessages("%x\t(%s)\t %s\n", CommandDetail->Tag - DebuggerEventTagStartSeed, // CommandDetail->IsEnabled ? "enabled" : "disabled", CommandEventQueryEventState(CommandDetail->Tag) ? "enabled" : "disabled", /* Query is live now */ CommandMessage.c_str()); if (!IsThereAnyEvents) { IsThereAnyEvents = TRUE; } } if (!IsThereAnyEvents) { ShowMessages("no active/disabled events \n"); } } /** * @brief Disable a special event * * @param Tag the tag of the target event * @return BOOLEAN if the operation was successful then it returns * true otherwise it returns false */ BOOLEAN CommandEventDisableEvent(UINT64 Tag) { PLIST_ENTRY TempList = 0; BOOLEAN Result = FALSE; TempList = &g_EventTrace; while (&g_EventTrace != TempList->Blink) { TempList = TempList->Blink; PDEBUGGER_GENERAL_EVENT_DETAIL CommandDetail = CONTAINING_RECORD(TempList, DEBUGGER_GENERAL_EVENT_DETAIL, CommandsEventList); if (CommandDetail->Tag == Tag || Tag == DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG) { // // Put it to FALSE, to indicate that it's not active // CommandDetail->IsEnabled = FALSE; if (!Result) { Result = TRUE; } if (Tag != DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG) { // // Only, one command exist with a tag, so we need to return as we // find it // return TRUE; } } } // // Not found // return Result; } /** * @brief enables a special event * * @param Tag the tag of the target event * @return BOOLEAN if the operation was successful then it returns * true otherwise it returns false */ BOOLEAN CommandEventEnableEvent(UINT64 Tag) { PLIST_ENTRY TempList = 0; BOOLEAN Result = FALSE; TempList = &g_EventTrace; while (&g_EventTrace != TempList->Blink) { TempList = TempList->Blink; PDEBUGGER_GENERAL_EVENT_DETAIL CommandDetail = CONTAINING_RECORD(TempList, DEBUGGER_GENERAL_EVENT_DETAIL, CommandsEventList); if (CommandDetail->Tag == Tag || Tag == DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG) { // // Put it to TRUE, to indicate that it's active // CommandDetail->IsEnabled = TRUE; if (!Result) { Result = TRUE; } if (Tag != DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG) { // // Only, one command exist with a tag, so we need to return as we // find it // return TRUE; } } } // // Not found // return Result; } /** * @brief disable and remove a special event * * @param Tag the tag of the target event * @return BOOLEAN if the operation was successful then it returns * true otherwise it returns false */ BOOLEAN CommandEventClearEvent(UINT64 Tag) { PLIST_ENTRY TempList = 0; BOOLEAN Result = FALSE; PDEBUGGER_GENERAL_EVENT_DETAIL TmpCommandDetail = NULL; TempList = &g_EventTrace; while (&g_EventTrace != TempList->Flink) { TempList = TempList->Flink; PDEBUGGER_GENERAL_EVENT_DETAIL CommandDetail = CONTAINING_RECORD(TempList, DEBUGGER_GENERAL_EVENT_DETAIL, CommandsEventList); if (CommandDetail->Tag == Tag || Tag == DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG) { // // Free the buffer for string of command // free(CommandDetail->CommandStringBuffer); if (!Result) { Result = TRUE; // // We'll save one element so we can traverse and deallocate // all of the elements // TmpCommandDetail = CommandDetail; } if (Tag != DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG) { // // Remove it from the list // RemoveEntryList(&CommandDetail->CommandsEventList); // // Free the event it self // free(CommandDetail); // // Only, one command exist with a tag, so we need to return as we // find it // return TRUE; } } } if (Result && Tag == DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG) { // // We have to deallocate all the elements (note that CommandDetail->CommandStringBuffer is // already deallocated) // while ((UINT64)TmpCommandDetail != (UINT64)&g_EventTrace.Flink) { PVOID Temp = (PVOID)TmpCommandDetail; TmpCommandDetail = (PDEBUGGER_GENERAL_EVENT_DETAIL)TmpCommandDetail->CommandsEventList.Flink; free(Temp); } // // Reinitialize list head // InitializeListHead(&g_EventTrace); } // // Either not found or DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG is specified // return Result; } /** * @brief Clears all the events and resets the tag * * @return VOID */ VOID CommandEventsClearAllEventsAndResetTags() { // // Check if events are initialized // if (g_EventTraceInitialized) { CommandEventClearEvent(DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG); // // Indicate that it's not initialized // g_EventTraceInitialized = FALSE; // // Reset tag numbering mechanism // g_EventTag = DebuggerEventTagStartSeed; } } /** * @brief Handle events after modification * * @param Tag the tag of the target event * @param ModifyEventRequest Results * @return VOID */ VOID CommandEventsHandleModifiedEvent( UINT64 Tag, PDEBUGGER_MODIFY_EVENTS ModifyEventRequest) { if (ModifyEventRequest->KernelStatus == DEBUGGER_OPERATION_WAS_SUCCESSFUL) { // // Successful, nothing to show but we should also // do the same (change) the user-mode structures // that hold the event data // if (ModifyEventRequest->TypeOfAction == DEBUGGER_MODIFY_EVENTS_ENABLE) { if (!CommandEventEnableEvent(Tag)) { ShowMessages("err, the event was successfully, " "(enabled|disabled|cleared) but " "can't apply it to the user-mode structures\n"); } } else if (ModifyEventRequest->TypeOfAction == DEBUGGER_MODIFY_EVENTS_DISABLE) { if (!CommandEventDisableEvent(Tag)) { ShowMessages("err, the event was successfully, " "(enabled|disabled|cleared) but " "can't apply it to the user-mode structures\n"); } else { // // The action was applied successfully // if (g_BreakPrintingOutput) { // // It is because we didn't query the target debuggee auto-flush // variable // if (!g_IsConnectedToRemoteDebuggee && !g_IsSerialConnectedToRemoteDebuggee && !g_IsSerialConnectedToRemoteDebugger) { if (!g_AutoFlush) { ShowMessages( "auto-flush mode is disabled, if there is still " "messages or buffers in the kernel, you continue to see " "the messages when you run 'g' until the kernel " "buffers are empty. you can run 'settings autoflush " "on' and after disabling and clearing events, " "kernel buffers will be flushed automatically\n"); } else { // // We should flush buffers here // CommandFlushRequestFlush(); } } } } } else if (ModifyEventRequest->TypeOfAction == DEBUGGER_MODIFY_EVENTS_CLEAR) { if (!CommandEventClearEvent(Tag)) { ShowMessages("err, the event was successfully, " "(enabled|disabled|cleared) but " "can't apply it to the user-mode structures\n"); } else { // // If HyperDbg is operating at the Debugger Mode, we'll indicate that // the event will be cleared after continuing the debuggee // if (g_IsSerialConnectedToRemoteDebuggee) { ShowMessages("%s successfully cleared, but please note in the Debugger Mode (current mode), HyperDbg " "cannot clear events instantly. Instead, it first disables the events, and when " "you continue the debuggee (e.g., by pressing the 'g' command), the event will be cleared. " "Reapplying the same event without first continuing the debuggee may result in undefined behavior\n", Tag == DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG ? "events are" : "the event is"); } // // The action was applied successfully // if (g_BreakPrintingOutput) { // // It is because we didn't query the target debuggee auto-flush // variable // if (!g_IsConnectedToRemoteDebuggee) { if (!g_AutoFlush) { ShowMessages( "auto-flush mode is disabled, if there is still " "messages or buffers in the kernel, you continue to see " "the messages when you run 'g' until the kernel " "buffers are empty. you can run 'settings autoflush " "on' and after disabling and clearing events, " "kernel buffers will be flushed automatically\n"); } else { // // We should flush buffers here // CommandFlushRequestFlush(); } } } } } else if (ModifyEventRequest->TypeOfAction == DEBUGGER_MODIFY_EVENTS_QUERY_STATE) { // // Nothing to show // } else { ShowMessages( "err, the event was successfully, (enabled|disabled|cleared) but " "can't apply it to the user-mode structures\n"); } } else { // // Interpret error // ShowErrorMessage((UINT32)ModifyEventRequest->KernelStatus); return; } } /** * @brief modify a special event (enable/disable/clear) and send the * request directly to the kernel * @details if you pass DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG as the * tag then it will be applied to all the active/disabled events in the * kernel * * @param Tag the tag of the target event * @param TypeOfAction whether its a enable/disable/clear * @return BOOLEAN Shows whether the event is enabled or disabled */ BOOLEAN CommandEventsModifyAndQueryEvents(UINT64 Tag, DEBUGGER_MODIFY_EVENTS_TYPE TypeOfAction) { BOOLEAN Status; ULONG ReturnedLength; DEBUGGER_MODIFY_EVENTS ModifyEventRequest = {0}; // // Check if there is no event, then we shouldn't apply the // enable, disable, or clear commands, this command also // checks for DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG to // see if at least one tag exists or not; however, it's not // necessary as the kernel will check for the validity of // tag too, but let's not send it to kernel as we can prevent // invalid requests from user-mode too // if (!IsTagExist(Tag)) { if (Tag == DEBUGGER_MODIFY_EVENTS_APPLY_TO_ALL_TAG) { ShowMessages("there is no event\n"); } else { ShowMessages("err, tag id is invalid\n"); } return FALSE; } if (g_IsSerialConnectedToRemoteDebuggee) { // // Remote debuggee Debugger Mode // KdSendEventQueryAndModifyPacketToDebuggee(Tag, TypeOfAction, NULL); } else { // // Local debugging VMI-Mode // // // Check if the VMM module is loaded or not // AssertShowMessageReturnStmt(g_IsVmmModuleLoaded, g_DeviceHandle, ASSERT_MESSAGE_VMM_NOT_LOADED, ASSERT_MESSAGE_DRIVER_NOT_LOADED, AssertReturnFalse); // // Fill the structure to send it to the kernel // ModifyEventRequest.Tag = Tag; ModifyEventRequest.TypeOfAction = TypeOfAction; // // Send the request to the kernel // Status = DeviceIoControl(g_DeviceHandle, // Handle to device IOCTL_DEBUGGER_MODIFY_EVENTS, // IO Control Code (IOCTL) &ModifyEventRequest, // Input Buffer to driver. SIZEOF_DEBUGGER_MODIFY_EVENTS, // Input buffer length &ModifyEventRequest, // Output Buffer from driver. SIZEOF_DEBUGGER_MODIFY_EVENTS, // Length of output // buffer in bytes. &ReturnedLength, // Bytes placed in buffer. NULL // synchronous call ); if (!Status) { ShowMessages("ioctl failed with code 0x%x\n", GetLastError()); return FALSE; } // // Perform further actions // CommandEventsHandleModifiedEvent(Tag, &ModifyEventRequest); if (TypeOfAction == DEBUGGER_MODIFY_EVENTS_QUERY_STATE) { return ModifyEventRequest.IsEnabled; } } // // in all the cases except query state it shows whether the operation was // successful or not // return TRUE; }