From 8baf65a96d8ecd7128473500d8deff98bdd6598d Mon Sep 17 00:00:00 2001 From: Jakob Friedl <71284620+jakobfriedl@users.noreply.github.com> Date: Wed, 24 Sep 2025 19:26:17 +0200 Subject: [PATCH] Improved dual list selection widget. --- src/agent/nim.cfg | 2 +- src/client/layout.ini | 30 ++--- src/client/views/modals/generatePayload.nim | 26 +++-- .../views/widgets/dualListSelection.nim | 107 ++++++++---------- src/common/types.nim | 19 ++-- src/modules/manager.nim | 4 +- 6 files changed, 92 insertions(+), 96 deletions(-) diff --git a/src/agent/nim.cfg b/src/agent/nim.cfg index 58ce698..fdf88d4 100644 --- a/src/agent/nim.cfg +++ b/src/agent/nim.cfg @@ -4,5 +4,5 @@ --opt:size --passL:"-s" # Strip symbols, such as sensitive function names -d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER" --d:MODULES=1 +-d:MODULES=0 -o:"/mnt/c/Users/jakob/Documents/Projects/conquest/bin/monarch.x64.exe" \ No newline at end of file diff --git a/src/client/layout.ini b/src/client/layout.ini index 17a1c06..4198c2c 100644 --- a/src/client/layout.ini +++ b/src/client/layout.ini @@ -1,30 +1,30 @@ [Window][Sessions [Table View]] Pos=10,43 -Size=2101,474 +Size=1141,338 Collapsed=0 DockId=0x00000003,0 [Window][Listeners] -Pos=10,519 -Size=2848,1166 +Pos=10,383 +Size=1888,654 Collapsed=0 DockId=0x00000006,0 [Window][Eventlog] -Pos=2113,43 -Size=745,474 +Pos=1153,43 +Size=745,338 Collapsed=0 DockId=0x00000004,0 [Window][Dear ImGui Demo] -Pos=2113,43 -Size=745,474 +Pos=1153,43 +Size=745,338 Collapsed=0 DockId=0x00000004,1 [Window][Dockspace] Pos=0,0 -Size=2868,1695 +Size=1908,1047 Collapsed=0 [Window][[FACEDEAD] bob@LAPTOP-02] @@ -110,7 +110,7 @@ Size=76,76 Collapsed=0 [Window][Start Listener] -Pos=713,369 +Pos=704,411 Size=500,225 Collapsed=0 @@ -119,13 +119,13 @@ IsChild=1 Size=1363,540 [Window][Generate Payload] -Pos=1071,500 -Size=717,677 +Pos=704,185 +Size=500,677 Collapsed=0 [Window][Generate Payload/0_B6B17D5F] IsChild=1 -Size=326,310 +Size=217,310 [Table][0x32886A44,8] Column 0 Weight=0.6465 @@ -149,9 +149,9 @@ Column 3 Weight=0.9746 [Docking][Data] DockNode ID=0x00000009 Pos=100,200 Size=754,103 Selected=0x64D005CF -DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=2848,1642 Split=Y - DockNode ID=0x00000005 Parent=0x85940918 SizeRef=1888,474 Split=X +DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=1888,994 Split=Y + DockNode ID=0x00000005 Parent=0x85940918 SizeRef=1888,338 Split=X DockNode ID=0x00000003 Parent=0x00000005 SizeRef=2101,159 CentralNode=1 Selected=0x61E02D75 DockNode ID=0x00000004 Parent=0x00000005 SizeRef=745,159 Selected=0x5E5F7166 - DockNode ID=0x00000006 Parent=0x85940918 SizeRef=1888,1166 Selected=0x6BE22050 + DockNode ID=0x00000006 Parent=0x85940918 SizeRef=1888,654 Selected=0x6BE22050 diff --git a/src/client/views/modals/generatePayload.nim b/src/client/views/modals/generatePayload.nim index 934449e..9585da8 100644 --- a/src/client/views/modals/generatePayload.nim +++ b/src/client/views/modals/generatePayload.nim @@ -27,12 +27,9 @@ proc AgentModal*(listeners: seq[Listener]): AgentModalComponent = for technique in SleepObfuscationTechnique.low .. SleepObfuscationTechnique.high: result.sleepMaskTechniques.add($technique) - var modules: seq[string] + var modules: seq[ModuleType] for module in ModuleType: - # Magic to convert MODULE_SITUATIONAL_AWARENESS into SituationalAwareness, etc. - var name = ($module).split("_")[1..^1].mapIt(it.toLowerAscii().capitalizeAscii()).join("") - modules.add(name) - + modules.add(module) result.moduleSelection = DualListSelection(modules) proc resetModalValues(component: AgentModalComponent) = @@ -103,12 +100,27 @@ proc draw*(component: AgentModalComponent) = igSeparator() igDummy(vec2(0.0f, 10.0f)) + # Enable "Build" button if at least one module has been selected + igBeginDisabled(component.moduleSelection.items[1].len() == 0) + if igButton("Build", vec2(availableSize.x * 0.5 - textSpacing * 0.5, 0.0f)): - - + + # Get values + echo component.listeners[component.listener] + echo $component.sleepDelay + echo component.sleepMaskTechniques[component.sleepMask] + echo $component.spoofStack + + # Iterate over modules + var module: uint32 = 0 + for m in component.moduleSelection.items[1]: + module = module or uint32(m) + echo module + component.resetModalValues() igCloseCurrentPopup() + igEndDisabled() igSameLine(0.0f, textSpacing) if igButton("Close", vec2(availableSize.x * 0.5 - textSpacing * 0.5, 0.0f)): diff --git a/src/client/views/widgets/dualListSelection.nim b/src/client/views/widgets/dualListSelection.nim index 6ee40ae..aa7c083 100644 --- a/src/client/views/widgets/dualListSelection.nim +++ b/src/client/views/widgets/dualListSelection.nim @@ -1,73 +1,45 @@ -import strutils +import strutils, sequtils, algorithm import imguin/[cimgui, glfw_opengl, simple] import ../../utils/[appImGui, colors] import ../../../common/[types, utils] type - Direction = enum - Right = 0 - Left = 1 - DualListSelectionComponent* = ref object of RootObj - items: array[2, seq[string]] + items*: array[2, seq[ModuleType]] selection: array[2, ptr ImGuiSelectionBasicStorage] -proc DualListSelection*(items: seq[string]): DualListSelectionComponent = +proc DualListSelection*(items: seq[ModuleType]): DualListSelectionComponent = result = new DualListSelectionComponent result.items[0] = items result.items[1] = @[] result.selection[0] = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() result.selection[1] = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() -proc moveAll(component: DualListSelectionComponent, direction: Direction) = - - if direction == Right: - for m in component.items[0]: - component.items[1].add(m) - component.items[0].setLen(0) +proc moveAll(component: DualListSelectionComponent, src, dst: int) = + for m in component.items[src]: + component.items[dst].add(m) + component.items[dst].sort() + component.items[src].setLen(0) - ImGuiSelectionBasicStorage_Swap(component.selection[0], component.selection[1]) - ImGuiSelectionBasicStorage_Clear(component.selection[0]) + ImGuiSelectionBasicStorage_Swap(component.selection[src], component.selection[dst]) + ImGuiSelectionBasicStorage_Clear(component.selection[src]) - else: - for m in component.items[1]: - component.items[0].add(m) - component.items[1].setLen(0) +proc moveSelection(component: DualListSelectionComponent, src, dst: int) = + var keep: seq[ModuleType] + for i in 0 ..< component.items[src].len(): + let item = component.items[src][i] + if not component.selection[src].ImGuiSelectionBasicStorage_Contains(cast[ImGuiID](i)): + keep.add(item) + continue + component.items[dst].add(item) + component.items[dst].sort() + component.items[src] = keep - ImGuiSelectionBasicStorage_Swap(component.selection[1], component.selection[0]) - ImGuiSelectionBasicStorage_Clear(component.selection[1]) + ImGuiSelectionBasicStorage_Swap(component.selection[src], component.selection[dst]) + ImGuiSelectionBasicStorage_Clear(component.selection[src]) -proc moveSelection(component: DualListSelectionComponent, direction: Direction) = - - if direction == Right: - var - keep: seq[string] - - for i in 0 ..< component.items[0].len(): - let item = component.items[0][i] - if not component.selection[0].ImGuiSelectionBasicStorage_Contains(cast[ImGuiID](i)): - keep.add(item) - continue - component.items[1].add(item) - component.items[0] = keep - - ImGuiSelectionBasicStorage_Swap(component.selection[0], component.selection[1]) - ImGuiSelectionBasicStorage_Clear(component.selection[0]) - - else: - var - keep: seq[string] - - for i in 0 ..< component.items[1].len(): - let item = component.items[1][i] - if not component.selection[1].ImGuiSelectionBasicStorage_Contains(cast[ImGuiID](i)): - keep.add(item) - continue - component.items[0].add(item) - component.items[1] = keep - - ImGuiSelectionBasicStorage_Swap(component.selection[1], component.selection[0]) - ImGuiSelectionBasicStorage_Clear(component.selection[1]) +proc moduleName(module: ModuleType): string = + return ($module).split("_")[1..^1].mapIt(it.toLowerAscii().capitalizeAscii()).join("") proc draw*(component: DualListSelectionComponent) = @@ -80,7 +52,7 @@ proc draw*(component: DualListSelectionComponent) = var containerHeight: float - # Left selection container + # Left selection column igTableSetColumnIndex(0) var modules = component.items[0] @@ -105,7 +77,14 @@ proc draw*(component: DualListSelectionComponent) = for row in 0 ..< modules.len().int32: var isSelected = ImGuiSelectionBasicStorage_Contains(selection, cast[ImGuiID](row)) igSetNextItemSelectionUserData(row) - discard igSelectable_Bool(modules[row], isSelected, ImGuiSelectableFlags_AllowDoubleClick.int32, vec2(0.0f, 0.0f)) + discard igSelectable_Bool(modules[row].moduleName(), isSelected, ImGuiSelectableFlags_AllowDoubleClick.int32, vec2(0.0f, 0.0f)) + + # Move on Enter and double-click + if igIsItemFocused(): + if igIsKeyPressed_Bool(ImGuiKey_Enter, false) or igIsKeyPressed_Bool(ImGuiKey_KeypadEnter, false): + component.moveSelection(0, 1) + if igIsMouseDoubleClicked_Nil(ImGui_MouseButton_Left.int32): + component.moveSelection(0, 1) multiSelectIO = igEndMultiSelect() ImGuiSelectionBasicStorage_ApplyRequests(selection, multiSelectIO) @@ -118,15 +97,15 @@ proc draw*(component: DualListSelectionComponent) = let buttonSize = vec2(igGetFrameHeight(), igGetFrameHeight()) if igButton(">>", buttonSize): - component.moveAll(Right) + component.moveAll(0, 1) if igButton(">", buttonSize): - component.moveSelection(Right) + component.moveSelection(0, 1) if igButton("<", buttonSize): - component.moveSelection(Left) + component.moveSelection(1, 0) if igButton("<<", buttonSize): - component.moveAll(Left) + component.moveAll(1, 0) - # Right selection container + # Right selection column igTableSetColumnIndex(2) modules = component.items[1] @@ -148,12 +127,18 @@ proc draw*(component: DualListSelectionComponent) = for row in 0 ..< modules.len().int32: var isSelected = ImGuiSelectionBasicStorage_Contains(selection, cast[ImGuiID](row)) igSetNextItemSelectionUserData(row) - discard igSelectable_Bool(modules[row], isSelected, ImGuiSelectableFlags_AllowDoubleClick.int32, vec2(0.0f, 0.0f)) + discard igSelectable_Bool(modules[row].moduleName(), isSelected, ImGuiSelectableFlags_AllowDoubleClick.int32, vec2(0.0f, 0.0f)) + + # Move on Enter and double-click + if igIsItemFocused(): + if igIsKeyPressed_Bool(ImGuiKey_Enter, false) or igIsKeyPressed_Bool(ImGuiKey_KeypadEnter, false): + component.moveSelection(1, 0) + if igIsMouseDoubleClicked_Nil(ImGui_MouseButton_Left.int32): + component.moveSelection(1, 0) multiSelectIO = igEndMultiSelect() ImGuiSelectionBasicStorage_ApplyRequests(selection, multiSelectIO) igEndChild() - igEndTable() \ No newline at end of file diff --git a/src/common/types.nim b/src/common/types.nim index 6c11226..758b331 100644 --- a/src/common/types.nim +++ b/src/common/types.nim @@ -53,15 +53,15 @@ type CMD_SLEEPMASK = 17'u16 ModuleType* = enum - MODULE_ALL = 1'u32 - MODULE_SLEEP = 2'u32 - MODULE_SHELL = 4'u32 - MODULE_BOF = 8'u32 - MODULE_DOTNET = 16'u32 - MODULE_FILESYSTEM = 32'u32 - MODULE_FILETRANSFER = 64'u32 - MODULE_SCREENSHOT = 128'u32 - MODULE_SITUATIONAL_AWARENESS = 256'u32 + MODULE_ALL = 0'u32 + MODULE_SLEEP = 1'u32 + MODULE_SHELL = 2'u32 + MODULE_BOF = 4'u32 + MODULE_DOTNET = 8'u32 + MODULE_FILESYSTEM = 16'u32 + MODULE_FILETRANSFER = 32'u32 + MODULE_SCREENSHOT = 64'u32 + MODULE_SITUATIONAL_AWARENESS = 128'u32 StatusType* = enum STATUS_COMPLETED = 0'u8 @@ -101,7 +101,6 @@ type # Custom iterator for ModuleType, as it uses powers of 2 instead of standard increments iterator items*(e: typedesc[ModuleType]): ModuleType = - # yield MODULE_ALL yield MODULE_SLEEP yield MODULE_SHELL yield MODULE_BOF diff --git a/src/modules/manager.nim b/src/modules/manager.nim index 3ab5895..4b02782 100644 --- a/src/modules/manager.nim +++ b/src/modules/manager.nim @@ -1,7 +1,7 @@ import tables, strformat import ../common/types -const MODULES {.intdefine.} = 1 +const MODULES {.intdefine.} = 0 type ModuleManager* = object @@ -16,7 +16,7 @@ proc registerModule(module: Module) {.discardable.} = manager.commandsByName[cmd.name] = cmd # Import all modules -when ((MODULES and cast[uint32](MODULE_ALL)) == cast[uint32](MODULE_ALL)): +when (MODULES == cast[uint32](MODULE_ALL)): import sleep, shell,