Implemented listener management (start, stop, list) and database integration.
This commit is contained in:
31
server/agent.nim
Normal file
31
server/agent.nim
Normal file
@@ -0,0 +1,31 @@
|
||||
import ./types
|
||||
|
||||
proc agentUsage*(console: Console) =
|
||||
console.writeLine("""Manage, build and interact with agents.
|
||||
|
||||
Usage:
|
||||
agent [options] COMMAND
|
||||
|
||||
Commands:
|
||||
|
||||
list List all agents.
|
||||
build Build an agent to connect to an active listener.
|
||||
interact Interact with an active listener.
|
||||
|
||||
Options:
|
||||
-h, --help""")
|
||||
|
||||
proc agentList*(console: Console, args: varargs[string]) =
|
||||
discard
|
||||
|
||||
proc agentBuild*(console: Console, args: varargs[string]) =
|
||||
discard
|
||||
|
||||
proc agentInteract*(console: Console, args: varargs[string]) =
|
||||
|
||||
console.setIndicator("[AGENT] (username@hostname)> ")
|
||||
console.setStatusBar(@[("mode", "interact"), ("listeners", "X"), ("agents", "4")])
|
||||
|
||||
var command: string = console.readLine()
|
||||
|
||||
discard
|
||||
@@ -1,2 +1,2 @@
|
||||
# Compiler flags
|
||||
--threads:on
|
||||
# --threads:on
|
||||
129
server/console.nim
Normal file
129
server/console.nim
Normal file
@@ -0,0 +1,129 @@
|
||||
import prompt, terminal
|
||||
import argparse
|
||||
import strutils, strformat, times, system, unicode
|
||||
|
||||
import ./[types, agent]
|
||||
import listener/listener
|
||||
import db/database
|
||||
|
||||
#[
|
||||
Argument parsing
|
||||
]#
|
||||
var parser = newParser:
|
||||
help("Console Command & Control")
|
||||
|
||||
command("listener"):
|
||||
help("Manage, start and stop listeners.")
|
||||
|
||||
command("list"):
|
||||
help("List all active listeners.")
|
||||
command("start"):
|
||||
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)
|
||||
# flag("--dns", help="Use the DNS protocol for C2 communication.")
|
||||
command("stop"):
|
||||
help("Stop an active listener.")
|
||||
option("-n", "-name", help="Name of the listener to stop.", required=true)
|
||||
|
||||
command("agent"):
|
||||
help("Manage, build and interact with agents.")
|
||||
|
||||
command("list"):
|
||||
help("List all agents.")
|
||||
|
||||
command("build"):
|
||||
help("Build an agent to connect to an active listener.")
|
||||
|
||||
|
||||
command("interact"):
|
||||
help("Interact with an active listener.")
|
||||
|
||||
|
||||
command("help"):
|
||||
nohelpflag()
|
||||
|
||||
command("exit"):
|
||||
nohelpflag()
|
||||
|
||||
proc handleConsoleCommand*(console: Console, 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")
|
||||
console.writeLine(fgCyan, fmt"[{date}] ", resetStyle, styleBright, args[0])
|
||||
|
||||
try:
|
||||
let opts = parser.parse(args[0].split(" ").filterIt(it.len > 0))
|
||||
|
||||
case opts.command
|
||||
|
||||
of "exit": # Exit program
|
||||
echo "\n"
|
||||
quit(0)
|
||||
|
||||
of "help": # Display help menu
|
||||
console.writeLine(parser.help())
|
||||
|
||||
of "listener":
|
||||
case opts.listener.get.command
|
||||
of "list":
|
||||
console.listenerList()
|
||||
of "start":
|
||||
console.listenerStart(opts.listener.get.start.get.host, opts.listener.get.start.get.port)
|
||||
of "stop":
|
||||
console.listenerStop(opts.listener.get.stop.get.name)
|
||||
else:
|
||||
console.listenerUsage()
|
||||
|
||||
of "agent":
|
||||
case opts.agent.get.command
|
||||
of "list":
|
||||
console.agentList()
|
||||
of "build":
|
||||
console.agentBuild()
|
||||
of "interact":
|
||||
console.agentInteract()
|
||||
else:
|
||||
console.listenerUsage()
|
||||
|
||||
# Handle help flag
|
||||
except ShortCircuit as err:
|
||||
if err.flag == "argparse_help":
|
||||
console.writeLine(err.help)
|
||||
|
||||
# Handle invalid arguments
|
||||
except UsageError:
|
||||
console.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg())
|
||||
|
||||
console.writeLine("")
|
||||
|
||||
proc header(console: Console) =
|
||||
console.writeLine("")
|
||||
console.writeLine("┏┏┓┏┓┏┓┓┏┏┓┏╋")
|
||||
console.writeLine("┗┗┛┛┗┗┫┗┻┗ ┛┗ 0.1")
|
||||
console.writeLine(" ┗ @jakobfriedl")
|
||||
console.writeLine("─".repeat(21))
|
||||
console.writeLine("")
|
||||
|
||||
proc initPrompt*() =
|
||||
|
||||
var console = newConsole()
|
||||
|
||||
# Print header
|
||||
console.header()
|
||||
|
||||
# Initialize database
|
||||
console.dbInit()
|
||||
console.restartListeners()
|
||||
|
||||
# Main loop
|
||||
while true:
|
||||
console.setIndicator("[conquest]> ")
|
||||
console.setStatusBar(@[("mode", "manage"), ("listeners", $console.listeners), ("agents", $console.agents)])
|
||||
console.showPrompt()
|
||||
|
||||
var command: string = console.readLine()
|
||||
console.withOutput(handleConsoleCommand, command)
|
||||
|
||||
85
server/db/database.nim
Normal file
85
server/db/database.nim
Normal file
@@ -0,0 +1,85 @@
|
||||
import tiny_sqlite, net
|
||||
import ../types
|
||||
|
||||
import system, terminal, strformat
|
||||
|
||||
proc dbInit*(console: Console) =
|
||||
|
||||
try:
|
||||
let conquestDb = openDatabase(console.dbPath, mode=dbReadWrite)
|
||||
|
||||
# Create tables
|
||||
conquestDb.execScript("""
|
||||
CREATE TABLE listener (
|
||||
name TEXT PRIMARY KEY,
|
||||
address TEXT NOT NULL,
|
||||
port INTEGER NOT NULL UNIQUE,
|
||||
protocol TEXT NOT NULL CHECK (protocol IN ('http')),
|
||||
sleep INTEGER NOT NULL,
|
||||
jitter REAL NOT NULL
|
||||
);
|
||||
|
||||
""")
|
||||
|
||||
console.writeLine(fgGreen, "[+] ", console.dbPath, ": Database created.")
|
||||
conquestDb.close()
|
||||
except SqliteError:
|
||||
console.writeLine(fgGreen, "[+] ", console.dbPath, ": Database file found.")
|
||||
|
||||
proc dbStore*(console: Console, listener: Listener): bool =
|
||||
|
||||
try:
|
||||
let conquestDb = openDatabase(console.dbPath, mode=dbReadWrite)
|
||||
|
||||
conquestDb.exec("""
|
||||
INSERT INTO listener (name, address, port, protocol, sleep, jitter)
|
||||
VALUES (?, ?, ?, ?, ?, ?);
|
||||
""", listener.name, listener.address, listener.port, $listener.protocol, listener.sleep, listener.jitter)
|
||||
|
||||
conquestDb.close()
|
||||
except:
|
||||
console.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg())
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
proc dbGetAllListeners*(console: Console): seq[Listener] =
|
||||
|
||||
var listeners: seq[Listener] = @[]
|
||||
|
||||
try:
|
||||
let conquestDb = openDatabase(console.dbPath, mode=dbReadWrite)
|
||||
|
||||
for row in conquestDb.iterate("SELECT name, address, port, protocol, sleep, jitter FROM listener;"):
|
||||
let (name, address, port, protocol, sleep, jitter) = row.unpack((string, string, int, string, int, float ))
|
||||
|
||||
let l = Listener(
|
||||
name: name,
|
||||
address: address,
|
||||
port: port,
|
||||
protocol: stringToProtocol(protocol),
|
||||
sleep: sleep,
|
||||
jitter: jitter
|
||||
)
|
||||
listeners.add(l)
|
||||
|
||||
conquestDb.close()
|
||||
except:
|
||||
console.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg())
|
||||
|
||||
return listeners
|
||||
|
||||
proc dbDeleteListenerByName*(console: Console, name: string): bool =
|
||||
try:
|
||||
let conquestDb = openDatabase(console.dbPath, mode=dbReadWrite)
|
||||
|
||||
conquestDb.exec("DELETE FROM listener WHERE name = ?", name)
|
||||
|
||||
conquestDb.close()
|
||||
except:
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
proc dbStore*(agent: Agent): bool =
|
||||
discard
|
||||
15
server/listener/api.nim
Normal file
15
server/listener/api.nim
Normal file
@@ -0,0 +1,15 @@
|
||||
import prologue
|
||||
|
||||
import ../types
|
||||
|
||||
proc index*(ctx: Context) {.async.} =
|
||||
resp "Index"
|
||||
|
||||
proc agentRegister*(ctx: Context) {.async.} =
|
||||
resp "Register"
|
||||
|
||||
proc addTasks*(ctx: Context) {.async.} =
|
||||
|
||||
let name = ctx.getPathParams("name")
|
||||
|
||||
resp name
|
||||
111
server/listener/listener.nim
Normal file
111
server/listener/listener.nim
Normal file
@@ -0,0 +1,111 @@
|
||||
import strformat, strutils, sequtils, checksums/sha1, nanoid, terminal, sugar
|
||||
import prologue
|
||||
|
||||
import ./[api, utils]
|
||||
import ../types
|
||||
import ../db/database
|
||||
|
||||
|
||||
proc listenerUsage*(console: Console) =
|
||||
console.writeLine("""Manage, start and stop listeners.
|
||||
|
||||
Usage:
|
||||
listener [options] COMMAND
|
||||
|
||||
Commands:
|
||||
|
||||
list List all active listeners.
|
||||
start Starts a new HTTP listener.
|
||||
stop Stop an active listener.
|
||||
|
||||
Options:
|
||||
-h, --help""")
|
||||
|
||||
proc listenerList*(console: Console) =
|
||||
let listeners = console.dbGetAllListeners()
|
||||
console.drawTable(listeners)
|
||||
|
||||
proc listenerStart*(console: Console, host: string, portStr: string) =
|
||||
|
||||
# Validate arguments
|
||||
# if not validateIPv4Address(host):
|
||||
# console.writeLine(fgRed, styleBright, fmt"Invalid IPv4 IP address: {ip}.")
|
||||
# return
|
||||
if not validatePort(portStr):
|
||||
console.writeLine(fgRed, styleBright, fmt"[-] Invalid port number: {portStr}")
|
||||
return
|
||||
|
||||
let port = portStr.parseInt
|
||||
|
||||
# Create new listener
|
||||
let
|
||||
name: string = generate(alphabet=join(toSeq('A'..'Z'), ""), size=8)
|
||||
listenerSettings = newSettings(
|
||||
appName = name,
|
||||
debug = false,
|
||||
address = "", # For some reason, the program crashes when the ip parameter is passed to the newSettings function
|
||||
port = Port(port) # As a result, I will hardcode the listener to be served on all interfaces (0.0.0.0) by default
|
||||
) # TODO: fix this issue and start the listener on the address passed as the HOST parameter
|
||||
|
||||
var listener = newApp(settings = listenerSettings)
|
||||
|
||||
# Define API endpoints
|
||||
listener.addRoute("/", api.index, @[HttpGet])
|
||||
listener.addRoute("/register", api.agentRegister, @[HttpPost])
|
||||
listener.addRoute("/{name}/tasks", api.addTasks, @[HttpGet, HttpPost])
|
||||
|
||||
# Store listener in database
|
||||
let listenerInstance = newListener(name, host, port)
|
||||
if not console.dbStore(listenerInstance):
|
||||
return
|
||||
|
||||
# Start serving
|
||||
try:
|
||||
discard listener.runAsync()
|
||||
console.activeListeners.add(listener)
|
||||
inc console.listeners
|
||||
console.writeLine(fgGreen, "[+] ", resetStyle, "Started listener", fgGreen, fmt" {name} ", resetStyle, fmt"on port {portStr}.")
|
||||
except CatchableError as err:
|
||||
console.writeLine(fgRed, styleBright, "[-] Failed to start listener: ", getCurrentExceptionMsg())
|
||||
|
||||
proc restartListeners*(console: Console) =
|
||||
let listeners: seq[Listener] = console.dbGetAllListeners()
|
||||
|
||||
# Restart all active listeners that are stored in the database
|
||||
for l in listeners:
|
||||
let
|
||||
settings = newSettings(
|
||||
appName = l.name,
|
||||
debug = false,
|
||||
address = "",
|
||||
port = Port(l.port)
|
||||
)
|
||||
listener = newApp(settings = settings)
|
||||
|
||||
# Define API endpoints
|
||||
listener.addRoute("/", api.index, @[HttpGet])
|
||||
listener.addRoute("/register", api.agentRegister, @[HttpPost])
|
||||
listener.addRoute("/{name}/tasks", api.addTasks, @[HttpGet, HttpPost])
|
||||
|
||||
try:
|
||||
discard listener.runAsync()
|
||||
console.activeListeners.add(listener)
|
||||
inc console.listeners
|
||||
console.writeLine(fgGreen, "[+] ", resetStyle, "Restarted listener", fgGreen, fmt" {l.name} ", resetStyle, fmt"on port {$l.port}.")
|
||||
except CatchableError as err:
|
||||
console.writeLine(fgRed, styleBright, "[-] Failed to restart listener: ", getCurrentExceptionMsg())
|
||||
|
||||
# Delay before starting serving another listener to avoid crashing the application
|
||||
waitFor sleepAsync(10)
|
||||
|
||||
console.writeLine("")
|
||||
|
||||
proc listenerStop*(console: Console, name: string) =
|
||||
|
||||
if not console.dbDeleteListenerByName(name.toUpperAscii):
|
||||
console.writeLine(fgRed, styleBright, "[-] Failed to stop listener: ", getCurrentExceptionMsg())
|
||||
return
|
||||
|
||||
dec console.listeners
|
||||
console.writeLine(fgGreen, "[+] ", resetStyle, "Stopped listener ", fgGreen, fmt"{name.toUpperAscii}.")
|
||||
|
||||
63
server/listener/utils.nim
Normal file
63
server/listener/utils.nim
Normal file
@@ -0,0 +1,63 @@
|
||||
import re, strutils
|
||||
|
||||
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)
|
||||
|
||||
proc validatePort*(portStr: string): bool =
|
||||
try:
|
||||
let port: int = portStr.parseInt
|
||||
return port >= 1 and port <= 65535
|
||||
except ValueError:
|
||||
return false
|
||||
|
||||
# Table border characters
|
||||
const topLeft = "╭"
|
||||
const topMid = "┬"
|
||||
const topRight= "╮"
|
||||
const midLeft = "├"
|
||||
const midMid = "┼"
|
||||
const midRight= "┤"
|
||||
const botLeft = "╰"
|
||||
const botMid = "┴"
|
||||
const botRight= "╯"
|
||||
const hor = "─"
|
||||
const vert = "│"
|
||||
|
||||
# Format border
|
||||
proc border(left, mid, right: string, widths: seq[int]): string =
|
||||
var line = left
|
||||
for i, w in widths:
|
||||
line.add(hor.repeat(w))
|
||||
line.add(if i < widths.len - 1: mid else: right)
|
||||
return line
|
||||
|
||||
# Format a row of data
|
||||
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)
|
||||
return row
|
||||
|
||||
proc drawTable*(console: Console, listeners: seq[Listener]) =
|
||||
|
||||
# Column headers and widths
|
||||
let headers = @["Name", "Address", "Port", "Protocol", "Agents"]
|
||||
let widths = @[10, 15, 7, 10, 8]
|
||||
|
||||
console.writeLine(border(topLeft, topMid, topRight, widths))
|
||||
console.writeLine(row(headers, widths))
|
||||
console.writeLine(border(midLeft, midMid, midRight, widths))
|
||||
|
||||
for l in listeners:
|
||||
# TODO: Add number of agents connected to the listener
|
||||
let row = @[l.name, l.address, $l.port, $l.protocol, "X"]
|
||||
console.writeLine(row(row, widths))
|
||||
|
||||
console.writeLine(border(botLeft, botMid, botRight, widths))
|
||||
|
||||
|
||||
proc drawTable*(console: Console, agents: seq[Agent]) =
|
||||
discard
|
||||
@@ -1,11 +1,19 @@
|
||||
import ./tui
|
||||
import ./console
|
||||
|
||||
# Handle CTRL+C,
|
||||
proc exit() {.noconv.} =
|
||||
echo "Received CTRL+C. Type \"exit\" to close the application.\n"
|
||||
|
||||
proc main() =
|
||||
# Initialize TUI
|
||||
initUi()
|
||||
# initUi()
|
||||
|
||||
setControlCHook(exit)
|
||||
|
||||
# Initialize prompt interface
|
||||
initPrompt()
|
||||
|
||||
|
||||
|
||||
main()
|
||||
#[
|
||||
Start main function
|
||||
]#
|
||||
main()
|
||||
88
server/types.nim
Normal file
88
server/types.nim
Normal file
@@ -0,0 +1,88 @@
|
||||
import prompt
|
||||
import prologue
|
||||
|
||||
#[
|
||||
Console
|
||||
]#
|
||||
type
|
||||
Console* = ref object
|
||||
prompt*: Prompt
|
||||
listeners*: int
|
||||
agents*: int
|
||||
dbPath*: string
|
||||
activeListeners*: seq[Prologue]
|
||||
|
||||
Command* = object
|
||||
cmd*: string
|
||||
execute*: proc(console: Console, args: varargs[string])
|
||||
|
||||
proc newConsole*(): Console =
|
||||
var console = new Console
|
||||
var prompt = Prompt.init()
|
||||
console.prompt = prompt
|
||||
console.dbPath = "db/conquest.db"
|
||||
console.listeners = 0
|
||||
console.agents = 0
|
||||
console.activeListeners = @[]
|
||||
|
||||
return console
|
||||
|
||||
template writeLine*(console: Console, args: varargs[untyped]) =
|
||||
console.prompt.writeLine(args)
|
||||
proc readLine*(console: Console): string =
|
||||
return console.prompt.readLine()
|
||||
template setIndicator*(console: Console, indicator: string) =
|
||||
console.prompt.setIndicator(indicator)
|
||||
template showPrompt*(console: Console) =
|
||||
console.prompt.showPrompt()
|
||||
template hidePrompt*(console: Console) =
|
||||
console.prompt.hidePrompt()
|
||||
template setStatusBar*(console: Console, statusBar: seq[StatusBarItem]) =
|
||||
console.prompt.setStatusBar(statusBar)
|
||||
template clear*(console: Console) =
|
||||
console.prompt.clear()
|
||||
|
||||
# Overwrite withOutput function to handle function arguments
|
||||
proc withOutput*(console: Console, outputFunction: proc(console: Console, args: varargs[string]), args: varargs[string]) =
|
||||
console.hidePrompt()
|
||||
outputFunction(console, args)
|
||||
console.showPrompt()
|
||||
|
||||
#[
|
||||
Agent
|
||||
]#
|
||||
type
|
||||
Agent* = ref object
|
||||
name*: string
|
||||
|
||||
#[
|
||||
Listener
|
||||
]#
|
||||
type
|
||||
Protocol* = enum
|
||||
HTTP = "http"
|
||||
|
||||
Listener* = ref object
|
||||
name*: string
|
||||
address*: string
|
||||
port*: int
|
||||
protocol*: Protocol
|
||||
sleep*: int
|
||||
jitter*: float
|
||||
|
||||
proc newListener*(name: string, address: string, port: int): Listener =
|
||||
var listener = new Listener
|
||||
listener.name = name
|
||||
listener.address = address
|
||||
listener.port = port
|
||||
listener.protocol = HTTP
|
||||
listener.sleep = 5 # 5 seconds beaconing
|
||||
listener.jitter = 0.2 # 20% Jitter
|
||||
|
||||
return listener
|
||||
|
||||
proc stringToProtocol*(protocol: string): Protocol =
|
||||
case protocol
|
||||
of "http":
|
||||
return HTTP
|
||||
else: discard
|
||||
Reference in New Issue
Block a user