Implemented websocket (client <-> server) traffic encryption & compression.

This commit is contained in:
Jakob Friedl
2025-10-01 21:57:26 +02:00
parent 0937840b77
commit fbe85493b2
11 changed files with 160 additions and 206 deletions

View File

@@ -2,7 +2,7 @@ import whisky
import tables, strutils, strformat, json, parsetoml, base64, os # native_dialogs import tables, strutils, strformat, json, parsetoml, base64, os # native_dialogs
import ./utils/[appImGui, globals] import ./utils/[appImGui, globals]
import ./views/[dockspace, sessions, listeners, eventlog, console] import ./views/[dockspace, sessions, listeners, eventlog, console]
import ../common/[types, utils] import ../common/[types, utils, crypto]
import ./websocket import ./websocket
import sugar import sugar
@@ -39,9 +39,15 @@ proc main(ip: string = "localhost", port: int = 37573) =
let io = igGetIO() let io = igGetIO()
# Create key pair
let clientKeyPair = generateKeyPair()
# Initiate WebSocket connection # Initiate WebSocket connection
let ws = newWebSocket(fmt"ws://{ip}:{$port}") var connection = WsConnection(
defer: ws.close() ws: newWebSocket(fmt"ws://{ip}:{$port}"),
sessionKey: default(Key)
)
defer: connection.ws.close()
# main loop # main loop
while not app.handle.windowShouldClose: while not app.handle.windowShouldClose:
@@ -59,13 +65,17 @@ proc main(ip: string = "localhost", port: int = 37573) =
WebSocket communication with the team server WebSocket communication with the team server
]# ]#
# Continuously send heartbeat messages # Continuously send heartbeat messages
ws.sendHeartbeat() connection.ws.sendHeartbeat()
# Receive and parse websocket response message # Receive and parse websocket response message
let event = recvEvent(ws.receiveMessage().get()) let event = recvEvent(connection.ws.receiveMessage().get(), connection.sessionKey)
case event.eventType: case event.eventType:
of CLIENT_KEY_EXCHANGE:
connection.sessionKey = deriveSessionKey(clientKeyPair, decode(event.data["publicKey"].getStr()).toKey())
connection.sendPublicKey(clientKeyPair.publicKey)
of CLIENT_PROFILE: of CLIENT_PROFILE:
profile = parsetoml.parseString(event.data["profile"].getStr()) profile = parsetoml.parseString(event.data["profile"].getStr())
of CLIENT_LISTENER_ADD: of CLIENT_LISTENER_ADD:
let listener = event.data.to(UIListener) let listener = event.data.to(UIListener)
@@ -90,7 +100,7 @@ proc main(ip: string = "localhost", port: int = 37573) =
igSetNextWindowDockID(listenersWindow.DockNode.ID, ImGuiCond_FirstUseEver.int32) igSetNextWindowDockID(listenersWindow.DockNode.ID, ImGuiCond_FirstUseEver.int32)
else: else:
igSetNextWindowDockID(dockBottom, ImGuiCond_FirstUseEver.int32) igSetNextWindowDockID(dockBottom, ImGuiCond_FirstUseEver.int32)
consoles[agent.agentId].draw(ws) consoles[agent.agentId].draw(connection)
consoles[agent.agentId].showConsole = false consoles[agent.agentId].showConsole = false
of CLIENT_AGENT_CHECKIN: of CLIENT_AGENT_CHECKIN:
@@ -127,7 +137,7 @@ proc main(ip: string = "localhost", port: int = 37573) =
# Draw/update UI components/views # Draw/update UI components/views
if showSessionsTable: sessionsTable.draw(addr showSessionsTable) if showSessionsTable: sessionsTable.draw(addr showSessionsTable)
if showListeners: listenersTable.draw(addr showListeners, ws) if showListeners: listenersTable.draw(addr showListeners, connection)
if showEventlog: eventlog.draw(addr showEventlog) if showEventlog: eventlog.draw(addr showEventlog)
# Show console windows # Show console windows
@@ -136,7 +146,7 @@ proc main(ip: string = "localhost", port: int = 37573) =
if console.showConsole: if console.showConsole:
# Ensure that new console windows are docked to the bottom panel by default # Ensure that new console windows are docked to the bottom panel by default
igSetNextWindowDockID(dockBottom, ImGuiCond_FirstUseEver.int32) igSetNextWindowDockID(dockBottom, ImGuiCond_FirstUseEver.int32)
console.draw(ws) console.draw(connection)
newConsoleTable[agentId] = console newConsoleTable[agentId] = console
# Update the consoles table with only those sessions that have not been closed yet # Update the consoles table with only those sessions that have not been closed yet

View File

@@ -170,7 +170,7 @@ proc handleHelp(component: ConsoleComponent, parsed: seq[string]) =
# Command was not found # Command was not found
component.addItem(LOG_ERROR, fmt"The command '{parsed[1]}' does not exist.") component.addItem(LOG_ERROR, fmt"The command '{parsed[1]}' does not exist.")
proc handleAgentCommand*(component: ConsoleComponent, ws: WebSocket, input: string) = proc handleAgentCommand*(component: ConsoleComponent, connection: WsConnection, input: string) =
# Convert user input into sequence of string arguments # Convert user input into sequence of string arguments
let parsedArgs = parseInput(input) let parsedArgs = parseInput(input)
@@ -186,7 +186,7 @@ proc handleAgentCommand*(component: ConsoleComponent, ws: WebSocket, input: stri
command = getCommandByName(parsedArgs[0]) command = getCommandByName(parsedArgs[0])
task = createTask(component.agent.agentId, component.agent.listenerId, command, parsedArgs[1..^1]) task = createTask(component.agent.agentId, component.agent.listenerId, command, parsedArgs[1..^1])
ws.sendAgentTask(component.agent.agentId, task) connection.sendAgentTask(component.agent.agentId, task)
component.addItem(LOG_INFO, fmt"Tasked agent to {command.description.toLowerAscii()} ({Uuid.toString(task.taskId)})") component.addItem(LOG_INFO, fmt"Tasked agent to {command.description.toLowerAscii()} ({Uuid.toString(task.taskId)})")
except CatchableError: except CatchableError:
@@ -219,7 +219,7 @@ proc print(item: ConsoleItem) =
igSameLine(0.0f, 0.0f) igSameLine(0.0f, 0.0f)
igTextUnformatted(item.text.cstring, nil) igTextUnformatted(item.text.cstring, nil)
proc draw*(component: ConsoleComponent, ws: WebSocket) = proc draw*(component: ConsoleComponent, connection: WsConnection) =
igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}".cstring, addr component.showConsole, 0) igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}".cstring, addr component.showConsole, 0)
defer: igEnd() defer: igEnd()
@@ -340,7 +340,7 @@ proc draw*(component: ConsoleComponent, ws: WebSocket) =
component.addItem(LOG_COMMAND, command) component.addItem(LOG_COMMAND, command)
# Send command to team server # Send command to team server
component.handleAgentCommand(ws, command) component.handleAgentCommand(connection, command)
# Add command to console history # Add command to console history
component.history.add(command) component.history.add(command)

View File

