Implement listing agents; Add TODOs for further improvements

This commit is contained in:
Jakob Friedl
2025-05-14 12:42:23 +02:00
parent c4cbcecafa
commit 826aacafad
9 changed files with 93 additions and 29 deletions

View File

@@ -1,5 +1,5 @@
import terminal, strformat, times import terminal, strformat
import ../[types, globals] import ../[types, globals, utils]
import ../db/database import ../db/database
@@ -23,7 +23,8 @@ Options:
-h, --help""") -h, --help""")
proc agentList*(cq: Conquest, args: varargs[string]) = proc agentList*(cq: Conquest, args: varargs[string]) =
discard let agents = cq.dbGetAllAgents()
cq.drawTable(agents)
proc agentBuild*(cq: Conquest, args: varargs[string]) = proc agentBuild*(cq: Conquest, args: varargs[string]) =
discard discard
@@ -44,8 +45,6 @@ proc agentInteract*(cq: Conquest, args: varargs[string]) =
]# ]#
proc register*(agent: Agent): bool = proc register*(agent: Agent): bool =
let date: string = now().format("dd-MM-yyyy HH:mm:ss")
# The following line is required to be able to use the `cq` global variable for console output # The following line is required to be able to use the `cq` global variable for console output
{.cast(gcsafe).}: {.cast(gcsafe).}:
@@ -62,7 +61,7 @@ proc register*(agent: Agent): bool =
return false return false
cq.add(agent.name, agent) cq.add(agent.name, agent)
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") cq.writeLine(fgYellow, styleBright, fmt"[{agent.firstCheckin}] ", 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 return true
#[ #[

View File

@@ -1,2 +0,0 @@
# Compiler flags
--threads:on

View File

@@ -22,6 +22,7 @@ proc dbInit*(cq: Conquest) =
CREATE TABLE agents ( CREATE TABLE agents (
name TEXT PRIMARY KEY, name TEXT PRIMARY KEY,
listener TEXT NOT NULL, listener TEXT NOT NULL,
process TEXT NOT NULL,
pid INTEGER NOT NULL, pid INTEGER NOT NULL,
username TEXT NOT NULL, username TEXT NOT NULL,
hostname TEXT NOT NULL, hostname TEXT NOT NULL,
@@ -30,6 +31,7 @@ proc dbInit*(cq: Conquest) =
elevated BOOLEAN NOT NULL, elevated BOOLEAN NOT NULL,
sleep INTEGER DEFAULT 10, sleep INTEGER DEFAULT 10,
jitter REAL DEFAULT 0.1, jitter REAL DEFAULT 0.1,
firstCheckin DATETIME NOT NULL,
FOREIGN KEY (listener) REFERENCES listeners(name) FOREIGN KEY (listener) REFERENCES listeners(name)
); );
@@ -40,6 +42,9 @@ proc dbInit*(cq: Conquest) =
except SqliteError: except SqliteError:
cq.writeLine(fgGreen, "[+] ", cq.dbPath, ": Database file found.") cq.writeLine(fgGreen, "[+] ", cq.dbPath, ": Database file found.")
#[
Listeners
]#
proc dbStoreListener*(cq: Conquest, listener: Listener): bool = proc dbStoreListener*(cq: Conquest, listener: Listener): bool =
try: try:
@@ -108,15 +113,18 @@ proc listenerExists*(cq: Conquest, listenerName: string): bool =
cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg()) cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg())
return false return false
#[
Agents
]#
proc dbStoreAgent*(cq: Conquest, agent: Agent): bool = proc dbStoreAgent*(cq: Conquest, agent: Agent): bool =
try: try:
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite) let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
conquestDb.exec(""" conquestDb.exec("""
INSERT INTO agents (name, listener, sleep, jitter, pid,username, hostname, ip, os, elevated) INSERT INTO agents (name, listener, sleep, jitter, process, pid, username, hostname, ip, os, elevated, firstCheckin)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?); VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
""", agent.name, agent.listener, agent.sleep, agent.jitter, agent.pid, agent.username, agent.hostname, agent.ip, agent.os, agent.elevated) """, agent.name, agent.listener, agent.sleep, agent.jitter, agent.process, agent.pid, agent.username, agent.hostname, agent.ip, agent.os, agent.elevated, $agent.firstCheckin)
conquestDb.close() conquestDb.close()
except: except:
@@ -125,3 +133,36 @@ proc dbStoreAgent*(cq: Conquest, agent: Agent): bool =
return true 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, ip, os, elevated, firstCheckin FROM agents;"):
let (name, listener, sleep, jitter, process, pid, username, hostname, ip, os, elevated, firstCheckin) = row.unpack((string, string, int, float, string, int, string, string, string, string, bool, string))
let a = Agent(
name: name,
listener: listener,
sleep: sleep,
jitter: jitter,
process: process,
pid: pid,
username: username,
hostname: hostname,
ip: ip,
os: os,
elevated: elevated,
firstCheckin: firstCheckin,
tasks: @[]
)
agents.add(a)
conquestDb.close()
except:
cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg())
return agents

View File

@@ -1,5 +1,5 @@
import prologue, nanoid import prologue, nanoid
import terminal, sequtils, strutils import terminal, sequtils, strutils, times
import ../[types] import ../[types]
import ../agent/agent import ../agent/agent
@@ -26,6 +26,7 @@ proc register*(ctx: Context) {.async.} =
"hostname":"hostname", "hostname":"hostname",
"ip": "ip-address", "ip": "ip-address",
"os": "operating-system", "os": "operating-system",
"process": "agent.exe",
"pid": 1234, "pid": 1234,
"elevated": false "elevated": false
} }
@@ -34,11 +35,12 @@ proc register*(ctx: Context) {.async.} =
try: try:
let let
postData: JsonNode = parseJson(ctx.request.body) postData: JsonNode = parseJson(ctx.request.body)
agentRegistrationData = postData.to(AgentRegistrationData) agentRegistrationData: AgentRegistrationData = postData.to(AgentRegistrationData)
agentUuid = generate(alphabet=join(toSeq('A'..'Z'), ""), size=8) agentUuid: string = generate(alphabet=join(toSeq('A'..'Z'), ""), size=8)
listenerUuid = ctx.getPathParams("listener") listenerUuid: string = ctx.getPathParams("listener")
date: string = now().format("dd-MM-yyyy HH:mm:ss")
let agent: Agent = newAgent(agentUuid, listenerUuid, agentRegistrationData) let agent: Agent = newAgent(agentUuid, listenerUuid, date, agentRegistrationData)
# Fully register agent and add it to database # Fully register agent and add it to database
if not agent.register(): if not agent.register():

View File

@@ -1,8 +1,8 @@
import strformat, strutils, sequtils, checksums/sha1, nanoid, terminal import strformat, strutils, sequtils, checksums/sha1, nanoid, terminal
import prologue import prologue
import ./[api, utils] import ./api
import ../types import ../[types, utils]
import ../db/database import ../db/database
proc listenerUsage*(cq: Conquest) = proc listenerUsage*(cq: Conquest) =

3
server/nim.cfg Normal file
View File

@@ -0,0 +1,3 @@
# Compiler flags
--threads:on
-d:httpxServerName="nginx"

View File

@@ -31,6 +31,7 @@ var parser = newParser:
command("list"): command("list"):
help("List all agents.") help("List all agents.")
# TODO: Add a flag that allows the user to only list agents that are connected to a specific listener (-n <uuid>)
command("build"): command("build"):
help("Build an agent to connect to an active listener.") help("Build an agent to connect to an active listener.")
@@ -112,7 +113,6 @@ proc header(cq: Conquest) =
Conquest framework entry point Conquest framework entry point
]# ]#
proc main() = proc main() =
# Handle CTRL+C, # Handle CTRL+C,
proc exit() {.noconv.} = proc exit() {.noconv.} =
echo "Received CTRL+C. Type \"exit\" to close the application.\n" echo "Received CTRL+C. Type \"exit\" to close the application.\n"

View File

@@ -1,6 +1,6 @@
import prompt import prompt
import prologue import prologue
import tables import tables, times
#[ #[
Agent Agent
@@ -35,14 +35,17 @@ type
hostname*: string hostname*: string
ip*: string ip*: string
os*: string os*: string
process*: string
pid*: int pid*: int
elevated*: bool elevated*: bool
# TODO: Add additional fields: domain, ...
Agent* = ref object Agent* = ref object
name*: string name*: string
listener*: string listener*: string
sleep*: int sleep*: int
jitter*: float jitter*: float
process*: string
pid*: int pid*: int
username*: string username*: string
hostname*: string hostname*: string
@@ -50,11 +53,14 @@ type
os*: string os*: string
elevated*: bool elevated*: bool
tasks*: seq[Task] tasks*: seq[Task]
firstCheckin*: string
lastCheckin*: string
proc newAgent*(name, listener, username, hostname, ip, os: string, pid: int, elevated: bool): Agent = proc newAgent*(name, listener, username, hostname, process, ip, os, firstCheckin: string, pid: int, elevated: bool): Agent =
var agent = new Agent var agent = new Agent
agent.name = name agent.name = name
agent.listener = listener agent.listener = listener
agent.process = process
agent.pid = pid agent.pid = pid
agent.username = username agent.username = username
agent.hostname = hostname agent.hostname = hostname
@@ -64,13 +70,15 @@ proc newAgent*(name, listener, username, hostname, ip, os: string, pid: int, ele
agent.sleep = 10 agent.sleep = 10
agent.jitter = 0.2 agent.jitter = 0.2
agent.tasks = @[] agent.tasks = @[]
agent.firstCheckin = firstCheckin
return agent return agent
proc newAgent*(name, listener: string, postData: AgentRegistrationData): Agent = proc newAgent*(name, listener, firstCheckin: string, postData: AgentRegistrationData): Agent =
var agent = new Agent var agent = new Agent
agent.name = name agent.name = name
agent.listener = listener agent.listener = listener
agent.process = postData.process
agent.pid = postData.pid agent.pid = postData.pid
agent.username = postData.username agent.username = postData.username
agent.hostname = postData.hostname agent.hostname = postData.hostname
@@ -80,10 +88,10 @@ proc newAgent*(name, listener: string, postData: AgentRegistrationData): Agent =
agent.sleep = 10 agent.sleep = 10
agent.jitter = 0.2 agent.jitter = 0.2
agent.tasks = @[] agent.tasks = @[]
agent.firstCheckin = firstCheckin
return agent return agent
#[ #[
Listener Listener
]# ]#
@@ -118,7 +126,7 @@ proc stringToProtocol*(protocol: string): Protocol =
#[ #[
Conquest Conquest framework
]# ]#
type type
Conquest* = ref object Conquest* = ref object

View File

@@ -1,6 +1,6 @@
import re, strutils import re, strutils
import ../types import ./types
proc validateIPv4Address*(ip: string): bool = proc validateIPv4Address*(ip: string): bool =
let ipv4Pattern = re"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$" let ipv4Pattern = re"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"
@@ -45,7 +45,7 @@ proc drawTable*(cq: Conquest, listeners: seq[Listener]) =
# Column headers and widths # Column headers and widths
let headers = @["Name", "Address", "Port", "Protocol", "Agents"] let headers = @["Name", "Address", "Port", "Protocol", "Agents"]
let widths = @[10, 15, 7, 10, 8] let widths = @[10, 17, 7, 10, 8]
cq.writeLine(border(topLeft, topMid, topRight, widths)) cq.writeLine(border(topLeft, topMid, topRight, widths))
cq.writeLine(row(headers, widths)) cq.writeLine(row(headers, widths))
@@ -60,4 +60,17 @@ proc drawTable*(cq: Conquest, listeners: seq[Listener]) =
proc drawTable*(cq: Conquest, agents: seq[Agent]) = proc drawTable*(cq: Conquest, agents: seq[Agent]) =
discard
let headers: seq[string] = @["Name", "Address", "Username", "Hostname", "Operating System", "Process", "PID"]
let widths = @[10, 17, 25, 20, 22, 15, 8]
cq.writeLine(border(topLeft, topMid, topRight, widths))
cq.writeLine(row(headers, widths))
cq.writeLine(border(midLeft, midMid, midRight, widths))
# TODO: Highlight elevated processes
for a in agents:
let row = @[a.name, a.ip, a.username, a.hostname, a.os, a.process, $a.pid]
cq.writeLine(row(row, widths))
cq.writeLine(border(botLeft, botMid, botRight, widths))