Refactor profile de/serialization, removing unnecessary overhead caused by TLV format.
This commit is contained in:
@@ -9,32 +9,18 @@ proc deserializeConfiguration(config: string): AgentCtx =
|
||||
|
||||
var agentKeyPair = generateKeyPair()
|
||||
|
||||
var ctx = new AgentCtx
|
||||
ctx.agentId = generateUUID()
|
||||
ctx.agentPublicKey = agentKeyPair.publicKey
|
||||
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())
|
||||
)
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Agent configuration
|
||||
-d:CONFIGURATION=PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER
|
||||
-d:CONFIGURATION=PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER
|
||||
-o:"/mnt/c/Users/jakob/Documents/Projects/conquest/bin/monarch.x64.exe"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -69,9 +76,6 @@ proc init*(T: type Unpacker, data: string): 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)
|
||||
)
|
||||
|
||||
@@ -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}"))
|
||||
|
||||
|
||||
@@ -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]
|
||||
@@ -11,35 +11,17 @@ 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)))
|
||||
# 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 =
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user