Refactored textarea from console, eventlog and buildlog into a separate widget to reduce code duplication.

This commit is contained in:
Jakob Friedl
2025-10-13 21:55:29 +02:00
parent 756ee09eeb
commit d9372dc880
10 changed files with 179 additions and 329 deletions

View File

@@ -130,21 +130,21 @@ proc main(ip: string = "localhost", port: int = 37573) =
of CLIENT_CONSOLE_ITEM:
let agentId = event.data["agentId"].getStr()
consoles[agentId].addItem(
consoles[agentId].console.addItem(
cast[LogType](event.data["logType"].getInt()),
event.data["message"].getStr(),
event.timestamp.fromUnix().local().format("dd-MM-yyyy HH:mm:ss")
)
of CLIENT_EVENTLOG_ITEM:
eventlog.addItem(
eventlog.textarea.addItem(
cast[LogType](event.data["logType"].getInt()),
event.data["message"].getStr(),
event.timestamp.fromUnix().local().format("dd-MM-yyyy HH:mm:ss")
)
of CLIENT_BUILDLOG_ITEM:
listenersTable.generatePayloadModal.addBuildlogItem(
listenersTable.generatePayloadModal.buildLog.addItem(
cast[LogType](event.data["logType"].getInt()),
event.data["message"].getStr(),
event.timestamp.fromUnix().local().format("dd-MM-yyyy HH:mm:ss")
@@ -193,7 +193,7 @@ proc main(ip: string = "localhost", port: int = 37573) =
# This is done to ensure that closed console windows can be opened again
consoles = newConsoleTable
# igShowDemoWindow(nil)
igShowDemoWindow(nil)
# render
app.render()

View File

@@ -5,6 +5,8 @@ import ../utils/[appImGui, colors]
import ../../common/[types, utils]
import ../../modules/manager
import ../core/[task, websocket]
import ./widgets/textarea
export addItem
const MAX_INPUT_LENGTH = 512
type
@@ -12,53 +14,21 @@ type
agent*: UIAgent
showConsole*: bool
inputBuffer: array[MAX_INPUT_LENGTH, char]
console*: ConsoleItems # Stores all console items
consoleFiltered*: ConsoleItems # Temporarily stores console items that are displayed to the user
console*: TextareaWidget
history: seq[string]
historyPosition: int
currentInput: string
textSelect: ptr TextSelect
filter: ptr ImGuiTextFilter
#[
Helper functions for text selection
]#
proc getText(item: ConsoleItem): cstring =
if item.itemType != LOG_OUTPUT:
# let timestamp = item.timestamp.fromUnix().format("dd-MM-yyyy HH:mm:ss")
return "[" & item.timestamp & "]" & $item.itemType & item.text
else:
return $item.itemType & item.text
proc getNumLines(data: pointer): csize_t {.cdecl.} =
if data.isNil:
return 0
let console = cast[ConsoleItems](data)
return console.items.len().csize_t
proc getLineAtIndex(i: csize_t, data: pointer, outLen: ptr csize_t): cstring {.cdecl.} =
if data.isNil:
return nil
let console = cast[ConsoleItems](data)
let line = console.items[i].getText()
if not outLen.isNil:
outLen[] = line.len.csize_t
return line
proc Console*(agent: UIAgent): ConsoleComponent =
result = new ConsoleComponent
result.agent = agent
result.showConsole = true
zeroMem(addr result.inputBuffer[0], MAX_INPUT_LENGTH)
result.console = new ConsoleItems
result.console.items = @[]
result.consoleFiltered = new ConsoleItems
result.consoleFiltered.items = @[]
result.console = Textarea()
result.history = @[]
result.historyPosition = -1
result.currentInput = ""
# Text selection covers only console items that are shown to the user even after using text filters
result.textSelect = textselect_create(getLineAtIndex, getNumLines, cast[pointer](result.consoleFiltered), 0)
result.filter = ImGuiTextFilter_ImGuiTextFilter("")
#[
@@ -173,47 +143,36 @@ proc callback(data: ptr ImGuiInputTextCallbackData): cint {.cdecl.} =
else: discard
#[
API to add new console item
]#
proc addItem*(component: ConsoleComponent, itemType: LogType, data: string, timestamp: string = now().format("dd-MM-yyyy HH:mm:ss")) =
for line in data.split("\n"):
component.console.items.add(ConsoleItem(
timestamp: timestamp,
itemType: itemType,
text: line
))
#[
Handling console commands
]#
proc displayHelp(component: ConsoleComponent) =
for module in getModules(component.agent.modules):
for cmd in module.commands:
component.addItem(LOG_OUTPUT, " * " & cmd.name.alignLeft(15) & cmd.description)
component.addItem(LOG_OUTPUT, "")
component.console.addItem(LOG_OUTPUT, " * " & cmd.name.alignLeft(15) & cmd.description)
component.console.addItem(LOG_OUTPUT, "")
proc displayCommandHelp(component: ConsoleComponent, command: Command) =
var usage = command.name & " " & command.arguments.mapIt(
if it.isRequired: "<" & it.name & ">" else: "[" & it.name & "]"
).join(" ")
component.addItem(LOG_OUTPUT, command.description)
component.addItem(LOG_OUTPUT, "Usage : " & usage)
component.addItem(LOG_OUTPUT, "Example : " & command.example)
component.addItem(LOG_OUTPUT, "")
component.console.addItem(LOG_OUTPUT, command.description)
component.console.addItem(LOG_OUTPUT, "Usage : " & usage)
component.console.addItem(LOG_OUTPUT, "Example : " & command.example)
component.console.addItem(LOG_OUTPUT, "")
if command.arguments.len > 0:
component.addItem(LOG_OUTPUT, "Arguments:")
component.console.addItem(LOG_OUTPUT, "Arguments:")
let header = @["Name", "Type", "Required", "Description"]
component.addItem(LOG_OUTPUT, " " & header[0].alignLeft(15) & " " & header[1].alignLeft(6) & " " & header[2].alignLeft(8) & " " & header[3])
component.addItem(LOG_OUTPUT, " " & '-'.repeat(15) & " " & '-'.repeat(6) & " " & '-'.repeat(8) & " " & '-'.repeat(20))
component.console.addItem(LOG_OUTPUT, " " & header[0].alignLeft(15) & " " & header[1].alignLeft(6) & " " & header[2].alignLeft(8) & " " & header[3])
component.console.addItem(LOG_OUTPUT, " " & '-'.repeat(15) & " " & '-'.repeat(6) & " " & '-'.repeat(8) & " " & '-'.repeat(20))
for arg in command.arguments:
let isRequired = if arg.isRequired: "YES" else: "NO"
component.addItem(LOG_OUTPUT, " * " & arg.name.alignLeft(15) & " " & ($arg.argumentType).toUpperAscii().alignLeft(6) & " " & isRequired.align(8) & " " & arg.description)
component.addItem(LOG_OUTPUT, "")
component.console.addItem(LOG_OUTPUT, " * " & arg.name.alignLeft(15) & " " & ($arg.argumentType).toUpperAscii().alignLeft(6) & " " & isRequired.align(8) & " " & arg.description)
component.console.addItem(LOG_OUTPUT, "")
proc handleHelp(component: ConsoleComponent, parsed: seq[string]) =
try:
@@ -224,7 +183,7 @@ proc handleHelp(component: ConsoleComponent, parsed: seq[string]) =
component.displayHelp()
except ValueError:
# Command was not found
component.addItem(LOG_ERROR, "The command '" & parsed[1] & "' does not exist.")
component.console.addItem(LOG_ERROR, "The command '" & parsed[1] & "' does not exist.")
proc handleAgentCommand*(component: ConsoleComponent, connection: WsConnection, input: string) =
# Convert user input into sequence of string arguments
@@ -242,37 +201,10 @@ proc handleAgentCommand*(component: ConsoleComponent, connection: WsConnection,
task = createTask(component.agent.agentId, component.agent.listenerId, command, parsedArgs[1..^1])
connection.sendAgentTask(component.agent.agentId, input, task)
component.addItem(LOG_INFO, "Tasked agent to " & command.description.toLowerAscii() & " (" & Uuid.toString(task.taskId) & ")")
component.console.addItem(LOG_INFO, "Tasked agent to " & command.description.toLowerAscii() & " (" & Uuid.toString(task.taskId) & ")")
except CatchableError:
component.addItem(LOG_ERROR, getCurrentExceptionMsg())
#[
Drawing
]#
proc print(item: ConsoleItem) =
if item.itemType != LOG_OUTPUT:
# let timestamp = item.timestamp.fromUnix().format("dd-MM-yyyy HH:mm:ss")
igTextColored(GRAY, "[" & item.timestamp & "]", nil)
igSameLine(0.0f, 0.0f)
case item.itemType:
of LOG_INFO, LOG_INFO_SHORT:
igTextColored(CONSOLE_INFO, $item.itemType)
of LOG_ERROR, LOG_ERROR_SHORT:
igTextColored(CONSOLE_ERROR, $item.itemType)
of LOG_SUCCESS, LOG_SUCCESS_SHORT:
igTextColored(CONSOLE_SUCCESS, $item.itemType)
of LOG_WARNING, LOG_WARNING_SHORT:
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)
component.console.addItem(LOG_ERROR, getCurrentExceptionMsg())
proc draw*(component: ConsoleComponent, connection: WsConnection) =
igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}".cstring, addr component.showConsole, 0)
@@ -338,45 +270,10 @@ proc draw*(component: ConsoleComponent, connection: WsConnection) =
igSameLine(0.0f, textSpacing)
component.filter.ImGuiTextFilter_Draw("##ConsoleSearch", searchBoxWidth)
try:
# 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_ScrollbarBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f))
igPushStyleColor_Vec4(ImGui_Col_Border.int32, vec4(0.2f, 0.2f, 0.2f, 1.0f))
igPushStyleVar_Float(ImGui_StyleVar_FrameBorderSize .int32, 1.0f)
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):
# Reset console items shown in the UI
component.consoleFiltered.items = @[]
# Display console items
for item in component.console.items:
# Apply filter
if component.filter.ImGuiTextFilter_IsActive():
if not component.filter.ImGuiTextFilter_PassFilter(item.getText(), nil):
continue
component.consoleFiltered.items.add(item)
item.print()
# Auto-scroll to bottom
if igGetScrollY() >= igGetScrollMaxY():
igSetScrollHereY(1.0f)
# Update selection
component.textSelect.textselect_update()
except IndexDefect:
# CTRL+A crashes when no items are in the console
discard
finally:
igPopStyleColor(3)
igPopStyleVar(1)
igEndChild()
#[
Console textarea
]#
component.console.draw(vec2(-1.0f, -footerHeight), component.filter)
# Padding
igDummy(vec2(0.0f, consolePadding))
@@ -397,7 +294,7 @@ proc draw*(component: ConsoleComponent, connection: WsConnection) =
let command = ($(addr component.inputBuffer[0])).strip()
if not command.isEmptyOrWhitespace():
component.addItem(LOG_COMMAND, command)
component.console.addItem(LOG_COMMAND, command)
# Send command to team server
component.handleAgentCommand(connection, command)

