Implemented profile embedding via patching a placeholder in the agent executable. Agent correctly deserializes and parses the profile and listener configuration.
This commit is contained in:
148
src/server/core/builder.nim
Normal file
148
src/server/core/builder.nim
Normal file
@@ -0,0 +1,148 @@
|
||||
import terminal, strformat, strutils, sequtils, tables, times, system, osproc, streams, base64, parsetoml
|
||||
|
||||
import ../utils
|
||||
import ../../common/[types, utils, profile, serialize]
|
||||
import ../db/database
|
||||
|
||||
const PLACEHOLDER = "PLACEHOLDER"
|
||||
|
||||
proc serializeConfiguration(cq: Conquest, listener: Listener, sleep: int): seq[byte] =
|
||||
|
||||
var packer = Packer.init()
|
||||
|
||||
# Add listener configuration
|
||||
packer.add(uint8(CONFIG_LISTENER_UUID))
|
||||
packer.add(uint32(sizeof(uint32)))
|
||||
packer.add(string.toUuid(listener.listenerId))
|
||||
|
||||
packer.add(uint8(CONFIG_LISTENER_IP))
|
||||
packer.add(uint32(listener.address.len))
|
||||
packer.addData(string.toBytes(listener.address))
|
||||
|
||||
packer.add(uint8(CONFIG_LISTENER_PORT))
|
||||
packer.add(uint32(sizeof(uint32)))
|
||||
packer.add(uint32(listener.port))
|
||||
|
||||
packer.add(uint8(CONFIG_SLEEP_DELAY))
|
||||
packer.add(uint32(sizeof(uint32)))
|
||||
packer.add(uint32(sleep))
|
||||
|
||||
# Add key exchange information
|
||||
packer.add(uint8(CONFIG_PUBLIC_KEY))
|
||||
packer.add(uint32(sizeof(Key)))
|
||||
packer.addData(cq.keyPair.publicKey)
|
||||
|
||||
# Add C2 profile string
|
||||
let profileString = cq.profile.toTomlString()
|
||||
packer.add(uint8(CONFIG_PROFILE))
|
||||
packer.add(uint32(profileString.len))
|
||||
packer.addData(string.toBytes(profileString))
|
||||
|
||||
let data = packer.pack()
|
||||
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Profile configuration serialized.")
|
||||
return data
|
||||
|
||||
proc compile(cq: Conquest, placeholderLength: int): string =
|
||||
|
||||
let
|
||||
cqDir = cq.profile.getString("conquest_directory")
|
||||
configFile = fmt"{cqDir}/src/agent/nim.cfg"
|
||||
exeFile = fmt"{cqDir}/bin/monarch.x64.exe"
|
||||
agentBuildScript = fmt"{cqDir}/src/agent/build.sh"
|
||||
|
||||
# Create/overwrite nim.cfg file to set placeholder for agent configuration
|
||||
let config = fmt"""
|
||||
# Agent configuration
|
||||
-d:CONFIGURATION={PLACEHOLDER & "A".repeat(placeholderLength - (2 * len(PLACEHOLDER))) & PLACEHOLDER}
|
||||
-o:"{exeFile}"
|
||||
""".replace(" ", "")
|
||||
|
||||
writeFile(configFile, config)
|
||||
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Configuration file created.")
|
||||
|
||||
# Build agent by executing the ./build.sh script on the system.
|
||||
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "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.writeLine(line)
|
||||
|
||||
let exitCode = process.waitForExit()
|
||||
|
||||
# Check if the build succeeded or not
|
||||
if exitCode == 0:
|
||||
cq.writeLine(fgGreen, "[*] ", resetStyle, "Agent payload generated successfully.")
|
||||
return exeFile
|
||||
else:
|
||||
cq.writeLine(fgRed, styleBright, "[-] ", resetStyle, "Build script exited with code ", $exitCode)
|
||||
return ""
|
||||
|
||||
except CatchableError as err:
|
||||
cq.writeLine(fgRed, styleBright, "[-] ", resetStyle, "An error occurred: ", err.msg)
|
||||
return ""
|
||||
|
||||
proc patch(cq: Conquest, unpatchedExePath: string, configuration: seq[byte]): bool =
|
||||
|
||||
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "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.writeLine(fgBlack, styleBright, "[+] ", resetStyle, fmt"Placeholder found at offset {placeholderPos}.")
|
||||
# cq.writeLine(exeBytes[placeholderPos..placeholderPos + len(configuration)])
|
||||
|
||||
# Patch placeholder bytes
|
||||
for i, c in Bytes.toString(configuration):
|
||||
exeBytes[placeholderPos + i] = c
|
||||
|
||||
writeFile(unpatchedExePath, exeBytes)
|
||||
cq.writeLine(fgGreen, "[+] ", resetStyle, fmt"Agent payload patched successfully: {unpatchedExePath}.")
|
||||
|
||||
except CatchableError as err:
|
||||
cq.writeLine(fgRed, styleBright, "[-] ", resetStyle, "An error occurred: ", err.msg)
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
# Agent generation
|
||||
proc agentBuild*(cq: Conquest, listener, sleep: string): bool {.discardable.} =
|
||||
|
||||
# Verify that listener exists
|
||||
if not cq.dbListenerExists(listener.toUpperAscii):
|
||||
cq.writeLine(fgRed, styleBright, fmt"[-] Listener {listener.toUpperAscii} does not exist.")
|
||||
return false
|
||||
|
||||
let listener = cq.listeners[listener.toUpperAscii]
|
||||
|
||||
var config: seq[byte]
|
||||
if sleep.isEmptyOrWhitespace():
|
||||
# If no sleep value has been defined, take the default from the profile
|
||||
config = cq.serializeConfiguration(listener, cq.profile.getInt("agent.sleep"))
|
||||
else:
|
||||
config = cq.serializeConfiguration(listener, parseInt(sleep))
|
||||
|
||||
let unpatchedExePath = cq.compile(config.len)
|
||||
if unpatchedExePath.isEmptyOrWhitespace():
|
||||
return false
|
||||
|
||||
if not cq.patch(unpatchedExePath, config):
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user