@@ -22,7 +22,7 @@ proc ListenersTable*(title: string): ListenersTableComponent =
result.startListenerModal = ListenerModal() result.startListenerModal = ListenerModal()
result.generatePayloadModal = AgentModal() result.generatePayloadModal = AgentModal()
proc draw*(component: ListenersTableComponent, showComponent: ptr bool, ws: WebSocket) = proc draw*(component: ListenersTableComponent, showComponent: ptr bool, connection: WsConnection) =
igBegin(component.title, showComponent, 0) igBegin(component.title, showComponent, 0)
defer: igEnd() defer: igEnd()
@@ -41,11 +41,11 @@ proc draw*(component: ListenersTableComponent, showComponent: ptr bool, ws: WebS
let listener = component.startListenerModal.draw() let listener = component.startListenerModal.draw()
if listener != nil: if listener != nil:
ws.sendStartListener(listener) connection.sendStartListener(listener)
let buildInformation = component.generatePayloadModal.draw(component.listeners) let buildInformation = component.generatePayloadModal.draw(component.listeners)
if buildInformation != nil: if buildInformation != nil:
ws.sendAgentBuild(buildInformation) connection.sendAgentBuild(buildInformation)
#[ #[
Listener table Listener table
@@ -106,7 +106,7 @@ proc draw*(component: ListenersTableComponent, showComponent: ptr bool, ws: WebS
if not ImGuiSelectionBasicStorage_Contains(component.selection, cast[ImGuiID](i)): if not ImGuiSelectionBasicStorage_Contains(component.selection, cast[ImGuiID](i)):
newListeners.add(listener) newListeners.add(listener)
else: else:
ws.sendStopListener(listener.listenerId) connection.sendStopListener(listener.listenerId)
component.listeners = newListeners component.listeners = newListeners
ImGuiSelectionBasicStorage_Clear(component.selection) ImGuiSelectionBasicStorage_Clear(component.selection)

View File

@@ -1,20 +1,30 @@
import whisky import whisky
import times, tables, json import times, tables, json, base64
import ../common/[types, utils, serialize, event] import ../common/[types, utils, serialize, event]
export sendHeartbeat, recvEvent export sendHeartbeat, recvEvent
#[ #[
Client -> Server Client -> Server
]# ]#
proc sendStartListener*(ws: WebSocket, listener: UIListener) = proc sendPublicKey*(connection: WsConnection, publicKey: Key) =
let event = Event(
eventType: CLIENT_KEY_EXCHANGE,
timestamp: now().toTime().toUnix(),
data: %*{
"publicKey": encode(Bytes.toString(publicKey))
}
)
connection.ws.sendEvent(event, connection.sessionKey)
proc sendStartListener*(connection: WsConnection, listener: UIListener) =
let event = Event( let event = Event(
eventType: CLIENT_LISTENER_START, eventType: CLIENT_LISTENER_START,
timestamp: now().toTime().toUnix(), timestamp: now().toTime().toUnix(),
data: %listener data: %listener
) )
ws.sendEvent(event) connection.ws.sendEvent(event, connection.sessionKey)
proc sendStopListener*(ws: WebSocket, listenerId: string) = proc sendStopListener*(connection: WsConnection, listenerId: string) =
let event = Event( let event = Event(
eventType: CLIENT_LISTENER_STOP, eventType: CLIENT_LISTENER_STOP,
timestamp: now().toTime().toUnix(), timestamp: now().toTime().toUnix(),
@@ -22,9 +32,9 @@ proc sendStopListener*(ws: WebSocket, listenerId: string) =
"listenerId": listenerId "listenerId": listenerId
} }
) )
ws.sendEvent(event) connection.ws.sendEvent(event, connection.sessionKey)
proc sendAgentBuild*(ws: WebSocket, buildInformation: AgentBuildInformation) = proc sendAgentBuild*(connection: WsConnection, buildInformation: AgentBuildInformation) =
let event = Event( let event = Event(
eventType: CLIENT_AGENT_BUILD, eventType: CLIENT_AGENT_BUILD,
timestamp: now().toTime().toUnix(), timestamp: now().toTime().toUnix(),
@@ -36,9 +46,9 @@ proc sendAgentBuild*(ws: WebSocket, buildInformation: AgentBuildInformation) =
"modules": buildInformation.modules "modules": buildInformation.modules
} }
) )
ws.sendEvent(event) connection.ws.sendEvent(event, connection.sessionKey)
proc sendAgentTask*(ws: WebSocket, agentId: string, task: Task) = proc sendAgentTask*(connection: WsConnection, agentId: string, task: Task) =
let event = Event( let event = Event(
eventType: CLIENT_AGENT_TASK, eventType: CLIENT_AGENT_TASK,
timestamp: now().toTime().toUnix(), timestamp: now().toTime().toUnix(),
@@ -47,4 +57,4 @@ proc sendAgentTask*(ws: WebSocket, agentId: string, task: Task) =
"task": task "task": task
} }
) )
ws.sendEvent(event) connection.ws.sendEvent(event, connection.sessionKey)

