Started work on agent registration
This commit is contained in:
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2025 Jakob Friedl
|
Copyright (cq) 2025 Jakob Friedl
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
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
|
|
||||||
59
server/agent/agent.nim
Normal file
59
server/agent/agent.nim
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import terminal, strformat, times
|
||||||
|
import ../[types, globals]
|
||||||
|
|
||||||
|
|
||||||
|
#[
|
||||||
|
Agent management mode
|
||||||
|
These console commands allow dealing with agents from the Conquest framework's prompt interface
|
||||||
|
]#
|
||||||
|
proc agentUsage*(cq: Conquest) =
|
||||||
|
cq.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 agent.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help""")
|
||||||
|
|
||||||
|
proc agentList*(cq: Conquest, args: varargs[string]) =
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc agentBuild*(cq: Conquest, args: varargs[string]) =
|
||||||
|
discard
|
||||||
|
|
||||||
|
# Switch to interact mode
|
||||||
|
proc agentInteract*(cq: Conquest, args: varargs[string]) =
|
||||||
|
|
||||||
|
cq.setIndicator("[AGENT] (username@hostname)> ")
|
||||||
|
cq.setStatusBar(@[("mode", "interact"), ("listeners", "X"), ("agents", "4")])
|
||||||
|
|
||||||
|
var command: string = cq.readLine()
|
||||||
|
|
||||||
|
discard
|
||||||
|
|
||||||
|
#[
|
||||||
|
Agent API
|
||||||
|
Functions relevant for dealing with the agent API, such as registering new agents, querying tasks and posting results
|
||||||
|
]#
|
||||||
|
proc notifyAgentRegister*(agent: Agent) =
|
||||||
|
|
||||||
|
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
|
||||||
|
{.cast(gcsafe).}:
|
||||||
|
cq.writeLine(fgYellow, styleBright, fmt"[{date}] Agent {agent.name} connected.", "\n")
|
||||||
|
|
||||||
|
#[
|
||||||
|
Agent interaction mode
|
||||||
|
When interacting with a agent, the following functions are called:
|
||||||
|
- addTask, to add a new tasks to the agents task queue
|
||||||
|
- getTaskResult, get the result for the task from the agent
|
||||||
|
]#
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
||||||
@@ -3,10 +3,10 @@ import ../types
|
|||||||
|
|
||||||
import system, terminal, strformat
|
import system, terminal, strformat
|
||||||
|
|
||||||
proc dbInit*(console: Console) =
|
proc dbInit*(cq: Conquest) =
|
||||||
|
|
||||||
try:
|
try:
|
||||||
let conquestDb = openDatabase(console.dbPath, mode=dbReadWrite)
|
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||||
|
|
||||||
# Create tables
|
# Create tables
|
||||||
conquestDb.execScript("""
|
conquestDb.execScript("""
|
||||||
@@ -21,15 +21,15 @@ proc dbInit*(console: Console) =
|
|||||||
|
|
||||||
""")
|
""")
|
||||||
|
|
||||||
console.writeLine(fgGreen, "[+] ", console.dbPath, ": Database created.")
|
cq.writeLine(fgGreen, "[+] ", cq.dbPath, ": Database created.")
|
||||||
conquestDb.close()
|
conquestDb.close()
|
||||||
except SqliteError:
|
except SqliteError:
|
||||||
console.writeLine(fgGreen, "[+] ", console.dbPath, ": Database file found.")
|
cq.writeLine(fgGreen, "[+] ", cq.dbPath, ": Database file found.")
|
||||||
|
|
||||||
proc dbStore*(console: Console, listener: Listener): bool =
|
proc dbStoreListener*(cq: Conquest, listener: Listener): bool =
|
||||||
|
|
||||||
try:
|
try:
|
||||||
let conquestDb = openDatabase(console.dbPath, mode=dbReadWrite)
|
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||||
|
|
||||||
conquestDb.exec("""
|
conquestDb.exec("""
|
||||||
INSERT INTO listener (name, address, port, protocol, sleep, jitter)
|
INSERT INTO listener (name, address, port, protocol, sleep, jitter)
|
||||||
@@ -38,17 +38,17 @@ proc dbStore*(console: Console, listener: Listener): bool =
|
|||||||
|
|
||||||
conquestDb.close()
|
conquestDb.close()
|
||||||
except:
|
except:
|
||||||
console.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg())
|
cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg())
|
||||||
return false
|
return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc dbGetAllListeners*(console: Console): seq[Listener] =
|
proc dbGetAllListeners*(cq: Conquest): seq[Listener] =
|
||||||
|
|
||||||
var listeners: seq[Listener] = @[]
|
var listeners: seq[Listener] = @[]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
let conquestDb = openDatabase(console.dbPath, mode=dbReadWrite)
|
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||||
|
|
||||||
for row in conquestDb.iterate("SELECT name, address, port, protocol, sleep, jitter FROM listener;"):
|
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 (name, address, port, protocol, sleep, jitter) = row.unpack((string, string, int, string, int, float ))
|
||||||
@@ -65,13 +65,13 @@ proc dbGetAllListeners*(console: Console): seq[Listener] =
|
|||||||
|
|
||||||
conquestDb.close()
|
conquestDb.close()
|
||||||
except:
|
except:
|
||||||
console.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg())
|
cq.writeLine(fgRed, styleBright, "[-] ", getCurrentExceptionMsg())
|
||||||
|
|
||||||
return listeners
|
return listeners
|
||||||
|
|
||||||
proc dbDeleteListenerByName*(console: Console, name: string): bool =
|
proc dbDeleteListenerByName*(cq: Conquest, name: string): bool =
|
||||||
try:
|
try:
|
||||||
let conquestDb = openDatabase(console.dbPath, mode=dbReadWrite)
|
let conquestDb = openDatabase(cq.dbPath, mode=dbReadWrite)
|
||||||
|
|
||||||
conquestDb.exec("DELETE FROM listener WHERE name = ?", name)
|
conquestDb.exec("DELETE FROM listener WHERE name = ?", name)
|
||||||
|
|
||||||
@@ -81,5 +81,5 @@ proc dbDeleteListenerByName*(console: Console, name: string): bool =
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc dbStore*(agent: Agent): bool =
|
proc dbStoreAgent*(agent: Agent): bool =
|
||||||
discard
|
discard
|
||||||
3
server/globals.nim
Normal file
3
server/globals.nim
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import ./types
|
||||||
|
|
||||||
|
var cq*: Conquest
|
||||||
@@ -1,15 +1,62 @@
|
|||||||
import prologue
|
import prologue, nanoid
|
||||||
|
import terminal, sequtils, strutils
|
||||||
|
|
||||||
import ../types
|
import ../[types]
|
||||||
|
import ../agent/agent
|
||||||
|
import ./utils
|
||||||
|
|
||||||
proc index*(ctx: Context) {.async.} =
|
#[
|
||||||
resp "Index"
|
POST /{listener-uuid}/register
|
||||||
|
Called from agent to register itself to the conquest server
|
||||||
proc agentRegister*(ctx: Context) {.async.} =
|
]#
|
||||||
resp "Register"
|
proc register*(ctx: Context) {.async.} =
|
||||||
|
|
||||||
proc addTasks*(ctx: Context) {.async.} =
|
# Check headers
|
||||||
|
doAssert(ctx.request.getHeader("CONTENT-TYPE") == @["application/json"])
|
||||||
|
doAssert(ctx.request.getHeader("USER-AGENT") == @["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"])
|
||||||
|
|
||||||
|
# Handle POST data, the register data should look like the following
|
||||||
|
#[
|
||||||
|
{
|
||||||
|
"username": "username",
|
||||||
|
"hostname":"hostname",
|
||||||
|
"ip": "ip-address",
|
||||||
|
"os": "operating-system"
|
||||||
|
"pid": 1234
|
||||||
|
"elevated": false
|
||||||
|
}
|
||||||
|
]#
|
||||||
|
|
||||||
let name = ctx.getPathParams("name")
|
let
|
||||||
|
postData: JsonNode = %ctx.request.body()
|
||||||
|
name = generate(alphabet=join(toSeq('A'..'Z'), ""), size=8)
|
||||||
|
|
||||||
|
let agent = new Agent
|
||||||
|
agent.name = name
|
||||||
|
notifyAgentRegister(agent)
|
||||||
|
|
||||||
|
|
||||||
|
resp agent.name
|
||||||
|
|
||||||
|
#[
|
||||||
|
GET /{listener-uuid}/{agent-uuid}/tasks
|
||||||
|
Called from agent to check for new tasks
|
||||||
|
]#
|
||||||
|
proc getTasks*(ctx: Context) {.async.} =
|
||||||
|
|
||||||
|
stdout.writeLine(ctx.getPathParams("listener"))
|
||||||
|
let name = ctx.getPathParams("agent")
|
||||||
|
|
||||||
|
|
||||||
resp name
|
resp name
|
||||||
|
|
||||||
|
#[
|
||||||
|
POST /{listener-uuid}/{agent-uuid}/results
|
||||||
|
Called from agent to post results of a task
|
||||||
|
|
||||||
|
]#
|
||||||
|
proc postResults*(ctx: Context) {.async.} =
|
||||||
|
|
||||||
|
let name = ctx.getPathParams("agent")
|
||||||
|
|
||||||
|
resp name
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
import strformat, strutils, sequtils, checksums/sha1, nanoid, terminal, sugar
|
import strformat, strutils, sequtils, checksums/sha1, nanoid, terminal
|
||||||
import prologue
|
import prologue
|
||||||
|
|
||||||
import ./[api, utils]
|
import ./[api, utils]
|
||||||
import ../types
|
import ../types
|
||||||
import ../db/database
|
import ../db/database
|
||||||
|
|
||||||
|
proc listenerUsage*(cq: Conquest) =
|
||||||
proc listenerUsage*(console: Console) =
|
cq.writeLine("""Manage, start and stop listeners.
|
||||||
console.writeLine("""Manage, start and stop listeners.
|
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
listener [options] COMMAND
|
listener [options] COMMAND
|
||||||
@@ -21,18 +20,18 @@ Commands:
|
|||||||
Options:
|
Options:
|
||||||
-h, --help""")
|
-h, --help""")
|
||||||
|
|
||||||
proc listenerList*(console: Console) =
|
proc listenerList*(cq: Conquest) =
|
||||||
let listeners = console.dbGetAllListeners()
|
let listeners = cq.dbGetAllListeners()
|
||||||
console.drawTable(listeners)
|
cq.drawTable(listeners)
|
||||||
|
|
||||||
proc listenerStart*(console: Console, host: string, portStr: string) =
|
proc listenerStart*(cq: Conquest, host: string, portStr: string) =
|
||||||
|
|
||||||
# Validate arguments
|
# Validate arguments
|
||||||
# if not validateIPv4Address(host):
|
# if not validateIPv4Address(host):
|
||||||
# console.writeLine(fgRed, styleBright, fmt"Invalid IPv4 IP address: {ip}.")
|
# cq.writeLine(fgRed, styleBright, fmt"Invalid IPv4 IP address: {ip}.")
|
||||||
# return
|
# return
|
||||||
if not validatePort(portStr):
|
if not validatePort(portStr):
|
||||||
console.writeLine(fgRed, styleBright, fmt"[-] Invalid port number: {portStr}")
|
cq.writeLine(fgRed, styleBright, fmt"[-] Invalid port number: {portStr}")
|
||||||
return
|
return
|
||||||
|
|
||||||
let port = portStr.parseInt
|
let port = portStr.parseInt
|
||||||
@@ -50,26 +49,25 @@ proc listenerStart*(console: Console, host: string, portStr: string) =
|
|||||||
var listener = newApp(settings = listenerSettings)
|
var listener = newApp(settings = listenerSettings)
|
||||||
|
|
||||||
# Define API endpoints
|
# Define API endpoints
|
||||||
listener.addRoute("/", api.index, @[HttpGet])
|
listener.post("{listener}/register", api.register)
|
||||||
listener.addRoute("/register", api.agentRegister, @[HttpPost])
|
listener.get("{listener}/{agent}/tasks", api.getTasks)
|
||||||
listener.addRoute("/{name}/tasks", api.addTasks, @[HttpGet, HttpPost])
|
listener.post("{listener}/{agent}/results", api.postResults)
|
||||||
|
|
||||||
# Store listener in database
|
# Store listener in database
|
||||||
let listenerInstance = newListener(name, host, port)
|
let listenerInstance = newListener(name, host, port)
|
||||||
if not console.dbStore(listenerInstance):
|
if not cq.dbStoreListener(listenerInstance):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Start serving
|
# Start serving
|
||||||
try:
|
try:
|
||||||
discard listener.runAsync()
|
discard listener.runAsync()
|
||||||
console.activeListeners.add(listener)
|
inc cq.listeners
|
||||||
inc console.listeners
|
cq.writeLine(fgGreen, "[+] ", resetStyle, "Started listener", fgGreen, fmt" {name} ", resetStyle, fmt"on port {portStr}.")
|
||||||
console.writeLine(fgGreen, "[+] ", resetStyle, "Started listener", fgGreen, fmt" {name} ", resetStyle, fmt"on port {portStr}.")
|
|
||||||
except CatchableError as err:
|
except CatchableError as err:
|
||||||
console.writeLine(fgRed, styleBright, "[-] Failed to start listener: ", getCurrentExceptionMsg())
|
cq.writeLine(fgRed, styleBright, "[-] Failed to start listener: ", getCurrentExceptionMsg())
|
||||||
|
|
||||||
proc restartListeners*(console: Console) =
|
proc restartListeners*(cq: Conquest) =
|
||||||
let listeners: seq[Listener] = console.dbGetAllListeners()
|
let listeners: seq[Listener] = cq.dbGetAllListeners()
|
||||||
|
|
||||||
# Restart all active listeners that are stored in the database
|
# Restart all active listeners that are stored in the database
|
||||||
for l in listeners:
|
for l in listeners:
|
||||||
@@ -83,29 +81,28 @@ proc restartListeners*(console: Console) =
|
|||||||
listener = newApp(settings = settings)
|
listener = newApp(settings = settings)
|
||||||
|
|
||||||
# Define API endpoints
|
# Define API endpoints
|
||||||
listener.addRoute("/", api.index, @[HttpGet])
|
listener.post("{listener}/register", api.register)
|
||||||
listener.addRoute("/register", api.agentRegister, @[HttpPost])
|
listener.get("{listener}/{agent}/tasks", api.getTasks)
|
||||||
listener.addRoute("/{name}/tasks", api.addTasks, @[HttpGet, HttpPost])
|
listener.post("{listener}/{agent}/results", api.postResults)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
discard listener.runAsync()
|
discard listener.runAsync()
|
||||||
console.activeListeners.add(listener)
|
inc cq.listeners
|
||||||
inc console.listeners
|
cq.writeLine(fgGreen, "[+] ", resetStyle, "Restarted listener", fgGreen, fmt" {l.name} ", resetStyle, fmt"on port {$l.port}.")
|
||||||
console.writeLine(fgGreen, "[+] ", resetStyle, "Restarted listener", fgGreen, fmt" {l.name} ", resetStyle, fmt"on port {$l.port}.")
|
|
||||||
except CatchableError as err:
|
except CatchableError as err:
|
||||||
console.writeLine(fgRed, styleBright, "[-] Failed to restart listener: ", getCurrentExceptionMsg())
|
cq.writeLine(fgRed, styleBright, "[-] Failed to restart listener: ", getCurrentExceptionMsg())
|
||||||
|
|
||||||
# Delay before starting serving another listener to avoid crashing the application
|
# Delay before starting serving another listener to avoid crashing the application
|
||||||
waitFor sleepAsync(10)
|
waitFor sleepAsync(10)
|
||||||
|
|
||||||
console.writeLine("")
|
cq.writeLine("")
|
||||||
|
|
||||||
proc listenerStop*(console: Console, name: string) =
|
proc listenerStop*(cq: Conquest, name: string) =
|
||||||
|
|
||||||
if not console.dbDeleteListenerByName(name.toUpperAscii):
|
if not cq.dbDeleteListenerByName(name.toUpperAscii):
|
||||||
console.writeLine(fgRed, styleBright, "[-] Failed to stop listener: ", getCurrentExceptionMsg())
|
cq.writeLine(fgRed, styleBright, "[-] Failed to stop listener: ", getCurrentExceptionMsg())
|
||||||
return
|
return
|
||||||
|
|
||||||
dec console.listeners
|
dec cq.listeners
|
||||||
console.writeLine(fgGreen, "[+] ", resetStyle, "Stopped listener ", fgGreen, fmt"{name.toUpperAscii}.")
|
cq.writeLine(fgGreen, "[+] ", resetStyle, "Stopped listener ", fgGreen, name.toUpperAscii, resetStyle, ".")
|
||||||
|
|
||||||
@@ -41,23 +41,23 @@ proc row(cells: seq[string], widths: seq[int]): string =
|
|||||||
row.add(" " & cell.alignLeft(widths[i] - 2) & " " & vert)
|
row.add(" " & cell.alignLeft(widths[i] - 2) & " " & vert)
|
||||||
return row
|
return row
|
||||||
|
|
||||||
proc drawTable*(console: Console, listeners: seq[Listener]) =
|
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, 15, 7, 10, 8]
|
||||||
|
|
||||||
console.writeLine(border(topLeft, topMid, topRight, widths))
|
cq.writeLine(border(topLeft, topMid, topRight, widths))
|
||||||
console.writeLine(row(headers, widths))
|
cq.writeLine(row(headers, widths))
|
||||||
console.writeLine(border(midLeft, midMid, midRight, widths))
|
cq.writeLine(border(midLeft, midMid, midRight, widths))
|
||||||
|
|
||||||
for l in listeners:
|
for l in listeners:
|
||||||
# TODO: Add number of agents connected to the listener
|
# TODO: Add number of agents connected to the listener
|
||||||
let row = @[l.name, l.address, $l.port, $l.protocol, "X"]
|
let row = @[l.name, l.address, $l.port, $l.protocol, "X"]
|
||||||
console.writeLine(row(row, widths))
|
cq.writeLine(row(row, widths))
|
||||||
|
|
||||||
console.writeLine(border(botLeft, botMid, botRight, widths))
|
cq.writeLine(border(botLeft, botMid, botRight, widths))
|
||||||
|
|
||||||
|
|
||||||
proc drawTable*(console: Console, agents: seq[Agent]) =
|
proc drawTable*(cq: Conquest, agents: seq[Agent]) =
|
||||||
discard
|
discard
|
||||||
@@ -1,19 +1,141 @@
|
|||||||
import ./console
|
import prompt, terminal
|
||||||
|
import argparse
|
||||||
|
import strutils, strformat, times, system, unicode
|
||||||
|
|
||||||
# Handle CTRL+C,
|
import ./[types, globals]
|
||||||
proc exit() {.noconv.} =
|
import agent/agent, listener/listener, db/database
|
||||||
echo "Received CTRL+C. Type \"exit\" to close the application.\n"
|
|
||||||
|
|
||||||
proc main() =
|
|
||||||
# Initialize TUI
|
|
||||||
# initUi()
|
|
||||||
|
|
||||||
setControlCHook(exit)
|
|
||||||
|
|
||||||
# Initialize prompt interface
|
|
||||||
initPrompt()
|
|
||||||
|
|
||||||
#[
|
#[
|
||||||
Start main function
|
Argument parsing
|
||||||
|
]#
|
||||||
|
var parser = newParser:
|
||||||
|
help("Conquest 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 agent.")
|
||||||
|
|
||||||
|
|
||||||
|
command("help"):
|
||||||
|
nohelpflag()
|
||||||
|
|
||||||
|
command("exit"):
|
||||||
|
nohelpflag()
|
||||||
|
|
||||||
|
proc handleConsoleCommand*(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}] ", 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
|
||||||
|
cq.writeLine(parser.help())
|
||||||
|
|
||||||
|
of "listener":
|
||||||
|
case opts.listener.get.command
|
||||||
|
of "list":
|
||||||
|
cq.listenerList()
|
||||||
|
of "start":
|
||||||
|
cq.listenerStart(opts.listener.get.start.get.host, opts.listener.get.start.get.port)
|
||||||
|
of "stop":
|
||||||
|
cq.listenerStop(opts.listener.get.stop.get.name)
|
||||||
|
else:
|
||||||
|
cq.listenerUsage()
|
||||||
|
|
||||||
|
of "agent":
|
||||||
|
case opts.agent.get.command
|
||||||
|
of "list":
|
||||||
|
cq.agentList()
|
||||||
|
of "build":
|
||||||
|
cq.agentBuild()
|
||||||
|
of "interact":
|
||||||
|
cq.agentInteract()
|
||||||
|
else:
|
||||||
|
cq.agentUsage()
|
||||||
|
|
||||||
|
# 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("")
|
||||||
|
|
||||||
|
proc header(cq: Conquest) =
|
||||||
|
cq.writeLine("")
|
||||||
|
cq.writeLine("┏┏┓┏┓┏┓┓┏┏┓┏╋")
|
||||||
|
cq.writeLine("┗┗┛┛┗┗┫┗┻┗ ┛┗ V0.1")
|
||||||
|
cq.writeLine(" ┗ @jakobfriedl")
|
||||||
|
cq.writeLine("─".repeat(21))
|
||||||
|
cq.writeLine("")
|
||||||
|
|
||||||
|
|
||||||
|
#[
|
||||||
|
Conquest framework entry point
|
||||||
]#
|
]#
|
||||||
main()
|
proc main() =
|
||||||
|
|
||||||
|
# Handle CTRL+C,
|
||||||
|
proc exit() {.noconv.} =
|
||||||
|
echo "Received CTRL+C. Type \"exit\" to close the application.\n"
|
||||||
|
|
||||||
|
setControlCHook(exit)
|
||||||
|
|
||||||
|
# Initialize framework
|
||||||
|
cq = initConquest()
|
||||||
|
|
||||||
|
# Print header
|
||||||
|
cq.header()
|
||||||
|
|
||||||
|
# Initialize database
|
||||||
|
cq.dbInit()
|
||||||
|
cq.restartListeners()
|
||||||
|
|
||||||
|
# Main loop
|
||||||
|
while true:
|
||||||
|
cq.setIndicator("[conquest]> ")
|
||||||
|
cq.setStatusBar(@[("mode", "manage"), ("listeners", $cq.listeners), ("agents", $cq.agents)])
|
||||||
|
cq.showPrompt()
|
||||||
|
|
||||||
|
var command: string = cq.readLine()
|
||||||
|
cq.withOutput(handleConsoleCommand, command)
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
main()
|
||||||
118
server/types.nim
118
server/types.nim
@@ -2,58 +2,110 @@ import prompt
|
|||||||
import prologue
|
import prologue
|
||||||
|
|
||||||
#[
|
#[
|
||||||
Console
|
Conquest
|
||||||
]#
|
]#
|
||||||
type
|
type
|
||||||
Console* = ref object
|
Conquest* = ref object
|
||||||
prompt*: Prompt
|
prompt*: Prompt
|
||||||
listeners*: int
|
listeners*: int
|
||||||
agents*: int
|
agents*: int
|
||||||
dbPath*: string
|
dbPath*: string
|
||||||
activeListeners*: seq[Prologue]
|
|
||||||
|
|
||||||
Command* = object
|
proc initConquest*(): Conquest =
|
||||||
cmd*: string
|
var cq = new Conquest
|
||||||
execute*: proc(console: Console, args: varargs[string])
|
|
||||||
|
|
||||||
proc newConsole*(): Console =
|
|
||||||
var console = new Console
|
|
||||||
var prompt = Prompt.init()
|
var prompt = Prompt.init()
|
||||||
console.prompt = prompt
|
cq.prompt = prompt
|
||||||
console.dbPath = "db/conquest.db"
|
cq.dbPath = "db/conquest.db"
|
||||||
console.listeners = 0
|
cq.listeners = 0
|
||||||
console.agents = 0
|
cq.agents = 0
|
||||||
console.activeListeners = @[]
|
|
||||||
|
|
||||||
return console
|
return cq
|
||||||
|
|
||||||
template writeLine*(console: Console, args: varargs[untyped]) =
|
template writeLine*(cq: Conquest, args: varargs[untyped]) =
|
||||||
console.prompt.writeLine(args)
|
cq.prompt.writeLine(args)
|
||||||
proc readLine*(console: Console): string =
|
proc readLine*(cq: Conquest): string =
|
||||||
return console.prompt.readLine()
|
return cq.prompt.readLine()
|
||||||
template setIndicator*(console: Console, indicator: string) =
|
template setIndicator*(cq: Conquest, indicator: string) =
|
||||||
console.prompt.setIndicator(indicator)
|
cq.prompt.setIndicator(indicator)
|
||||||
template showPrompt*(console: Console) =
|
template showPrompt*(cq: Conquest) =
|
||||||
console.prompt.showPrompt()
|
cq.prompt.showPrompt()
|
||||||
template hidePrompt*(console: Console) =
|
template hidePrompt*(cq: Conquest) =
|
||||||
console.prompt.hidePrompt()
|
cq.prompt.hidePrompt()
|
||||||
template setStatusBar*(console: Console, statusBar: seq[StatusBarItem]) =
|
template setStatusBar*(cq: Conquest, statusBar: seq[StatusBarItem]) =
|
||||||
console.prompt.setStatusBar(statusBar)
|
cq.prompt.setStatusBar(statusBar)
|
||||||
template clear*(console: Console) =
|
template clear*(cq: Conquest) =
|
||||||
console.prompt.clear()
|
cq.prompt.clear()
|
||||||
|
|
||||||
# Overwrite withOutput function to handle function arguments
|
# Overwrite withOutput function to handle function arguments
|
||||||
proc withOutput*(console: Console, outputFunction: proc(console: Console, args: varargs[string]), args: varargs[string]) =
|
proc withOutput*(cq: Conquest, outputFunction: proc(cq: Conquest, args: varargs[string]), args: varargs[string]) =
|
||||||
console.hidePrompt()
|
cq.hidePrompt()
|
||||||
outputFunction(console, args)
|
outputFunction(cq, args)
|
||||||
console.showPrompt()
|
cq.showPrompt()
|
||||||
|
|
||||||
#[
|
#[
|
||||||
Agent
|
Agent
|
||||||
]#
|
]#
|
||||||
type
|
type
|
||||||
|
|
||||||
|
TaskCommand* = enum
|
||||||
|
ExecuteShell = "shell"
|
||||||
|
ExecuteBof = "bof"
|
||||||
|
ExecuteAssembly = "dotnet"
|
||||||
|
ExecutePe = "pe"
|
||||||
|
|
||||||
|
TaskStatus* = enum
|
||||||
|
Created = "created"
|
||||||
|
Completed = "completed"
|
||||||
|
Pending = "pending"
|
||||||
|
Failed = "failed"
|
||||||
|
Cancelled = "cancelled"
|
||||||
|
|
||||||
|
TaskResult* = string
|
||||||
|
|
||||||
|
Task* = ref object
|
||||||
|
id*: int
|
||||||
|
agent*: string
|
||||||
|
command*: TaskCommand
|
||||||
|
args*: seq[string]
|
||||||
|
result*: TaskResult
|
||||||
|
status*: TaskStatus
|
||||||
|
|
||||||
|
AgentRegistrationData* = object
|
||||||
|
username*: string
|
||||||
|
hostname*: string
|
||||||
|
ip*: string
|
||||||
|
os*: string
|
||||||
|
pid*: int
|
||||||
|
elevated*: bool
|
||||||
|
|
||||||
Agent* = ref object
|
Agent* = ref object
|
||||||
name*: string
|
name*: string
|
||||||
|
listener*: string
|
||||||
|
sleep*: int
|
||||||
|
jitter*: float
|
||||||
|
pid*: int
|
||||||
|
username*: string
|
||||||
|
hostname*: string
|
||||||
|
ip*: string
|
||||||
|
os*: string
|
||||||
|
elevated*: bool
|
||||||
|
tasks*: seq[Task]
|
||||||
|
|
||||||
|
proc newAgent*(name, listener, username, hostname, ip, os: string, pid: int, elevated: bool): Agent =
|
||||||
|
var agent = new Agent
|
||||||
|
agent.name = name
|
||||||
|
agent.listener = listener
|
||||||
|
agent.pid = pid
|
||||||
|
agent.username = username
|
||||||
|
agent.hostname = hostname
|
||||||
|
agent.ip = ip
|
||||||
|
agent.os = os
|
||||||
|
agent.elevated = elevated
|
||||||
|
agent.sleep = 10
|
||||||
|
agent.jitter = 0.2
|
||||||
|
agent.tasks = @[]
|
||||||
|
|
||||||
|
return agent
|
||||||
|
|
||||||
#[
|
#[
|
||||||
Listener
|
Listener
|
||||||
|
|||||||
Reference in New Issue
Block a user