From 0a98d11df2167ee21deb800d25cf56dbeee408a1 Mon Sep 17 00:00:00 2001 From: Jakob Friedl <71284620+jakobfriedl@users.noreply.github.com> Date: Sun, 18 May 2025 12:51:26 +0200 Subject: [PATCH] Implemented switch to agent interaction mode. --- .gitignore | 4 + README.md | 3 +- server/agent/agent.nim | 24 ++++- server/agent/commands.nim | 47 +++++++++ server/db/database.nim | 190 +---------------------------------- server/db/dbAgent.nim | 117 +++++++++++++++++++++ server/db/dbListener.nim | 71 +++++++++++++ server/listener/api.nim | 2 +- server/listener/listener.nim | 5 +- server/nim.cfg | 3 +- server/server.nim | 10 +- server/types.nim | 6 +- server/utils.nim | 8 +- 13 files changed, 279 insertions(+), 211 deletions(-) create mode 100644 server/agent/commands.nim create mode 100644 server/db/dbAgent.nim create mode 100644 server/db/dbListener.nim diff --git a/.gitignore b/.gitignore index 4bbd08d..8b52d98 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ # Ignore agents agents/ *.db +# Ignore binaries +*/bin/* +server/server +*.exe # Nim nimcache/ diff --git a/README.md b/README.md index 4468959..45bb6c7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ -# Conquest Command & Control Framework +# Conquest Framework +> Command - Control - Conquer ## Acknowledgements diff --git a/server/agent/agent.nim b/server/agent/agent.nim index 4b2a9b4..b9dceda 100644 --- a/server/agent/agent.nim +++ b/server/agent/agent.nim @@ -1,4 +1,5 @@ import terminal, strformat, strutils, tables +import ./commands import ../[types, globals, utils] import ../db/database @@ -84,12 +85,27 @@ proc agentKill*(cq: Conquest, name: string) = cq.writeLine(fgYellow, styleBright, "[+] ", resetStyle, "Terminated agent ", fgYellow, styleBright, name.toUpperAscii, resetStyle, ".") # Switch to interact mode -proc agentInteract*(cq: Conquest, args: varargs[string]) = +proc agentInteract*(cq: Conquest, name: string) = - cq.setIndicator("[AGENT] (username@hostname)> ") - cq.setStatusBar(@[("[mode]", "interact"), ("[username]", "X"), ("[hostname]", "4"), ("[ip]", "127.0.0.1"), ("[domain]", "domain.local")]) + # Verify that agent exists + if not cq.dbAgentExists(name.toUpperAscii): + cq.writeLine(fgRed, styleBright, fmt"[-] Agent {name.toUpperAscii} does not exist.") + return - var command: string = cq.readLine() + let agent = cq.agents[name.toUpperAscii] + var command: string = "" + + # Change prompt indicator to show agent interaction + cq.setIndicator(fmt"[{agent.name}]> ") + cq.setStatusBar(@[("[mode]", "interact"), ("[username]", fmt"{agent.username}"), ("[hostname]", fmt"{agent.hostname}"), ("[ip]", fmt"{agent.ip}"), ("[domain]", fmt"{agent.domain}")]) + cq.writeLine(fgYellow, "[+] ", resetStyle, fmt"Started interacting with agent ", fgYellow, agent.name, resetStyle, ". Type 'help' to list available commands.\n") + cq.interactAgent = agent + + while command != "exit": + command = cq.readLine() + cq.withOutput(handleAgentCommand, command) + + cq.interactAgent = nil #[ Agent API diff --git a/server/agent/commands.nim b/server/agent/commands.nim new file mode 100644 index 0000000..1666b98 --- /dev/null +++ b/server/agent/commands.nim @@ -0,0 +1,47 @@ +import argparse, times, strformat, terminal +import ../[types] + +#[ + Agnet Argument parsing +]# +var parser = newParser: + help("Conquest Command & Control") + + command("shell"): + help("Execute a shell command.") + + command("help"): + nohelpflag() + + command("exit"): + nohelpflag() + +proc handleAgentCommand*(cq: Conquest, args: varargs[string]) = + + # Return if no command (or just whitespace) is entered + if args[0].replace(" ", "").len == 0: return + + let date: string = now().format("dd-MM-yyyy HH:mm:ss") + cq.writeLine(fgCyan, fmt"[{date}] ", fgYellow, fmt"[{cq.interactAgent.name}] ", resetStyle, styleBright, args[0]) + + try: + let opts = parser.parse(args[0].split(" ").filterIt(it.len > 0)) + + case opts.command + + of "exit": # Exit program + discard + + of "help": # Display help menu + cq.writeLine(parser.help()) + + # Handle help flag + except ShortCircuit as err: + if err.flag == "argparse_help": + cq.writeLine(err.help) + + # Handle invalid arguments + except UsageError: + cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) + + cq.writeLine("") \ No newline at end of file diff --git a/server/db/database.nim b/server/db/database.nim index b236df7..f01a2c9 100644 --- a/server/db/database.nim +++ b/server/db/database.nim @@ -1,7 +1,9 @@ -import tiny_sqlite, net +import system, terminal, tiny_sqlite import ../types +import ./[dbAgent, dbListener] -import system, terminal, strformat +# Export functions so that only ./db/database is required to be imported +export dbAgent, dbListener proc dbInit*(cq: Conquest) = @@ -40,187 +42,3 @@ proc dbInit*(cq: Conquest) = conquestDb.close() except SqliteError: cq.writeLine(fgGreen, "[+] ", cq.dbPath, ": Database file found.") - -#[ - Listener database functions -]# -proc dbStoreListener*(cq: Conquest, listener: Listener): bool = - - try: - let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) - - conquestDb.exec(""" - INSERT INTO listeners (name, address, port, protocol) - VALUES (?, ?, ?, ?); - """, listener.name, listener.address, listener.port, $listener.protocol) - - conquestDb.close() - except: - cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) - return false - - return true - -proc dbGetAllListeners*(cq: Conquest): seq[Listener] = - - var listeners: seq[Listener] = @[] - - try: - let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) - - for row in conquestDb.iterate("SELECT name, address, port, protocol FROM listeners;"): - let (name, address, port, protocol) = row.unpack((string, string, int, string)) - - let l = Listener( - name: name, - address: address, - port: port, - protocol: stringToProtocol(protocol), - ) - listeners.add(l) - - conquestDb.close() - except: - cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) - - return listeners - -proc dbDeleteListenerByName*(cq: Conquest, name: string): bool = - try: - let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) - - conquestDb.exec("DELETE FROM listeners WHERE name = ?", name) - - conquestDb.close() - except: - return false - - return true - -proc dbListenerExists*(cq: Conquest, listenerName: string): bool = - try: - let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) - - let res = conquestDb.one("SELECT 1 FROM listeners WHERE name = ? LIMIT 1", listenerName) - - conquestDb.close() - - return res.isSome - except: - cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) - return false - -#[ - Agent database functions -]# -proc dbStoreAgent*(cq: Conquest, agent: Agent): bool = - - try: - let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) - - conquestDb.exec(""" - INSERT INTO agents (name, listener, process, pid, username, hostname, domain, ip, os, elevated, sleep, jitter, firstCheckin) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); - """, agent.name, agent.listener, agent.process, agent.pid, agent.username, agent.hostname, agent.domain, agent.ip, agent.os, agent.elevated, agent.sleep, agent.jitter, $agent.firstCheckin) - - conquestDb.close() - except: - cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) - return false - - return true - -proc dbGetAllAgents*(cq: Conquest): seq[Agent] = - - var agents: seq[Agent] = @[] - - try: - let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) - - for row in conquestDb.iterate("SELECT name, listener, sleep, jitter, process, pid, username, hostname, domain, ip, os, elevated, firstCheckin FROM agents;"): - let (name, listener, sleep, jitter, process, pid, username, hostname, domain, ip, os, elevated, firstCheckin) = row.unpack((string, string, int, float, string, int, string, string, string, string, string, bool, string)) - - let a = Agent( - name: name, - listener: listener, - sleep: sleep, - pid: pid, - username: username, - hostname: hostname, - domain: domain, - ip: ip, - os: os, - elevated: elevated, - firstCheckin: firstCheckin, - jitter: jitter, - process: process, - tasks: @[] - ) - - agents.add(a) - - conquestDb.close() - except: - cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) - - return agents - -proc dbGetAllAgentsByListener*(cq: Conquest, listenerName: string): seq[Agent] = - - var agents: seq[Agent] = @[] - - try: - let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) - - for row in conquestDb.iterate("SELECT name, listener, sleep, jitter, process, pid, username, hostname, domain, ip, os, elevated, firstCheckin FROM agents WHERE listener = ?;", listenerName): - let (name, listener, sleep, jitter, process, pid, username, hostname, domain, ip, os, elevated, firstCheckin) = row.unpack((string, string, int, float, string, int, string, string, string, string, string, bool, string)) - - let a = Agent( - name: name, - listener: listener, - sleep: sleep, - pid: pid, - username: username, - hostname: hostname, - domain: domain, - ip: ip, - os: os, - elevated: elevated, - firstCheckin: firstCheckin, - jitter: jitter, - process: process, - tasks: @[] - ) - - agents.add(a) - - conquestDb.close() - except: - cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) - - return agents - -proc dbDeleteAgentByName*(cq: Conquest, name: string): bool = - try: - let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) - - conquestDb.exec("DELETE FROM agents WHERE name = ?", name) - - conquestDb.close() - except: - return false - - return true - -proc dbAgentExists*(cq: Conquest, agentName: string): bool = - try: - let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) - - let res = conquestDb.one("SELECT 1 FROM agents WHERE name = ? LIMIT 1", agentName) - - conquestDb.close() - - return res.isSome - except: - cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) - return false \ No newline at end of file diff --git a/server/db/dbAgent.nim b/server/db/dbAgent.nim new file mode 100644 index 0000000..3c8c175 --- /dev/null +++ b/server/db/dbAgent.nim @@ -0,0 +1,117 @@ +import system, terminal, tiny_sqlite +import ../types + +#[ + Agent database functions +]# +proc dbStoreAgent*(cq: Conquest, agent: Agent): bool = + + try: + let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) + + conquestDb.exec(""" + INSERT INTO agents (name, listener, process, pid, username, hostname, domain, ip, os, elevated, sleep, jitter, firstCheckin) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + """, agent.name, agent.listener, agent.process, agent.pid, agent.username, agent.hostname, agent.domain, agent.ip, agent.os, agent.elevated, agent.sleep, agent.jitter, $agent.firstCheckin) + + conquestDb.close() + except: + cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) + return false + + return true + +proc dbGetAllAgents*(cq: Conquest): seq[Agent] = + + var agents: seq[Agent] = @[] + + try: + let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) + + for row in conquestDb.iterate("SELECT name, listener, sleep, jitter, process, pid, username, hostname, domain, ip, os, elevated, firstCheckin FROM agents;"): + let (name, listener, sleep, jitter, process, pid, username, hostname, domain, ip, os, elevated, firstCheckin) = row.unpack((string, string, int, float, string, int, string, string, string, string, string, bool, string)) + + let a = Agent( + name: name, + listener: listener, + sleep: sleep, + pid: pid, + username: username, + hostname: hostname, + domain: domain, + ip: ip, + os: os, + elevated: elevated, + firstCheckin: firstCheckin, + jitter: jitter, + process: process, + tasks: @[] + ) + + agents.add(a) + + conquestDb.close() + except: + cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) + + return agents + +proc dbGetAllAgentsByListener*(cq: Conquest, listenerName: string): seq[Agent] = + + var agents: seq[Agent] = @[] + + try: + let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) + + for row in conquestDb.iterate("SELECT name, listener, sleep, jitter, process, pid, username, hostname, domain, ip, os, elevated, firstCheckin FROM agents WHERE listener = ?;", listenerName): + let (name, listener, sleep, jitter, process, pid, username, hostname, domain, ip, os, elevated, firstCheckin) = row.unpack((string, string, int, float, string, int, string, string, string, string, string, bool, string)) + + let a = Agent( + name: name, + listener: listener, + sleep: sleep, + pid: pid, + username: username, + hostname: hostname, + domain: domain, + ip: ip, + os: os, + elevated: elevated, + firstCheckin: firstCheckin, + jitter: jitter, + process: process, + tasks: @[] + ) + + agents.add(a) + + conquestDb.close() + except: + cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) + + return agents + +proc dbDeleteAgentByName*(cq: Conquest, name: string): bool = + try: + let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) + + conquestDb.exec("DELETE FROM agents WHERE name = ?", name) + + conquestDb.close() + except: + return false + + return true + +proc dbAgentExists*(cq: Conquest, agentName: string): bool = + try: + let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) + + let res = conquestDb.one("SELECT 1 FROM agents WHERE name = ? LIMIT 1", agentName) + + conquestDb.close() + + return res.isSome + except: + cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) + return false \ No newline at end of file diff --git a/server/db/dbListener.nim b/server/db/dbListener.nim new file mode 100644 index 0000000..623ce91 --- /dev/null +++ b/server/db/dbListener.nim @@ -0,0 +1,71 @@ +import system, terminal, tiny_sqlite +import ../types + +#[ + Listener database functions +]# +proc dbStoreListener*(cq: Conquest, listener: Listener): bool = + + try: + let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) + + conquestDb.exec(""" + INSERT INTO listeners (name, address, port, protocol) + VALUES (?, ?, ?, ?); + """, listener.name, listener.address, listener.port, $listener.protocol) + + conquestDb.close() + except: + cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) + return false + + return true + +proc dbGetAllListeners*(cq: Conquest): seq[Listener] = + + var listeners: seq[Listener] = @[] + + try: + let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) + + for row in conquestDb.iterate("SELECT name, address, port, protocol FROM listeners;"): + let (name, address, port, protocol) = row.unpack((string, string, int, string)) + + let l = Listener( + name: name, + address: address, + port: port, + protocol: stringToProtocol(protocol), + ) + listeners.add(l) + + conquestDb.close() + except: + cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) + + return listeners + +proc dbDeleteListenerByName*(cq: Conquest, name: string): bool = + try: + let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) + + conquestDb.exec("DELETE FROM listeners WHERE name = ?", name) + + conquestDb.close() + except: + return false + + return true + +proc dbListenerExists*(cq: Conquest, listenerName: string): bool = + try: + let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) + + let res = conquestDb.one("SELECT 1 FROM listeners WHERE name = ? LIMIT 1", listenerName) + + conquestDb.close() + + return res.isSome + except: + cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) + return false \ No newline at end of file diff --git a/server/listener/api.nim b/server/listener/api.nim index 2afca22..c43be38 100644 --- a/server/listener/api.nim +++ b/server/listener/api.nim @@ -1,5 +1,5 @@ import prologue, nanoid -import terminal, sequtils, strutils, times +import sequtils, strutils, times import ../[types] import ../agent/agent diff --git a/server/listener/listener.nim b/server/listener/listener.nim index f36bca4..86494ea 100644 --- a/server/listener/listener.nim +++ b/server/listener/listener.nim @@ -1,4 +1,4 @@ -import strformat, strutils, sequtils, checksums/sha1, nanoid, terminal +import strformat, strutils, sequtils, nanoid, terminal import prologue import ./api @@ -27,9 +27,6 @@ proc listenerList*(cq: Conquest) = proc listenerStart*(cq: Conquest, host: string, portStr: string) = # Validate arguments - # if not validateIPv4Address(host): - # cq.writeLine(fgRed, styleBright, fmt"Invalid IPv4 IP address: {ip}.") - # return if not validatePort(portStr): cq.writeLine(fgRed, styleBright, fmt"[-] Invalid port number: {portStr}") return diff --git a/server/nim.cfg b/server/nim.cfg index 9f51a60..2d5efbe 100644 --- a/server/nim.cfg +++ b/server/nim.cfg @@ -1,3 +1,4 @@ # Compiler flags --threads:on --d:httpxServerName="nginx" \ No newline at end of file +-d:httpxServerName="nginx" +#--outdir:"../bin" \ No newline at end of file diff --git a/server/server.nim b/server/server.nim index ca55863..fbf3fcc 100644 --- a/server/server.nim +++ b/server/server.nim @@ -1,5 +1,4 @@ -import prompt, terminal -import argparse +import prompt, terminal, argparse import strutils, strformat, times, system, tables import ./[types, globals] @@ -44,7 +43,7 @@ var parser = newParser: command("interact"): help("Interact with an active agent.") - + option("-n", "-name", help="Name of the agent.", required=true) command("help"): nohelpflag() @@ -92,7 +91,7 @@ proc handleConsoleCommand*(cq: Conquest, args: varargs[string]) = of "kill": cq.agentKill(opts.agent.get.kill.get.name) of "interact": - cq.agentInteract() + cq.agentInteract(opts.agent.get.interact.get.name) else: cq.agentUsage() @@ -115,7 +114,6 @@ proc header(cq: Conquest) = cq.writeLine("─".repeat(21)) cq.writeLine("") - #[ Conquest framework entry point ]# @@ -135,7 +133,7 @@ proc main() = # Initialize database cq.dbInit() cq.restartListeners() - cq.addMutliple(cq.dbGetAllAgents()) + cq.addMultiple(cq.dbGetAllAgents()) # Main loop while true: diff --git a/server/types.nim b/server/types.nim index 1b849c1..4c5b91f 100644 --- a/server/types.nim +++ b/server/types.nim @@ -1,6 +1,6 @@ import prompt import prologue -import tables, times +import tables #[ Agent types & procs @@ -114,6 +114,7 @@ type dbPath*: string listeners*: Table[string, Listener] agents*: Table[string, Agent] + interactAgent*: Agent proc add*(cq: Conquest, listener: Listener) = cq.listeners[listener.name] = listener @@ -121,7 +122,7 @@ proc add*(cq: Conquest, listener: Listener) = proc add*(cq: Conquest, agent: Agent) = cq.agents[agent.name] = agent -proc addMutliple*(cq: Conquest, agents: seq[Agent]) = +proc addMultiple*(cq: Conquest, agents: seq[Agent]) = for a in agents: cq.agents[a.name] = a @@ -138,6 +139,7 @@ proc initConquest*(): Conquest = cq.dbPath = "db/conquest.db" cq.listeners = initTable[string, Listener]() cq.agents = initTable[string, Agent]() + cq.interactAgent = nil return cq diff --git a/server/utils.nim b/server/utils.nim index 3646f42..487674c 100644 --- a/server/utils.nim +++ b/server/utils.nim @@ -1,10 +1,6 @@ -import re, strutils, strformat, terminal, tables, sequtils +import re, strutils, terminal, tables, sequtils -import ./types - -proc validateIPv4Address*(ip: string): bool = - let ipv4Pattern = re"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$" - return ip.match(ipv4Pattern) +import ./[types] proc validatePort*(portStr: string): bool = try: