diff --git a/src/client/main.nim b/src/client/main.nim index 826c7e6..dace48a 100644 --- a/src/client/main.nim +++ b/src/client/main.nim @@ -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() diff --git a/src/client/views/console.nim b/src/client/views/console.nim index f6414f4..4fc4b0b 100644 --- a/src/client/views/console.nim +++ b/src/client/views/console.nim @@ -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) diff --git a/src/client/views/eventlog.nim b/src/client/views/eventlog.nim index bc1e0ff..3b15892 100644 --- a/src/client/views/eventlog.nim +++ b/src/client/views/eventlog.nim @@ -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)) \ No newline at end of file diff --git a/src/client/views/modals/generatePayload.nim b/src/client/views/modals/generatePayload.nim index 04c5eb4..a57d530 100644 --- a/src/client/views/modals/generatePayload.nim +++ b/src/client/views/modals/generatePayload.nim @@ -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() diff --git a/src/client/views/modals/startListener.nim b/src/client/views/modals/startListener.nim index afdda79..8b4f751 100644 --- a/src/client/views/modals/startListener.nim +++ b/src/client/views/modals/startListener.nim @@ -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)) diff --git a/src/client/views/widgets/dualListSelection.nim b/src/client/views/widgets/dualListSelection.nim index 9e6fe07..c34442d 100644 --- a/src/client/views/widgets/dualListSelection.nim +++ b/src/client/views/widgets/dualListSelection.nim @@ -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): diff --git a/src/client/views/widgets/textarea.nim b/src/client/views/widgets/textarea.nim new file mode 100644 index 0000000..25001a3 --- /dev/null +++ b/src/client/views/widgets/textarea.nim @@ -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() \ No newline at end of file diff --git a/src/modules/manager.nim b/src/modules/manager.nim index 1aa487c..87795f1 100644 --- a/src/modules/manager.nim +++ b/src/modules/manager.nim @@ -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] diff --git a/src/modules/situationalAwareness.nim b/src/modules/systeminfo.nim similarity index 85% rename from src/modules/situationalAwareness.nim rename to src/modules/systeminfo.nim index 55d1461..ea6b4a3 100644 --- a/src/modules/situationalAwareness.nim +++ b/src/modules/systeminfo.nim @@ -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)) \ No newline at end of file diff --git a/src/server/api/handlers.nim b/src/server/api/handlers.nim index bd09f28..3f7d97f 100644 --- a/src/server/api/handlers.nim +++ b/src/server/api/handlers.nim @@ -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: