Moved task parsing logic to the client to be able to support dotnet/bof commands when operating from a different machine than the team server. Disabled sequence tracking due to issues.

This commit is contained in:
Jakob Friedl
2025-09-30 10:04:29 +02:00
parent 13a245ebf2
commit 039c857027
14 changed files with 94 additions and 92 deletions

View File

@@ -4,5 +4,5 @@
--opt:size
--passL:"-s" # Strip symbols, such as sensitive function names
-d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER"
-d:MODULES="223"
-d:MODULES="255"
-o:"/mnt/c/Users/jakob/Documents/Projects/conquest/bin/monarch.x64.exe"

View File

@@ -1,6 +1,6 @@
import std/paths
import strutils, sequtils, times, tables
import ../../common/[types, sequence, crypto, utils, serialize]
import ../common/[types, sequence, crypto, utils, serialize]
proc parseInput*(input: string): seq[string] =
var i = 0
@@ -84,12 +84,12 @@ proc parseArgument*(argument: Argument, value: string): TaskArg =
return arg
proc createTask*(cq: Conquest, agentId: string, command: Command, arguments: seq[string]): Task =
proc createTask*(agentId, listenerId: string, command: Command, arguments: seq[string]): Task =
# Construct the task payload prefix
var task: Task
task.taskId = string.toUuid(generateUUID())
task.listenerId = string.toUuid(cq.agents[agentId].listenerId)
task.listenerId = string.toUuid(listenerId)
task.timestamp = uint32(now().toTime().toUnix())
task.command = cast[uint16](command.commandType)
task.argCount = uint8(arguments.len)
@@ -124,4 +124,4 @@ proc createTask*(cq: Conquest, agentId: string, command: Command, arguments: seq
task.header = taskHeader
# Return the task object for serialization
return task
return task

View File

@@ -1,12 +1,13 @@
import whisky
import strformat, strutils, times
import strformat, strutils, times, json
import imguin/[cimgui, glfw_opengl, simple]
import ../utils/[appImGui, colors]
import ../../common/[types]
import ../websocket
import ../../common/[types, utils]
import ../../modules/manager
import ../[task, websocket]
const MAX_INPUT_LENGTH = 512
type
type
ConsoleComponent* = ref object of RootObj
agent*: UIAgent
showConsole*: bool
@@ -124,6 +125,32 @@ proc addItem*(component: ConsoleComponent, itemType: LogType, data: string, time
text: line
))
#[
Handling console commands
]#
proc handleAgentCommand*(component: ConsoleComponent, ws: WebSocket, input: string) =
# Convert user input into sequence of string arguments
let parsedArgs = parseInput(input)
# Handle 'help' command
if parsedArgs[0] == "help":
# cq.handleHelp(parsedArgs)
component.addItem(LOG_WARNING, "Help")
return
# Handle commands with actions on the agent
try:
let
command = getCommandByName(parsedArgs[0])
task = createTask(component.agent.agentId, component.agent.listenerId, command, parsedArgs[1..^1])
ws.sendAgentTask(component.agent.agentId, task)
component.addItem(LOG_INFO, fmt"Tasked agent to {command.description.toLowerAscii()} ({Uuid.toString(task.taskId)})")
except CatchableError:
component.addItem(LOG_ERROR, getCurrentExceptionMsg())
#[
Drawing
]#
@@ -271,7 +298,7 @@ proc draw*(component: ConsoleComponent, ws: WebSocket) =
component.addItem(LOG_COMMAND, command)
# Send command to team server
ws.sendAgentCommand(component.agent.agentId, command)
component.handleAgentCommand(ws, command)
# Add command to console history
component.history.add(command)

View File