View File

@@ -3,26 +3,66 @@ when defined(server):
when defined(client): when defined(client):
import whisky import whisky
import times, json import times, json, zippy
import ./[types, utils, serialize] import ./[types, utils, serialize, crypto]
proc sendEvent*(ws: WebSocket, event: Event) = proc sendEvent*(ws: WebSocket, event: Event, key: Key = default(Key)) =
var packer = Packer.init() var packer = Packer.init()
let iv = generateBytes(Iv)
var
data = string.toBytes($event.data)
packer.add(cast[uint8](event.eventType)) packer.add(cast[uint8](event.eventType))
packer.add(cast[uint32](event.timestamp)) packer.add(cast[uint32](event.timestamp))
packer.addDataWithLengthPrefix(string.toBytes($event.data))
let data = packer.pack() if event.eventType != CLIENT_KEY_EXCHANGE and event.eventType != CLIENT_HEARTBEAT:
# Compress data
let compressed = compress(data, BestCompression, dfGzip)
# Encrypt data
let (encData, gmac) = encrypt(key, iv, compressed)
packer.addData(iv) # 12 bytes IV
packer.addData(gmac) # 16 bytes Authentication Tag
packer.addDataWithLengthPrefix(encData)
else:
packer.addDataWithLengthPrefix(data)
let body = packer.pack()
ws.send(Bytes.toString(data), BinaryMessage) ws.send(Bytes.toString(body), BinaryMessage)
proc recvEvent*(message: Message, key: Key = default(Key)): Event =
proc recvEvent*(message: Message): Event =
var unpacker = Unpacker.init(message.data) var unpacker = Unpacker.init(message.data)
let
eventType = cast[EventType](unpacker.getUint8())
timestamp = cast[int64](unpacker.getUint32())
var data: string
if eventType != CLIENT_KEY_EXCHANGE and eventType != CLIENT_HEARTBEAT:
let
iv = unpacker.getByteArray(Iv)
gmac = unpacker.getByteArray(AuthenticationTag)
encData = string.toBytes(unpacker.getDataWithLengthPrefix())
# Decrypt data
let (decData, tag) = decrypt(key, iv, encData)
if tag != gmac:
raise newException(CatchableError, "Invalid authentication tag.")
# Decompress data
data = Bytes.toString(uncompress(decData, dfGzip))
else:
data = unpacker.getDataWithLengthPrefix()
return Event( return Event(
eventType: cast[EventType](unpacker.getUint8()), eventType: eventType,
timestamp: cast[int64](unpacker.getUint32()), timestamp: timestamp,
data: parseJson(unpacker.getDataWithLengthPrefix()) data: parseJson(data)
) )
proc sendHeartbeat*(ws: WebSocket) = proc sendHeartbeat*(ws: WebSocket) =

View File

@@ -93,7 +93,6 @@ proc getUint64*(unpacker: Unpacker): uint64 =
unpacker.position += 8 unpacker.position += 8
proc getBytes*(unpacker: Unpacker, length: int): seq[byte] = proc getBytes*(unpacker: Unpacker, length: int): seq[byte] =
if length <= 0: if length <= 0:
return @[] return @[]

View File

@@ -1,8 +1,10 @@
import tables import tables
import times import times
import parsetoml, json import parsetoml, json
import mummy
import system import system
import mummy
when defined(client):
import whisky
# Custom Binary Task structure # Custom Binary Task structure
const const
@@ -242,10 +244,11 @@ type
type type
EventType* = enum EventType* = enum
CLIENT_HEARTBEAT = 0'u8 # Basic checkin CLIENT_HEARTBEAT = 0'u8 # Basic checkin
CLIENT_KEY_EXCHANGE = 200'u8
# Sent by client # Sent by client
CLIENT_AGENT_BUILD = 1'u8 # Generate an agent binary for a specific listener CLIENT_AGENT_BUILD = 1'u8 # Generate an agent binary for a specific listener
CLIENT_AGENT_TASK = 2'u8 # Instruct TS to send queue a command for a specific agent CLIENT_AGENT_TASK = 2'u8 # Instruct TS to send queue a command for a specific agent
CLIENT_LISTENER_START = 3'u8 # Start a listener on the TS CLIENT_LISTENER_START = 3'u8 # Start a listener on the TS
CLIENT_LISTENER_STOP = 4'u8 # Stop a listener CLIENT_LISTENER_STOP = 4'u8 # Stop a listener
@@ -255,8 +258,8 @@ type
CLIENT_AGENT_ADD = 102'u8 # Add agent to sessions table CLIENT_AGENT_ADD = 102'u8 # Add agent to sessions table
CLIENT_AGENT_CHECKIN = 103'u8 # Update agent checkin CLIENT_AGENT_CHECKIN = 103'u8 # Update agent checkin
CLIENT_AGENT_PAYLOAD = 104'u8 # Return agent payload binary CLIENT_AGENT_PAYLOAD = 104'u8 # Return agent payload binary
CLIENT_CONSOLE_ITEM = 105'u8 # Add entry to a agent's console CLIENT_CONSOLE_ITEM = 105'u8 # Add entry to a agent's console
CLIENT_EVENTLOG_ITEM = 106'u8 # Add entry to the eventlog CLIENT_EVENTLOG_ITEM = 106'u8 # Add entry to the eventlog
Event* = object Event* = object
eventType*: EventType eventType*: EventType
@@ -271,8 +274,12 @@ type
Profile* = TomlValueRef Profile* = TomlValueRef
UIClient* = ref object WsConnection* = ref object
ws*: WebSocket when defined(server):
ws*: mummy.WebSocket
when defined(client):
ws*: whisky.WebSocket
sessionKey*: Key
Conquest* = ref object Conquest* = ref object
dbPath*: string dbPath*: string
@@ -281,7 +288,7 @@ type
agents*: Table[string, Agent] agents*: Table[string, Agent]
keyPair*: KeyPair keyPair*: KeyPair
profile*: Profile profile*: Profile
client*: UIClient client*: WsConnection
AgentCtx* = ref object AgentCtx* = ref object
agentId*: string agentId*: string

View File

@@ -5,7 +5,7 @@ import parsetoml
import ../api/routes import ../api/routes
import ../db/database import ../db/database
import ../core/logger import ../core/logger
import ../../common/[types, utils, profile] import ../../common/[types, profile]
import ../websocket import ../websocket
proc serve(listener: Listener) {.thread.} = proc serve(listener: Listener) {.thread.} =

View File

@@ -1,132 +0,0 @@
import prompt, terminal, argparse, parsetoml, times, json, math
import strutils, strformat, system, tables
import ./[agent, listener, builder]
import ../globals
import ../db/database
import ../core/logger
import ../../common/[types, crypto, utils, profile, event]
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.interactAgent = nil
cq.profile = profile
cq.keyPair = loadKeyPair(CONQUEST_ROOT & "/" & profile.getString("private-key-file"))
cq.dbPath = CONQUEST_ROOT & "/" & profile.getString("database-file")
cq.client = nil
return cq
#[
WebSocket
]#
proc upgradeHandler(request: Request) =
{.cast(gcsafe).}:
let ws = request.upgradeToWebSocket()
cq.client = UIClient(
ws: ws
)
proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) {.gcsafe.} =
{.cast(gcsafe).}:
case event:
of OpenEvent:
# New client connected to team server
# Send profile, sessions and listeners to the UI client
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")
of MessageEvent:
# Continuously send heartbeat messages
ws.sendHeartbeat()
let event = message.recvEvent()
case event.eventType:
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)
# Increased websocket message length in order to support dotnet assembly execution
let server = newServer(router, websocketHandler, maxMessageLen = 1024 * 1024 * 1024)
server.serve(Port(cq.profile.getInt("team-server.port")), "0.0.0.0")

View File

@@ -1,11 +1,11 @@
import terminal, parsetoml, json, math import terminal, parsetoml, json, math, base64
import strutils, strformat, system, tables import strutils, strformat, system, tables
import ./core/[listener, builder] import ./core/[listener, builder]
import ./globals import ./globals
import ./db/database import ./db/database
import ./core/logger import ./core/logger
import ../common/[types, crypto, profile, event] import ../common/[types, crypto, utils, profile, event]
import ./websocket import ./websocket
import mummy, mummy/routers import mummy, mummy/routers
@@ -25,7 +25,7 @@ proc init*(T: type Conquest, profile: Profile): Conquest =
cq.profile = profile cq.profile = profile
cq.keyPair = loadKeyPair(CONQUEST_ROOT & "/" & profile.getString("private-key-file")) cq.keyPair = loadKeyPair(CONQUEST_ROOT & "/" & profile.getString("private-key-file"))
cq.dbPath = CONQUEST_ROOT & "/" & profile.getString("database-file") cq.dbPath = CONQUEST_ROOT & "/" & profile.getString("database-file")
cq.client = nil cq.client = nil
return cq return cq
#[ #[
@@ -34,7 +34,7 @@ proc init*(T: type Conquest, profile: Profile): Conquest =
proc upgradeHandler(request: Request) = proc upgradeHandler(request: Request) =
{.cast(gcsafe).}: {.cast(gcsafe).}:
let ws = request.upgradeToWebSocket() let ws = request.upgradeToWebSocket()
cq.client = UIClient( cq.client = WsConnection(
ws: ws ws: ws
) )
@@ -43,21 +43,31 @@ proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) {.
case event: case event:
of OpenEvent: of OpenEvent:
# New client connected to team server # New client connected to team server
# Send profile, sessions and listeners to the UI client # Send the public key for the key exchange, all other information with be transmitted when the key exchange is completed
cq.client.sendProfile(cq.profile) cq.client.sendPublicKey(cq.keyPair.publicKey)
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")
of MessageEvent: of MessageEvent:
# Continuously send heartbeat messages # Continuously send heartbeat messages
ws.sendHeartbeat() ws.sendHeartbeat()
let event = message.recvEvent() let event = message.recvEvent(cq.client.sessionKey)
case event.eventType: case event.eventType:
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")
of CLIENT_AGENT_TASK: of CLIENT_AGENT_TASK:
let agentId = event.data["agentId"].getStr() let agentId = event.data["agentId"].getStr()
let task = event.data["task"].to(Task) let task = event.data["task"].to(Task)

View File

@@ -1,6 +1,5 @@
import times, json, base64, parsetoml import times, json, base64, parsetoml
import ../common/[types, event] import ../common/[types, utils, event]
export sendHeartbeat, recvEvent export sendHeartbeat, recvEvent
proc `%`*(agent: Agent): JsonNode = proc `%`*(agent: Agent): JsonNode =
@@ -29,7 +28,18 @@ proc `%`*(listener: Listener): JsonNode =
#[ #[
Server -> Client Server -> Client
]# ]#
proc sendProfile*(client: UIClient, profile: Profile) = proc sendPublicKey*(client: WsConnection, publicKey: Key) =
let event = Event(
eventType: CLIENT_KEY_EXCHANGE,
timestamp: now().toTime().toUnix(),
data: %*{
"publicKey": encode(Bytes.toString(publicKey))
}
)
if client != nil:
client.ws.sendEvent(event, client.sessionKey)
proc sendProfile*(client: WsConnection, profile: Profile) =
let event = Event( let event = Event(
eventType: CLIENT_PROFILE, eventType: CLIENT_PROFILE,
timestamp: now().toTime().toUnix(), timestamp: now().toTime().toUnix(),
@@ -38,9 +48,9 @@ proc sendProfile*(client: UIClient, profile: Profile) =
} }
) )
if client != nil: if client != nil:
client.ws.sendEvent(event) client.ws.sendEvent(event, client.sessionKey)
proc sendEventlogItem*(client: UIClient, logType: LogType, message: string) = proc sendEventlogItem*(client: WsConnection, logType: LogType, message: string) =
let event = Event( let event = Event(
eventType: CLIENT_EVENTLOG_ITEM, eventType: CLIENT_EVENTLOG_ITEM,
timestamp: now().toTime().toUnix(), timestamp: now().toTime().toUnix(),
@@ -50,27 +60,27 @@ proc sendEventlogItem*(client: UIClient, logType: LogType, message: string) =
} }
) )
if client != nil: if client != nil:
client.ws.sendEvent(event) client.ws.sendEvent(event, client.sessionKey)
proc sendAgent*(client: UIClient, agent: Agent) = proc sendAgent*(client: WsConnection, agent: Agent) =
let event = Event( let event = Event(
eventType: CLIENT_AGENT_ADD, eventType: CLIENT_AGENT_ADD,
timestamp: now().toTime().toUnix(), timestamp: now().toTime().toUnix(),
data: %agent data: %agent
) )
if client != nil: if client != nil:
client.ws.sendEvent(event) client.ws.sendEvent(event, client.sessionKey)
proc sendListener*(client: UIClient, listener: Listener) = proc sendListener*(client: WsConnection, listener: Listener) =
let event = Event( let event = Event(
eventType: CLIENT_LISTENER_ADD, eventType: CLIENT_LISTENER_ADD,
timestamp: now().toTime().toUnix(), timestamp: now().toTime().toUnix(),
data: %listener data: %listener
) )
if client != nil: if client != nil:
client.ws.sendEvent(event) client.ws.sendEvent(event, client.sessionKey)
proc sendAgentCheckin*(client: UIClient, agentId: string) = proc sendAgentCheckin*(client: WsConnection, agentId: string) =
let event = Event( let event = Event(
eventType: CLIENT_AGENT_CHECKIN, eventType: CLIENT_AGENT_CHECKIN,
timestamp: now().toTime().toUnix(), timestamp: now().toTime().toUnix(),
@@ -79,9 +89,9 @@ proc sendAgentCheckin*(client: UIClient, agentId: string) =
} }
) )
if client != nil: if client != nil:
client.ws.sendEvent(event) client.ws.sendEvent(event, client.sessionKey)
proc sendAgentPayload*(client: UIClient, bytes: seq[byte]) = proc sendAgentPayload*(client: WsConnection, bytes: seq[byte]) =
let event = Event( let event = Event(
eventType: CLIENT_AGENT_PAYLOAD, eventType: CLIENT_AGENT_PAYLOAD,
timestamp: now().toTime().toUnix(), timestamp: now().toTime().toUnix(),
@@ -90,9 +100,9 @@ proc sendAgentPayload*(client: UIClient, bytes: seq[byte]) =
} }
) )
if client != nil: if client != nil:
client.ws.sendEvent(event) client.ws.sendEvent(event, client.sessionKey)
proc sendConsoleItem*(client: UIClient, agentId: string, logType: LogType, message: string) = proc sendConsoleItem*(client: WsConnection, agentId: string, logType: LogType, message: string) =
let event = Event( let event = Event(
eventType: CLIENT_CONSOLE_ITEM, eventType: CLIENT_CONSOLE_ITEM,
timestamp: now().toTime().toUnix(), timestamp: now().toTime().toUnix(),
@@ -103,4 +113,4 @@ proc sendConsoleItem*(client: UIClient, agentId: string, logType: LogType, messa
} }
) )
if client != nil: if client != nil:
client.ws.sendEvent(event) client.ws.sendEvent(event, client.sessionKey)