View File

@@ -1,115 +1,22 @@
import strformat, strutils, times
import imguin/[cimgui, glfw_opengl, simple]
import ../utils/[appImGui, colors]
import ./widgets/textarea
import ../../common/types
export addItem
type
EventlogComponent* = ref object of RootObj
title: string
log*: ConsoleItems
textSelect: ptr TextSelect
showTimestamps: bool
proc getText(item: ConsoleItem): cstring =
if item.itemType != LOG_OUTPUT:
return fmt"[{item.timestamp}]{$item.itemType}{item.text}".string
else:
return fmt"{$item.itemType}{item.text}".string
proc getNumLines(data: pointer): csize_t {.cdecl.} =
if data.isNil:
return 0
let log = cast[ConsoleItems](data)
return log.items.len().csize_t
proc getLineAtIndex(i: csize_t, data: pointer, outLen: ptr csize_t): cstring {.cdecl.} =
if data.isNil:
return nil
let log = cast[ConsoleItems](data)
let line = log.items[i].getText()
if not outLen.isNil:
outLen[] = line.len.csize_t
return line
textarea*: TextareaWidget
proc Eventlog*(title: string): EventlogComponent =
result = new EventlogComponent
result.title = title
result.log = new ConsoleItems
result.log.items = @[]
result.textSelect = textselect_create(getLineAtIndex, getNumLines, cast[pointer](result.log), 0)
result.showTimestamps = false
#[
API to add new log entry
]#
proc addItem*(component: EventlogComponent, itemType: LogType, data: string, timestamp: string = now().format("dd-MM-yyyy HH:mm:ss")) =
for line in data.split("\n"):
component.log.items.add(ConsoleItem(
timestamp: timestamp,
itemType: itemType,
text: line
))
#[
Drawing
]#
proc print(component: EventlogComponent, item: ConsoleItem) =
if (item.itemType != LOG_OUTPUT) and component.showTimestamps:
igTextColored(vec4(0.6f, 0.6f, 0.6f, 1.0f), fmt"[{item.timestamp}]".cstring)
igSameLine(0.0f, 0.0f)
case item.itemType:
of LOG_INFO, LOG_INFO_SHORT:
igTextColored(CONSOLE_INFO, $item.itemType)
of LOG_ERROR, LOG_ERROR_SHORT:
igTextColored(CONSOLE_ERROR, $item.itemType)
of LOG_SUCCESS, LOG_SUCCESS_SHORT:
igTextColored(CONSOLE_SUCCESS, $item.itemType)
of LOG_WARNING, LOG_WARNING_SHORT:
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)
result.textarea = Textarea(showTimestamps = false)
proc draw*(component: EventlogComponent, showComponent: ptr bool) =
igBegin(component.title, showComponent, 0)
defer: igEnd()
try:
# Set styles of the eventlog window
igPushStyleColor_Vec4(ImGui_Col_FrameBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f))
igPushStyleColor_Vec4(ImGui_Col_ScrollbarBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f))
igPushStyleColor_Vec4(ImGui_Col_Border.int32, vec4(0.2f, 0.2f, 0.2f, 1.0f))
igPushStyleVar_Float(ImGui_StyleVar_FrameBorderSize .int32, 1.0f)
let childWindowFlags = ImGuiChildFlags_NavFlattened.int32 or ImGui_ChildFlags_Borders.int32 or ImGui_ChildFlags_AlwaysUseWindowPadding.int32 or ImGuiChildFlags_FrameStyle.int32
if igBeginChild_Str("##Log", vec2(-1.0f, -1.0f), childWindowFlags, ImGuiWindowFlags_HorizontalScrollbar.int32):
# Display eventlog items
for item in component.log.items:
component.print(item)
# Right click context menu to toggle timestamps in eventlog
if igBeginPopupContextWindow("EventlogSettings", ImGui_PopupFlags_MouseButtonRight.int32):
if igCheckbox("Show timestamps", addr component.showTimestamps):
igCloseCurrentPopup()
igEndPopup()
component.textSelect.textselect_update()
# Auto-scroll to bottom
if igGetScrollY() >= igGetScrollMaxY():
igSetScrollHereY(1.0f)
except IndexDefect:
# CTRL+A crashes when no items are in the eventlog
discard
finally:
igPopStyleColor(3)
igPopStyleVar(1)
igEndChild()
component.textarea.draw(vec2(-1.0f, -1.0f))

