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: of CLIENT_CONSOLE_ITEM:
let agentId = event.data["agentId"].getStr() let agentId = event.data["agentId"].getStr()
consoles[agentId].addItem( consoles[agentId].console.addItem(
cast[LogType](event.data["logType"].getInt()), cast[LogType](event.data["logType"].getInt()),
event.data["message"].getStr(), event.data["message"].getStr(),
event.timestamp.fromUnix().local().format("dd-MM-yyyy HH:mm:ss") event.timestamp.fromUnix().local().format("dd-MM-yyyy HH:mm:ss")
) )
of CLIENT_EVENTLOG_ITEM: of CLIENT_EVENTLOG_ITEM:
eventlog.addItem( eventlog.textarea.addItem(
cast[LogType](event.data["logType"].getInt()), cast[LogType](event.data["logType"].getInt()),
event.data["message"].getStr(), event.data["message"].getStr(),
event.timestamp.fromUnix().local().format("dd-MM-yyyy HH:mm:ss") event.timestamp.fromUnix().local().format("dd-MM-yyyy HH:mm:ss")
) )
of CLIENT_BUILDLOG_ITEM: of CLIENT_BUILDLOG_ITEM:
listenersTable.generatePayloadModal.addBuildlogItem( listenersTable.generatePayloadModal.buildLog.addItem(
cast[LogType](event.data["logType"].getInt()), cast[LogType](event.data["logType"].getInt()),
event.data["message"].getStr(), event.data["message"].getStr(),
event.timestamp.fromUnix().local().format("dd-MM-yyyy HH:mm:ss") 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 # This is done to ensure that closed console windows can be opened again
consoles = newConsoleTable consoles = newConsoleTable
# igShowDemoWindow(nil) igShowDemoWindow(nil)
# render # render
app.render() app.render()

View File

@@ -5,6 +5,8 @@ import ../utils/[appImGui, colors]
import ../../common/[types, utils] import ../../common/[types, utils]
import ../../modules/manager import ../../modules/manager
import ../core/[task, websocket] import ../core/[task, websocket]
import ./widgets/textarea
export addItem
const MAX_INPUT_LENGTH = 512 const MAX_INPUT_LENGTH = 512
type type
@@ -12,53 +14,21 @@ type
agent*: UIAgent agent*: UIAgent
showConsole*: bool showConsole*: bool
inputBuffer: array[MAX_INPUT_LENGTH, char] inputBuffer: array[MAX_INPUT_LENGTH, char]
console*: ConsoleItems # Stores all console items console*: TextareaWidget
consoleFiltered*: ConsoleItems # Temporarily stores console items that are displayed to the user
history: seq[string] history: seq[string]
historyPosition: int historyPosition: int
currentInput: string currentInput: string
textSelect: ptr TextSelect
filter: ptr ImGuiTextFilter 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 = proc Console*(agent: UIAgent): ConsoleComponent =
result = new ConsoleComponent result = new ConsoleComponent
result.agent = agent result.agent = agent
result.showConsole = true result.showConsole = true
zeroMem(addr result.inputBuffer[0], MAX_INPUT_LENGTH) zeroMem(addr result.inputBuffer[0], MAX_INPUT_LENGTH)
result.console = new ConsoleItems result.console = Textarea()
result.console.items = @[]
result.consoleFiltered = new ConsoleItems
result.consoleFiltered.items = @[]
result.history = @[] result.history = @[]
result.historyPosition = -1 result.historyPosition = -1
result.currentInput = "" 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("") result.filter = ImGuiTextFilter_ImGuiTextFilter("")
#[ #[
@@ -173,47 +143,36 @@ proc callback(data: ptr ImGuiInputTextCallbackData): cint {.cdecl.} =
else: discard 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 Handling console commands
]# ]#
proc displayHelp(component: ConsoleComponent) = proc displayHelp(component: ConsoleComponent) =
for module in getModules(component.agent.modules): for module in getModules(component.agent.modules):
for cmd in module.commands: for cmd in module.commands:
component.addItem(LOG_OUTPUT, " * " & cmd.name.alignLeft(15) & cmd.description) component.console.addItem(LOG_OUTPUT, " * " & cmd.name.alignLeft(15) & cmd.description)
component.addItem(LOG_OUTPUT, "") component.console.addItem(LOG_OUTPUT, "")
proc displayCommandHelp(component: ConsoleComponent, command: Command) = proc displayCommandHelp(component: ConsoleComponent, command: Command) =
var usage = command.name & " " & command.arguments.mapIt( var usage = command.name & " " & command.arguments.mapIt(
if it.isRequired: "<" & it.name & ">" else: "[" & it.name & "]" if it.isRequired: "<" & it.name & ">" else: "[" & it.name & "]"
).join(" ") ).join(" ")
component.addItem(LOG_OUTPUT, command.description) component.console.addItem(LOG_OUTPUT, command.description)
component.addItem(LOG_OUTPUT, "Usage : " & usage) component.console.addItem(LOG_OUTPUT, "Usage : " & usage)
component.addItem(LOG_OUTPUT, "Example : " & command.example) component.console.addItem(LOG_OUTPUT, "Example : " & command.example)
component.addItem(LOG_OUTPUT, "") component.console.addItem(LOG_OUTPUT, "")
if command.arguments.len > 0: if command.arguments.len > 0:
component.addItem(LOG_OUTPUT, "Arguments:") component.console.addItem(LOG_OUTPUT, "Arguments:")
let header = @["Name", "Type", "Required", "Description"] 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.console.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, " " & '-'.repeat(15) & " " & '-'.repeat(6) & " " & '-'.repeat(8) & " " & '-'.repeat(20))
for arg in command.arguments: for arg in command.arguments:
let isRequired = if arg.isRequired: "YES" else: "NO" 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.console.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, "")
proc handleHelp(component: ConsoleComponent, parsed: seq[string]) = proc handleHelp(component: ConsoleComponent, parsed: seq[string]) =
try: try:
@@ -224,7 +183,7 @@ proc handleHelp(component: ConsoleComponent, parsed: seq[string]) =
component.displayHelp() component.displayHelp()
except ValueError: except ValueError:
# Command was not found # 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) = proc handleAgentCommand*(component: ConsoleComponent, connection: WsConnection, input: string) =
# Convert user input into sequence of string arguments # 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]) task = createTask(component.agent.agentId, component.agent.listenerId, command, parsedArgs[1..^1])
connection.sendAgentTask(component.agent.agentId, input, task) 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: except CatchableError:
component.addItem(LOG_ERROR, getCurrentExceptionMsg()) component.console.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)
proc draw*(component: ConsoleComponent, connection: WsConnection) = proc draw*(component: ConsoleComponent, connection: WsConnection) =
igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}".cstring, addr component.showConsole, 0) 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) igSameLine(0.0f, textSpacing)
component.filter.ImGuiTextFilter_Draw("##ConsoleSearch", searchBoxWidth) component.filter.ImGuiTextFilter_Draw("##ConsoleSearch", searchBoxWidth)
try: #[
# Set styles of the console window Console textarea
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)) component.console.draw(vec2(-1.0f, -footerHeight), component.filter)
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()
# Padding # Padding
igDummy(vec2(0.0f, consolePadding)) igDummy(vec2(0.0f, consolePadding))
@@ -397,7 +294,7 @@ proc draw*(component: ConsoleComponent, connection: WsConnection) =
let command = ($(addr component.inputBuffer[0])).strip() let command = ($(addr component.inputBuffer[0])).strip()
if not command.isEmptyOrWhitespace(): if not command.isEmptyOrWhitespace():
component.addItem(LOG_COMMAND, command) component.console.addItem(LOG_COMMAND, command)
# Send command to team server # Send command to team server
component.handleAgentCommand(connection, command) component.handleAgentCommand(connection, command)

