diff --git a/src/agent/core/context.nim b/src/agent/core/context.nim index 7abc118..f20320b 100644 --- a/src/agent/core/context.nim +++ b/src/agent/core/context.nim @@ -9,32 +9,18 @@ proc deserializeConfiguration(config: string): AgentCtx = var agentKeyPair = generateKeyPair() - var ctx = new AgentCtx - ctx.agentId = generateUUID() - ctx.agentPublicKey = agentKeyPair.publicKey - - while unpacker.getPosition() != config.len(): - - let - configType = cast[ConfigType](unpacker.getUint8()) - length = int(unpacker.getUint32()) - data = unpacker.getBytes(length) - - case configType: - of CONFIG_LISTENER_UUID: - ctx.listenerId = Uuid.toString(Bytes.toUint32(data)) - of CONFIG_LISTENER_IP: - ctx.ip = Bytes.toString(data) - of CONFIG_LISTENER_PORT: - ctx.port = int(Bytes.toUint32(data)) - of CONFIG_SLEEP_DELAY: - ctx.sleep = int(Bytes.toUint32(data)) - of CONFIG_PUBLIC_KEY: - let serverPublicKey = Bytes.toString(data).toKey() - ctx.sessionKey = deriveSessionKey(agentKeyPair, serverPublicKey) - of CONFIG_PROFILE: - ctx.profile = parseString(Bytes.toString(data)) - else: discard + var ctx = AgentCtx( + agentId: generateUUID(), + listenerId: Uuid.toString(unpacker.getUint32()), + ip: unpacker.getDataWithLengthPrefix(), + port: int(unpacker.getUint32()), + sleep: int(unpacker.getUint32()), + sessionKey: deriveSessionKey(agentKeyPair, unpacker.getByteArray(Key)), + agentPublicKey: agentKeyPair.publicKey, + profile: parseString(unpacker.getDataWithLengthPrefix()) + ) + + wipeKey(agentKeyPair.privateKey) echo "[+] Profile configuration deserialized." return ctx @@ -42,34 +28,11 @@ proc deserializeConfiguration(config: string): AgentCtx = proc init*(T: type AgentCtx): AgentCtx = try: - # The agent configuration is read at compile time using define/-d statements in nim.cfg - # This configuration file can be dynamically generated from the teamserver management interface - # Downside to this is obviously that readable strings, such as the listener UUID can be found in the binary when not defined(CONFIGURATION): raise newException(CatchableError, "Missing agent configuration.") return deserializeConfiguration(CONFIGURATION) - # Create agent configuration - # var agentKeyPair = generateKeyPair() - # let serverPublicKey = decode(ServerPublicKey).toKey() - - # let ctx = AgentCtx( - # 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, - # profile: parseString(decode(ProfileString)) - # ) - - # # Cleanup agent's secret key - # wipeKey(agentKeyPair.privateKey) - - # return ctx - except CatchableError as err: echo "[-] " & err.msg return nil diff --git a/src/agent/nim.cfg b/src/agent/nim.cfg index f5d398b..95432bf 100644 --- a/src/agent/nim.cfg +++ b/src/agent/nim.cfg @@ -1,3 +1,3 @@ # Agent configuration --d:CONFIGURATION=PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER +-d:CONFIGURATION=PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER -o:"/mnt/c/Users/jakob/Documents/Projects/conquest/bin/monarch.x64.exe" diff --git a/src/agent/protocol/registration.nim b/src/agent/protocol/registration.nim index 14b0753..5ee08c2 100644 --- a/src/agent/protocol/registration.nim +++ b/src/agent/protocol/registration.nim @@ -228,12 +228,12 @@ proc serializeRegistrationData*(ctx: AgentCtx, data: var AgentRegistrationData): # Serialize registration data packer .add(data.metadata.listenerId) - .addVarLengthMetadata(data.metadata.username) - .addVarLengthMetadata(data.metadata.hostname) - .addVarLengthMetadata(data.metadata.domain) - .addVarLengthMetadata(data.metadata.ip) - .addVarLengthMetadata(data.metadata.os) - .addVarLengthMetadata(data.metadata.process) + .addDataWithLengthPrefix(data.metadata.username) + .addDataWithLengthPrefix(data.metadata.hostname) + .addDataWithLengthPrefix(data.metadata.domain) + .addDataWithLengthPrefix(data.metadata.ip) + .addDataWithLengthPrefix(data.metadata.os) + .addDataWithLengthPrefix(data.metadata.process) .add(data.metadata.pid) .add(data.metadata.isElevated) .add(data.metadata.sleep) diff --git a/src/common/serialize.nim b/src/common/serialize.nim index 8f204e1..a77e312 100644 --- a/src/common/serialize.nim +++ b/src/common/serialize.nim @@ -1,5 +1,9 @@ import streams, strutils, tables import ./[types, utils, crypto, sequence] + +#[ + Packer +]# type Packer* = ref object stream: StringStream @@ -32,16 +36,16 @@ proc addArgument*(packer: Packer, arg: TaskArg): Packer {.discardable.} = packer.addData(arg.data) return packer -proc addVarLengthMetadata*(packer: Packer, metadata: seq[byte]): Packer {.discardable.} = - # Add length of metadata field - packer.add(cast[uint32](metadata.len)) +proc addDataWithLengthPrefix*(packer: Packer, data: seq[byte]): Packer {.discardable.} = + # Add length of variable-length data field + packer.add(cast[uint32](data.len)) - if metadata.len <= 0: + if data.len <= 0: # Field is empty (e.g. not domain joined) return packer # Add content - packer.addData(metadata) + packer.addData(data) return packer proc pack*(packer: Packer): seq[byte] = @@ -59,6 +63,9 @@ proc reset*(packer: Packer): Packer {.discardable.} = packer.stream = newStringStream() return packer +#[ + Unpacker +]# type Unpacker* = ref object stream: StringStream @@ -68,10 +75,7 @@ proc init*(T: type Unpacker, data: string): Unpacker = result = new Unpacker result.stream = newStringStream(data) result.position = 0 - -proc getPosition*(unpacker: Unpacker): int = - return unpacker.position - + proc getUint8*(unpacker: Unpacker): uint8 = result = unpacker.stream.readUint8() unpacker.position += 1 @@ -100,38 +104,16 @@ proc getBytes*(unpacker: Unpacker, length: int): seq[byte] = if bytesRead != length: raise newException(IOError, "Not enough data to read") -proc getKey*(unpacker: Unpacker): Key = - var key: Key +proc getByteArray*(unpacker: Unpacker, T: typedesc[Key | Iv | AuthenticationTag]): array = + var bytes: array[sizeof(T), byte] - let bytesRead = unpacker.stream.readData(key[0].unsafeAddr, 32) + let bytesRead = unpacker.stream.readData(bytes[0].unsafeAddr, sizeof(T)) unpacker.position += bytesRead - if bytesRead != 32: - raise newException(IOError, "Not enough data to read key") + if bytesRead != sizeof(T): + raise newException(IOError, "Not enough data to read structure.") - return key - -proc getIv*(unpacker: Unpacker): Iv = - var iv: Iv - - let bytesRead = unpacker.stream.readData(iv[0].unsafeAddr, 12) - unpacker.position += bytesRead - - if bytesRead != 12: - raise newException(IOError, "Not enough data to read IV") - - return iv - -proc getAuthenticationTag*(unpacker: Unpacker): AuthenticationTag = - var tag: AuthenticationTag - - let bytesRead = unpacker.stream.readData(tag[0].unsafeAddr, 16) - unpacker.position += bytesRead - - if bytesRead != 16: - raise newException(IOError, "Not enough data to read authentication tag") - - return tag + return bytes proc getArgument*(unpacker: Unpacker): TaskArg = result.argType = unpacker.getUint8() @@ -150,9 +132,8 @@ proc getArgument*(unpacker: Unpacker): TaskArg = else: discard -proc getVarLengthMetadata*(unpacker: Unpacker): string = - - # Read length of metadata field +proc getDataWithLengthPrefix*(unpacker: Unpacker): string = + # Read length of variable-length field let length = unpacker.getUint32() if length <= 0: @@ -185,7 +166,7 @@ proc deserializeHeader*(unpacker: Unpacker): Header= size: unpacker.getUint32(), agentId: unpacker.getUint32(), seqNr: unpacker.getUint32(), - iv: unpacker.getIv(), - gmac: unpacker.getAuthenticationTag() + iv: unpacker.getByteArray(Iv), + gmac: unpacker.getByteArray(AuthenticationTag) ) \ No newline at end of file diff --git a/src/modules/shell.nim b/src/modules/shell.nim index cd88b96..fb978bc 100644 --- a/src/modules/shell.nim +++ b/src/modules/shell.nim @@ -44,7 +44,7 @@ when defined(agent): else: discard - echo fmt" [>] Executing: {command} {arguments}." + echo fmt" [>] Executing command: {command} {arguments}" let (output, status) = execCmdEx(fmt("{command} {arguments}")) diff --git a/src/server/core/builder.nim b/src/server/core/builder.nim index 574f417..65e7cf0 100644 --- a/src/server/core/builder.nim +++ b/src/server/core/builder.nim @@ -1,4 +1,4 @@ -import terminal, strformat, strutils, sequtils, tables, times, system, osproc, streams, base64, parsetoml +import terminal, strformat, strutils, tables, system, osproc, streams, parsetoml import ../utils import ../../common/[types, utils, profile, serialize] @@ -10,36 +10,18 @@ proc serializeConfiguration(cq: Conquest, listener: Listener, sleep: int): seq[b var packer = Packer.init() - # Add listener configuration - packer.add(uint8(CONFIG_LISTENER_UUID)) - packer.add(uint32(sizeof(uint32))) + # Add listener configuration + # Variable length data is prefixed with a 4-byte length indicator 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.addDataWithLengthPrefix(string.toBytes(listener.address)) 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)) + packer.addDataWithLengthPrefix(string.toBytes(cq.profile.toTomlString())) let data = packer.pack() cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Profile configuration serialized.") + return data proc compile(cq: Conquest, placeholderLength: int): string = diff --git a/src/server/protocol/packer.nim b/src/server/protocol/packer.nim index 6136358..e888e3a 100644 --- a/src/server/protocol/packer.nim +++ b/src/server/protocol/packer.nim @@ -79,7 +79,7 @@ proc deserializeNewAgent*(cq: Conquest, data: seq[byte]): Agent = validatePacket(header, cast[uint8](MSG_REGISTER)) # Key exchange - let agentPublicKey = unpacker.getKey() + let agentPublicKey = unpacker.getByteArray(Key) let sessionKey = deriveSessionKey(cq.keyPair, agentPublicKey) # Decrypt payload @@ -91,12 +91,12 @@ proc deserializeNewAgent*(cq: Conquest, data: seq[byte]): Agent = let listenerId = unpacker.getUint32() - username = unpacker.getVarLengthMetadata() - hostname = unpacker.getVarLengthMetadata() - domain = unpacker.getVarLengthMetadata() - ip = unpacker.getVarLengthMetadata() - os = unpacker.getVarLengthMetadata() - process = unpacker.getVarLengthMetadata() + username = unpacker.getDataWithLengthPrefix() + hostname = unpacker.getDataWithLengthPrefix() + domain = unpacker.getDataWithLengthPrefix() + ip = unpacker.getDataWithLengthPrefix() + os = unpacker.getDataWithLengthPrefix() + process = unpacker.getDataWithLengthPrefix() pid = unpacker.getUint32() isElevated = unpacker.getUint8() sleep = unpacker.getUint32()