View File

@@ -3,7 +3,8 @@ import imguin/[cimgui, glfw_opengl, simple]
import ../../utils/[appImGui, colors]
import ../../../common/[types, profile, utils]
import ../../../modules/manager
import ../widgets/dualListSelection
import ../widgets/[dualListSelection, textarea]
export addItem
type
AgentModalComponent* = ref object of RootObj
@@ -12,8 +13,8 @@ type
sleepMask: int32
spoofStack: bool
sleepMaskTechniques: seq[string]
moduleSelection: DualListSelectionComponent[Module]
buildLog: ConsoleItems
moduleSelection: DualListSelectionWidget[Module]
buildLog*: TextareaWidget
proc AgentModal*(): AgentModalComponent =
@@ -37,9 +38,7 @@ proc AgentModal*(): AgentModalComponent =
return cmp(x.moduleType, y.moduleType)
result.moduleSelection = DualListSelection(modules, moduleName, compareModules, moduleDesc)
result.buildlog = new ConsoleItems
result.buildLog.items = @[]
result.buildLog = Textarea(showTimestamps = false)
proc resetModalValues*(component: AgentModalComponent) =
component.listener = 0
@@ -47,33 +46,7 @@ proc resetModalValues*(component: AgentModalComponent) =
component.sleepMask = 0
component.spoofStack = false
component.moduleSelection.reset()
component.buildLog.items = @[]
proc addBuildlogItem*(component: AgentModalComponent, itemType: LogType, data: string, timestamp: string = now().format("dd-MM-yyyy HH:mm:ss")) =
for line in data.split("\n"):
component.buildLog.items.add(ConsoleItem(
timestamp: timestamp,
itemType: itemType,
text: line
))
proc print(component: AgentModalComponent, item: ConsoleItem) =
case item.itemType:
of LOG_INFO, LOG_INFO_SHORT:
igTextColored(CONSOLE_INFO, $item.itemType)
of LOG_ERROR, LOG_ERROR_SHORT:
igTextColored(CONSOLE_ERROR, $item.itemType)
of LOG_SUCCESS, LOG_SUCCESS_SHORT:
igTextColored(CONSOLE_SUCCESS, $item.itemType)
of LOG_WARNING, LOG_WARNING_SHORT:
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)
component.buildLog.clear()
proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBuildInformation =
@@ -142,32 +115,8 @@ proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBui
igDummy(vec2(0.0f, 10.0f))
igText("Build log: ")
try:
# Set styles of the log window
igPushStyleColor_Vec4(ImGui_Col_FrameBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f))
igPushStyleColor_Vec4(ImGui_Col_ScrollbarBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f))
igPushStyleColor_Vec4(ImGui_Col_Border.int32, vec4(0.2f, 0.2f, 0.2f, 1.0f))
igPushStyleVar_Float(ImGui_StyleVar_FrameBorderSize .int32, 1.0f)
let buildLogHeight = 250.0f
let childWindowFlags = ImGuiChildFlags_NavFlattened.int32 or ImGui_ChildFlags_Borders.int32 or ImGui_ChildFlags_AlwaysUseWindowPadding.int32 or ImGuiChildFlags_FrameStyle.int32
if igBeginChild_Str("##Log", vec2(-1.0f, buildLogHeight), childWindowFlags, ImGuiWindowFlags_HorizontalScrollbar.int32):
# Display eventlog items
for item in component.buildLog.items:
component.print(item)
# Auto-scroll to bottom
if igGetScrollY() >= igGetScrollMaxY():
igSetScrollHereY(1.0f)
except IndexDefect:
# CTRL+A crashes when no items are in the eventlog
discard
finally:
igPopStyleColor(3)
igPopStyleVar(1)
igEndChild()
let buildLogHeight = 250.0f
component.buildLog.draw(vec2(-1.0f, buildLogHeight))
igDummy(vec2(0.0f, 10.0f))
igSeparator()

