Implemented websocket (client <-> server) traffic encryption & compression.
This commit is contained in:
@@ -2,7 +2,7 @@ import whisky
|
||||
import tables, strutils, strformat, json, parsetoml, base64, os # native_dialogs
|
||||
import ./utils/[appImGui, globals]
|
||||
import ./views/[dockspace, sessions, listeners, eventlog, console]
|
||||
import ../common/[types, utils]
|
||||
import ../common/[types, utils, crypto]
|
||||
import ./websocket
|
||||
|
||||
import sugar
|
||||
@@ -39,9 +39,15 @@ proc main(ip: string = "localhost", port: int = 37573) =
|
||||
|
||||
let io = igGetIO()
|
||||
|
||||
# Create key pair
|
||||
let clientKeyPair = generateKeyPair()
|
||||
|
||||
# Initiate WebSocket connection
|
||||
let ws = newWebSocket(fmt"ws://{ip}:{$port}")
|
||||
defer: ws.close()
|
||||
var connection = WsConnection(
|
||||
ws: newWebSocket(fmt"ws://{ip}:{$port}"),
|
||||
sessionKey: default(Key)
|
||||
)
|
||||
defer: connection.ws.close()
|
||||
|
||||
# main loop
|
||||
while not app.handle.windowShouldClose:
|
||||
@@ -59,13 +65,17 @@ proc main(ip: string = "localhost", port: int = 37573) =
|
||||
WebSocket communication with the team server
|
||||
]#
|
||||
# Continuously send heartbeat messages
|
||||
ws.sendHeartbeat()
|
||||
connection.ws.sendHeartbeat()
|
||||
|
||||
# Receive and parse websocket response message
|
||||
let event = recvEvent(ws.receiveMessage().get())
|
||||
let event = recvEvent(connection.ws.receiveMessage().get(), connection.sessionKey)
|
||||
case event.eventType:
|
||||
of CLIENT_KEY_EXCHANGE:
|
||||
connection.sessionKey = deriveSessionKey(clientKeyPair, decode(event.data["publicKey"].getStr()).toKey())
|
||||
connection.sendPublicKey(clientKeyPair.publicKey)
|
||||
|
||||
of CLIENT_PROFILE:
|
||||
profile = parsetoml.parseString(event.data["profile"].getStr())
|
||||
profile = parsetoml.parseString(event.data["profile"].getStr())
|
||||
|
||||
of CLIENT_LISTENER_ADD:
|
||||
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)
|
||||
else:
|
||||
igSetNextWindowDockID(dockBottom, ImGuiCond_FirstUseEver.int32)
|
||||
consoles[agent.agentId].draw(ws)
|
||||
consoles[agent.agentId].draw(connection)
|
||||
consoles[agent.agentId].showConsole = false
|
||||
|
||||
of CLIENT_AGENT_CHECKIN:
|
||||
@@ -127,7 +137,7 @@ proc main(ip: string = "localhost", port: int = 37573) =
|
||||
|
||||
# Draw/update UI components/views
|
||||
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)
|
||||
|
||||
# Show console windows
|
||||
@@ -136,7 +146,7 @@ proc main(ip: string = "localhost", port: int = 37573) =
|
||||
if console.showConsole:
|
||||
# Ensure that new console windows are docked to the bottom panel by default
|
||||
igSetNextWindowDockID(dockBottom, ImGuiCond_FirstUseEver.int32)
|
||||
console.draw(ws)
|
||||
console.draw(connection)
|
||||
newConsoleTable[agentId] = console
|
||||
|
||||
# Update the consoles table with only those sessions that have not been closed yet
|
||||
|
||||
@@ -170,7 +170,7 @@ proc handleHelp(component: ConsoleComponent, parsed: seq[string]) =
|
||||
# Command was not found
|
||||
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
|
||||
let parsedArgs = parseInput(input)
|
||||
@@ -186,7 +186,7 @@ proc handleAgentCommand*(component: ConsoleComponent, ws: WebSocket, input: stri
|
||||
command = getCommandByName(parsedArgs[0])
|
||||
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)})")
|
||||
|
||||
except CatchableError:
|
||||
@@ -219,7 +219,7 @@ proc print(item: ConsoleItem) =
|
||||
igSameLine(0.0f, 0.0f)
|
||||
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)
|
||||
defer: igEnd()
|
||||
|
||||
@@ -340,7 +340,7 @@ proc draw*(component: ConsoleComponent, ws: WebSocket) =
|
||||
component.addItem(LOG_COMMAND, command)
|
||||
|
||||
# Send command to team server
|
||||
component.handleAgentCommand(ws, command)
|
||||
component.handleAgentCommand(connection, command)
|
||||
|
||||
# Add command to console history
|
||||
component.history.add(command)
|
||||
|
||||
@@ -22,7 +22,7 @@ proc ListenersTable*(title: string): ListenersTableComponent =
|
||||
result.startListenerModal = ListenerModal()
|
||||
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)
|
||||
defer: igEnd()
|
||||
|
||||
@@ -41,11 +41,11 @@ proc draw*(component: ListenersTableComponent, showComponent: ptr bool, ws: WebS
|
||||
|
||||
let listener = component.startListenerModal.draw()
|
||||
if listener != nil:
|
||||
ws.sendStartListener(listener)
|
||||
connection.sendStartListener(listener)
|
||||
|
||||
let buildInformation = component.generatePayloadModal.draw(component.listeners)
|
||||
if buildInformation != nil:
|
||||
ws.sendAgentBuild(buildInformation)
|
||||
connection.sendAgentBuild(buildInformation)
|
||||
|
||||
#[
|
||||
Listener table
|
||||
@@ -106,7 +106,7 @@ proc draw*(component: ListenersTableComponent, showComponent: ptr bool, ws: WebS
|
||||
if not ImGuiSelectionBasicStorage_Contains(component.selection, cast[ImGuiID](i)):
|
||||
newListeners.add(listener)
|
||||
else:
|
||||
ws.sendStopListener(listener.listenerId)
|
||||
connection.sendStopListener(listener.listenerId)
|
||||
|
||||
component.listeners = newListeners
|
||||
ImGuiSelectionBasicStorage_Clear(component.selection)
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
import whisky
|
||||
import times, tables, json
|
||||
import times, tables, json, base64
|
||||
import ../common/[types, utils, serialize, event]
|
||||
export sendHeartbeat, recvEvent
|
||||
|
||||
#[
|
||||
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(
|
||||
eventType: CLIENT_LISTENER_START,
|
||||
timestamp: now().toTime().toUnix(),
|
||||
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(
|
||||
eventType: CLIENT_LISTENER_STOP,
|
||||
timestamp: now().toTime().toUnix(),
|
||||
@@ -22,9 +32,9 @@ proc sendStopListener*(ws: WebSocket, listenerId: string) =
|
||||
"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(
|
||||
eventType: CLIENT_AGENT_BUILD,
|
||||
timestamp: now().toTime().toUnix(),
|
||||
@@ -36,9 +46,9 @@ proc sendAgentBuild*(ws: WebSocket, buildInformation: AgentBuildInformation) =
|
||||
"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(
|
||||
eventType: CLIENT_AGENT_TASK,
|
||||
timestamp: now().toTime().toUnix(),
|
||||
@@ -47,4 +57,4 @@ proc sendAgentTask*(ws: WebSocket, agentId: string, task: Task) =
|
||||
"task": task
|
||||
}
|
||||
)
|
||||
ws.sendEvent(event)
|
||||
connection.ws.sendEvent(event, connection.sessionKey)
|
||||
|
||||
@@ -3,26 +3,66 @@ when defined(server):
|
||||
when defined(client):
|
||||
import whisky
|
||||
|
||||
import times, json
|
||||
import ./[types, utils, serialize]
|
||||
import times, json, zippy
|
||||
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()
|
||||
|
||||
let iv = generateBytes(Iv)
|
||||
|
||||
var
|
||||
data = string.toBytes($event.data)
|
||||
|
||||
packer.add(cast[uint8](event.eventType))
|
||||
packer.add(cast[uint32](event.timestamp))
|
||||
packer.addDataWithLengthPrefix(string.toBytes($event.data))
|
||||
let data = packer.pack()
|
||||
|
||||
ws.send(Bytes.toString(data), BinaryMessage)
|
||||
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(body), BinaryMessage)
|
||||
|
||||
proc recvEvent*(message: Message, key: Key = default(Key)): Event =
|
||||
|
||||
proc recvEvent*(message: Message): Event =
|
||||
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(
|
||||
eventType: cast[EventType](unpacker.getUint8()),
|
||||
timestamp: cast[int64](unpacker.getUint32()),
|
||||
data: parseJson(unpacker.getDataWithLengthPrefix())
|
||||
eventType: eventType,
|
||||
timestamp: timestamp,
|
||||
data: parseJson(data)
|
||||
)
|
||||
|
||||
proc sendHeartbeat*(ws: WebSocket) =
|
||||
|
||||
@@ -93,7 +93,6 @@ proc getUint64*(unpacker: Unpacker): uint64 =
|
||||
unpacker.position += 8
|
||||
|
||||
proc getBytes*(unpacker: Unpacker, length: int): seq[byte] =
|
||||
|
||||
if length <= 0:
|
||||
return @[]
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import tables
|
||||
import times
|
||||
import parsetoml, json
|
||||
import mummy
|
||||
import system
|
||||
import mummy
|
||||
when defined(client):
|
||||
import whisky
|
||||
|
||||
# Custom Binary Task structure
|
||||
const
|
||||
@@ -242,10 +244,11 @@ type
|
||||
type
|
||||
EventType* = enum
|
||||
CLIENT_HEARTBEAT = 0'u8 # Basic checkin
|
||||
CLIENT_KEY_EXCHANGE = 200'u8
|
||||
|
||||
# Sent by client
|
||||
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_STOP = 4'u8 # Stop a listener
|
||||
|
||||
@@ -255,8 +258,8 @@ type
|
||||
CLIENT_AGENT_ADD = 102'u8 # Add agent to sessions table
|
||||
CLIENT_AGENT_CHECKIN = 103'u8 # Update agent checkin
|
||||
CLIENT_AGENT_PAYLOAD = 104'u8 # Return agent payload binary
|
||||
CLIENT_CONSOLE_ITEM = 105'u8 # Add entry to a agent's console
|
||||
CLIENT_EVENTLOG_ITEM = 106'u8 # Add entry to the eventlog
|
||||
CLIENT_CONSOLE_ITEM = 105'u8 # Add entry to a agent's console
|
||||
CLIENT_EVENTLOG_ITEM = 106'u8 # Add entry to the eventlog
|
||||
|
||||
Event* = object
|
||||
eventType*: EventType
|
||||
@@ -271,8 +274,12 @@ type
|
||||
|
||||
Profile* = TomlValueRef
|
||||
|
||||
UIClient* = ref object
|
||||
ws*: WebSocket
|
||||
WsConnection* = ref object
|
||||
when defined(server):
|
||||
ws*: mummy.WebSocket
|
||||
when defined(client):
|
||||
ws*: whisky.WebSocket
|
||||
sessionKey*: Key
|
||||
|
||||
Conquest* = ref object
|
||||
dbPath*: string
|
||||
@@ -281,7 +288,7 @@ type
|
||||
agents*: Table[string, Agent]
|
||||
keyPair*: KeyPair
|
||||
profile*: Profile
|
||||
client*: UIClient
|
||||
client*: WsConnection
|
||||
|
||||
AgentCtx* = ref object
|
||||
agentId*: string
|
||||
|
||||
@@ -5,7 +5,7 @@ import parsetoml
|
||||
import ../api/routes
|
||||
import ../db/database
|
||||
import ../core/logger
|
||||
import ../../common/[types, utils, profile]
|
||||
import ../../common/[types, profile]
|
||||
import ../websocket
|
||||
|
||||
proc serve(listener: Listener) {.thread.} =
|
||||
|
||||
@@ -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")
|
||||
@@ -1,11 +1,11 @@
|
||||
import terminal, parsetoml, json, math
|
||||
import terminal, parsetoml, json, math, base64
|
||||
import strutils, strformat, system, tables
|
||||
|
||||
import ./core/[listener, builder]
|
||||
import ./globals
|
||||
import ./db/database
|
||||
import ./core/logger
|
||||
import ../common/[types, crypto, profile, event]
|
||||
import ../common/[types, crypto, utils, profile, event]
|
||||
import ./websocket
|
||||
import mummy, mummy/routers
|
||||
|
||||
@@ -34,7 +34,7 @@ proc init*(T: type Conquest, profile: Profile): Conquest =
|
||||
proc upgradeHandler(request: Request) =
|
||||
{.cast(gcsafe).}:
|
||||
let ws = request.upgradeToWebSocket()
|
||||
cq.client = UIClient(
|
||||
cq.client = WsConnection(
|
||||
ws: ws
|
||||
)
|
||||
|
||||
@@ -43,21 +43,31 @@ proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) {.
|
||||
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")
|
||||
# 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)
|
||||
|
||||
of MessageEvent:
|
||||
# Continuously send heartbeat messages
|
||||
ws.sendHeartbeat()
|
||||
|
||||
let event = message.recvEvent()
|
||||
let event = message.recvEvent(cq.client.sessionKey)
|
||||
|
||||
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:
|
||||
let agentId = event.data["agentId"].getStr()
|
||||
let task = event.data["task"].to(Task)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import times, json, base64, parsetoml
|
||||
import ../common/[types, event]
|
||||
|
||||
import ../common/[types, utils, event]
|
||||
export sendHeartbeat, recvEvent
|
||||
|
||||
proc `%`*(agent: Agent): JsonNode =
|
||||
@@ -29,7 +28,18 @@ proc `%`*(listener: Listener): JsonNode =
|
||||
#[
|
||||
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(
|
||||
eventType: CLIENT_PROFILE,
|
||||
timestamp: now().toTime().toUnix(),
|
||||
@@ -38,9 +48,9 @@ proc sendProfile*(client: UIClient, profile: Profile) =
|
||||
}
|
||||
)
|
||||
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(
|
||||
eventType: CLIENT_EVENTLOG_ITEM,
|
||||
timestamp: now().toTime().toUnix(),
|
||||
@@ -50,27 +60,27 @@ proc sendEventlogItem*(client: UIClient, logType: LogType, message: string) =
|
||||
}
|
||||
)
|
||||
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(
|
||||
eventType: CLIENT_AGENT_ADD,
|
||||
timestamp: now().toTime().toUnix(),
|
||||
data: %agent
|
||||
)
|
||||
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(
|
||||
eventType: CLIENT_LISTENER_ADD,
|
||||
timestamp: now().toTime().toUnix(),
|
||||
data: %listener
|
||||
)
|
||||
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(
|
||||
eventType: CLIENT_AGENT_CHECKIN,
|
||||
timestamp: now().toTime().toUnix(),
|
||||
@@ -79,9 +89,9 @@ proc sendAgentCheckin*(client: UIClient, agentId: string) =
|
||||
}
|
||||
)
|
||||
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(
|
||||
eventType: CLIENT_AGENT_PAYLOAD,
|
||||
timestamp: now().toTime().toUnix(),
|
||||
@@ -90,9 +100,9 @@ proc sendAgentPayload*(client: UIClient, bytes: seq[byte]) =
|
||||
}
|
||||
)
|
||||
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(
|
||||
eventType: CLIENT_CONSOLE_ITEM,
|
||||
timestamp: now().toTime().toUnix(),
|
||||
@@ -103,4 +113,4 @@ proc sendConsoleItem*(client: UIClient, agentId: string, logType: LogType, messa
|
||||
}
|
||||
)
|
||||
if client != nil:
|
||||
client.ws.sendEvent(event)
|
||||
client.ws.sendEvent(event, client.sessionKey)
|
||||
|
||||
Reference in New Issue
Block a user