Implemented switch to agent interaction mode.

This commit is contained in:
Jakob Friedl
2025-05-18 12:51:26 +02:00
parent 56f8994069
commit 0a98d11df2
13 changed files with 279 additions and 211 deletions

4
.gitignore vendored
View File

@@ -1,6 +1,10 @@
# Ignore agents
agents/
*.db
# Ignore binaries
*/bin/*
server/server
*.exe
# Nim
nimcache/

View File

@@ -1,5 +1,6 @@
# Conquest Command & Control Framework
# Conquest Framework
> Command - Control - Conquer
## Acknowledgements

View File

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

47
server/agent/commands.nim Normal file
View File

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

View File

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

117
server/db/dbAgent.nim Normal file
View File

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

71
server/db/dbListener.nim Normal file
View File

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

View File

@@ -1,5 +1,5 @@
import prologue, nanoid
import terminal, sequtils, strutils, times
import sequtils, strutils, times
import ../[types]
import ../agent/agent

View File

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

View File

@@ -1,3 +1,4 @@
# Compiler flags
--threads:on
-d:httpxServerName="nginx"
-d:httpxServerName="nginx"
#--outdir:"../bin"

View File

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

View File

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

View File

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