Added console filter.

This commit is contained in:
Jakob Friedl
2025-09-18 12:35:26 +02:00
parent 5d09efd823
commit 669a436946
6 changed files with 172 additions and 75 deletions

View File

@@ -1,43 +1,43 @@
[Window][Sessions [Table View]] [Window][Sessions [Table View]]
Pos=10,43 Pos=10,43
Size=2117,435 Size=1477,281
Collapsed=0 Collapsed=0
DockId=0x00000003,0 DockId=0x00000003,0
[Window][Listeners] [Window][Listeners]
Pos=10,480 Pos=10,326
Size=2528,869 Size=944,663
Collapsed=0 Collapsed=0
DockId=0x00000006,0 DockId=0x00000007,0
[Window][Eventlog] [Window][Eventlog]
Pos=2129,43 Pos=1489,43
Size=409,435 Size=409,281
Collapsed=0 Collapsed=0
DockId=0x00000004,0 DockId=0x00000004,0
[Window][Dear ImGui Demo] [Window][Dear ImGui Demo]
Pos=2129,43 Pos=1489,43
Size=409,435 Size=409,281
Collapsed=0 Collapsed=0
DockId=0x00000004,1 DockId=0x00000004,1
[Window][Dockspace] [Window][Dockspace]
Pos=0,0 Pos=0,0
Size=2548,1359 Size=1908,999
Collapsed=0 Collapsed=0
[Window][[FACEDEAD] bob@LAPTOP-02] [Window][[FACEDEAD] bob@LAPTOP-02]
Pos=10,480 Pos=956,326
Size=2528,869 Size=942,663
Collapsed=0 Collapsed=0
DockId=0x00000006,1 DockId=0x00000008,0
[Window][[C9D8E7F6] charlie@SERVER-03] [Window][[C9D8E7F6] charlie@SERVER-03]
Pos=10,480 Pos=956,326
Size=1261,869 Size=942,663
Collapsed=0 Collapsed=0
DockId=0x00000005,0 DockId=0x00000006,0
[Window][Debug##Default] [Window][Debug##Default]
Pos=60,60 Pos=60,60
@@ -45,22 +45,22 @@ Size=400,400
Collapsed=0 Collapsed=0
[Window][[G1H2I3J5] diana@WORKSTATION-04] [Window][[G1H2I3J5] diana@WORKSTATION-04]
Pos=10,480 Pos=10,326
Size=2528,869 Size=944,663
Collapsed=0 Collapsed=0
DockId=0x00000006,1 DockId=0x00000007,1
[Window][[DEADBEEF] alice@DESKTOP-01] [Window][[DEADBEEF] alice@DESKTOP-01]
Pos=10,604 Pos=10,326
Size=2848,1081 Size=1888,663
Collapsed=0 Collapsed=0
DockId=0x00000006,2 DockId=0x00000007,1
[Window][Example: Console] [Window][Example: Console]
Pos=10,466 Pos=10,572
Size=1888,523 Size=2848,1113
Collapsed=0 Collapsed=0
DockId=0x00000006,1 DockId=0x00000007,2
[Window][Example: Assets Browser] [Window][Example: Assets Browser]
Pos=60,60 Pos=60,60
@@ -78,25 +78,27 @@ Size=1717,576
Collapsed=0 Collapsed=0
[Table][0x32886A44,8] [Table][0x32886A44,8]
Column 0 Weight=0.6513 Column 0 Weight=0.6465
Column 1 Weight=0.9753 Column 1 Weight=0.9697
Column 2 Weight=0.3274 Column 2 Weight=0.6696
Column 3 Weight=1.2856 Column 3 Weight=0.9639
Column 4 Weight=1.7323 Column 4 Weight=1.7143
Column 5 Weight=1.1492 Column 5 Weight=1.1544
Column 6 Weight=0.4263 Column 6 Weight=0.4271
Column 7 Weight=1.4527 Column 7 Weight=1.4546
[Table][0xB6880529,2] [Table][0xB6880529,2]
RefScale=27 RefScale=27
Column 0 Sort=0v Column 0 Sort=0v
[Docking][Data] [Docking][Data]
DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=2528,1306 Split=Y DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=1888,946 Split=Y
DockNode ID=0x00000001 Parent=0x85940918 SizeRef=1024,435 Split=X DockNode ID=0x00000001 Parent=0x85940918 SizeRef=1024,281 Split=X
DockNode ID=0x00000003 Parent=0x00000001 SizeRef=613,159 CentralNode=1 Selected=0x61E02D75 DockNode ID=0x00000003 Parent=0x00000001 SizeRef=613,159 CentralNode=1 Selected=0x61E02D75
DockNode ID=0x00000004 Parent=0x00000001 SizeRef=409,159 Selected=0x5E5F7166 DockNode ID=0x00000004 Parent=0x00000001 SizeRef=409,159 Selected=0x5E5F7166
DockNode ID=0x00000002 Parent=0x85940918 SizeRef=1024,869 Split=X Selected=0x8D780333 DockNode ID=0x00000002 Parent=0x85940918 SizeRef=1024,663 Split=X Selected=0x8D780333
DockNode ID=0x00000005 Parent=0x00000002 SizeRef=1261,869 Selected=0x65D642C0 DockNode ID=0x00000005 Parent=0x00000002 SizeRef=944,663 Split=X Selected=0x8D780333
DockNode ID=0x00000006 Parent=0x00000002 SizeRef=1265,869 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

View File

@@ -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)

