Updated directory structure.

This commit is contained in:
Jakob Friedl
2025-07-15 23:26:54 +02:00
parent 453971c0db
commit 668a4984d1
30 changed files with 139 additions and 136 deletions

View File

@@ -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)

View File

@@ -11,8 +11,8 @@ Basic API-only Commands
- [x] ls/dir : List all files in directory (including hidden ones) - [x] ls/dir : List all files in directory (including hidden ones)
- [x] rm : Remove a file - [x] rm : Remove a file
- [x] rmdir : Remove a empty directory - [x] rmdir : Remove a empty directory
- [ ] mv : Move a file - [x] mv : Move a file
- [ ] cp : Copy a file - [x] cp : Copy a file
- [ ] cat/type : Display contents of a file - [ ] cat/type : Display contents of a file
- [ ] env : Display environment variables - [ ] env : Display environment variables
- [ ] ps : List processes - [ ] ps : List processes
@@ -26,7 +26,7 @@ Basic API-only Commands
Execution 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) - [ ] bof : Execute Beacon Object File in memory and retrieve output (bof /local/path/file.o)
- Read from listener endpoint directly to memory - Read from listener endpoint directly to memory
- Base for all kinds of BOFs (Situational Awareness, ...) - Base for all kinds of BOFs (Situational Awareness, ...)

View File

@@ -1,4 +1,4 @@
#!/bin/bash #!/bin/bash
CONQUEST_ROOT="/mnt/c/Users/jakob/Documents/Projects/conquest" CONQUEST_ROOT="/mnt/c/Users/jakob/Documents/Projects/conquest"
nim --os:windows --cpu:amd64 --gcc.exe:x86_64-w64-mingw32-gcc --gcc.linkerexe:x86_64-w64-mingw32-gcc -d:release --outdir:"$CONQUEST_ROOT/bin" -o:"monarch.x64.exe" c $CONQUEST_ROOT/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

View File

@@ -1,5 +1,5 @@
import winim, tables import winim, tables
import ../../server/types import ../../types
export Task, CommandType, TaskResult, TaskStatus export Task, CommandType, TaskResult, TaskStatus
type type

View File

@@ -1,8 +1,9 @@
import terminal, strformat, strutils, sequtils, tables, json, times, base64, system, osproc, streams 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 Agent management mode
@@ -121,7 +122,7 @@ proc agentBuild*(cq: Conquest, listener, sleep, payload: string) =
let listener = cq.listeners[listener.toUpperAscii] let listener = cq.listeners[listener.toUpperAscii]
# Create/overwrite nim.cfg file to set agent configuration # 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 # 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) 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.") cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Configuration file created.")
# Build agent by executing the ./build.sh script on the system. # 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...") cq.writeLine(fgBlack, styleBright, "[*] ", resetStyle, "Building agent...")
@@ -166,87 +167,3 @@ proc agentBuild*(cq: Conquest, listener, sleep, payload: string) =
except CatchableError as err: except CatchableError as err:
cq.writeLine(fgRed, styleBright, "[-] ", resetStyle, "An error occurred: ", err.msg) 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

View File

@@ -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

View File

@@ -1,8 +1,8 @@
import prologue, nanoid, json import prologue, nanoid, json
import sequtils, strutils, times import sequtils, strutils, times
import ../[types] import ./agentApi
import ../agent/agent import ../../types
proc error404*(ctx: Context) {.async.} = proc error404*(ctx: Context) {.async.} =
resp "", Http404 resp "", Http404

View File