View File

@@ -60,13 +60,6 @@ proc draw*(component: ListenerModalComponent): UIListener =
# HTTP Listener settings
if component.protocols[component.protocol] == $HTTP:
# Callback hosts
igText("Hosts (Callback): ")
igSameLine(0.0f, textSpacing)
igGetContentRegionAvail(addr availableSize)
igSetNextItemWidth(availableSize.x)
igInputTextMultiline("##InputCallbackHosts", addr component.callbackHosts[0], 256 * 32, vec2(0.0f, 3.0f * igGetTextLineHeightWithSpacing()), ImGui_InputTextFlags_CharsNoBlank.int32, nil, nil)
# Listener bindAddress
igText("Host (Bind): ")
igSameLine(0.0f, textSpacing)
@@ -81,6 +74,13 @@ proc draw*(component: ListenerModalComponent): UIListener =
igSetNextItemWidth(availableSize.x)
igInputScalar("##InputPortBind", ImGuiDataType_U16.int32, addr component.bindPort, addr step, nil, "%hu", ImGui_InputTextFlags_CharsDecimal.int32)
# Callback hosts
igText("Hosts (Callback): ")
igSameLine(0.0f, textSpacing)
igGetContentRegionAvail(addr availableSize)
igSetNextItemWidth(availableSize.x)
igInputTextMultiline("##InputCallbackHosts", addr component.callbackHosts[0], 256 * 32, vec2(0.0f, 3.0f * igGetTextLineHeightWithSpacing()), ImGui_InputTextFlags_CharsNoBlank.int32, nil, nil)
igGetContentRegionAvail(addr availableSize)
igDummy(vec2(0.0f, 10.0f))

