diff --git a/src/client/layout.ini b/src/client/layout.ini index aa54bc3..4de0de7 100644 --- a/src/client/layout.ini +++ b/src/client/layout.ini @@ -1,43 +1,43 @@ [Window][Sessions [Table View]] Pos=10,43 -Size=2016,548 +Size=2101,474 Collapsed=0 DockId=0x00000003,0 [Window][Listeners] -Pos=10,593 -Size=2528,804 +Pos=10,519 +Size=2848,1166 Collapsed=0 DockId=0x00000006,0 [Window][Eventlog] -Pos=2028,43 -Size=510,548 +Pos=2113,43 +Size=745,474 Collapsed=0 DockId=0x00000004,0 [Window][Dear ImGui Demo] -Pos=2028,43 -Size=510,548 +Pos=2113,43 +Size=745,474 Collapsed=0 DockId=0x00000004,1 [Window][Dockspace] Pos=0,0 -Size=2548,1407 +Size=2868,1695 Collapsed=0 [Window][[FACEDEAD] bob@LAPTOP-02] -Pos=10,593 -Size=2528,804 +Pos=10,661 +Size=2848,1024 Collapsed=0 -DockId=0x00000006,1 +DockId=0x00000006,3 [Window][[C9D8E7F6] charlie@SERVER-03] -Pos=10,593 -Size=2528,804 +Pos=10,661 +Size=2848,1024 Collapsed=0 -DockId=0x00000006,1 +DockId=0x00000006,4 [Window][Debug##Default] Pos=60,60 @@ -45,22 +45,22 @@ Size=400,400 Collapsed=0 [Window][[G1H2I3J5] diana@WORKSTATION-04] -Pos=10,593 -Size=2528,804 +Pos=10,372 +Size=1888,617 Collapsed=0 DockId=0x00000006,1 [Window][[DEADBEEF] alice@DESKTOP-01] -Pos=10,716 -Size=2848,969 +Pos=10,661 +Size=2848,1024 Collapsed=0 DockId=0x00000006,2 [Window][Example: Console] -Pos=10,572 -Size=2848,1113 +Pos=10,661 +Size=2848,1024 Collapsed=0 -DockId=0x00000005,2 +DockId=0x00000006,2 [Window][Example: Assets Browser] Pos=60,60 @@ -110,8 +110,17 @@ Size=76,76 Collapsed=0 [Window][Start Listener] -Pos=955,591 -Size=637,225 +Pos=713,369 +Size=500,225 +Collapsed=0 + +[Window][Dear ImGui Demo/0_E1BADA21] +IsChild=1 +Size=1363,540 + +[Window][Generate Payload] +Pos=704,337 +Size=500,325 Collapsed=0 [Table][0x32886A44,8] @@ -136,9 +145,9 @@ Column 3 Weight=0.9746 [Docking][Data] DockNode ID=0x00000009 Pos=100,200 Size=754,103 Selected=0x64D005CF -DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=2528,1354 Split=Y - DockNode ID=0x00000005 Parent=0x85940918 SizeRef=1888,548 Split=X - DockNode ID=0x00000003 Parent=0x00000005 SizeRef=1376,159 CentralNode=1 Selected=0x61E02D75 - DockNode ID=0x00000004 Parent=0x00000005 SizeRef=510,159 Selected=0x5E5F7166 - DockNode ID=0x00000006 Parent=0x85940918 SizeRef=1888,804 Selected=0x6BE22050 +DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=2848,1642 Split=Y + DockNode ID=0x00000005 Parent=0x85940918 SizeRef=1888,474 Split=X + DockNode ID=0x00000003 Parent=0x00000005 SizeRef=2101,159 CentralNode=1 Selected=0x61E02D75 + DockNode ID=0x00000004 Parent=0x00000005 SizeRef=745,159 Selected=0x0FA43D88 + DockNode ID=0x00000006 Parent=0x85940918 SizeRef=1888,1166 Selected=0x6BE22050 diff --git a/src/client/main.nim b/src/client/main.nim index 0390eb8..5f5e49a 100644 --- a/src/client/main.nim +++ b/src/client/main.nim @@ -62,7 +62,7 @@ proc main() = case message.getMessageType() of CLIENT_EVENT_LOG: message.receiveEventlogItem(addr eventlog) - else: discard + else: discard # Draw/update UI components/views diff --git a/src/client/views/listeners.nim b/src/client/views/listeners.nim index f10cce9..da8f0dd 100644 --- a/src/client/views/listeners.nim +++ b/src/client/views/listeners.nim @@ -2,7 +2,8 @@ import strutils import imguin/[cimgui, glfw_opengl, simple] import ../utils/appImGui import ../../common/[types, utils] -import ./modals/startListener +import ./modals/[startListener, generatePayload] +import ../websocket import whisky type @@ -11,6 +12,7 @@ type listeners*: seq[Listener] selection: ptr ImGuiSelectionBasicStorage startListenerModal: ListenerModalComponent + generatePayloadModal: AgentModalComponent let exampleListeners: seq[Listener] = @[ Listener( @@ -33,6 +35,8 @@ proc ListenersTable*(title: string): ListenersTableComponent = result.listeners = exampleListeners result.selection = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() result.startListenerModal = ListenerModal() + result.generatePayloadModal = AgentModal(result.listeners) + proc draw*(component: ListenersTableComponent, showComponent: ptr bool, ws: WebSocket) = igBegin(component.title, showComponent, 0) @@ -43,13 +47,21 @@ proc draw*(component: ListenersTableComponent, showComponent: ptr bool, ws: WebS # Listener creation modal if igButton("Start Listener", vec2(0.0f, 0.0f)): igOpenPopup_str("Start Listener", ImGui_PopupFlags_None.int32) + igSameLine(0.0f, textSpacing) + # Payload generation modal + if igButton("Generate Payload", vec2(0.0f, 0.0f)): + igOpenPopup_str("Generate Payload", ImGui_PopupFlags_None.int32) let listener = component.startListenerModal.draw() if listener != nil: - # TODO: Start listener - ws.send("Starting listener: " & listener.listenerId) + ws.sendStartListener(listener) component.listeners.add(listener) + component.generatePayloadModal.draw() + + + + #[ Listener table ]# @@ -109,8 +121,8 @@ proc draw*(component: ListenersTableComponent, showComponent: ptr bool, ws: WebS for i in 0 ..< component.listeners.len(): if not ImGuiSelectionBasicStorage_Contains(component.selection, cast[ImGuiID](i)): newListeners.add(component.listeners[i]) - - # TODO: Stop/kill listener + else: + ws.sendStopListener(component.listeners[i].listenerId) component.listeners = newListeners ImGuiSelectionBasicStorage_Clear(component.selection) diff --git a/src/client/views/modals/buildAgent.nim b/src/client/views/modals/buildAgent.nim deleted file mode 100644 index e69de29..0000000 diff --git a/src/client/views/modals/generatePayload.nim b/src/client/views/modals/generatePayload.nim new file mode 100644 index 0000000..ff3f880 --- /dev/null +++ b/src/client/views/modals/generatePayload.nim @@ -0,0 +1,107 @@ +import strutils +import imguin/[cimgui, glfw_opengl, simple] +import ../../utils/appImGui +import ../../../common/[types, utils] + + +type + AgentModalComponent* = ref object of RootObj + listener: int32 + sleepDelay: uint32 + sleepMask: int32 + spoofStack: bool + listeners: seq[string] + sleepMaskTechniques: seq[string] + +proc AgentModal*(listeners: seq[Listener]): AgentModalComponent = + result = new AgentModalComponent + result.listener = 0 + result.sleepDelay = 5 + result.sleepMask = 0 + result.spoofStack = false + + for l in listeners: + result.listeners.add(l.listenerId) + + for s in SleepObfuscationTechnique.low .. SleepObfuscationTechnique.high: + result.sleepMaskTechniques.add($s) + +proc resetModalValues(component: AgentModalComponent) = + discard + +proc draw*(component: AgentModalComponent) = + let textSpacing = igGetStyle().ItemSpacing.x + + # Center modal + let vp = igGetMainViewport() + var center: ImVec2 + ImGuiViewport_GetCenter(addr center, vp) + igSetNextWindowPos(center, ImGuiCond_Appearing.int32, vec2(0.5f, 0.5f)) + + let modalWidth = max(500.0f, vp.Size.x * 0.25) + igSetNextWindowSize(vec2(modalWidth, 0.0f), ImGuiCond_Always.int32) + + var show = true + let windowFlags = ImGuiWindowFlags_None.int32 # or ImGuiWindowFlags_NoMove.int32 + if igBeginPopupModal("Generate Payload", addr show, windowFlags): + defer: igEndPopup() + + var availableSize: ImVec2 + igGetContentRegionAvail(addr availableSize) + + # Listener selection + igText("Listener: ") + igSameLine(0.0f, textSpacing) + igGetContentRegionAvail(addr availableSize) + igSetNextItemWidth(availableSize.x) + igCombo_Str("##InputListener", addr component.listener, (component.listeners.join("\0") & "\0").cstring , component.listeners.len().int32) + + # Sleep delay + let step: uint32 = 1 + igText("Sleep delay: ") + igSameLine(0.0f, textSpacing) + igSetNextItemWidth(availableSize.x) + igInputScalar("##InputSleepDelay", ImGuiDataType_U32.int32, addr component.sleepDelay, addr step, nil, "%hu", ImGui_InputTextFlags_CharsDecimal.int32) + + # Agent sleep obfuscation technique dropdown selection + igText("Sleep mask: ") + igSameLine(0.0f, textSpacing) + igSetNextItemWidth(availableSize.x) + igCombo_Str("##InputSleepMask", addr component.sleepMask, (component.sleepMaskTechniques.join("\0") & "\0").cstring , component.sleepMaskTechniques.len().int32) + + # Stack spoofing checkbox (only for EKKO/ZILEAN) + igText("Stack spoofing: ") + igSameLine(0.0f, textSpacing) + igSetNextItemWidth(availableSize.x) + + igBeginDisabled((component.sleepMaskTechniques[component.sleepMask] != $EKKO and component.sleepMaskTechniques[component.sleepMask] != $ZILEAN)) + if (component.sleepMaskTechniques[component.sleepMask] != $EKKO and component.sleepMaskTechniques[component.sleepMask] != $ZILEAN): + component.spoofStack = false + igCheckbox("##InputSpoofStack", addr component.spoofStack) + igEndDisabled() + + igDummy(vec2(0.0f, 10.0f)) + igSeparator() + igDummy(vec2(0.0f, 10.0f)) + + igText("Modules: ") + + + + igGetContentRegionAvail(addr availableSize) + + igDummy(vec2(0.0f, 10.0f)) + igSeparator() + igDummy(vec2(0.0f, 10.0f)) + + if igButton("Build", vec2(availableSize.x * 0.5 - textSpacing * 0.5, 0.0f)): + + + component.resetModalValues() + igCloseCurrentPopup() + + igSameLine(0.0f, textSpacing) + + if igButton("Close", vec2(availableSize.x * 0.5 - textSpacing * 0.5, 0.0f)): + component.resetModalValues() + igCloseCurrentPopup() \ No newline at end of file diff --git a/src/client/views/modals/startListener.nim b/src/client/views/modals/startListener.nim index 07d908e..b7d02b6 100644 --- a/src/client/views/modals/startListener.nim +++ b/src/client/views/modals/startListener.nim @@ -89,6 +89,5 @@ proc draw*(component: ListenerModalComponent): Listener = igSameLine(0.0f, textSpacing) if igButton("Close", vec2(availableSize.x * 0.5 - textSpacing * 0.5, 0.0f)): - component.resetModalValues() igCloseCurrentPopup() \ No newline at end of file diff --git a/src/client/websocket.nim b/src/client/websocket.nim index b364dd7..1d61339 100644 --- a/src/client/websocket.nim +++ b/src/client/websocket.nim @@ -65,7 +65,7 @@ proc sendAgentBuild*(ws: WebSocket, listenerId: string, sleepDelay: int, sleepMa ws.send(Bytes.toString(data), BinaryMessage) -#[ +#[ [ Retrieval Functions ] Server -> Client ]# @@ -97,7 +97,7 @@ proc receiveAgentConnection*(message: Message, sessions: ptr SessionsTableCompon sleep: int(unpacker.getUint32()), tasks: @[], firstCheckin: cast[int64](unpacker.getUint32()).fromUnix().utc(), - latestCheckin: now(), + latestCheckin: cast[int64](unpacker.getUint32()).fromUnix().utc(), ) sessions.agents.add(agent) diff --git a/src/common/types.nim b/src/common/types.nim index 38d2ef9..36cb746 100644 --- a/src/common/types.nim +++ b/src/common/types.nim @@ -219,6 +219,7 @@ type interactAgent*: Agent keyPair*: KeyPair profile*: Profile + ws*: WebSocket AgentCtx* = ref object agentId*: string diff --git a/src/server/core/listener.nim b/src/server/core/listener.nim index 19ffcee..fac261e 100644 --- a/src/server/core/listener.nim +++ b/src/server/core/listener.nim @@ -8,6 +8,7 @@ import ../api/routes import ../db/database import ../core/logger import ../../common/[types, utils, profile] +import ../websocket/send #[ Listener management @@ -32,22 +33,16 @@ proc listenerList*(cq: Conquest) = cq.drawTable(listeners) proc serve(listener: Listener) {.thread.} = - try: + try: listener.server.serve(Port(listener.port), listener.address) - except Exception: + except Exception as err: discard -proc listenerStart*(cq: Conquest, host: string, portStr: string) = +proc listenerStart*(cq: Conquest, name: string, host: string, port: int, protocol: Protocol) = # Validate arguments try: - if not validatePort(portStr): - raise newException(CatchableError, fmt"[ - ] Invalid port number: {portStr}") - - let port = portStr.parseInt - # Create new listener - let name: string = generateUUID() var router: Router router.notFoundHandler = routes.error404 router.methodNotAllowedHandler = routes.error405 @@ -78,7 +73,7 @@ proc listenerStart*(cq: Conquest, host: string, portStr: string) = listenerId: name, address: host, port: port, - protocol: HTTP + protocol: protocol ) # Start serving @@ -90,10 +85,12 @@ proc listenerStart*(cq: Conquest, host: string, portStr: string) = if not cq.dbStoreListener(listener): raise newException(CatchableError, "Failed to store listener in database.") - cq.success("Started listener", fgGreen, fmt" {name} ", resetStyle, fmt"on {host}:{portStr}.") + cq.success("Started listener", fgGreen, fmt" {name} ", resetStyle, fmt"on {host}:{$port}.") + cq.ws.sendEventlogItem(LOG_SUCCESS_SHORT, fmt"Started listener {name} on {host}:{$port}.") except CatchableError as err: cq.error("Failed to start listener: ", err.msg) + cq.ws.sendEventlogItem(LOG_ERROR_SHORT, fmt"Failed to start listener: {err.msg}.") proc restartListeners*(cq: Conquest) = var listeners: seq[Listener] = cq.dbGetAllListeners() diff --git a/src/server/core/server.nim b/src/server/core/server.nim index 659cb5e..48913dd 100644 --- a/src/server/core/server.nim +++ b/src/server/core/server.nim @@ -6,7 +6,7 @@ import ../globals import ../db/database import ../core/logger import ../../common/[types, crypto, profile] -import ../protocol/websocket +import ../websocket/[receive, send] import mummy, mummy/routers #[ @@ -88,9 +88,11 @@ proc handleConsoleCommand(cq: Conquest, args: string) = of "list": cq.listenerList() of "start": - cq.listenerStart(opts.listener.get.start.get.ip, opts.listener.get.start.get.port) + #cq.listenerStart(opts.listener.get.start.get.ip, opts.listener.get.start.get.port) + discard of "stop": - cq.listenerStop(opts.listener.get.stop.get.name) + #cq.listenerStop(opts.listener.get.stop.get.name) + discard else: cq.listenerUsage() @@ -143,22 +145,34 @@ proc init*(T: type Conquest, profile: Profile): Conquest = WebSocket ]# proc upgradeHandler(request: Request) = - let ws = request.upgradeToWebSocket() + {.cast(gcsafe).}: + let ws = request.upgradeToWebSocket() + cq.ws = ws + # Send client connection message + ws.sendEventlogItem(LOG_SUCCESS_SHORT, "CQ-V1") - # Send client connection message - ws.sendEventlogItem(LOG_SUCCESS_SHORT, now().toTime().toUnix(), "CQ-V1") +proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) {.gcsafe.} = + {.cast(gcsafe).}: + case event: + of OpenEvent: + discard + of MessageEvent: + ws.sendHeartbeat() - -proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) = - case event: - of OpenEvent: - discard - of MessageEvent: - ws.sendHeartbeat() - of ErrorEvent: - discard - of CloseEvent: - discard + 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: @@ -196,6 +210,7 @@ proc startServer*(profilePath: string) = cq.restartListeners() cq.addMultiple(cq.dbGetAllAgents()) + # Start websocket server var router: Router router.get("/*", upgradeHandler) let server = newServer(router, websocketHandler) diff --git a/src/server/protocol/websocket.nim b/src/server/protocol/websocket.nim deleted file mode 100644 index 573ff3c..0000000 --- a/src/server/protocol/websocket.nim +++ /dev/null @@ -1,47 +0,0 @@ -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()) - ) \ No newline at end of file diff --git a/src/server/websocket/receive.nim b/src/server/websocket/receive.nim new file mode 100644 index 0000000..4dfe3d4 --- /dev/null +++ b/src/server/websocket/receive.nim @@ -0,0 +1,42 @@ +import times, tables +import ../globals +import ../../common/[types, utils, serialize] +import mummy +import ./send +import ../core/[task, listener] + +#[ + [ Retrieval functions ] + Client -> Server +]# +proc getMessageType*(message: Message): WsMessageAction = + var unpacker = Unpacker.init(message.data) + return cast[WsMessageAction](unpacker.getUint8()) + +proc receiveStartListener*(message: Message) = + var unpacker = Unpacker.init(message.data) + + discard unpacker.getUint8() + let + listenerId = Uuid.toString(unpacker.getUint32()) + address = unpacker.getDataWithLengthPrefix() + port = int(unpacker.getUint16()) + protocol = cast[Protocol](unpacker.getUint8()) + cq.ws.sendEventlogItem(LOG_INFO_SHORT, "Attempting to start listener.") + cq.listenerStart(listenerId, address, port, protocol) + +proc receiveStopListener*(message: Message) = + var unpacker = Unpacker.init(message.data) + + discard unpacker.getUint8() + let listenerId = Uuid.toString(unpacker.getUint32()) + cq.listenerStop(listenerId) + +proc receiveAgentCommand*(message: Message) = + var unpacker = Unpacker.init(message.data) + + discard unpacker.getUint8() + let + agentId = Uuid.toString(unpacker.getUint32()) + command = unpacker.getDataWithLengthPrefix() + diff --git a/src/server/websocket/send.nim b/src/server/websocket/send.nim new file mode 100644 index 0000000..6badffd --- /dev/null +++ b/src/server/websocket/send.nim @@ -0,0 +1,79 @@ +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, message: string, timestamp: int64 = now().toTime().toUnix()) = + 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) + +proc sendConsoleItem*(ws: WebSocket, agentId: string, logType: LogType, message: string, timestamp: int64 = now().toTime().toUnix()) = + var packer = Packer.init() + + packer.add(cast[uint8](CLIENT_CONSOLE_LOG)) + packer.add(string.toUUid(agentId)) + 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) + +proc sendAgentCheckin*(ws: WebSocket, agentId: string, timestamp: int64) = + var packer = Packer.init() + + packer.add(cast[uint8](CLIENT_AGENT_CHECKIN)) + packer.add(string.toUUid(agentId)) + packer.add(cast[uint32](timestamp)) + let data = packer.pack() + + ws.send(Bytes.toString(data), BinaryMessage) + +proc sendAgentPayload*(ws: WebSocket, payload: seq[byte]) = + var packer = Packer.init() + + packer.add(cast[uint8](CLIENT_AGENT_BINARY)) + packer.addDataWithLengthPrefix(payload) + let data = packer.pack() + + ws.send(Bytes.toString(data), BinaryMessage) + +proc sendAgentConnection*(ws: WebSocket, agent: Agent) = + var packer = Packer.init() + + packer.add(cast[uint8](CLIENT_AGENT_CONNECTION)) + packer.add(string.toUuid(agent.agentId)) + packer.add(string.toUuid(agent.listenerId)) + packer.addDataWithLengthPrefix(string.toBytes(agent.username)) + packer.addDataWithLengthPrefix(string.toBytes(agent.hostname)) + packer.addDataWithLengthPrefix(string.toBytes(agent.domain)) + packer.addDataWithLengthPrefix(string.toBytes(agent.ip)) + packer.addDataWithLengthPrefix(string.toBytes(agent.os)) + packer.addDataWithLengthPrefix(string.toBytes(agent.process)) + packer.add(uint32(agent.pid)) + packer.add(uint8(agent.elevated)) + packer.add(uint32(agent.sleep)) + packer.add(cast[uint32](agent.firstCheckin)) + packer.add(cast[uint32](agent.latestCheckin)) + let data = packer.pack() + + ws.send(Bytes.toString(data), BinaryMessage) +