Waffles Crypt: A Modular Approach to Shellcode Encryption and Obfuscation in C/C++
Waffles Crypt is a versatile C/C++ tool for encrypting and obfuscating shellcode.
Waffles Crypt is a versatile C/C++ tool for encrypting and obfuscating shellcode. It supports XOR, RC4, and AES encryption, with custom MAC, IPv4, and IPv6-based deobfuscation functions that don’t rely on Windows APIs. You can XOR-encrypt your keys and brute-force them at runtime, eliminating the need to store them. It also lets you combine these techniques for max evasion!
Table of contents
- Intro
- Waffles Crypt Usage
- Making code as modular as Lego
- Using structures to store menu options
- Create Custom Deobfuscation Functions
- Understanding how evasion techniques affect Binary Entropy
- Conclusion
Intro
I started this project to practice the encryption and obfuscation techniques I learned with Maldev Academy. My goal was to create a tool that not only allows me to combine these techniques but also generates unique, ready-to-use code each time, ensuring it runs as expected with just a simple copy and paste.
Here are the available options:
Waffles Crypt Usage
As mentioned earlier, you have the flexibility to perform a simple encryption or obfuscation, or you can combine techniques, such as AES encryption with IPv4 obfuscation. You can even apply all available methods together, as illustrated in the image above!
The program generates files containing the necessary functions for decryption and deobfuscation, as shown below:
Here’s a snippet from the AES decryption file, that uses runtime XOR key brute-forcing and obfuscation:
You can directly copy this code into your Visual Studio project, and it will work seamlessly with newly encrypted shellcode:
Making code as modular as Lego
I aimed to make the code as modular as possible for easy reuse.
For instance, I stored all AES decryption functions in a string named aes_functions
. This allows me to simply add the aes_functions
string whenever the script uses AES decryption.
Another example is the AES encryption with runtime brute-force key option. I included the xor_functions
and bruteforce_key_functions
strings, which are essential for both brute-forcing the key at runtime and decrypting it.
This modular approach also proved useful for incorporating the functions needed for deobfuscation, as shown below:
Using structures to store menu options
To make the interface more user-friendly, I opted for a menu instead of command-line arguments. However, this made it challenging to store the user-selected options in variables.
This was particularly true when dealing with numerous individual files in the project, as I did:
The solution I developed was to use a C structure to store all the user’s selected options. This allowed me to pass the structure as a single argument, instead of multiple variables:
Create Custom Deobfuscation Functions
As mentioned earlier, I developed custom functions for shellcode deobfuscation.
For instance, when deobfuscating IPv4-obfuscated shellcode, you might typically use the WinAPI function RtlIpv4StringToAddressA
. This simplifies the deobfuscation process, as you only need to provide the IPv4 address, and it gets converted back to shellcode.
However, relying on such API functions can raise suspicion, as an EDR can easily detect that you’re converting IP addresses into shellcode, an operation that is uncommon in legitimate applications
To avoid this while retaining the benefits of shellcode obfuscation, I created custom deobfuscation functions, which aren’t particularly difficult to implement.
I wrote these functions in C/C++, which added some complexity. However, using resources like GeeksforGeeks, you can easily create your own.
Theoretically, converting an IPv4 address back to shellcode is straightforward if you understand the obfuscation process.
For example, to convert the shellcode 0x12 0x13 0x14 0x15
into an IPv4 address, you can convert 0x12
to its decimal equivalent and store it using sprintf
like this:
1
2
3
4
5
6
7
// Function takes in 4 raw bytes and returns them in an IPv4 string format
char* GenerateIpv4(int a, int b, int c, int d) {
char* Output = (char*)HeapAlloc(GetProcessHeap(), 0, 32);
sprintf(Output,"\"%d.%d.%d.%d\"", a, b, c, d);
return (char*)Output;
}
This would output the IPv4 address 18.19.20.21
.
To reverse this process, you can split the address by the dots using the GeeksforGeeks tutorial. Then, convert the numeric strings into integers with atoi()
and store them in an integer array, which would look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main()
{
char str[] = "18.19.20.21";
// Returns first token
char *token = strtok(str, ".");
int temp_array[4];
// Keep printing tokens while one of the
// delimiters present in str[].
while (token != NULL)
{
temp_array[index] = atoi(token);
token = strtok(NULL, ".");
}
return 0;
}
// temp_array = {18,19,20,21}
Next, you just need to convert the integer to hexadecimal and store it in an unsigned char array. Like so:
1
sprintf(temp_char, "0x%02X", temp_array[i]);
Using this method, you can create your own custom deobfuscation functions!
Understanding how evasion techniques affect Binary Entropy
This project also helped me understand which techniques resulted in lower levels of binary entropy.
Binary entropy can serve as an indicator of whether a file is malicious. Malware typically contains a significant amount of shellcode, which is highly random, resulting in high entropy levels compared to benign code. In contrast, files containing structured data, such as English text, show lower entropy due to their more predictable patterns.
Below is an image from Practical Security Analytics, demonstrating how file entropy can be used for threat detection and hunting.
So, maintaining “good” entropy values is crucial for staying stealthy.
Below are the entropy values for various techniques using AES encryption.
Interestingly, the technique of brute-forcing the key at runtime had the lowest entropy. This could be due to the smaller amount of code involved. In larger programs, obfuscating the shellcode or combining multiple techniques might yield better results.
Another finding was that RC4 generally had lower entropy. This makes sense, as TinyAES requires three hexadecimal code arrays (sbox
, rsbox
, and rcon
) as well as the encrypted shellcode, key, and IV; Each contributing to higher entropy levels.
Conclusion
Wrapping up, this project was not only a lot of fun but also a valuable learning experience. It helped me improve my C/C++ programming skills and gave me a much clearer understanding of various encryption and obfuscation techniques, along with how to strategically combine them for better results.
While I can’t share the full project due to the possibility of getting the project code signatured, I hope this breakdown has given you a solid starting point or at least sparked some ideas for your own exploration of encryption/obfuscation techniques.
Thanks for following along, and happy hacking! :)