Started work on websocket communication: Parsing/Serialization of WebSocket packets.
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
author = "Jakob Friedl"
|
author = "Jakob Friedl"
|
||||||
description = "Command & control framework written in Nim"
|
description = "Conquest command & control/post-exploitation framework"
|
||||||
license = "BSD-3-Clause"
|
license = "BSD-3-Clause"
|
||||||
srcDir = "src"
|
srcDir = "src"
|
||||||
|
|
||||||
@@ -29,4 +29,5 @@ requires "winim >= 3.9.4"
|
|||||||
requires "ptr_math >= 0.3.0"
|
requires "ptr_math >= 0.3.0"
|
||||||
requires "imguin >= 1.92.2.1"
|
requires "imguin >= 1.92.2.1"
|
||||||
requires "zippy >= 0.10.16"
|
requires "zippy >= 0.10.16"
|
||||||
requires "mummy >= 0.4.6"
|
requires "mummy >= 0.4.6"
|
||||||
|
requires "whisky >= 0.1.3"
|
||||||
@@ -1,41 +1,41 @@
|
|||||||
[Window][Sessions [Table View]]
|
[Window][Sessions [Table View]]
|
||||||
Pos=10,43
|
Pos=10,43
|
||||||
Size=1533,946
|
Size=2016,548
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000003,0
|
DockId=0x00000003,0
|
||||||
|
|
||||||
[Window][Listeners]
|
[Window][Listeners]
|
||||||
Pos=10,43
|
Pos=10,593
|
||||||
Size=1533,946
|
Size=2528,804
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000003,1
|
DockId=0x00000006,0
|
||||||
|
|
||||||
[Window][Eventlog]
|
[Window][Eventlog]
|
||||||
Pos=1545,43
|
Pos=2028,43
|
||||||
Size=353,946
|
Size=510,548
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000004,0
|
DockId=0x00000004,0
|
||||||
|
|
||||||
[Window][Dear ImGui Demo]
|
[Window][Dear ImGui Demo]
|
||||||
Pos=1545,43
|
Pos=2028,43
|
||||||
Size=353,946
|
Size=510,548
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000004,1
|
DockId=0x00000004,1
|
||||||
|
|
||||||
[Window][Dockspace]
|
[Window][Dockspace]
|
||||||
Pos=0,0
|
Pos=0,0
|
||||||
Size=1908,999
|
Size=2548,1407
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][[FACEDEAD] bob@LAPTOP-02]
|
[Window][[FACEDEAD] bob@LAPTOP-02]
|
||||||
Pos=956,326
|
Pos=10,593
|
||||||
Size=942,663
|
Size=2528,804
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000005,0
|
DockId=0x00000006,1
|
||||||
|
|
||||||
[Window][[C9D8E7F6] charlie@SERVER-03]
|
[Window][[C9D8E7F6] charlie@SERVER-03]
|
||||||
Pos=10,434
|
Pos=10,593
|
||||||
Size=1888,555
|
Size=2528,804
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,1
|
DockId=0x00000006,1
|
||||||
|
|
||||||
@@ -45,16 +45,16 @@ Size=400,400
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][[G1H2I3J5] diana@WORKSTATION-04]
|
[Window][[G1H2I3J5] diana@WORKSTATION-04]
|
||||||
Pos=10,434
|
Pos=10,593
|
||||||
Size=1888,555
|
Size=2528,804
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000006,0
|
DockId=0x00000006,1
|
||||||
|
|
||||||
[Window][[DEADBEEF] alice@DESKTOP-01]
|
[Window][[DEADBEEF] alice@DESKTOP-01]
|
||||||
Pos=10,402
|
Pos=10,716
|
||||||
Size=1888,587
|
Size=2848,969
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000005,1
|
DockId=0x00000006,2
|
||||||
|
|
||||||
[Window][Example: Console]
|
[Window][Example: Console]
|
||||||
Pos=10,572
|
Pos=10,572
|
||||||
@@ -110,8 +110,8 @@ Size=76,76
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Start Listener]
|
[Window][Start Listener]
|
||||||
Pos=704,387
|
Pos=955,591
|
||||||
Size=500,225
|
Size=637,225
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Table][0x32886A44,8]
|
[Table][0x32886A44,8]
|
||||||
@@ -136,9 +136,9 @@ Column 3 Weight=0.9746
|
|||||||
|
|
||||||
[Docking][Data]
|
[Docking][Data]
|
||||||
DockNode ID=0x00000009 Pos=100,200 Size=754,103 Selected=0x64D005CF
|
DockNode ID=0x00000009 Pos=100,200 Size=754,103 Selected=0x64D005CF
|
||||||
DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=1888,946 Split=Y
|
DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=2528,1354 Split=Y
|
||||||
DockNode ID=0x00000005 Parent=0x85940918 SizeRef=1888,389 Split=X
|
DockNode ID=0x00000005 Parent=0x85940918 SizeRef=1888,548 Split=X
|
||||||
DockNode ID=0x00000003 Parent=0x00000005 SizeRef=1533,159 CentralNode=1 Selected=0x61E02D75
|
DockNode ID=0x00000003 Parent=0x00000005 SizeRef=1376,159 CentralNode=1 Selected=0x61E02D75
|
||||||
DockNode ID=0x00000004 Parent=0x00000005 SizeRef=353,159 Selected=0x5E5F7166
|
DockNode ID=0x00000004 Parent=0x00000005 SizeRef=510,159 Selected=0x5E5F7166
|
||||||
DockNode ID=0x00000006 Parent=0x85940918 SizeRef=1888,555 Selected=0x65D642C0
|
DockNode ID=0x00000006 Parent=0x85940918 SizeRef=1888,804 Selected=0x6BE22050
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import tables
|
import whisky
|
||||||
|
import tables, strutils
|
||||||
import ./utils/appImGui
|
import ./utils/appImGui
|
||||||
import ./views/[dockspace, sessions, listeners, eventlog, console]
|
import ./views/[dockspace, sessions, listeners, eventlog, console]
|
||||||
|
import ../common/[types, utils]
|
||||||
|
import ./websocket
|
||||||
|
|
||||||
proc main() =
|
proc main() =
|
||||||
var app = createApp(1024, 800, imnodes = true, title = "Conquest", docking = true)
|
var app = createApp(1024, 800, imnodes = true, title = "Conquest", docking = true)
|
||||||
@@ -35,6 +38,10 @@ proc main() =
|
|||||||
|
|
||||||
let io = igGetIO()
|
let io = igGetIO()
|
||||||
|
|
||||||
|
# Initiate WebSocket connection
|
||||||
|
let ws = newWebSocket("ws://localhost:12345")
|
||||||
|
defer: ws.close()
|
||||||
|
|
||||||
# main loop
|
# main loop
|
||||||
while not app.handle.windowShouldClose:
|
while not app.handle.windowShouldClose:
|
||||||
pollEvents()
|
pollEvents()
|
||||||
@@ -44,10 +51,24 @@ proc main() =
|
|||||||
continue
|
continue
|
||||||
newFrame()
|
newFrame()
|
||||||
|
|
||||||
|
#[
|
||||||
|
WebSocket communication with the team server
|
||||||
|
]#
|
||||||
|
# Continuously send heartbeat messages
|
||||||
|
ws.sendHeartbeat()
|
||||||
|
|
||||||
|
# Receive and parse websocket response message
|
||||||
|
let message = ws.receiveMessage().get()
|
||||||
|
case message.getMessageType()
|
||||||
|
of CLIENT_EVENT_LOG:
|
||||||
|
message.receiveEventlogItem(addr eventlog)
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
|
||||||
# Draw/update UI components/views
|
# Draw/update UI components/views
|
||||||
dockspace.draw(addr showConquest, views, addr dockTop, addr dockBottom, addr dockTopLeft, addr dockTopRight)
|
dockspace.draw(addr showConquest, views, addr dockTop, addr dockBottom, addr dockTopLeft, addr dockTopRight)
|
||||||
if showSessionsTable: sessionsTable.draw(addr showSessionsTable)
|
if showSessionsTable: sessionsTable.draw(addr showSessionsTable)
|
||||||
if showListeners: listenersTable.draw(addr showListeners)
|
if showListeners: listenersTable.draw(addr showListeners, ws)
|
||||||
if showEventlog: eventlog.draw(addr showEventlog)
|
if showEventlog: eventlog.draw(addr showEventlog)
|
||||||
|
|
||||||
# Show console windows
|
# Show console windows
|
||||||
@@ -56,7 +77,7 @@ proc main() =
|
|||||||
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()
|
console.draw(ws)
|
||||||
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
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import whisky
|
||||||
import strformat, strutils, times
|
import strformat, strutils, times
|
||||||
import imguin/[cimgui, glfw_opengl, simple]
|
import imguin/[cimgui, glfw_opengl, simple]
|
||||||
import ../utils/[appImGui, colors]
|
import ../utils/[appImGui, colors]
|
||||||
@@ -113,11 +114,11 @@ proc callback(data: ptr ImGuiInputTextCallbackData): cint {.cdecl.} =
|
|||||||
#[
|
#[
|
||||||
API to add new console item
|
API to add new console item
|
||||||
]#
|
]#
|
||||||
proc addItem*(component: ConsoleComponent, itemType: LogType, data: string) =
|
proc addItem*(component: ConsoleComponent, itemType: LogType, data: string, timestamp: int64 = now().toTime().toUnix()) =
|
||||||
|
|
||||||
for line in data.split("\n"):
|
for line in data.split("\n"):
|
||||||
component.console.items.add(ConsoleItem(
|
component.console.items.add(ConsoleItem(
|
||||||
timestamp: if itemType == LOG_OUTPUT: 0 else: now().toTime().toUnix(),
|
timestamp: if itemType == LOG_OUTPUT: 0 else: timestamp,
|
||||||
itemType: itemType,
|
itemType: itemType,
|
||||||
text: line
|
text: line
|
||||||
))
|
))
|
||||||
@@ -148,7 +149,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) =
|
proc draw*(component: ConsoleComponent, ws: WebSocket) =
|
||||||
igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0)
|
igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0)
|
||||||
defer: igEnd()
|
defer: igEnd()
|
||||||
|
|
||||||
@@ -251,22 +252,25 @@ proc draw*(component: ConsoleComponent) =
|
|||||||
let inputFlags = ImGuiInputTextFlags_EnterReturnsTrue.int32 or ImGuiInputTextFlags_EscapeClearsAll.int32 or ImGuiInputTextFlags_CallbackHistory.int32 or ImGuiInputTextFlags_CallbackCompletion.int32
|
let inputFlags = ImGuiInputTextFlags_EnterReturnsTrue.int32 or ImGuiInputTextFlags_EscapeClearsAll.int32 or ImGuiInputTextFlags_CallbackHistory.int32 or ImGuiInputTextFlags_CallbackCompletion.int32
|
||||||
if igInputText("##Input", addr component.inputBuffer[0], MAX_INPUT_LENGTH, inputFlags, callback, cast[pointer](component)):
|
if igInputText("##Input", addr component.inputBuffer[0], MAX_INPUT_LENGTH, inputFlags, callback, cast[pointer](component)):
|
||||||
|
|
||||||
let command = $(addr component.inputBuffer[0]).cstring
|
let command = ($(addr component.inputBuffer[0])).strip()
|
||||||
component.addItem(LOG_COMMAND, command)
|
if not command.isEmptyOrWhitespace():
|
||||||
|
|
||||||
|
component.addItem(LOG_COMMAND, command)
|
||||||
|
|
||||||
# For testing
|
# For testing
|
||||||
component.addItem(LOG_ERROR, "error message")
|
# component.addItem(LOG_ERROR, "error message")
|
||||||
component.addItem(LOG_SUCCESS, "success message")
|
# component.addItem(LOG_SUCCESS, "success message")
|
||||||
component.addItem(LOG_INFO, "info message")
|
# component.addItem(LOG_INFO, "info message")
|
||||||
component.addItem(LOG_WARNING, "warning message")
|
# component.addItem(LOG_WARNING, "warning message")
|
||||||
component.addItem(LOG_OUTPUT, "error message\nLong output\n\tindented output\nasdasd")
|
# component.addItem(LOG_OUTPUT, "error message\nLong output\n\tindented output\nasdasd")
|
||||||
|
|
||||||
# TODO: Handle command execution
|
# TODO: Handle command execution
|
||||||
# console.handleCommand(command)
|
# console.handleCommand(command)
|
||||||
|
ws.send("CMD:" & component.agent.agentId & ":" & command)
|
||||||
|
|
||||||
# Add command to console history
|
# Add command to console history
|
||||||
component.history.add(command)
|
component.history.add(command)
|
||||||
component.historyPosition = -1
|
component.historyPosition = -1
|
||||||
|
|
||||||
zeroMem(addr component.inputBuffer[0], MAX_INPUT_LENGTH)
|
zeroMem(addr component.inputBuffer[0], MAX_INPUT_LENGTH)
|
||||||
focusInput = true
|
focusInput = true
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import ../utils/[appImGui, colors]
|
|||||||
import ../../common/types
|
import ../../common/types
|
||||||
|
|
||||||
type
|
type
|
||||||
EventlogComponent = ref object of RootObj
|
EventlogComponent* = ref object of RootObj
|
||||||
title: string
|
title: string
|
||||||
log*: ConsoleItems
|
log*: ConsoleItems
|
||||||
textSelect: ptr TextSelect
|
textSelect: ptr TextSelect
|
||||||
@@ -41,11 +41,11 @@ proc Eventlog*(title: string): EventlogComponent =
|
|||||||
#[
|
#[
|
||||||
API to add new log entry
|
API to add new log entry
|
||||||
]#
|
]#
|
||||||
proc addItem*(component: EventlogComponent, itemType: LogType, data: string) =
|
proc addItem*(component: EventlogComponent, itemType: LogType, data: string, timestamp: int64 = now().toTime().toUnix()) =
|
||||||
|
|
||||||
for line in data.split("\n"):
|
for line in data.split("\n"):
|
||||||
component.log.items.add(ConsoleItem(
|
component.log.items.add(ConsoleItem(
|
||||||
timestamp: if itemType == LOG_OUTPUT: 0 else: now().toTime().toUnix(),
|
timestamp: if itemType == LOG_OUTPUT: 0 else: timestamp,
|
||||||
itemType: itemType,
|
itemType: itemType,
|
||||||
text: line
|
text: line
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import imguin/[cimgui, glfw_opengl, simple]
|
|||||||
import ../utils/appImGui
|
import ../utils/appImGui
|
||||||
import ../../common/[types, utils]
|
import ../../common/[types, utils]
|
||||||
import ./modals/startListener
|
import ./modals/startListener
|
||||||
|
import whisky
|
||||||
|
|
||||||
type
|
type
|
||||||
ListenersTableComponent = ref object of RootObj
|
ListenersTableComponent* = ref object of RootObj
|
||||||
title: string
|
title: string
|
||||||
listeners: seq[Listener]
|
listeners*: seq[Listener]
|
||||||
selection: ptr ImGuiSelectionBasicStorage
|
selection: ptr ImGuiSelectionBasicStorage
|
||||||
startListenerModal: ListenerModalComponent
|
startListenerModal: ListenerModalComponent
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ proc ListenersTable*(title: string): ListenersTableComponent =
|
|||||||
result.selection = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage()
|
result.selection = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage()
|
||||||
result.startListenerModal = ListenerModal()
|
result.startListenerModal = ListenerModal()
|
||||||
|
|
||||||
proc draw*(component: ListenersTableComponent, showComponent: ptr bool) =
|
proc draw*(component: ListenersTableComponent, showComponent: ptr bool, ws: WebSocket) =
|
||||||
igBegin(component.title, showComponent, 0)
|
igBegin(component.title, showComponent, 0)
|
||||||
defer: igEnd()
|
defer: igEnd()
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ proc draw*(component: ListenersTableComponent, showComponent: ptr bool) =
|
|||||||
let listener = component.startListenerModal.draw()
|
let listener = component.startListenerModal.draw()
|
||||||
if listener != nil:
|
if listener != nil:
|
||||||
# TODO: Start listener
|
# TODO: Start listener
|
||||||
|
ws.send("Starting listener: " & listener.listenerId)
|
||||||
component.listeners.add(listener)
|
component.listeners.add(listener)
|
||||||
|
|
||||||
#[
|
#[
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import ../utils/appImGui
|
|||||||
import ../../common/[types, utils]
|
import ../../common/[types, utils]
|
||||||
|
|
||||||
type
|
type
|
||||||
SessionsTableComponent = ref object of RootObj
|
SessionsTableComponent* = ref object of RootObj
|
||||||
title: string
|
title: string
|
||||||
agents: seq[Agent]
|
agents*: seq[Agent]
|
||||||
selection: ptr ImGuiSelectionBasicStorage
|
selection: ptr ImGuiSelectionBasicStorage
|
||||||
consoles: ptr Table[string, ConsoleComponent]
|
consoles: ptr Table[string, ConsoleComponent]
|
||||||
|
|
||||||
|
|||||||
135
src/client/websocket.nim
Normal file
135
src/client/websocket.nim
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import times, tables
|
||||||
|
import ../common/[types, utils, serialize]
|
||||||
|
import views/[sessions, listeners, console, eventlog]
|
||||||
|
import whisky
|
||||||
|
|
||||||
|
#[
|
||||||
|
[ Sending Functions ]
|
||||||
|
Client -> Server
|
||||||
|
- Heartbeat
|
||||||
|
- ListenerStart
|
||||||
|
- ListenerStop
|
||||||
|
- AgentBuild
|
||||||
|
- AgentCommand
|
||||||
|
]#
|
||||||
|
proc sendHeartbeat*(ws: WebSocket) =
|
||||||
|
var packer = Packer.init()
|
||||||
|
|
||||||
|
packer.add(cast[uint8](CLIENT_HEARTBEAT))
|
||||||
|
let data = packer.pack()
|
||||||
|
|
||||||
|
ws.send(Bytes.toString(data), BinaryMessage)
|
||||||
|
|
||||||
|
proc sendStartListener*(ws: WebSocket, listener: Listener) =
|
||||||
|
var packer = Packer.init()
|
||||||
|
|
||||||
|
packer.add(cast[uint8](CLIENT_LISTENER_START))
|
||||||
|
packer.add(string.toUUid(listener.listenerId))
|
||||||
|
packer.addDataWithLengthPrefix(string.toBytes(listener.address))
|
||||||
|
packer.add(cast[uint16](listener.port))
|
||||||
|
packer.add(cast[uint8](listener.protocol))
|
||||||
|
|
||||||
|
let data = packer.pack()
|
||||||
|
|
||||||
|
ws.send(Bytes.toString(data), BinaryMessage)
|
||||||
|
|
||||||
|
proc sendStopListener*(ws: WebSocket, listenerId: string) =
|
||||||
|
var packer = Packer.init()
|
||||||
|
|
||||||
|
packer.add(cast[uint8](CLIENT_LISTENER_STOP))
|
||||||
|
packer.add(string.toUuid(listenerId))
|
||||||
|
let data = packer.pack()
|
||||||
|
|
||||||
|
ws.send(Bytes.toString(data), BinaryMessage)
|
||||||
|
|
||||||
|
proc sendAgentCommand*(ws: WebSocket, agentId: string, command: string) =
|
||||||
|
var packer = Packer.init()
|
||||||
|
|
||||||
|
packer.add(cast[uint8](CLIENT_AGENT_COMMAND))
|
||||||
|
packer.add(string.toUuid(agentId))
|
||||||
|
packer.addDataWithLengthPrefix(string.toBytes(command))
|
||||||
|
let data = packer.pack()
|
||||||
|
|
||||||
|
ws.send(Bytes.toString(data), BinaryMessage)
|
||||||
|
|
||||||
|
proc sendAgentBuild*(ws: WebSocket, listenerId: string, sleepDelay: int, sleepMask: SleepObfuscationTechnique, spoofStack: bool, modules: uint32) =
|
||||||
|
var packer = Packer.init()
|
||||||
|
|
||||||
|
packer.add(cast[uint8](CLIENT_AGENT_BUILD))
|
||||||
|
packer.add(string.toUuid(listenerId))
|
||||||
|
packer.add(cast[uint32](sleepDelay))
|
||||||
|
packer.add(cast[uint8](sleepMask))
|
||||||
|
packer.add(cast[uint8](spoofStack))
|
||||||
|
packer.add(modules)
|
||||||
|
let data = packer.pack()
|
||||||
|
|
||||||
|
ws.send(Bytes.toString(data), BinaryMessage)
|
||||||
|
|
||||||
|
#[
|
||||||
|
[ Retrieval Functions ]
|
||||||
|
Server -> Client
|
||||||
|
]#
|
||||||
|
proc getMessageType*(message: Message): WsMessageAction =
|
||||||
|
var unpacker = Unpacker.init(message.data)
|
||||||
|
return cast[WsMessageAction](unpacker.getUint8())
|
||||||
|
|
||||||
|
proc receiveAgentPayload*(message: Message): seq[byte] =
|
||||||
|
var unpacker = Unpacker.init(message.data)
|
||||||
|
|
||||||
|
discard unpacker.getUint8()
|
||||||
|
return string.toBytes(unpacker.getDataWithLengthPrefix())
|
||||||
|
|
||||||
|
proc receiveAgentConnection*(message: Message, sessions: ptr SessionsTableComponent) =
|
||||||
|
var unpacker = Unpacker.init(message.data)
|
||||||
|
|
||||||
|
discard unpacker.getUint8()
|
||||||
|
let agent = Agent(
|
||||||
|
agentId: Uuid.toString(unpacker.getUint32()),
|
||||||
|
listenerId: Uuid.toString(unpacker.getUint32()),
|
||||||
|
username: unpacker.getDataWithLengthPrefix(),
|
||||||
|
hostname: unpacker.getDataWithLengthPrefix(),
|
||||||
|
domain: unpacker.getDataWithLengthPrefix(),
|
||||||
|
ip: unpacker.getDataWithLengthPrefix(),
|
||||||
|
os: unpacker.getDataWithLengthPrefix(),
|
||||||
|
process: unpacker.getDataWithLengthPrefix(),
|
||||||
|
pid: int(unpacker.getUint32()),
|
||||||
|
elevated: unpacker.getUint8() != 0,
|
||||||
|
sleep: int(unpacker.getUint32()),
|
||||||
|
tasks: @[],
|
||||||
|
firstCheckin: cast[int64](unpacker.getUint32()).fromUnix().utc(),
|
||||||
|
latestCheckin: now(),
|
||||||
|
)
|
||||||
|
|
||||||
|
sessions.agents.add(agent)
|
||||||
|
|
||||||
|
proc receiveAgentCheckin*(message: Message, sessions: ptr SessionsTableComponent)=
|
||||||
|
var unpacker = Unpacker.init(message.data)
|
||||||
|
|
||||||
|
discard unpacker.getUint8()
|
||||||
|
let agentId = Uuid.toString(unpacker.getUint32())
|
||||||
|
let timestamp = cast[int64](unpacker.getUint32())
|
||||||
|
|
||||||
|
# TODO: Update checkin
|
||||||
|
|
||||||
|
proc receiveConsoleItem*(message: Message, consoles: ptr Table[string, ConsoleComponent]) =
|
||||||
|
var unpacker = Unpacker.init(message.data)
|
||||||
|
|
||||||
|
discard unpacker.getUint8()
|
||||||
|
let
|
||||||
|
agentId = Uuid.toString(unpacker.getUint32())
|
||||||
|
logType = cast[LogType](unpacker.getUint8())
|
||||||
|
timestamp = cast[int64](unpacker.getUint32())
|
||||||
|
message = unpacker.getDataWithLengthPrefix()
|
||||||
|
|
||||||
|
consoles[][agentId].addItem(logType, message, timestamp)
|
||||||
|
|
||||||
|
proc receiveEventlogItem*(message: Message, eventlog: ptr EventlogComponent) =
|
||||||
|
var unpacker = Unpacker.init(message.data)
|
||||||
|
|
||||||
|
discard unpacker.getUint8()
|
||||||
|
let
|
||||||
|
logType = cast[LogType](unpacker.getUint8())
|
||||||
|
timestamp = cast[int64](unpacker.getUint32())
|
||||||
|
message = unpacker.getDataWithLengthPrefix()
|
||||||
|
|
||||||
|
eventlog[].addItem(logType, message, timestamp)
|
||||||
@@ -81,7 +81,7 @@ type
|
|||||||
CONFIG_PUBLIC_KEY = 4'u8
|
CONFIG_PUBLIC_KEY = 4'u8
|
||||||
CONFIG_PROFILE = 5'u8
|
CONFIG_PROFILE = 5'u8
|
||||||
|
|
||||||
LogType* = enum
|
LogType* {.size: sizeof(uint8).} = enum
|
||||||
LOG_INFO = " [INFO] "
|
LOG_INFO = " [INFO] "
|
||||||
LOG_ERROR = " [FAIL] "
|
LOG_ERROR = " [FAIL] "
|
||||||
LOG_SUCCESS = " [DONE] "
|
LOG_SUCCESS = " [DONE] "
|
||||||
@@ -193,7 +193,7 @@ type
|
|||||||
|
|
||||||
# Listener structure
|
# Listener structure
|
||||||
type
|
type
|
||||||
Protocol* = enum
|
Protocol* {.size: sizeof(uint8).} = enum
|
||||||
HTTP = "http"
|
HTTP = "http"
|
||||||
|
|
||||||
Listener* = ref object of RootObj
|
Listener* = ref object of RootObj
|
||||||
@@ -262,4 +262,85 @@ type
|
|||||||
text*: string
|
text*: string
|
||||||
|
|
||||||
ConsoleItems* = ref object
|
ConsoleItems* = ref object
|
||||||
items*: seq[ConsoleItem]
|
items*: seq[ConsoleItem]
|
||||||
|
|
||||||
|
#[
|
||||||
|
Client <-> Server WebSocket communication
|
||||||
|
]#
|
||||||
|
type
|
||||||
|
WsMessageAction* = enum
|
||||||
|
# Sent by client
|
||||||
|
CLIENT_HEARTBEAT = 0'u8 # Basic checkin
|
||||||
|
CLIENT_AGENT_COMMAND = 1'u8 # Instruct TS to send queue a command for a specific agent
|
||||||
|
CLIENT_LISTENER_START = 2'u8 # Start a listener on the TS
|
||||||
|
CLIENT_LISTENER_STOP = 3'u8 # Stop a listener
|
||||||
|
CLIENT_AGENT_BUILD = 4'u8 # Generate an agent binary for a specific listener
|
||||||
|
|
||||||
|
# Sent by team server
|
||||||
|
CLIENT_AGENT_BINARY = 100'u8 # Return the agent binary to write to the operator's client machine
|
||||||
|
CLIENT_AGENT_CONNECTION = 101'u8 # Notify new agent connection
|
||||||
|
CLIENT_AGENT_CHECKIN = 102'u8 # Update agent checkin
|
||||||
|
CLIENT_CONSOLE_LOG = 103'u8 # Add entry to a agent's console
|
||||||
|
CLIENT_EVENT_LOG = 104'u8 # Add entry to the eventlog
|
||||||
|
|
||||||
|
CLIENT_CONNECTION = 200'u8 # Return team server profile
|
||||||
|
|
||||||
|
# Client -> Server
|
||||||
|
WsHeartbeat* = object
|
||||||
|
msgType* = CLIENT_HEARTBEAT
|
||||||
|
|
||||||
|
WsCommand* = object
|
||||||
|
msgType* = CLIENT_AGENT_COMMAND
|
||||||
|
agentId*: uint32
|
||||||
|
command*: seq[byte] # Command input field in the console window, prefixed with length
|
||||||
|
|
||||||
|
WsListenerStart* = object
|
||||||
|
msgType* = CLIENT_LISTENER_START
|
||||||
|
listener*: Listener
|
||||||
|
|
||||||
|
WsListenerStop* = object
|
||||||
|
msgType* = CLIENT_LISTENER_STOP
|
||||||
|
listenerId*: uint32
|
||||||
|
|
||||||
|
WsAgentBuild* = object
|
||||||
|
msgType* = CLIENT_AGENT_BUILD
|
||||||
|
listenerId*: uint32
|
||||||
|
sleepDelay*: uint32
|
||||||
|
sleepMask*: SleepObfuscationTechnique
|
||||||
|
spoofStack*: uint8
|
||||||
|
modules*: uint64
|
||||||
|
|
||||||
|
# Server -> Client
|
||||||
|
WsAgentBinary* = object
|
||||||
|
msgType* = CLIENT_AGENT_BINARY
|
||||||
|
agentPayload*: seq[byte] # Agent binary in byte-form, opens file browser to select location on the client
|
||||||
|
|
||||||
|
WsAgentConnection* = object
|
||||||
|
msgType* = CLIENT_AGENT_CONNECTION
|
||||||
|
agent*: Agent
|
||||||
|
|
||||||
|
WsAgentCheckin* = object
|
||||||
|
msgType* = CLIENT_AGENT_CHECKIN
|
||||||
|
agentId*: uint32
|
||||||
|
timestamp*: uint32
|
||||||
|
|
||||||
|
WsConsoleLog* = object
|
||||||
|
msgType* = CLIENT_CONSOLE_LOG
|
||||||
|
agentId*: uint32
|
||||||
|
logType*: LogType
|
||||||
|
timestamp*: uint32
|
||||||
|
data*: seq[byte]
|
||||||
|
|
||||||
|
WsEventLog* = object
|
||||||
|
msgType* = CLIENT_EVENT_LOG
|
||||||
|
logType*: LogType
|
||||||
|
timestamp*: uint32
|
||||||
|
data*: seq[byte]
|
||||||
|
|
||||||
|
WsClientConnection* = object
|
||||||
|
msgType* = CLIENT_CONNECTION
|
||||||
|
version: uint8
|
||||||
|
profile*: seq[byte]
|
||||||
|
agents*: seq[Agent]
|
||||||
|
listeners*: seq[Listener]
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import prompt, terminal, argparse, parsetoml
|
import prompt, terminal, argparse, parsetoml, times
|
||||||
import strutils, strformat, system, tables
|
import strutils, strformat, system, tables
|
||||||
|
|
||||||
import ./[agent, listener, builder]
|
import ./[agent, listener, builder]
|
||||||
@@ -6,6 +6,8 @@ import ../globals
|
|||||||
import ../db/database
|
import ../db/database
|
||||||
import ../core/logger
|
import ../core/logger
|
||||||
import ../../common/[types, crypto, profile]
|
import ../../common/[types, crypto, profile]
|
||||||
|
import ../protocol/websocket
|
||||||
|
import mummy, mummy/routers
|
||||||
|
|
||||||
#[
|
#[
|
||||||
Argument parsing
|
Argument parsing
|
||||||
@@ -137,6 +139,33 @@ proc init*(T: type Conquest, profile: Profile): Conquest =
|
|||||||
cq.dbPath = CONQUEST_ROOT & "/" & profile.getString("database-file")
|
cq.dbPath = CONQUEST_ROOT & "/" & profile.getString("database-file")
|
||||||
return cq
|
return cq
|
||||||
|
|
||||||
|
#[
|
||||||
|
WebSocket
|
||||||
|
]#
|
||||||
|
proc upgradeHandler(request: Request) =
|
||||||
|
let ws = request.upgradeToWebSocket()
|
||||||
|
|
||||||
|
# Send client connection message
|
||||||
|
ws.sendEventlogItem(LOG_SUCCESS_SHORT, now().toTime().toUnix(), "CQ-V1")
|
||||||
|
|
||||||
|
|
||||||
|
proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) =
|
||||||
|
case event:
|
||||||
|
of OpenEvent:
|
||||||
|
discard
|
||||||
|
of MessageEvent:
|
||||||
|
ws.sendHeartbeat()
|
||||||
|
of ErrorEvent:
|
||||||
|
discard
|
||||||
|
of CloseEvent:
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc serve(server: Server) {.thread.} =
|
||||||
|
try:
|
||||||
|
server.serve(Port(12345))
|
||||||
|
except Exception:
|
||||||
|
discard
|
||||||
|
|
||||||
proc startServer*(profilePath: string) =
|
proc startServer*(profilePath: string) =
|
||||||
|
|
||||||
# Ensure that the conquest root directory was passed as a compile-time define
|
# Ensure that the conquest root directory was passed as a compile-time define
|
||||||
@@ -167,6 +196,13 @@ proc startServer*(profilePath: string) =
|
|||||||
cq.restartListeners()
|
cq.restartListeners()
|
||||||
cq.addMultiple(cq.dbGetAllAgents())
|
cq.addMultiple(cq.dbGetAllAgents())
|
||||||
|
|
||||||
|
var router: Router
|
||||||
|
router.get("/*", upgradeHandler)
|
||||||
|
let server = newServer(router, websocketHandler)
|
||||||
|
|
||||||
|
var thread: Thread[Server]
|
||||||
|
createThread(thread, serve, server)
|
||||||
|
|
||||||
# Main loop
|
# Main loop
|
||||||
while true:
|
while true:
|
||||||
cq.prompt.setIndicator("[conquest]> ")
|
cq.prompt.setIndicator("[conquest]> ")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Compiler flags
|
# Compiler flags
|
||||||
-d:server
|
-d:server
|
||||||
--threads:on
|
--threads:on
|
||||||
|
--mm:orc
|
||||||
-d:httpxServerName=""
|
-d:httpxServerName=""
|
||||||
-o:"bin/server"
|
-o:"bin/server"
|
||||||
47
src/server/protocol/websocket.nim
Normal file
47
src/server/protocol/websocket.nim
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import times, tables
|
||||||
|
import ../../common/[types, utils, serialize]
|
||||||
|
import mummy
|
||||||
|
|
||||||
|
#[
|
||||||
|
[ Sending functions ]
|
||||||
|
Server -> Client
|
||||||
|
]#
|
||||||
|
proc sendHeartbeat*(ws: WebSocket) =
|
||||||
|
var packer = Packer.init()
|
||||||
|
|
||||||
|
packer.add(cast[uint8](CLIENT_HEARTBEAT))
|
||||||
|
let data = packer.pack()
|
||||||
|
|
||||||
|
ws.send(Bytes.toString(data), BinaryMessage)
|
||||||
|
|
||||||
|
proc sendEventlogItem*(ws: WebSocket, logType: LogType, timestamp: int64, message: string) =
|
||||||
|
var packer = Packer.init()
|
||||||
|
|
||||||
|
packer.add(cast[uint8](CLIENT_EVENT_LOG))
|
||||||
|
packer.add(cast[uint8](logType))
|
||||||
|
packer.add(cast[uint32](timestamp))
|
||||||
|
packer.addDataWithLengthPrefix(string.toBytes(message))
|
||||||
|
let data = packer.pack()
|
||||||
|
|
||||||
|
ws.send(Bytes.toString(data), BinaryMessage)
|
||||||
|
|
||||||
|
#[
|
||||||
|
[ Retrieval functions ]
|
||||||
|
Client -> Server
|
||||||
|
]#
|
||||||
|
proc getMessageType*(message: Message): WsMessageAction =
|
||||||
|
var unpacker = Unpacker.init(message.data)
|
||||||
|
return cast[WsMessageAction](unpacker.getUint8())
|
||||||
|
|
||||||
|
proc receiveStartListener*(message: Message): Listener =
|
||||||
|
var unpacker = Unpacker.init(message.data)
|
||||||
|
|
||||||
|
discard unpacker.getUint8()
|
||||||
|
|
||||||
|
return Listener(
|
||||||
|
server: nil,
|
||||||
|
listenerId: Uuid.toString(unpacker.getUint32()),
|
||||||
|
address: unpacker.getDataWithLengthPrefix(),
|
||||||
|
port: int(unpacker.getUint16()),
|
||||||
|
protocol: cast[Protocol](unpacker.getUint8())
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user