View File

@@ -1,6 +1,6 @@
import strformat, strutils, times import strformat, strutils, times
import imguin/[cimgui, glfw_opengl, simple] import imguin/[cimgui, glfw_opengl, simple]
import ../utils/appImGui import ../utils/[appImGui, colors]
import ../../common/[types] import ../../common/[types]
const MAX_INPUT_LENGTH = 512 const MAX_INPUT_LENGTH = 512
@@ -14,13 +14,41 @@ type
historyPosition: int historyPosition: int
currentInput: string currentInput: string
textSelect: ptr TextSelect textSelect: ptr TextSelect
filter: ptr ImGuiTextFilter
#[ #[
Helper functions for text selection Helper functions for text selection
]# ]#
proc getItemText(item: ConsoleItem): cstring = proc getText(item: ConsoleItem): cstring =
let timestamp = item.timestamp.format("dd-MM-yyyy HH:mm:ss") if item.timestamp > 0:
let timestamp = item.timestamp.fromUnix().format("dd-MM-yyyy HH:mm:ss")
return fmt"[{timestamp}]{$item.itemType}{item.text}".string 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.} = proc getNumLines(data: pointer): csize_t {.cdecl.} =
if data.isNil: if data.isNil:
@@ -32,7 +60,7 @@ proc getLineAtIndex(i: csize_t, data: pointer, outLen: ptr csize_t): cstring {.c
if data.isNil: if data.isNil:
return nil return nil
let console = cast[ConsoleItems](data) let console = cast[ConsoleItems](data)
let line = getItemText(console.items[i]) let line = console.items[i].getText()
if not outLen.isNil: if not outLen.isNil:
outLen[] = line.len.csize_t outLen[] = line.len.csize_t
return line return line
@@ -48,6 +76,7 @@ proc Console*(agent: Agent): ConsoleComponent =
result.historyPosition = -1 result.historyPosition = -1
result.currentInput = "" result.currentInput = ""
result.textSelect = textselect_create(getLineAtIndex, getNumLines, cast[pointer](result.console), 0) 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 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) igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0)
defer: igEnd() defer: igEnd()
let io = igGetIO()
var focusInput = false 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. Huge thanks to @dinau for implementing ImGuiTextSelect into imguin very rapidly after I requested it.
]# ]#
let consolePadding: float = 10.0f 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 let textSpacing = igGetStyle().ItemSpacing.x
# Padding # Padding
igDummy(vec2(0.0f, consolePadding)) 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: try:
# Set styles of the console window # Set styles of the console window
igPushStyleColor_Vec4(ImGui_Col_FrameBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f)) 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 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): if igBeginChild_Str("##Console", vec2(-1.0f, -footerHeight), childWindowFlags, ImGuiWindowFlags_HorizontalScrollbar.int32):
# Display console items # Display console items
for entry in component.console.items: for item 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) # Apply filter
igSameLine(0.0f, textSpacing) if component.filter.ImGuiTextFilter_IsActive():
igTextColored(vec4(0.0f, 1.0f, 1.0f, 1.0f), $entry.itemType) if not component.filter.ImGuiTextFilter_PassFilter(item.getText(), nil):
igSameLine(0.0f, textSpacing) continue
igTextUnformatted(entry.text.cstring, nil)
item.print()
component.textSelect.textselect_update() 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)): if igInputText("##Input", addr component.inputBuffer[0], MAX_INPUT_LENGTH, inputFlags, callback, cast[pointer](component)):
let command = $(addr component.inputBuffer[0]).cstring let command = $(addr component.inputBuffer[0]).cstring
let commandItem = ConsoleItem( var commandItem = ConsoleItem(
timestamp: now(), timestamp: now().toTime().toUnix(),
itemType: LOG_COMMAND, itemType: LOG_COMMAND,
text: command text: command
) )
component.console.items.add(commandItem) 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 # TODO: Handle command execution
# console.handleCommand(command) # console.handleCommand(command)
@@ -199,13 +286,13 @@ proc draw*(component: ConsoleComponent) =
zeroMem(addr component.inputBuffer[0], MAX_INPUT_LENGTH) zeroMem(addr component.inputBuffer[0], MAX_INPUT_LENGTH)
focusInput = true focusInput = true
#[
Session information (optional footer)
]#
# igSeparator()
# let sessionInfo = fmt"{component.agent.username}@{component.agent.hostname} [{component.agent.ip}]"
# igText(sessionInfo)
igSetItemDefaultFocus() igSetItemDefaultFocus()
if focusInput: if focusInput:
igSetKeyboardFocusHere(-1) 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)