View File

@@ -1,115 +1,22 @@
import strformat, strutils, times import strformat, strutils, times
import imguin/[cimgui, glfw_opengl, simple] import imguin/[cimgui, glfw_opengl, simple]
import ../utils/[appImGui, colors] import ../utils/[appImGui, colors]
import ./widgets/textarea
import ../../common/types import ../../common/types
export addItem
type type
EventlogComponent* = ref object of RootObj EventlogComponent* = ref object of RootObj
title: string title: string
log*: ConsoleItems textarea*: TextareaWidget
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
proc Eventlog*(title: string): EventlogComponent = proc Eventlog*(title: string): EventlogComponent =
result = new EventlogComponent result = new EventlogComponent
result.title = title result.title = title
result.log = new ConsoleItems result.textarea = Textarea(showTimestamps = false)
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)
proc draw*(component: EventlogComponent, showComponent: ptr bool) = proc draw*(component: EventlogComponent, showComponent: ptr bool) =
igBegin(component.title, showComponent, 0) igBegin(component.title, showComponent, 0)
defer: igEnd() defer: igEnd()
try: component.textarea.draw(vec2(-1.0f, -1.0f))
# 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()

View File

@@ -3,7 +3,8 @@ import imguin/[cimgui, glfw_opengl, simple]
import ../../utils/[appImGui, colors] import ../../utils/[appImGui, colors]
import ../../../common/[types, profile, utils] import ../../../common/[types, profile, utils]
import ../../../modules/manager import ../../../modules/manager
import ../widgets/dualListSelection import ../widgets/[dualListSelection, textarea]
export addItem
type type
AgentModalComponent* = ref object of RootObj AgentModalComponent* = ref object of RootObj
@@ -12,8 +13,8 @@ type
sleepMask: int32 sleepMask: int32
spoofStack: bool spoofStack: bool
sleepMaskTechniques: seq[string] sleepMaskTechniques: seq[string]
moduleSelection: DualListSelectionComponent[Module] moduleSelection: DualListSelectionWidget[Module]
buildLog: ConsoleItems buildLog*: TextareaWidget
proc AgentModal*(): AgentModalComponent = proc AgentModal*(): AgentModalComponent =
@@ -37,9 +38,7 @@ proc AgentModal*(): AgentModalComponent =
return cmp(x.moduleType, y.moduleType) return cmp(x.moduleType, y.moduleType)
result.moduleSelection = DualListSelection(modules, moduleName, compareModules, moduleDesc) result.moduleSelection = DualListSelection(modules, moduleName, compareModules, moduleDesc)
result.buildLog = Textarea(showTimestamps = false)
result.buildlog = new ConsoleItems
result.buildLog.items = @[]
proc resetModalValues*(component: AgentModalComponent) = proc resetModalValues*(component: AgentModalComponent) =
component.listener = 0 component.listener = 0
@@ -47,33 +46,7 @@ proc resetModalValues*(component: AgentModalComponent) =
component.sleepMask = 0 component.sleepMask = 0
component.spoofStack = false component.spoofStack = false
component.moduleSelection.reset() component.moduleSelection.reset()
component.buildLog.items = @[] component.buildLog.clear()
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)
proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBuildInformation = 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)) igDummy(vec2(0.0f, 10.0f))
igText("Build log: ") igText("Build log: ")
try: let buildLogHeight = 250.0f
# Set styles of the log window component.buildLog.draw(vec2(-1.0f, buildLogHeight))
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()
igDummy(vec2(0.0f, 10.0f)) igDummy(vec2(0.0f, 10.0f))
igSeparator() igSeparator()

View File

@@ -60,13 +60,6 @@ proc draw*(component: ListenerModalComponent): UIListener =
# HTTP Listener settings # HTTP Listener settings
if component.protocols[component.protocol] == $HTTP: 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 # Listener bindAddress
igText("Host (Bind): ") igText("Host (Bind): ")
igSameLine(0.0f, textSpacing) igSameLine(0.0f, textSpacing)
@@ -81,6 +74,13 @@ proc draw*(component: ListenerModalComponent): UIListener =
igSetNextItemWidth(availableSize.x) igSetNextItemWidth(availableSize.x)
igInputScalar("##InputPortBind", ImGuiDataType_U16.int32, addr component.bindPort, addr step, nil, "%hu", ImGui_InputTextFlags_CharsDecimal.int32) 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) igGetContentRegionAvail(addr availableSize)
igDummy(vec2(0.0f, 10.0f)) igDummy(vec2(0.0f, 10.0f))

View File

@@ -4,7 +4,7 @@ import ../../utils/[appImGui, colors, utils]
import ../../../common/[types, utils] import ../../../common/[types, utils]
type type
DualListSelectionComponent*[T] = ref object of RootObj DualListSelectionWidget*[T] = ref object of RootObj
items*: array[2, seq[T]] items*: array[2, seq[T]]
selection: array[2, ptr ImGuiSelectionBasicStorage] selection: array[2, ptr ImGuiSelectionBasicStorage]
display: proc(item: T): string display: proc(item: T): string
@@ -14,8 +14,8 @@ type
proc defaultDisplay[T](item: T): string = proc defaultDisplay[T](item: T): string =
return $item 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] = 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 DualListSelectionComponent[T] result = new DualListSelectionWidget[T]
result.items[0] = items result.items[0] = items
result.items[1] = @[] result.items[1] = @[]
result.selection[0] = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() 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.compare = compare
result.tooltip = tooltip 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]: for m in component.items[src]:
component.items[dst].add(m) component.items[dst].add(m)
component.items[dst].sort(component.compare) 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_Swap(component.selection[src], component.selection[dst])
ImGuiSelectionBasicStorage_Clear(component.selection[src]) 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] var keep: seq[T]
for i in 0 ..< component.items[src].len(): for i in 0 ..< component.items[src].len():
let item = component.items[src][i] 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_Swap(component.selection[src], component.selection[dst])
ImGuiSelectionBasicStorage_Clear(component.selection[src]) ImGuiSelectionBasicStorage_Clear(component.selection[src])
proc reset*[T](component: DualListSelectionComponent[T]) = proc reset*[T](component: DualListSelectionWidget[T]) =
component.moveAll(1, 0) 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): 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, bof,
dotnet, dotnet,
screenshot, screenshot,
situationalAwareness systeminfo
registerModule(sleep.module) registerModule(sleep.module)
registerModule(shell.module) registerModule(shell.module)
registerModule(bof.module) registerModule(bof.module)
@@ -35,7 +35,7 @@ when (MODULES == cast[uint32](MODULE_ALL)):
registerModule(filesystem.module) registerModule(filesystem.module)
registerModule(filetransfer.module) registerModule(filetransfer.module)
registerModule(screenshot.module) registerModule(screenshot.module)
registerModule(situationalAwareness.module) registerModule(systeminfo.module)
# Import modules individually # Import modules individually
when ((MODULES and cast[uint32](MODULE_SLEEP)) == cast[uint32](MODULE_SLEEP)): 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 import screenshot
registerModule(screenshot.module) registerModule(screenshot.module)
when ((MODULES and cast[uint32](MODULE_SITUATIONAL_AWARENESS)) == cast[uint32](MODULE_SITUATIONAL_AWARENESS)): when ((MODULES and cast[uint32](MODULE_SITUATIONAL_AWARENESS)) == cast[uint32](MODULE_SITUATIONAL_AWARENESS)):
import situationalAwareness import systeminfo
registerModule(situationalAwareness.module) registerModule(systeminfo.module)
proc getCommandByType*(cmdType: CommandType): Command = proc getCommandByType*(cmdType: CommandType): Command =
return manager.commandsByType[cmdType] return manager.commandsByType[cmdType]

View File

@@ -3,11 +3,10 @@ import ../common/[types, utils]
# Declare function prototypes # Declare function prototypes
proc executePs(ctx: AgentCtx, task: Task): TaskResult proc executePs(ctx: AgentCtx, task: Task): TaskResult
proc executeEnv(ctx: AgentCtx, task: Task): TaskResult proc executeEnv(ctx: AgentCtx, task: Task): TaskResult
proc executeWhoami(ctx: AgentCtx, task: Task): TaskResult
# Module definition # Module definition
let module* = Module( let module* = Module(
name: protect("situational-awareness"), name: protect("systeminfo"),
description: protect("Retrieve information about the target system and environment."), description: protect("Retrieve information about the target system and environment."),
moduleType: MODULE_SITUATIONAL_AWARENESS, moduleType: MODULE_SITUATIONAL_AWARENESS,
commands: @[ commands: @[
@@ -26,14 +25,6 @@ let module* = Module(
example: protect("env"), example: protect("env"),
arguments: @[], arguments: @[],
execute: executeEnv 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): when not defined(agent):
proc executePs(ctx: AgentCtx, task: Task): TaskResult = nil proc executePs(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeEnv(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): when defined(agent):
@@ -144,17 +134,5 @@ when defined(agent):
return createTaskResult(task, STATUS_COMPLETED, RESULT_STRING, string.toBytes(output)) 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: except CatchableError as err:
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg)) 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: of RESULT_STRING:
if int(taskResult.length) > 0: if int(taskResult.length) > 0:
cq.client.sendConsoleItem(agentId, LOG_INFO, "Output:") cq.client.sendConsoleItem(agentId, LOG_INFO, "Output:")
cq.info("Output:")
cq.client.sendConsoleItem(agentId, LOG_OUTPUT, Bytes.toString(taskResult.data)) cq.client.sendConsoleItem(agentId, LOG_OUTPUT, Bytes.toString(taskResult.data))
of RESULT_BINARY: of RESULT_BINARY: