diff --git a/server/agent/agent.nim b/server/agent/agent.nim index e76aaec..7aad55f 100644 --- a/server/agent/agent.nim +++ b/server/agent/agent.nim @@ -1,4 +1,4 @@ -import terminal, strformat, strutils +import terminal, strformat, strutils, tables import ../[types, globals, utils] import ../db/database @@ -16,6 +16,7 @@ Usage: Commands: list List all agents. + info Display details for a specific agent. kill Terminate the connection of an active listener and remove it from the interface. interact Interact with an active agent. @@ -23,10 +24,46 @@ Options: -h, --help""") # List agents -proc agentList*(cq: Conquest, args: varargs[string]) = +proc agentList*(cq: Conquest) = let agents = cq.dbGetAllAgents() cq.drawTable(agents) +proc agentList*(cq: Conquest, listener: string) = + + # Check if listener exists + if not cq.dbListenerExists(listener.toUpperAscii): + cq.writeLine(fgRed, styleBright, fmt"[-] Listener {listener.toUpperAscii} does not exist.") + return + + let agents = cq.dbGetAllAgentsByListener(listener.toUpperAscii) + cq.drawTable(agents) + +# Display agent properties and details +proc agentInfo*(cq: Conquest, name: string) = + # Check if agent supplied via -n parameter exists in database + if not cq.dbAgentExists(name.toUpperAscii): + cq.writeLine(fgRed, styleBright, fmt"[-] Agent {name.toUpperAscii} does not exist.") + return + + let agent = cq.agents[name.toUpperAscii] + + # TODO: Improve formating + cq.writeLine(fmt""" +Agent name (UUID): {agent.name} +Connected to listener: {agent.listener} +────────────────────────────────────────── +Username: {agent.username} +Hostname: {agent.hostname} +Domain: {agent.domain} +IP-Address: {agent.ip} +Operating system: {agent.os} +────────────────────────────────────────── +Process name: {agent.process} +Process ID: {$agent.pid} +Process elevated: {$agent.elevated} +First checkin: {agent.firstCheckin} + """) + # Terminate agent and remove it from the database proc agentKill*(cq: Conquest, name: string) = @@ -67,7 +104,7 @@ proc register*(agent: Agent): bool = # 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): + if not cq.dbListenerExists(agent.listener.toUpperAscii): cq.writeLine(fgRed, styleBright, fmt"[-] Agent from {agent.ip} attempted to register to non-existent listener: {agent.listener}.", "\n") return false diff --git a/server/db/database.nim b/server/db/database.nim index 02dc0ea..b236df7 100644 --- a/server/db/database.nim +++ b/server/db/database.nim @@ -165,6 +165,41 @@ proc dbGetAllAgents*(cq: Conquest): seq[Agent] = 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) diff --git a/server/globals.nim b/server/globals.nim index 304873d..dd7fa70 100644 --- a/server/globals.nim +++ b/server/globals.nim @@ -1,3 +1,11 @@ import ./types -var cq*: Conquest \ No newline at end of file +# Global variable for handling listeners, agents and console output +var cq*: Conquest + +# Colors +# https://colors.sh/ +# TODO Replace all colored output with custom colors +const yellow* = "\e[48;5;232m" +const resetColor* = "\e[0m" + diff --git a/server/server.nim b/server/server.nim index 97f9fa2..11a8b4a 100644 --- a/server/server.nim +++ b/server/server.nim @@ -20,22 +20,28 @@ var parser = newParser: help("Starts a new HTTP listener.") option("-h", "-host", default=some("0.0.0.0"), help="IPv4 address to listen on.", required=false) option("-p", "-port", help="Port to listen on.", required=true) + # TODO: Future features: # flag("--dns", help="Use the DNS protocol for C2 communication.") # flag("--doh", help="Use DNS over HTTPS for C2 communication.) command("stop"): help("Stop an active listener.") - option("-n", "-name", help="Name of the listener to stop.", required=true) + option("-n", "-name", help="Name of the listener.", required=true) command("agent"): help("Manage, build and interact with agents.") command("list"): help("List all agents.") + option("-n", "-name", help="Name of the listener.") # TODO: Add a flag that allows the user to only list agents that are connected to a specific listener (-n ) + command("info"): + help("Display details for a specific agent.") + option("-n", "-name", help="Name of the agent.", required=true) + command("kill"): help("Terminate the connection of an active listener and remove it from the interface.") - option("-n", "-name", help="Name of the agent to stop.", required=true) + option("-n", "-name", help="Name of the agent.", required=true) command("interact"): help("Interact with an active agent.") @@ -81,7 +87,12 @@ proc handleConsoleCommand*(cq: Conquest, args: varargs[string]) = of "agent": case opts.agent.get.command of "list": - cq.agentList() + if opts.agent.get.list.get.name == "": + cq.agentList() + else: + cq.agentList(opts.agent.get.list.get.name) + of "info": + cq.agentInfo(opts.agent.get.info.get.name) of "kill": cq.agentKill(opts.agent.get.kill.get.name) of "interact": @@ -140,4 +151,4 @@ proc main() = cq.withOutput(handleConsoleCommand, command) when isMainModule: - main() \ No newline at end of file + main() diff --git a/server/utils.nim b/server/utils.nim index 1adca78..82766d6 100644 --- a/server/utils.nim +++ b/server/utils.nim @@ -1,4 +1,4 @@ -import re, strutils +import re, strutils, strformat, terminal import ./types @@ -38,7 +38,16 @@ proc border(left, mid, right: string, widths: seq[int]): string = proc row(cells: seq[string], widths: seq[int]): string = var row = vert for i, cell in cells: - row.add(" " & cell.alignLeft(widths[i] - 2) & " " & vert) + # Truncate content of a cell with "..." when the value to be inserted is longer than the designated width + let w = widths[i] - 2 + let c = if cell.len > w: + if w >= 3: + cell[0 ..< w - 3] & "..." + else: + ".".repeat(max(0, w)) + else: + cell + row.add(" " & c.alignLeft(w) & " " & vert) return row proc drawTable*(cq: Conquest, listeners: seq[Listener]) = @@ -62,7 +71,7 @@ proc drawTable*(cq: Conquest, listeners: seq[Listener]) = proc drawTable*(cq: Conquest, agents: seq[Agent]) = let headers: seq[string] = @["Name", "Address", "Username", "Hostname", "Operating System", "Process", "PID"] - let widths = @[10, 17, 25, 20, 22, 15, 8] + let widths = @[10, 17, 20, 20, 20, 15, 7] cq.writeLine(border(topLeft, topMid, topRight, widths)) cq.writeLine(row(headers, widths))