View File

@@ -18,7 +18,7 @@ let exampleAgents: seq[Agent] = @[
listenerId: "L1234567", listenerId: "L1234567",
username: "alice", username: "alice",
hostname: "DESKTOP-01", hostname: "DESKTOP-01",
domain: "CORP", domain: "corp.local",
ip: "192.168.1.10", ip: "192.168.1.10",
os: "Windows 10", os: "Windows 10",
process: "explorer.exe", process: "explorer.exe",
@@ -35,7 +35,7 @@ let exampleAgents: seq[Agent] = @[
listenerId: "L7654321", listenerId: "L7654321",
username: "bob", username: "bob",
hostname: "LAPTOP-02", hostname: "LAPTOP-02",
domain: "SALES", domain: "corp.local",
ip: "10.0.0.5", ip: "10.0.0.5",
os: "Windows 11", os: "Windows 11",
process: "cmd.exe", process: "cmd.exe",
@@ -52,7 +52,7 @@ let exampleAgents: seq[Agent] = @[
listenerId: "L2468135", listenerId: "L2468135",
username: "charlie", username: "charlie",
hostname: "SERVER-03", hostname: "SERVER-03",
domain: "IT", domain: "child.corp.local",
ip: "172.16.0.20", ip: "172.16.0.20",
os: "Windows Server 2019", os: "Windows Server 2019",
process: "powershell.exe", process: "powershell.exe",
@@ -69,7 +69,7 @@ let exampleAgents: seq[Agent] = @[
listenerId: "L1357924", listenerId: "L1357924",
username: "diana", username: "diana",
hostname: "WORKSTATION-04", hostname: "WORKSTATION-04",
domain: "HR", domain: "external.local",
ip: "192.168.2.15", ip: "192.168.2.15",
os: "Windows 10", os: "Windows 10",
process: "chrome.exe", process: "chrome.exe",

View File

@@ -256,8 +256,8 @@ type
# Definitions for ImGui User interface # Definitions for ImGui User interface
type type
ConsoleItem* = ref object ConsoleItem* = ref object
timestamp*: DateTime
itemType*: LogType itemType*: LogType
timestamp*: int64
text*: string text*: string
ConsoleItems* = ref object ConsoleItems* = ref object

View File

@@ -43,16 +43,16 @@ template writeLine*(cq: Conquest, args: varargs[untyped] = "") =
# Wrapper functions for logging/console output # Wrapper functions for logging/console output
template info*(cq: Conquest, args: varargs[untyped] = "") = 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] = "") = 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] = "") = 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] = "") = 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] = "") = template input*(cq: Conquest, args: varargs[untyped] = "") =
if cq.interactAgent != nil: if cq.interactAgent != nil: