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
import .. / utils
2025-08-19 20:03:34 +02:00
import .. / .. / common / [ types , utils , profile , serialize , crypto ]
2025-08-18 22:05:23 +02:00
import .. / db / database
const PLACEHOLDER = " PLACEHOLDER "
proc serializeConfiguration ( cq : Conquest , listener : Listener , sleep : int ) : seq [ byte ] =
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-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 ) )
packer . add ( uint32 ( sleep ) )
packer . addData ( cq . keyPair . publicKey )
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
var aesKey = generateKey ( )
let iv = generateIV ( )
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 15:08:52 +02:00
cq . writeLine ( fgBlack , styleBright , fmt" [{getTimestamp()}] [ * ] " , resetStyle , " 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-08-18 22:05:23 +02:00
proc compile ( cq : Conquest , placeholderLength : int ) : string =
let
cqDir = cq . profile . getString ( " conquest_directory " )
configFile = fmt" {cqDir}/src/agent/nim.cfg "
exeFile = fmt" {cqDir}/bin/monarch.x64.exe "
agentBuildScript = fmt" {cqDir}/src/agent/build.sh "
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-08-18 22:05:23 +02:00
writeFile ( configFile , config )
2025-08-19 20:58:47 +02:00
2025-08-21 15:08:52 +02:00
cq . writeLine ( fgBlack , styleBright , fmt" [{getTimestamp()}] [ * ] " , resetStyle , 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 15:08:52 +02:00
cq . writeLine ( fgBlack , styleBright , fmt" [{getTimestamp()}] [ * ] " , resetStyle , " 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 ) :
cq . writeLine ( line )
let exitCode = process . waitForExit ( )
# Check if the build succeeded or not
if exitCode = = 0 :
2025-08-21 15:08:52 +02:00
cq . writeLine ( fgBlack , styleBright , fmt" [{getTimestamp()}] " , fgGreen , " [ + ] " , resetStyle , " Agent payload generated successfully. " )
2025-08-18 22:05:23 +02:00
return exeFile
else :
2025-08-21 15:08:52 +02:00
cq . writeLine ( fgBlack , styleBright , fmt" [{getTimestamp()}] " , fgRed , " [ - ] " , resetStyle , " Build script exited with code " , $ exitCode )
2025-08-18 22:05:23 +02:00
return " "
except CatchableError as err :
2025-08-21 15:08:52 +02:00
cq . writeLine ( fgBlack , styleBright , fmt" [{getTimestamp()}] " , fgRed , " [ - ] " , resetStyle , " An error occurred: " , err . msg )
2025-08-18 22:05:23 +02:00
return " "
proc patch ( cq : Conquest , unpatchedExePath : string , configuration : seq [ byte ] ) : bool =
2025-08-21 15:08:52 +02:00
cq . writeLine ( fgBlack , styleBright , fmt" [{getTimestamp()}] [ * ] " , resetStyle , " 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 15:08:52 +02:00
cq . writeLine ( fgBlack , styleBright , fmt" [{getTimestamp()}] [ + ] " , resetStyle , 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
writeFile ( unpatchedExePath , exeBytes )
2025-08-21 15:08:52 +02:00
cq . writeLine ( fgBlack , styleBright , fmt" [{getTimestamp()}] " , fgGreen , " [ + ] " , resetStyle , fmt" Agent payload patched successfully: {unpatchedExePath}. " )
2025-08-18 22:05:23 +02:00
except CatchableError as err :
2025-08-21 15:08:52 +02:00
cq . writeLine ( fgBlack , styleBright , fmt" [{getTimestamp()}] " , fgRed , styleBright , " [ - ] " , resetStyle , " An error occurred: " , err . msg )
2025-08-18 22:05:23 +02:00
return false
return true
# Agent generation
proc agentBuild * ( cq : Conquest , listener , sleep : string ) : bool {. discardable . } =
# Verify that listener exists
if not cq . dbListenerExists ( listener . toUpperAscii ) :
2025-08-21 15:08:52 +02:00
cq . writeLine ( fgRed , styleBright , fmt" [ - ] Listener {listener.toUpperAscii} does not exist. " )
2025-08-18 22:05:23 +02:00
return false
let listener = cq . listeners [ listener . toUpperAscii ]
var config : seq [ byte ]
if sleep . isEmptyOrWhitespace ( ) :
# If no sleep value has been defined, take the default from the profile
config = cq . serializeConfiguration ( listener , cq . profile . getInt ( " agent.sleep " ) )
else :
config = cq . serializeConfiguration ( listener , parseInt ( sleep ) )
let unpatchedExePath = cq . compile ( config . len )
if unpatchedExePath . isEmptyOrWhitespace ( ) :
return false
if not cq . patch ( unpatchedExePath , config ) :
return false
return true