@@ -1,9 +1,10 @@
import strformat, strutils, sequtils, nanoid, terminal import strformat, strutils, sequtils, nanoid, terminal
import prologue import prologue
import ./api import ./endpoints
import ../[types, utils] import ../utils
import ../db/database import ../db/database
import ../../types
proc listenerUsage*(cq: Conquest) = proc listenerUsage*(cq: Conquest) =
cq.writeLine("""Manage, start and stop listeners. cq.writeLine("""Manage, start and stop listeners.
@@ -46,10 +47,10 @@ proc listenerStart*(cq: Conquest, host: string, portStr: string) =
var listener = newApp(settings = listenerSettings) var listener = newApp(settings = listenerSettings)
# Define API endpoints # Define API endpoints
listener.post("{listener}/register", api.register) listener.post("{listener}/register", endpoints.register)
listener.get("{listener}/{agent}/tasks", api.getTasks) listener.get("{listener}/{agent}/tasks", endpoints.getTasks)
listener.post("{listener}/{agent}/{task}/results", api.postResults) listener.post("{listener}/{agent}/{task}/results", endpoints.postResults)
listener.registerErrorHandler(Http404, api.error404) listener.registerErrorHandler(Http404, endpoints.error404)
# Store listener in database # Store listener in database
var listenerInstance = newListener(name, host, port) var listenerInstance = newListener(name, host, port)
@@ -79,10 +80,10 @@ proc restartListeners*(cq: Conquest) =
listener = newApp(settings = settings) listener = newApp(settings = settings)
# Define API endpoints # Define API endpoints
listener.post("{listener}/register", api.register) listener.post("{listener}/register", endpoints.register)
listener.get("{listener}/{agent}/tasks", api.getTasks) listener.get("{listener}/{agent}/tasks", endpoints.getTasks)
listener.post("{listener}/{agent}/{task}/results", api.postResults) listener.post("{listener}/{agent}/{task}/results", endpoints.postResults)
listener.registerErrorHandler(Http404, api.error404) listener.registerErrorHandler(Http404, endpoints.error404)
try: try:
discard listener.runAsync() discard listener.runAsync()

View File

@@ -1,6 +1,5 @@
import argparse, times, strformat, terminal, nanoid, tables, json, sequtils import argparse, times, strformat, terminal, nanoid, tables, json, sequtils
import ./taskDispatcher import ../../types
import ../types
#[ #[
Agent Argument parsing Agent Argument parsing
@@ -224,6 +223,19 @@ proc packageArguments(cq: Conquest, command: Command, arguments: seq[string]): J
else: else:
result[argument.name] = %"" 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) = proc handleAgentCommand*(cq: Conquest, input: string) =
# Return if no command (or just whitespace) is entered # Return if no command (or just whitespace) is entered
if input.replace(" ", "").len == 0: return if input.replace(" ", "").len == 0: return

View File

@@ -1,6 +1,7 @@
import system, terminal, tiny_sqlite import system, terminal, tiny_sqlite
import ../types
import ./[dbAgent, dbListener] import ./[dbAgent, dbListener]
import ../../types
# Export functions so that only ./db/database is required to be imported # Export functions so that only ./db/database is required to be imported
export dbAgent, dbListener export dbAgent, dbListener

View File

@@ -1,5 +1,5 @@
import system, terminal, tiny_sqlite, times import system, terminal, tiny_sqlite, times
import ../types import ../../types
#[ #[
Agent database functions Agent database functions

View File

@@ -1,5 +1,5 @@
import system, terminal, tiny_sqlite import system, terminal, tiny_sqlite
import ../types import ../../types
#[ #[
Listener database functions Listener database functions

View File

@@ -1,4 +1,4 @@
import ./types import ../types
# Global variable for handling listeners, agents and console output # Global variable for handling listeners, agents and console output
var cq*: Conquest var cq*: Conquest

View File

@@ -1,8 +1,9 @@
import prompt, terminal, argparse import prompt, terminal, argparse
import strutils, strformat, times, system, tables import strutils, strformat, times, system, tables
import ./[types, globals] import ./globals
import agent/agent, listener/listener, db/database import core/agent, core/listener, db/database
import ../types
#[ #[
Argument parsing Argument parsing
@@ -136,7 +137,7 @@ proc main() =
setControlCHook(exit) setControlCHook(exit)
# Initialize framework # Initialize framework
let dbPath: string = "../server/db/conquest.db" let dbPath: string = "../src/server/db/conquest.db"
cq = initConquest(dbPath) cq = initConquest(dbPath)
# Print header # Print header

View File

@@ -1,7 +1,7 @@
import strutils, terminal, tables, sequtils, times, strformat import strutils, terminal, tables, sequtils, times, strformat
import std/wordwrap import std/wordwrap
import ./[types] import ../types
proc parseOctets*(ip: string): tuple[first, second, third, fourth: int] = proc parseOctets*(ip: string): tuple[first, second, third, fourth: int] =
# TODO: Verify that address is in correct, expected format # TODO: Verify that address is in correct, expected format

View File

@@ -1,8 +1,7 @@
import prompt import prompt
import prologue import prologue
import tables, sequtils import tables
import times import times
import terminal
#[ #[
Agent types & procs Agent types & procs
@@ -140,7 +139,6 @@ proc stringToProtocol*(protocol: string): Protocol =
return HTTP return HTTP
else: discard else: discard
#[ #[
Conquest framework types & procs Conquest framework types & procs
]# ]#