Improved dual list selection widget.

This commit is contained in:
Jakob Friedl
2025-09-24 19:26:17 +02:00
parent b6c81755a0
commit 8baf65a96d
6 changed files with 92 additions and 96 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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)):

View File

@@ -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) =
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)
if direction == Right:
for m in component.items[0]:
component.items[1].add(m)
component.items[0].setLen(0)
ImGuiSelectionBasicStorage_Swap(component.selection[src], component.selection[dst])
ImGuiSelectionBasicStorage_Clear(component.selection[src])
ImGuiSelectionBasicStorage_Swap(component.selection[0], component.selection[1])
ImGuiSelectionBasicStorage_Clear(component.selection[0])
else:
for m in component.items[1]:
component.items[0].add(m)
component.items[1].setLen(0)
ImGuiSelectionBasicStorage_Swap(component.selection[1], component.selection[0])
ImGuiSelectionBasicStorage_Clear(component.selection[1])
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)):
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[1].add(item)
component.items[0] = keep
component.items[dst].add(item)
component.items[dst].sort()
component.items[src] = keep
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:
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()

View File

@@ -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

View File

@@ -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,