2025-07-18 18:47:57 +02:00
|
|
|
import prologue, json, terminal, strformat
|
|
|
|
|
import sequtils, strutils, times, base64
|
2025-05-09 12:04:14 +02:00
|
|
|
|
2025-07-16 10:33:13 +02:00
|
|
|
import ./handlers
|
2025-07-18 18:47:57 +02:00
|
|
|
import ../[utils, globals]
|
2025-07-18 14:24:07 +02:00
|
|
|
import ../../common/types
|
2025-05-13 23:42:04 +02:00
|
|
|
|
2025-07-18 18:47:57 +02:00
|
|
|
proc encode(bytes: seq[seq[byte]]): string =
|
|
|
|
|
result = ""
|
|
|
|
|
for task in bytes:
|
|
|
|
|
result &= encode(task)
|
|
|
|
|
|
2025-05-13 23:42:04 +02:00
|
|
|
proc error404*(ctx: Context) {.async.} =
|
|
|
|
|
resp "", Http404
|
2025-05-09 12:04:14 +02:00
|
|
|
|
2025-05-12 21:53:37 +02:00
|
|
|
#[
|
|
|
|
|
POST /{listener-uuid}/register
|
|
|
|
|
Called from agent to register itself to the conquest server
|
|
|
|
|
]#
|
|
|
|
|
proc register*(ctx: Context) {.async.} =
|
|
|
|
|
|
|
|
|
|
# Check headers
|
2025-05-13 23:42:04 +02:00
|
|
|
# If POST data is not JSON data, return 404 error code
|
|
|
|
|
if ctx.request.contentType != "application/json":
|
|
|
|
|
resp "", Http404
|
|
|
|
|
return
|
2025-05-12 21:53:37 +02:00
|
|
|
|
2025-05-13 23:42:04 +02:00
|
|
|
# The JSON data for the agent registration has to be in the following format
|
2025-05-12 21:53:37 +02:00
|
|
|
#[
|
|
|
|
|
{
|
|
|
|
|
"username": "username",
|
|
|
|
|
"hostname":"hostname",
|
2025-05-14 15:48:01 +02:00
|
|
|
"domain": "domain.local",
|
2025-05-12 21:53:37 +02:00
|
|
|
"ip": "ip-address",
|
2025-05-13 23:42:04 +02:00
|
|
|
"os": "operating-system",
|
2025-05-14 12:42:23 +02:00
|
|
|
"process": "agent.exe",
|
2025-05-13 23:42:04 +02:00
|
|
|
"pid": 1234,
|
2025-05-29 14:19:55 +02:00
|
|
|
"elevated": false.
|
|
|
|
|
"sleep": 10
|
2025-05-12 21:53:37 +02:00
|
|
|
}
|
|
|
|
|
]#
|
|
|
|
|
|
2025-05-13 23:42:04 +02:00
|
|
|
try:
|
|
|
|
|
let
|
|
|
|
|
postData: JsonNode = parseJson(ctx.request.body)
|
2025-05-14 12:42:23 +02:00
|
|
|
agentRegistrationData: AgentRegistrationData = postData.to(AgentRegistrationData)
|
2025-07-16 14:45:45 +02:00
|
|
|
agentUuid: string = generateUUID()
|
2025-05-14 12:42:23 +02:00
|
|
|
listenerUuid: string = ctx.getPathParams("listener")
|
2025-05-23 13:55:00 +02:00
|
|
|
date: DateTime = now()
|
2025-05-13 23:42:04 +02:00
|
|
|
|
2025-07-16 14:45:45 +02:00
|
|
|
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
|
|
|
|
|
)
|
|
|
|
|
|
2025-05-13 23:42:04 +02:00
|
|
|
# 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
|
2025-05-12 21:53:37 +02:00
|
|
|
|
2025-05-13 23:42:04 +02:00
|
|
|
except CatchableError:
|
|
|
|
|
# JSON data is invalid or does not match the expected format (described above)
|
|
|
|
|
resp "", Http404
|
2025-05-12 21:53:37 +02:00
|
|
|
|
2025-05-13 23:42:04 +02:00
|
|
|
return
|
2025-05-09 12:04:14 +02:00
|
|
|
|
2025-05-12 21:53:37 +02:00
|
|
|
#[
|
|
|
|
|
GET /{listener-uuid}/{agent-uuid}/tasks
|
|
|
|
|
Called from agent to check for new tasks
|
|
|
|
|
]#
|
|
|
|
|
proc getTasks*(ctx: Context) {.async.} =
|
2025-05-09 12:04:14 +02:00
|
|
|
|
2025-05-22 20:03:22 +02:00
|
|
|
let
|
|
|
|
|
listener = ctx.getPathParams("listener")
|
|
|
|
|
agent = ctx.getPathParams("agent")
|
2025-07-18 14:24:07 +02:00
|
|
|
|
|
|
|
|
try:
|
2025-07-18 18:47:57 +02:00
|
|
|
var response: seq[byte]
|
2025-07-18 14:24:07 +02:00
|
|
|
let tasks = getTasks(listener, agent)
|
2025-07-18 18:47:57 +02:00
|
|
|
|
|
|
|
|
if tasks.len <= 0:
|
|
|
|
|
resp "", Http200
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 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))
|
|
|
|
|
|
|
|
|
|
for task in tasks:
|
|
|
|
|
response.add(uint32(task.len).toBytes())
|
|
|
|
|
response.add(task)
|
|
|
|
|
|
|
|
|
|
await ctx.respond(
|
|
|
|
|
code = Http200,
|
|
|
|
|
body = response.toString()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Notify operator that agent collected tasks
|
|
|
|
|
{.cast(gcsafe).}:
|
|
|
|
|
let date = now().format("dd-MM-yyyy HH:mm:ss")
|
|
|
|
|
cq.writeLine(fgBlack, styleBright, fmt"[{date}] [*] ", resetStyle, fmt"{$response.len} bytes sent.")
|
|
|
|
|
|
2025-07-18 14:24:07 +02:00
|
|
|
except CatchableError:
|
2025-05-22 20:03:22 +02:00
|
|
|
resp "", Http404
|
|
|
|
|
|
2025-05-12 21:53:37 +02:00
|
|
|
#[
|
2025-05-22 20:03:22 +02:00
|
|
|
POST /{listener-uuid}/{agent-uuid}/{task-uuid}/results
|
2025-05-12 21:53:37 +02:00
|
|
|
Called from agent to post results of a task
|
|
|
|
|
|
|
|
|
|
]#
|
|
|
|
|
proc postResults*(ctx: Context) {.async.} =
|
|
|
|
|
|
2025-05-22 20:03:22 +02:00
|
|
|
let
|
|
|
|
|
listener = ctx.getPathParams("listener")
|
|
|
|
|
agent = ctx.getPathParams("agent")
|
|
|
|
|
task = ctx.getPathParams("task")
|
2025-05-12 21:53:37 +02:00
|
|
|
|
2025-05-22 20:03:22 +02:00
|
|
|
# Check headers
|
|
|
|
|
# If POST data is not JSON data, return 404 error code
|
|
|
|
|
if ctx.request.contentType != "application/json":
|
|
|
|
|
resp "", Http404
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
let
|
|
|
|
|
taskResultJson: JsonNode = parseJson(ctx.request.body)
|
2025-05-29 15:26:50 +02:00
|
|
|
taskResult: TaskResult = taskResultJson.to(TaskResult)
|
2025-05-22 20:03:22 +02:00
|
|
|
|
|
|
|
|
# Handle and display task result
|
|
|
|
|
handleResult(listener, agent, task, taskResult)
|
|
|
|
|
|
|
|
|
|
except CatchableError:
|
|
|
|
|
# JSON data is invalid or does not match the expected format (described above)
|
|
|
|
|
resp "", Http404
|
|
|
|
|
|
|
|
|
|
return
|