@@ -50,8 +50,8 @@ proc draw*(component: DockspaceComponent, showComponent: ptr bool, views: Table[
igDockBuilderAddNode(dockspaceId, ImGuiDockNodeFlags_DockSpace.int32)
igDockBuilderSetNodeSize(dockspaceId, vp.WorkSize)
discard igDockBuilderSplitNode(dockspaceId, ImGuiDir_Down, 0.8f, dockBottom, dockTop)
discard igDockBuilderSplitNode(dockTop[], ImGuiDir_Right, 0.4f, dockTopRight, dockTopLeft)
discard igDockBuilderSplitNode(dockspaceId, ImGuiDir_Down, 5.0f, dockBottom, dockTop)
discard igDockBuilderSplitNode(dockTop[], ImGuiDir_Right, 0.5f, dockTopRight, dockTopLeft)
igDockBuilderDockWindow("Sessions [Table View]", dockTopLeft[])
igDockBuilderDockWindow("Listeners", dockBottom[])

View File

@@ -38,13 +38,13 @@ proc sendAgentBuild*(ws: WebSocket, buildInformation: AgentBuildInformation) =
)
ws.sendEvent(event)
proc sendAgentCommand*(ws: WebSocket, agentId: string, command: string) =
proc sendAgentTask*(ws: WebSocket, agentId: string, task: Task) =
let event = Event(
eventType: CLIENT_AGENT_COMMAND,
eventType: CLIENT_AGENT_TASK,
timestamp: now().toTime().toUnix(),
data: %*{
"agentId": agentId,
"command": command
"task": task
}
)
ws.sendEvent(event)

View File

@@ -8,23 +8,23 @@ proc nextSequence*(agentId: uint32): uint32 =
return sequenceTable[agentId]
proc validateSequence(agentId: uint32, seqNr: uint32, packetType: uint8): bool =
let lastSeqNr = sequenceTable.getOrDefault(agentId, 0'u32)
# let lastSeqNr = sequenceTable.getOrDefault(agentId, 0'u32)
# Heartbeat messages are not used for sequence tracking
if cast[PacketType](packetType) == MSG_HEARTBEAT:
return true
# # Heartbeat messages are not used for sequence tracking
# if cast[PacketType](packetType) == MSG_HEARTBEAT:
# return true
# In order to keep agents running after server restart, accept all connection with seqNr = 1, to update the table
if seqNr == 1'u32:
sequenceTable[agentId] = seqNr
return true
# # In order to keep agents running after server restart, accept all connection with seqNr = 1, to update the table
# if seqNr == 1'u32:
# sequenceTable[agentId] = seqNr
# return true
# Validate that the sequence number of the current packet is higher than the currently stored one
if seqNr <= lastSeqNr:
return false
# # Validate that the sequence number of the current packet is higher than the currently stored one
# if seqNr <= lastSeqNr:
# return false
# Update sequence number
sequenceTable[agentId] = seqNr
# # Update sequence number
# sequenceTable[agentId] = seqNr
return true
proc validatePacket*(header: Header, expectedType: uint8) =

View File

@@ -246,7 +246,7 @@ type
# Sent by client
CLIENT_AGENT_BUILD = 1'u8 # Generate an agent binary for a specific listener
CLIENT_AGENT_COMMAND = 2'u8 # Instruct TS to send queue a command for a specific agent
CLIENT_AGENT_TASK = 2'u8 # Instruct TS to send queue a command for a specific agent
CLIENT_LISTENER_START = 3'u8 # Start a listener on the TS
CLIENT_LISTENER_STOP = 4'u8 # Stop a listener
@@ -336,5 +336,4 @@ type
sleepDelay*: uint32
sleepTechnique*: SleepObfuscationTechnique
spoofStack*: bool
modules*: uint32
modules*: uint32

View File

@@ -13,7 +13,7 @@ proc executeCopy(ctx: AgentCtx, task: Task): TaskResult
let module* = Module(
name: protect("filesystem"),
description: protect("Conduct simple filesystem operations via Windows API."),
moduleType: MODULE_DOTNET,
moduleType: MODULE_FILESYSTEM,
commands: @[
Command(
name: protect("pwd"),

View File

@@ -8,7 +8,7 @@ proc executeUpload(ctx: AgentCtx, task: Task): TaskResult
let module* = Module(
name: protect("filetransfer"),
description: protect("Upload/download files to/from the target system."),
moduleType: MODULE_FILESYSTEM,
moduleType: MODULE_FILETRANSFER,
commands: @[
Command(
name: protect("download"),

View File

@@ -44,7 +44,7 @@ proc register*(registrationData: seq[byte]): bool =
cq.error(err.msg)
return false
proc getTasks*(heartbeat: seq[byte]): seq[seq[byte]] =
proc getTasks*(heartbeat: seq[byte]): tuple[agentId: string, tasks: seq[seq[byte]]] =
{.cast(gcsafe).}:
@@ -75,11 +75,11 @@ proc getTasks*(heartbeat: seq[byte]): seq[seq[byte]] =
let taskData = cq.serializeTask(task)
tasks.add(taskData)
return tasks
return (agentId, tasks)
except CatchableError as err:
cq.error(err.msg)
return @[]
return ("", @[])
proc handleResult*(resultData: seq[byte]) =

View File

@@ -5,6 +5,7 @@ import ./handlers
import ../globals
import ../core/logger
import ../../common/[types, utils, serialize, profile]
import ../websocket
# Not Found
proc error404*(request: Request) =
@@ -73,7 +74,7 @@ proc httpGet*(request: Request) =
try:
var responseBytes: seq[byte]
let tasks: seq[seq[byte]] = getTasks(heartbeat)
let (agentId, tasks) = getTasks(heartbeat)
if tasks.len <= 0:
request.respond(200, body = "")
@@ -107,6 +108,7 @@ proc httpGet*(request: Request) =
request.respond(200, headers = headers, body = prefix & response & suffix)
# Notify operator that agent collected tasks
cq.client.sendConsoleItem(agentId, LOG_INFO, fmt"{$response.len} bytes sent.")
cq.info(fmt"{$response.len} bytes sent.")
except CatchableError:

View File

@@ -106,26 +106,27 @@ proc agentKill*(cq: Conquest, name: string) =
# Switch to interact mode
proc agentInteract*(cq: Conquest, name: string) =
# Verify that agent exists
if not cq.dbAgentExists(name.toUpperAscii):
cq.error(fmt"Agent {name.toUpperAscii} does not exist.")
return
let agent = cq.agents[name.toUpperAscii]
var command: string = ""
# Change prompt indicator to show agent interaction
cq.interactAgent = agent
cq.prompt.setIndicator(fmt"[{agent.agentId}]> ")
cq.prompt.setStatusBar(@[("[mode]", "interact"), ("[username]", fmt"{agent.username}"), ("[hostname]", fmt"{agent.hostname}"), ("[ip]", fmt"{agent.ip}"), ("[domain]", fmt"{agent.domain}")])
cq.info("Started interacting with agent ", fgYellow, styleBright, agent.agentId, resetStyle, ". Type 'help' to list available commands.\n")
while command.replace(" ", "") != "back":
command = cq.prompt.readLine()
cq.handleAgentCommand(name, command)
# Reset interactAgent field after interaction with agent is ended using 'back' command
cq.interactAgent = nil
discard
# Verify that agent exists
# if not cq.dbAgentExists(name.toUpperAscii):
# cq.error(fmt"Agent {name.toUpperAscii} does not exist.")
# return
# let agent = cq.agents[name.toUpperAscii]
# var command: string = ""
# # Change prompt indicator to show agent interaction
# cq.interactAgent = agent
# cq.prompt.setIndicator(fmt"[{agent.agentId}]> ")
# cq.prompt.setStatusBar(@[("[mode]", "interact"), ("[username]", fmt"{agent.username}"), ("[hostname]", fmt"{agent.hostname}"), ("[ip]", fmt"{agent.ip}"), ("[domain]", fmt"{agent.domain}")])
# cq.info("Started interacting with agent ", fgYellow, styleBright, agent.agentId, resetStyle, ". Type 'help' to list available commands.\n")
# while command.replace(" ", "") != "back":
# command = cq.prompt.readLine()
# cq.handleAgentCommand(name, command)
# # Reset interactAgent field after interaction with agent is ended using 'back' command
# cq.interactAgent = nil

View File

@@ -1,4 +1,4 @@
import prompt, terminal, argparse, parsetoml, times, json
import prompt, terminal, argparse, parsetoml, times, json, math
import strutils, strformat, system, tables
import ./[agent, listener, task, builder]
@@ -174,10 +174,10 @@ proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) {.
let event = message.recvEvent()
case event.eventType:
of CLIENT_AGENT_COMMAND:
of CLIENT_AGENT_TASK:
let agentId = event.data["agentId"].getStr()
let command = event.data["command"].getStr()
cq.handleAgentCommand(agentId, command)
let task = event.data["task"].to(Task)
cq.agents[agentId].tasks.add(task)
of CLIENT_LISTENER_START:
let listener = event.data.to(UIListener)
@@ -246,7 +246,9 @@ proc startServer*(profilePath: string) =
# Start websocket server
var router: Router
router.get("/*", upgradeHandler)
let server = newServer(router, websocketHandler)
# Increased websocket message length in order to support dotnet assembly execution
let server = newServer(router, websocketHandler, maxMessageLen = 1024 * 1024 * 1024)
var thread: Thread[Server]
createThread(thread, serve, server)

View File

@@ -1,6 +1,5 @@
import strformat, terminal, tables, sequtils, strutils
import ../protocol/parser
import ../core/logger
import ../websocket
import ../../modules/manager
@@ -50,31 +49,3 @@ proc handleHelp(cq: Conquest, parsed: seq[string]) =
except ValueError:
# Command was not found
cq.error(fmt"The command '{parsed[1]}' does not exist." & '\n')
proc handleAgentCommand*(cq: Conquest, agentId: string, input: string) =
cq.input(input)
# Convert user input into sequence of string arguments
let parsedArgs = parseInput(input)
# Handle 'help' command
if parsedArgs[0] == "help":
cq.handleHelp(parsedArgs)
return
# Handle commands with actions on the agent
try:
let
command = getCommandByName(parsedArgs[0])
task = cq.createTask(agentId, command, parsedArgs[1..^1])
# Add task to queue
cq.agents[agentId].tasks.add(task)
cq.client.sendConsoleItem(agentId, LOG_INFO, fmt"Tasked agent to {command.description.toLowerAscii()}")
cq.info(fmt"Tasked agent to {command.description.toLowerAscii()}")
except CatchableError:
cq.error(getCurrentExceptionMsg() & "\n")
return