View File

@@ -4,7 +4,7 @@ import ../../utils/[appImGui, colors, utils]
import ../../../common/[types, utils]
type
DualListSelectionComponent*[T] = ref object of RootObj
DualListSelectionWidget*[T] = ref object of RootObj
items*: array[2, seq[T]]
selection: array[2, ptr ImGuiSelectionBasicStorage]
display: proc(item: T): string
@@ -14,8 +14,8 @@ type
proc defaultDisplay[T](item: T): string =
return $item
proc DualListSelection*[T](items: seq[T], display: proc(item: T): string = defaultDisplay, compare: proc(x, y: T): int, tooltip: proc(item: T): string = nil): DualListSelectionComponent[T] =
result = new DualListSelectionComponent[T]
proc DualListSelection*[T](items: seq[T], display: proc(item: T): string = defaultDisplay, compare: proc(x, y: T): int, tooltip: proc(item: T): string = nil): DualListSelectionWidget[T] =
result = new DualListSelectionWidget[T]
result.items[0] = items
result.items[1] = @[]
result.selection[0] = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage()
@@ -24,7 +24,7 @@ proc DualListSelection*[T](items: seq[T], display: proc(item: T): string = defau
result.compare = compare
result.tooltip = tooltip
proc moveAll[T](component: DualListSelectionComponent[T], src, dst: int) =
proc moveAll[T](component: DualListSelectionWidget[T], src, dst: int) =
for m in component.items[src]:
component.items[dst].add(m)
component.items[dst].sort(component.compare)
@@ -33,7 +33,7 @@ proc moveAll[T](component: DualListSelectionComponent[T], src, dst: int) =
ImGuiSelectionBasicStorage_Swap(component.selection[src], component.selection[dst])
ImGuiSelectionBasicStorage_Clear(component.selection[src])
proc moveSelection[T](component: DualListSelectionComponent[T], src, dst: int) =
proc moveSelection[T](component: DualListSelectionWidget[T], src, dst: int) =
var keep: seq[T]
for i in 0 ..< component.items[src].len():
let item = component.items[src][i]
@@ -47,10 +47,10 @@ proc moveSelection[T](component: DualListSelectionComponent[T], src, dst: int) =
ImGuiSelectionBasicStorage_Swap(component.selection[src], component.selection[dst])
ImGuiSelectionBasicStorage_Clear(component.selection[src])
proc reset*[T](component: DualListSelectionComponent[T]) =
proc reset*[T](component: DualListSelectionWidget[T]) =
component.moveAll(1, 0)
proc draw*[T](component: DualListSelectionComponent[T]) =
proc draw*[T](component: DualListSelectionWidget[T]) =
if igBeginTable("split", 3, ImGuiTableFlags_None.int32, vec2(0.0f, 0.0f), 0.0f):

View File

@@ -0,0 +1,120 @@
import strutils, sequtils, algorithm, times
import imguin/[cimgui, glfw_opengl, simple]
import ../../utils/[appImGui, colors, utils]
import ../../../common/[types, utils]
type
TextareaWidget* = ref object of RootObj
content: ConsoleItems
contentDisplayed: ConsoleItems
textSelect: ptr TextSelect
showTimestamps: bool
# Text highlighting
proc getText(item: ConsoleItem): cstring =
if item.itemType != LOG_OUTPUT:
# let timestamp = item.timestamp.fromUnix().format("dd-MM-yyyy HH:mm:ss")
return "[" & item.timestamp & "]" & $item.itemType & item.text
else:
return $item.itemType & item.text
proc getNumLines(data: pointer): csize_t {.cdecl.} =
if data.isNil:
return 0
let content = cast[ConsoleItems](data)
return content.items.len().csize_t
proc getLineAtIndex(i: csize_t, data: pointer, outLen: ptr csize_t): cstring {.cdecl.} =
if data.isNil:
return nil
let content = cast[ConsoleItems](data)
let line = content.items[i].getText()
if not outLen.isNil:
outLen[] = line.len.csize_t
return line
proc Textarea*(showTimestamps: bool = true): TextareaWidget =
result = new TextareaWidget
result.content = new ConsoleItems
result.content.items = @[]
result.contentDisplayed = new ConsoleItems
result.contentDisplayed.items = @[]
result.textSelect = textselect_create(getLineAtIndex, getNumLines, cast[pointer](result.contentDisplayed), 0)
result.showTimestamps = showTimestamps
# API to add new content entry
proc addItem*(component: TextareaWidget, itemType: LogType, data: string, timestamp: string = now().format("dd-MM-yyyy HH:mm:ss")) =
for line in data.split("\n"):
component.content.items.add(ConsoleItem(
timestamp: timestamp,
itemType: itemType,
text: line
))
proc clear*(component: TextareaWidget) =
component.content.items.setLen(0)
component.contentDisplayed.items.setLen(0)
component.textSelect.textselect_clear_selection()
# Drawing
proc print(component: TextareaWidget, item: ConsoleItem) =
if item.itemType != LOG_OUTPUT and component.showTimestamps:
igTextColored(GRAY, "[" & item.timestamp & "]", nil)
igSameLine(0.0f, 0.0f)
case item.itemType:
of LOG_INFO, LOG_INFO_SHORT:
igTextColored(CONSOLE_INFO, $item.itemType)
of LOG_ERROR, LOG_ERROR_SHORT:
igTextColored(CONSOLE_ERROR, $item.itemType)
of LOG_SUCCESS, LOG_SUCCESS_SHORT:
igTextColored(CONSOLE_SUCCESS, $item.itemType)
of LOG_WARNING, LOG_WARNING_SHORT:
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 draw*(component: TextareaWidget, size: ImVec2, filter: ptr ImGuiTextFilter = nil) =
try:
# Set styles of the eventlog window
igPushStyleColor_Vec4(ImGui_Col_FrameBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f))
igPushStyleColor_Vec4(ImGui_Col_ScrollbarBg.int32, vec4(0.1f, 0.1f, 0.1f, 1.0f))
igPushStyleColor_Vec4(ImGui_Col_Border.int32, vec4(0.2f, 0.2f, 0.2f, 1.0f))
igPushStyleVar_Float(ImGui_StyleVar_FrameBorderSize .int32, 1.0f)
let childWindowFlags = ImGuiChildFlags_NavFlattened.int32 or ImGui_ChildFlags_Borders.int32 or ImGui_ChildFlags_AlwaysUseWindowPadding.int32 or ImGuiChildFlags_FrameStyle.int32
if igBeginChild_Str("##TextArea", size, childWindowFlags, ImGuiWindowFlags_HorizontalScrollbar.int32):
component.contentDisplayed.items.setLen(0)
# Display items
for item in component.content.items:
# Handle search/filter
if not filter.isNil():
if filter.ImGuiTextFilter_IsActive():
if not filter.ImGuiTextFilter_PassFilter(item.getText(), nil):
continue
component.contentDisplayed.items.add(item)
component.print(item)
# Auto-scroll to bottom
if igGetScrollY() >= igGetScrollMaxY():
igSetScrollHereY(1.0f)
component.textSelect.textselect_update()
except IndexDefect:
# CTRL+A crashes when no items are in the eventlog
discard
finally:
igPopStyleColor(3)
igPopStyleVar(1)
igEndChild()

View File

@@ -27,7 +27,7 @@ when (MODULES == cast[uint32](MODULE_ALL)):
bof,
dotnet,
screenshot,
situationalAwareness
systeminfo
registerModule(sleep.module)
registerModule(shell.module)
registerModule(bof.module)
@@ -35,7 +35,7 @@ when (MODULES == cast[uint32](MODULE_ALL)):
registerModule(filesystem.module)
registerModule(filetransfer.module)
registerModule(screenshot.module)
registerModule(situationalAwareness.module)
registerModule(systeminfo.module)
# Import modules individually
when ((MODULES and cast[uint32](MODULE_SLEEP)) == cast[uint32](MODULE_SLEEP)):
@@ -60,8 +60,8 @@ when ((MODULES and cast[uint32](MODULE_SCREENSHOT)) == cast[uint32](MODULE_SCREE
import screenshot
registerModule(screenshot.module)
when ((MODULES and cast[uint32](MODULE_SITUATIONAL_AWARENESS)) == cast[uint32](MODULE_SITUATIONAL_AWARENESS)):
import situationalAwareness
registerModule(situationalAwareness.module)
import systeminfo
registerModule(systeminfo.module)
proc getCommandByType*(cmdType: CommandType): Command =
return manager.commandsByType[cmdType]

View File

@@ -3,11 +3,10 @@ import ../common/[types, utils]
# Declare function prototypes
proc executePs(ctx: AgentCtx, task: Task): TaskResult
proc executeEnv(ctx: AgentCtx, task: Task): TaskResult
proc executeWhoami(ctx: AgentCtx, task: Task): TaskResult
# Module definition
let module* = Module(
name: protect("situational-awareness"),
name: protect("systeminfo"),
description: protect("Retrieve information about the target system and environment."),
moduleType: MODULE_SITUATIONAL_AWARENESS,
commands: @[
@@ -26,14 +25,6 @@ let module* = Module(
example: protect("env"),
arguments: @[],
execute: executeEnv
),
Command(
name: protect("whoami"),
commandType: CMD_WHOAMI,
description: protect("Get user information."),
example: protect("whoami"),
arguments: @[],
execute: executeWhoami
)
]
)
@@ -42,7 +33,6 @@ let module* = Module(
when not defined(agent):
proc executePs(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeEnv(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeWhoami(ctx: AgentCtx, task: Task): TaskResult = nil
when defined(agent):
@@ -144,17 +134,5 @@ when defined(agent):
return createTaskResult(task, STATUS_COMPLETED, RESULT_STRING, string.toBytes(output))
except CatchableError as err:
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg))
proc executeWhoami(ctx: AgentCtx, task: Task): TaskResult =
echo protect(" [>] Getting user information.")
try:
let output = protect("Not implemented")
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(output))
except CatchableError as err:
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg))

View File

@@ -110,7 +110,6 @@ proc handleResult*(resultData: seq[byte]) =
of RESULT_STRING:
if int(taskResult.length) > 0:
cq.client.sendConsoleItem(agentId, LOG_INFO, "Output:")
cq.info("Output:")
cq.client.sendConsoleItem(agentId, LOG_OUTPUT, Bytes.toString(taskResult.data))
of RESULT_BINARY: