From 669a436946bf6174e77aa1269e49c97c127281ca Mon Sep 17 00:00:00 2001 From: Jakob Friedl <71284620+jakobfriedl@users.noreply.github.com> Date: Thu, 18 Sep 2025 12:35:26 +0200 Subject: [PATCH] Added console filter. --- src/client/layout.ini | 80 +++++++++++---------- src/client/utils/colors.nim | 8 +++ src/client/views/console.nim | 131 ++++++++++++++++++++++++++++------ src/client/views/sessions.nim | 8 +-- src/common/types.nim | 12 ++-- src/server/core/logger.nim | 8 +-- 6 files changed, 172 insertions(+), 75 deletions(-) create mode 100644 src/client/utils/colors.nim diff --git a/src/client/layout.ini b/src/client/layout.ini index 08350c7..f945832 100644 --- a/src/client/layout.ini +++ b/src/client/layout.ini @@ -1,43 +1,43 @@ [Window][Sessions [Table View]] Pos=10,43 -Size=2117,435 +Size=1477,281 Collapsed=0 DockId=0x00000003,0 [Window][Listeners] -Pos=10,480 -Size=2528,869 +Pos=10,326 +Size=944,663 Collapsed=0 -DockId=0x00000006,0 +DockId=0x00000007,0 [Window][Eventlog] -Pos=2129,43 -Size=409,435 +Pos=1489,43 +Size=409,281 Collapsed=0 DockId=0x00000004,0 [Window][Dear ImGui Demo] -Pos=2129,43 -Size=409,435 +Pos=1489,43 +Size=409,281 Collapsed=0 DockId=0x00000004,1 [Window][Dockspace] Pos=0,0 -Size=2548,1359 +Size=1908,999 Collapsed=0 [Window][[FACEDEAD] bob@LAPTOP-02] -Pos=10,480 -Size=2528,869 +Pos=956,326 +Size=942,663 Collapsed=0 -DockId=0x00000006,1 +DockId=0x00000008,0 [Window][[C9D8E7F6] charlie@SERVER-03] -Pos=10,480 -Size=1261,869 +Pos=956,326 +Size=942,663 Collapsed=0 -DockId=0x00000005,0 +DockId=0x00000006,0 [Window][Debug##Default] Pos=60,60 @@ -45,22 +45,22 @@ Size=400,400 Collapsed=0 [Window][[G1H2I3J5] diana@WORKSTATION-04] -Pos=10,480 -Size=2528,869 +Pos=10,326 +Size=944,663 Collapsed=0 -DockId=0x00000006,1 +DockId=0x00000007,1 [Window][[DEADBEEF] alice@DESKTOP-01] -Pos=10,604 -Size=2848,1081 +Pos=10,326 +Size=1888,663 Collapsed=0 -DockId=0x00000006,2 +DockId=0x00000007,1 [Window][Example: Console] -Pos=10,466 -Size=1888,523 +Pos=10,572 +Size=2848,1113 Collapsed=0 -DockId=0x00000006,1 +DockId=0x00000007,2 [Window][Example: Assets Browser] Pos=60,60 @@ -78,25 +78,27 @@ Size=1717,576 Collapsed=0 [Table][0x32886A44,8] -Column 0 Weight=0.6513 -Column 1 Weight=0.9753 -Column 2 Weight=0.3274 -Column 3 Weight=1.2856 -Column 4 Weight=1.7323 -Column 5 Weight=1.1492 -Column 6 Weight=0.4263 -Column 7 Weight=1.4527 +Column 0 Weight=0.6465 +Column 1 Weight=0.9697 +Column 2 Weight=0.6696 +Column 3 Weight=0.9639 +Column 4 Weight=1.7143 +Column 5 Weight=1.1544 +Column 6 Weight=0.4271 +Column 7 Weight=1.4546 [Table][0xB6880529,2] RefScale=27 Column 0 Sort=0v [Docking][Data] -DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=2528,1306 Split=Y - DockNode ID=0x00000001 Parent=0x85940918 SizeRef=1024,435 Split=X - DockNode ID=0x00000003 Parent=0x00000001 SizeRef=613,159 CentralNode=1 Selected=0x61E02D75 - DockNode ID=0x00000004 Parent=0x00000001 SizeRef=409,159 Selected=0x5E5F7166 - DockNode ID=0x00000002 Parent=0x85940918 SizeRef=1024,869 Split=X Selected=0x8D780333 - DockNode ID=0x00000005 Parent=0x00000002 SizeRef=1261,869 Selected=0x65D642C0 - DockNode ID=0x00000006 Parent=0x00000002 SizeRef=1265,869 Selected=0x8D780333 +DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=1888,946 Split=Y + DockNode ID=0x00000001 Parent=0x85940918 SizeRef=1024,281 Split=X + DockNode ID=0x00000003 Parent=0x00000001 SizeRef=613,159 CentralNode=1 Selected=0x61E02D75 + DockNode ID=0x00000004 Parent=0x00000001 SizeRef=409,159 Selected=0x5E5F7166 + DockNode ID=0x00000002 Parent=0x85940918 SizeRef=1024,663 Split=X Selected=0x8D780333 + DockNode ID=0x00000005 Parent=0x00000002 SizeRef=944,663 Split=X Selected=0x8D780333 + DockNode ID=0x00000007 Parent=0x00000005 SizeRef=944,663 Selected=0x8D780333 + DockNode ID=0x00000008 Parent=0x00000005 SizeRef=942,663 Selected=0x4AD091E6 + DockNode ID=0x00000006 Parent=0x00000002 SizeRef=942,663 Selected=0x65D642C0 diff --git a/src/client/utils/colors.nim b/src/client/utils/colors.nim new file mode 100644 index 0000000..b7436d6 --- /dev/null +++ b/src/client/utils/colors.nim @@ -0,0 +1,8 @@ +import imguin/[cimgui, glfw_opengl, simple] +import ../utils/appImGui + +const CONSOLE_ERROR* = vec4(0.878f, 0.188f, 0.149f, 1.0f) +const CONSOLE_INFO* = vec4(0.588f, 0.843f, 0.89f, 1.0f) +const CONSOLE_SUCCESS* = vec4(0.176f, 0.569f, 0.075f, 1.0f) +const CONSOLE_WARNING* = vec4(1.0f, 0.5f, 0.0f, 1.0f) +const CONSOLE_COMMAND* = vec4(0.922f, 0.914f, 0.463f, 1.0f) \ No newline at end of file diff --git a/src/client/views/console.nim b/src/client/views/console.nim index 5178faf..d550823 100644 --- a/src/client/views/console.nim +++ b/src/client/views/console.nim @@ -1,6 +1,6 @@ import strformat, strutils, times import imguin/[cimgui, glfw_opengl, simple] -import ../utils/appImGui +import ../utils/[appImGui, colors] import ../../common/[types] const MAX_INPUT_LENGTH = 512 @@ -14,13 +14,41 @@ type historyPosition: int currentInput: string textSelect: ptr TextSelect + filter: ptr ImGuiTextFilter #[ Helper functions for text selection ]# -proc getItemText(item: ConsoleItem): cstring = - let timestamp = item.timestamp.format("dd-MM-yyyy HH:mm:ss") - return fmt"[{timestamp}] {$item.itemType} {item.text}".string +proc getText(item: ConsoleItem): cstring = + if item.timestamp > 0: + let timestamp = item.timestamp.fromUnix().format("dd-MM-yyyy HH:mm:ss") + return fmt"[{timestamp}]{$item.itemType}{item.text}".string + else: + return fmt"{$item.itemType}{item.text}".string + +proc print(item: ConsoleItem) = + if item.timestamp > 0: + let timestamp = item.timestamp.fromUnix().format("dd-MM-yyyy HH:mm:ss") + igTextColored(vec4(0.6f, 0.6f, 0.6f, 1.0f), fmt"[{timestamp}]".cstring) + igSameLine(0.0f, 0.0f) + + # https://rgbcolorpicker.com/0-1 + case item.itemType: + of LOG_INFO: + igTextColored(CONSOLE_INFO, $item.itemType) + of LOG_ERROR: + igTextColored(CONSOLE_ERROR, $item.itemType) + of LOG_SUCCESS: + igTextColored(CONSOLE_SUCCESS, $item.itemType) + of LOG_WARNING: + igTextColored(CONSOLE_WARNING, $item.itemType) + of LOG_COMMAND: + igTextColored(CONSOLE_COMMAND, $item.itemType) + of LOG_OUTPUT: + igTextColored(vec4(0.0f, 0.0f, 0.0f, 0.0f), $item.itemType) + + igSameLine(0.0f, 0.0f) + igTextUnformatted(item.text.cstring, nil) proc getNumLines(data: pointer): csize_t {.cdecl.} = if data.isNil: @@ -32,7 +60,7 @@ proc getLineAtIndex(i: csize_t, data: pointer, outLen: ptr csize_t): cstring {.c if data.isNil: return nil let console = cast[ConsoleItems](data) - let line = getItemText(console.items[i]) + let line = console.items[i].getText() if not outLen.isNil: outLen[] = line.len.csize_t return line @@ -48,6 +76,7 @@ proc Console*(agent: Agent): ConsoleComponent = result.historyPosition = -1 result.currentInput = "" result.textSelect = textselect_create(getLineAtIndex, getNumLines, cast[pointer](result.console), 0) + result.filter = ImGuiTextFilter_ImGuiTextFilter("") #[ Text input callback function for managing console history and autocompletion @@ -109,6 +138,8 @@ proc draw*(component: ConsoleComponent) = igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0) defer: igEnd() + let io = igGetIO() + var focusInput = false #[ @@ -126,11 +157,34 @@ proc draw*(component: ConsoleComponent) = Huge thanks to @dinau for implementing ImGuiTextSelect into imguin very rapidly after I requested it. ]# let consolePadding: float = 10.0f - let footerHeight = (consolePadding * 2) + (igGetStyle().ItemSpacing.y + igGetFrameHeightWithSpacing()) + let footerHeight = (consolePadding * 2) + (igGetStyle().ItemSpacing.y + igGetFrameHeightWithSpacing()) * 1.5f let textSpacing = igGetStyle().ItemSpacing.x # Padding igDummy(vec2(0.0f, consolePadding)) + + #[ + Filter & Options + ]# + var labelSize: ImVec2 + igCalcTextSize(addr labelSize, ICON_FA_MAGNIFYING_GLASS, nil, false, 0.0f) + igSameLine(0.0f, igGetWindowWidth() - 200.0f - (labelSize.x + textSpacing) - (igGetStyle().WindowPadding.x * 2)) + + # SHow tooltip when hovering the search icon + igTextUnformatted(ICON_FA_MAGNIFYING_GLASS.cstring, nil) + if igIsItemHovered(ImGuiHoveredFlags_None.int32): + igBeginTooltip() + igText("Press CTRL+F to focus console filter.") + igText("Use \",\" as a delimiter to filter for multiple values.") + igText("Use \"-\" to exclude values.") + igEndTooltip() + + if igIsWindowFocused(ImGui_FocusedFlags_ChildWindows.int32) and io.KeyCtrl and igIsKeyPressed_Bool(ImGuiKey_F, false): + igSetKeyboardFocusHere(0) + + igSameLine(0.0f, textSpacing) + component.filter.ImGuiTextFilter_Draw("##ConsoleSearch", 200.0f) + try: # Set styles of the console window igPushStyleColor_Vec4(ImGui_Col_FrameBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f)) @@ -141,13 +195,14 @@ proc draw*(component: ConsoleComponent) = let childWindowFlags = ImGuiChildFlags_NavFlattened.int32 or ImGui_ChildFlags_Borders.int32 or ImGui_ChildFlags_AlwaysUseWindowPadding.int32 or ImGuiChildFlags_FrameStyle.int32 if igBeginChild_Str("##Console", vec2(-1.0f, -footerHeight), childWindowFlags, ImGuiWindowFlags_HorizontalScrollbar.int32): # Display console items - for entry in component.console.items: - let timestamp = entry.timestamp.format("dd-MM-yyyy HH:mm:ss") - igTextColored(vec4(0.6f, 0.6f, 0.6f, 1.0f), fmt"[{timestamp}]".cstring) - igSameLine(0.0f, textSpacing) - igTextColored(vec4(0.0f, 1.0f, 1.0f, 1.0f), $entry.itemType) - igSameLine(0.0f, textSpacing) - igTextUnformatted(entry.text.cstring, nil) + for item in component.console.items: + + # Apply filter + if component.filter.ImGuiTextFilter_IsActive(): + if not component.filter.ImGuiTextFilter_PassFilter(item.getText(), nil): + continue + + item.print() component.textSelect.textselect_update() @@ -182,13 +237,45 @@ proc draw*(component: ConsoleComponent) = if igInputText("##Input", addr component.inputBuffer[0], MAX_INPUT_LENGTH, inputFlags, callback, cast[pointer](component)): let command = $(addr component.inputBuffer[0]).cstring - let commandItem = ConsoleItem( - timestamp: now(), + var commandItem = ConsoleItem( + timestamp: now().toTime().toUnix(), itemType: LOG_COMMAND, text: command ) component.console.items.add(commandItem) + # For testing + commandItem = ConsoleItem( + timestamp: now().toTime().toUnix(), + itemType: LOG_ERROR, + text: "error" + ) + component.console.items.add(commandItem) + commandItem = ConsoleItem( + timestamp: now().toTime().toUnix(), + itemType: LOG_SUCCESS, + text: "success" + ) + component.console.items.add(commandItem) + commandItem = ConsoleItem( + timestamp: now().toTime().toUnix(), + itemType: LOG_WARNING, + text: "warn" + ) + component.console.items.add(commandItem) + commandItem = ConsoleItem( + timestamp: 0, + itemType: LOG_OUTPUT, + text: "output" + ) + component.console.items.add(commandItem) + commandItem = ConsoleItem( + timestamp: now().toTime().toUnix(), + itemType: LOG_INFO, + text: "info" + ) + component.console.items.add(commandItem) + # TODO: Handle command execution # console.handleCommand(command) @@ -199,13 +286,13 @@ proc draw*(component: ConsoleComponent) = zeroMem(addr component.inputBuffer[0], MAX_INPUT_LENGTH) focusInput = true - #[ - Session information (optional footer) - ]# - # igSeparator() - # let sessionInfo = fmt"{component.agent.username}@{component.agent.hostname} [{component.agent.ip}]" - # igText(sessionInfo) - igSetItemDefaultFocus() if focusInput: igSetKeyboardFocusHere(-1) + + #[ + Session information + ]# + let sessionInfo = fmt"{component.agent.username}@{component.agent.hostname}.{component.agent.domain} [{component.agent.ip}] [{component.agent.process}/{$component.agent.pid}]" + igTextColored(vec4(0.75f, 0.75f, 0.75f, 1.0f), sessionInfo) + diff --git a/src/client/views/sessions.nim b/src/client/views/sessions.nim index 98bbd2d..eb260dd 100644 --- a/src/client/views/sessions.nim +++ b/src/client/views/sessions.nim @@ -18,7 +18,7 @@ let exampleAgents: seq[Agent] = @[ listenerId: "L1234567", username: "alice", hostname: "DESKTOP-01", - domain: "CORP", + domain: "corp.local", ip: "192.168.1.10", os: "Windows 10", process: "explorer.exe", @@ -35,7 +35,7 @@ let exampleAgents: seq[Agent] = @[ listenerId: "L7654321", username: "bob", hostname: "LAPTOP-02", - domain: "SALES", + domain: "corp.local", ip: "10.0.0.5", os: "Windows 11", process: "cmd.exe", @@ -52,7 +52,7 @@ let exampleAgents: seq[Agent] = @[ listenerId: "L2468135", username: "charlie", hostname: "SERVER-03", - domain: "IT", + domain: "child.corp.local", ip: "172.16.0.20", os: "Windows Server 2019", process: "powershell.exe", @@ -69,7 +69,7 @@ let exampleAgents: seq[Agent] = @[ listenerId: "L1357924", username: "diana", hostname: "WORKSTATION-04", - domain: "HR", + domain: "external.local", ip: "192.168.2.15", os: "Windows 10", process: "chrome.exe", diff --git a/src/common/types.nim b/src/common/types.nim index 4b3fef8..f371458 100644 --- a/src/common/types.nim +++ b/src/common/types.nim @@ -86,11 +86,11 @@ type # LOG_WARNING = "[ ! ] " # LOG_COMMAND = "[ > ] " # LOG_OUTPUT = "" - LOG_INFO = "[INFO]" - LOG_ERROR = "[FAIL]" - LOG_SUCCESS = "[DONE]" - LOG_WARNING = "[WARN]" - LOG_COMMAND = "[>>>>]" + LOG_INFO = " [INFO] " + LOG_ERROR = " [FAIL] " + LOG_SUCCESS = " [DONE] " + LOG_WARNING = " [WARN] " + LOG_COMMAND = " [>>>>] " LOG_OUTPUT = "" SleepObfuscationTechnique* = enum @@ -256,8 +256,8 @@ type # Definitions for ImGui User interface type ConsoleItem* = ref object - timestamp*: DateTime itemType*: LogType + timestamp*: int64 text*: string ConsoleItems* = ref object diff --git a/src/server/core/logger.nim b/src/server/core/logger.nim index a644f8b..ede28bc 100644 --- a/src/server/core/logger.nim +++ b/src/server/core/logger.nim @@ -43,16 +43,16 @@ template writeLine*(cq: Conquest, args: varargs[untyped] = "") = # Wrapper functions for logging/console output template info*(cq: Conquest, args: varargs[untyped] = "") = - cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}] ", $LOG_INFO, resetStyle, " ", args) + cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}]", $LOG_INFO, resetStyle, args) template error*(cq: Conquest, args: varargs[untyped] = "") = - cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}] ", fgRed, $LOG_ERROR, resetStyle, " ", args) + cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}]", fgRed, $LOG_ERROR, resetStyle, args) template success*(cq: Conquest, args: varargs[untyped] = "") = - cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}] ", fgGreen, $LOG_SUCCESS, resetStyle, " ", args) + cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}]", fgGreen, $LOG_SUCCESS, resetStyle, args) template warning*(cq: Conquest, args: varargs[untyped] = "") = - cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}] ", fgYellow, styleDim, $LOG_WARNING, resetStyle, " ", args) + cq.writeLine(fgBlack, styleBright, fmt"[{getTimestamp()}]", fgYellow, styleDim, $LOG_WARNING, resetStyle, args) template input*(cq: Conquest, args: varargs[untyped] = "") = if cq.interactAgent != nil: