Refactored textarea from console, eventlog and buildlog into a separate widget to reduce code duplication.
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
@@ -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()
|
||||
component.buildLog.draw(vec2(-1.0f, buildLogHeight))
|
||||
|
||||
igDummy(vec2(0.0f, 10.0f))
|
||||
igSeparator()
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
120
src/client/views/widgets/textarea.nim
Normal file
120
src/client/views/widgets/textarea.nim
Normal 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()
|
||||
@@ -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]
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -146,15 +136,3 @@ when defined(agent):
|
||||
|
||||
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))
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user