2025-10-01 21:57:26 +02:00
|
|
|
import terminal, parsetoml, json, math, base64
|
2025-10-01 13:25:15 +02:00
|
|
|
import strutils, strformat, system, tables
|
|
|
|
|
|
2025-10-01 15:27:06 +02:00
|
|
|
import ./core/[listener, builder]
|
2025-10-01 13:25:15 +02:00
|
|
|
import ./globals
|
|
|
|
|
import ./db/database
|
|
|
|
|
import ./core/logger
|
2025-10-01 21:57:26 +02:00
|
|
|
import ../common/[types, crypto, utils, profile, event]
|
2025-10-01 13:25:15 +02:00
|
|
|
import ./websocket
|
|
|
|
|
import mummy, mummy/routers
|
|
|
|
|
|
|
|
|
|
proc header() =
|
|
|
|
|
echo ""
|
|
|
|
|
echo "┏┏┓┏┓┏┓┓┏┏┓┏╋"
|
|
|
|
|
echo "┗┗┛┛┗┗┫┗┻┗ ┛┗ V0.1"
|
|
|
|
|
echo " ┗ @jakobfriedl"
|
|
|
|
|
echo "─".repeat(21)
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
proc init*(T: type Conquest, profile: Profile): Conquest =
|
|
|
|
|
var cq = new Conquest
|
|
|
|
|
cq.listeners = initTable[string, Listener]()
|
|
|
|
|
cq.threads = initTable[string, Thread[Listener]]()
|
|
|
|
|
cq.agents = initTable[string, Agent]()
|
|
|
|
|
cq.profile = profile
|
|
|
|
|
cq.keyPair = loadKeyPair(CONQUEST_ROOT & "/" & profile.getString("private-key-file"))
|
|
|
|
|
cq.dbPath = CONQUEST_ROOT & "/" & profile.getString("database-file")
|
2025-10-01 21:57:26 +02:00
|
|
|
cq.client = nil
|
2025-10-01 13:25:15 +02:00
|
|
|
return cq
|
|
|
|
|
|
|
|
|
|
#[
|
|
|
|
|
WebSocket
|
|
|
|
|
]#
|
|
|
|
|
proc upgradeHandler(request: Request) =
|
|
|
|
|
{.cast(gcsafe).}:
|
|
|
|
|
let ws = request.upgradeToWebSocket()
|
2025-10-01 21:57:26 +02:00
|
|
|
cq.client = WsConnection(
|
2025-10-01 13:25:15 +02:00
|
|
|
ws: ws
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) {.gcsafe.} =
|
|
|
|
|
{.cast(gcsafe).}:
|
|
|
|
|
case event:
|
|
|
|
|
of OpenEvent:
|
|
|
|
|
# New client connected to team server
|
2025-10-01 21:57:26 +02:00
|
|
|
# Send the public key for the key exchange, all other information with be transmitted when the key exchange is completed
|
|
|
|
|
cq.client.sendPublicKey(cq.keyPair.publicKey)
|
2025-10-01 13:25:15 +02:00
|
|
|
|
|
|
|
|
of MessageEvent:
|
|
|
|
|
# Continuously send heartbeat messages
|
|
|
|
|
ws.sendHeartbeat()
|
|
|
|
|
|
2025-10-01 21:57:26 +02:00
|
|
|
let event = message.recvEvent(cq.client.sessionKey)
|
2025-10-01 13:25:15 +02:00
|
|
|
|
|
|
|
|
case event.eventType:
|
2025-10-01 21:57:26 +02:00
|
|
|
of CLIENT_KEY_EXCHANGE:
|
|
|
|
|
let publicKey = decode(event.data["publicKey"].getStr()).toKey()
|
|
|
|
|
cq.client.sessionKey = deriveSessionKey(cq.keyPair, publicKey)
|
|
|
|
|
|
|
|
|
|
# Send relevant information to the client
|
|
|
|
|
# - C2 profile
|
|
|
|
|
# - agent sessions
|
|
|
|
|
# - listeners
|
|
|
|
|
cq.client.sendProfile(cq.profile)
|
|
|
|
|
for id, listener in cq.listeners:
|
|
|
|
|
cq.client.sendListener(listener)
|
|
|
|
|
for id, agent in cq.agents:
|
|
|
|
|
cq.client.sendAgent(agent)
|
|
|
|
|
cq.client.sendEventlogItem(LOG_SUCCESS_SHORT, "CQ-V1")
|
|
|
|
|
|
2025-10-01 13:25:15 +02:00
|
|
|
of CLIENT_AGENT_TASK:
|
|
|
|
|
let agentId = event.data["agentId"].getStr()
|
|
|
|
|
let task = event.data["task"].to(Task)
|
|
|
|
|
cq.agents[agentId].tasks.add(task)
|
|
|
|
|
|
|
|
|
|
of CLIENT_LISTENER_START:
|
|
|
|
|
let listener = event.data.to(UIListener)
|
|
|
|
|
cq.listenerStart(listener.listenerId, listener.address, listener.port, listener.protocol)
|
|
|
|
|
|
|
|
|
|
of CLIENT_LISTENER_STOP:
|
|
|
|
|
let listenerId = event.data["listenerId"].getStr()
|
|
|
|
|
cq.listenerStop(listenerId)
|
|
|
|
|
|
|
|
|
|
of CLIENT_AGENT_BUILD:
|
|
|
|
|
let
|
|
|
|
|
listenerId = event.data["listenerId"].getStr()
|
|
|
|
|
sleepDelay = event.data["sleepDelay"].getInt()
|
|
|
|
|
sleepTechnique = cast[SleepObfuscationTechnique](event.data["sleepTechnique"].getInt())
|
|
|
|
|
spoofStack = event.data["spoofStack"].getBool()
|
|
|
|
|
modules = cast[uint32](event.data["modules"].getInt())
|
|
|
|
|
|
|
|
|
|
let payload = cq.agentBuild(listenerId, sleepDelay, sleepTechnique, spoofStack, modules)
|
|
|
|
|
if payload.len() != 0:
|
|
|
|
|
cq.client.sendAgentPayload(payload)
|
|
|
|
|
|
|
|
|
|
else: discard
|
|
|
|
|
|
|
|
|
|
of ErrorEvent:
|
|
|
|
|
discard
|
|
|
|
|
of CloseEvent:
|
|
|
|
|
# Set the client instance to nil again to prevent debug error messages
|
|
|
|
|
cq.client = nil
|
|
|
|
|
|
|
|
|
|
proc startServer*(profilePath: string) =
|
|
|
|
|
|
|
|
|
|
# Ensure that the conquest root directory was passed as a compile-time define
|
|
|
|
|
when not defined(CONQUEST_ROOT):
|
|
|
|
|
quit(0)
|
|
|
|
|
|
|
|
|
|
header()
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Initialize framework context
|
|
|
|
|
# Load and parse profile
|
|
|
|
|
let profile = parsetoml.parseFile(profilePath)
|
|
|
|
|
cq = Conquest.init(profile)
|
|
|
|
|
|
|
|
|
|
cq.info("Using profile \"", profile.getString("name"), "\" (", profilePath ,").")
|
|
|
|
|
|
|
|
|
|
except CatchableError as err:
|
|
|
|
|
echo err.msg
|
|
|
|
|
quit(0)
|
|
|
|
|
|
|
|
|
|
# Initialize database
|
|
|
|
|
cq.dbInit()
|
|
|
|
|
for agent in cq.dbGetAllAgents():
|
|
|
|
|
cq.agents[agent.agentId] = agent
|
|
|
|
|
for listener in cq.dbGetAllListeners():
|
|
|
|
|
cq.listeners[listener.listenerId] = listener
|
|
|
|
|
|
|
|
|
|
# Restart existing listeners
|
|
|
|
|
for listenerId, listener in cq.listeners:
|
|
|
|
|
cq.listenerStart(listenerId, listener.address, listener.port, listener.protocol)
|
|
|
|
|
|
|
|
|
|
# Start websocket server
|
|
|
|
|
var router: Router
|
|
|
|
|
router.get("/*", upgradeHandler)
|
|
|
|
|
|
2025-10-02 10:25:37 +02:00
|
|
|
# Increased websocket message length in order to support dotnet assembly execution (1GB)
|
|
|
|
|
let server = newServer(router, websocketHandler, maxBodyLen = 1024 * 1024 * 1024, maxMessageLen = 1024 * 1024 * 1024)
|
2025-10-01 13:25:15 +02:00
|
|
|
server.serve(Port(cq.profile.getInt("team-server.port")), "0.0.0.0")
|
2025-07-16 10:33:13 +02:00
|
|
|
|
|
|
|
|
# Conquest framework entry point
|
|
|
|
|
when isMainModule:
|
2025-08-13 19:32:51 +02:00
|
|
|
import cligen; dispatch startServer
|