2025-09-01 20:27:00 +02:00
|
|
|
import std/paths
|
2025-08-29 15:58:26 +02:00
|
|
|
import strutils, sequtils, times
|
2025-09-01 20:27:00 +02:00
|
|
|
import ../../common/[types, sequence, crypto, utils, serialize]
|
2025-07-16 12:32:01 +02:00
|
|
|
|
2025-07-18 14:24:07 +02:00
|
|
|
proc parseInput*(input: string): seq[string] =
|
2025-07-16 12:32:01 +02:00
|
|
|
var i = 0
|
|
|
|
|
while i < input.len:
|
|
|
|
|
|
|
|
|
|
# Skip whitespaces/tabs
|
|
|
|
|
while i < input.len and input[i] in {' ', '\t'}:
|
|
|
|
|
inc i
|
|
|
|
|
if i >= input.len:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
var arg = ""
|
|
|
|
|
if input[i] == '"':
|
|
|
|
|
# Parse quoted argument
|
|
|
|
|
inc i # Skip opening quote
|
|
|
|
|
|
|
|
|
|
# Add parsed argument when quotation is closed
|
|
|
|
|
while i < input.len and input[i] != '"':
|
|
|
|
|
arg.add(input[i])
|
|
|
|
|
inc i
|
|
|
|
|
|
|
|
|
|
if i < input.len:
|
|
|
|
|
inc i # Skip closing quote
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
while i < input.len and input[i] notin {' ', '\t'}:
|
|
|
|
|
arg.add(input[i])
|
|
|
|
|
inc i
|
|
|
|
|
|
|
|
|
|
# Add argument to returned result
|
|
|
|
|
if arg.len > 0: result.add(arg)
|
2025-07-18 14:24:07 +02:00
|
|
|
|
|
|
|
|
proc parseArgument*(argument: Argument, value: string): TaskArg =
|
|
|
|
|
|
2025-08-19 20:03:34 +02:00
|
|
|
var arg: TaskArg
|
|
|
|
|
arg.argType = cast[uint8](argument.argumentType)
|
2025-07-18 14:24:07 +02:00
|
|
|
|
|
|
|
|
case argument.argumentType:
|
|
|
|
|
|
|
|
|
|
of INT:
|
|
|
|
|
# Length: 4 bytes
|
|
|
|
|
let intValue = cast[uint32](parseUInt(value))
|
2025-08-19 20:03:34 +02:00
|
|
|
arg.data = @[byte(intValue and 0xFF), byte((intValue shr 8) and 0xFF), byte((intValue shr 16) and 0xFF), byte((intValue shr 24) and 0xFF)]
|
2025-07-18 14:24:07 +02:00
|
|
|
|
2025-08-30 14:05:09 +02:00
|
|
|
of SHORT:
|
|
|
|
|
# Length: 2 bytes
|
|
|
|
|
let shortValue = cast[uint16](parseUint(value))
|
|
|
|
|
arg.data = @[byte(shortValue and 0xFF), byte((shortValue shr 8) and 0xFF)]
|
|
|
|
|
|
2025-07-18 14:24:07 +02:00
|
|
|
of LONG:
|
|
|
|
|
# Length: 8 bytes
|
|
|
|
|
var data = newSeq[byte](8)
|
2025-08-30 14:05:09 +02:00
|
|
|
let longValue = cast[uint64](parseUInt(value))
|
2025-07-18 14:24:07 +02:00
|
|
|
for i in 0..7:
|
2025-08-30 14:05:09 +02:00
|
|
|
data[i] = byte((longValue shr (i * 8)) and 0xFF)
|
2025-08-19 20:03:34 +02:00
|
|
|
arg.data = data
|
2025-07-18 14:24:07 +02:00
|
|
|
|
|
|
|
|
of BOOL:
|
|
|
|
|
# Length: 1 byte
|
|
|
|
|
if value == "true":
|
2025-08-19 20:03:34 +02:00
|
|
|
arg.data = @[1'u8]
|
2025-07-18 14:24:07 +02:00
|
|
|
elif value == "false":
|
2025-08-19 20:03:34 +02:00
|
|
|
arg.data = @[0'u8]
|
2025-07-18 14:24:07 +02:00
|
|
|
else:
|
|
|
|
|
raise newException(ValueError, "Invalid value for boolean argument.")
|
|
|
|
|
|
|
|
|
|
of STRING:
|
2025-08-29 15:58:26 +02:00
|
|
|
arg.data = string.toBytes(value)
|
2025-07-18 14:24:07 +02:00
|
|
|
|
|
|
|
|
of BINARY:
|
2025-09-01 20:27:00 +02:00
|
|
|
# A binary data argument consists of the file name (without the path) and the file content in bytes, both prefixed with their length as a uint32
|
|
|
|
|
var packer = Packer.init()
|
|
|
|
|
|
|
|
|
|
let fileName = cast[string](extractFilename(cast[Path](value)))
|
|
|
|
|
packer.addDataWithLengthPrefix(string.toBytes(fileName))
|
|
|
|
|
|
|
|
|
|
let fileContents = readFile(value)
|
|
|
|
|
packer.addDataWithLengthPrefix(string.toBytes(fileContents))
|
|
|
|
|
|
|
|
|
|
arg.data = packer.pack()
|
2025-07-18 14:24:07 +02:00
|
|
|
|
2025-08-19 20:03:34 +02:00
|
|
|
return arg
|
2025-07-18 14:24:07 +02:00
|
|
|
|
2025-07-28 21:29:47 +02:00
|
|
|
proc createTask*(cq: Conquest, command: Command, arguments: seq[string]): Task =
|
2025-07-18 14:24:07 +02:00
|
|
|
|
|
|
|
|
# Construct the task payload prefix
|
|
|
|
|
var task: Task
|
2025-08-18 22:05:23 +02:00
|
|
|
task.taskId = string.toUuid(generateUUID())
|
|
|
|
|
task.listenerId = string.toUuid(cq.interactAgent.listenerId)
|
2025-07-18 14:24:07 +02:00
|
|
|
task.timestamp = uint32(now().toTime().toUnix())
|
|
|
|
|
task.command = cast[uint16](command.commandType)
|
|
|
|
|
task.argCount = uint8(arguments.len)
|
|
|
|
|
|
|
|
|
|
var taskArgs: seq[TaskArg]
|
|
|
|
|
|
2025-08-29 15:58:26 +02:00
|
|
|
# Add the task arguments
|
|
|
|
|
if arguments.len() < command.arguments.filterIt(it.isRequired).len():
|
|
|
|
|
raise newException(CatchableError, "Missing required argument.")
|
|
|
|
|
|
|
|
|
|
for i, arg in arguments:
|
|
|
|
|
if i < command.arguments.len():
|
|
|
|
|
taskArgs.add(parseArgument(command.arguments[i], arg))
|
|
|
|
|
else:
|
|
|
|
|
# Optional arguments should ALWAYS be placed at the end of the command and take the same definition
|
|
|
|
|
taskArgs.add(parseArgument(command.arguments[^1], arg))
|
2025-07-18 14:24:07 +02:00
|
|
|
|
|
|
|
|
task.args = taskArgs
|
|
|
|
|
|
|
|
|
|
# Construct the header
|
|
|
|
|
var taskHeader: Header
|
|
|
|
|
taskHeader.magic = MAGIC
|
|
|
|
|
taskHeader.version = VERSION
|
|
|
|
|
taskHeader.packetType = cast[uint8](MSG_TASK)
|
2025-07-23 13:56:43 +02:00
|
|
|
taskHeader.flags = cast[uint16](FLAG_ENCRYPTED)
|
2025-07-18 14:24:07 +02:00
|
|
|
taskHeader.size = 0'u32
|
2025-08-18 22:05:23 +02:00
|
|
|
taskHeader.agentId = string.toUuid(cq.interactAgent.agentId)
|
2025-07-26 18:20:54 +02:00
|
|
|
taskHeader.seqNr = nextSequence(taskHeader.agentId)
|
2025-08-25 20:08:23 +02:00
|
|
|
taskHeader.iv = generateBytes(Iv) # Generate a random IV for AES-256 GCM
|
2025-07-23 13:47:37 +02:00
|
|
|
taskHeader.gmac = default(AuthenticationTag)
|
2025-07-18 14:24:07 +02:00
|
|
|
|
|
|
|
|
task.header = taskHeader
|
|
|
|
|
|
|
|
|
|
# Return the task object for serialization
|
|
|
|
|
return task
|