I'm writing this blog post as I came across this problem myself while trying to encrypt and decrypt my shellcode while writing malware.
As there are lots of easy to implement encryption and decryption mechanisms like XOR, they aren't resilient enough. Even though with proper XORring of the shellcode; while many AVs and EDRs may be bypassed, the shellcode can easily be decrypted using manual trial-and-error technique. Hence, the best way to go about encrypting the shellcode would be using the top-of-the-shelf encrypting algorithm like AES. Using AES algorithm to encrypt and decrypt shellcode using Windows Cryptography API (https://learn.microsoft.com/en-us/windows/win32/seccng/typical-cng-programming) is fine, they can easily be hooked by EDRs and all the work we did would be for naught.
The best way to protect our shellcode would be using a local implementation of AES which wouldn't be hooked by EDRs and at the same time protect from spying and static reversing.
For this reason, I'm choosing to use the `tiny-aes-c` library written by kokke (https://github.com/kokke/tiny-AES-c). There's only a header and the implementation file to import to our own project and we're ready to go. The only complain I have with this library is that it doesn't automatically pad our keys, IV and shellcode to the block size (16 bytes / 128 bits). This means we need to pad all of our stuff by ourselves. Well any ways lets make it all clear by encrypting some sample data using AES-128.
The skeleton code for using the `tiny-AES-c` would look something like this:
unsigned char key[] = "xxxxxx"; unsigned char iv[] = "\x00\x01\x02\x03\x04\x05"; unsigned char shellcode[] = "\x2e\x51\x54\xf6\xa5\xbf\xb7\xe5\x06\x61\x62\x76\x4e\xe1\xa9\x01\xb9\x1f\x68\xfe\xb9"; struct AES_ctx ctx; AES_init_ctx_iv(&ctx, key, iv); AES_CBC_encrypt_buffer(&ctx, shellcode, sizeof(shellcode) - 1); printf("Encrypted shellcode:\n"); for (int i = 0; i < sizeof(shellcode) - 1; i++) {
printf("\\x%02x", shellcode[i]);
}
Well, the code above is fine with a few catches. First is that we need to make sure our key and the initialization vector (iv) are both 16 bytes in length. This is because the AES algorithm and the tiny-aes-library encrypts data in blocks of 16 bytes using the key and IV.
unsigned char key[] = "asdfghjklzxcvbn"; unsigned char iv[] = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e";
As you can see, I've declared both the variables with 15 characters. This is because a end of string \x00 is added automatically to denote end of a char[] variable by the compiler. Therefore, it makes it 16 bytes in total.
Now, we also need to pad our shellcode to a multiple of 16 bytes too because of the requirement of the library, however this can get bothersome when we have to keep changing our shellcodes for various reasons. For this specific reason, I've created two functions to aid in using tiny-aes-c library.
The first function is:
int calculatePaddingLen(int shellcode_len) { int shellcode_spill = shellcode_len % 16; int fillers_required_len = 16 - shellcode_spill; return fillers_required_len; }
The code is self explanatory. It basically calculates the padding length requires beyond the shellcode length to make the shellcode a multiple of 16 bytes.
The second function is:
unsigned char* padShellcode(unsigned char* shellcode, int shellcode_len) { int fillers_required_len = calculatePaddingLen(shellcode_len); int final_shellcode_size = shellcode_len + fillers_required_len; unsigned char* new_shellcode_memory = (unsigned char *)malloc(final_shellcode_size); memcpy(new_shellcode_memory, shellcode, shellcode_len); int x = 0; for (x = shellcode_len; x < shellcode_len + fillers_required_len; x++) { new_shellcode_memory[x] = '\x00'; } return (unsigned char*)new_shellcode_memory; }
unsigned char key[] = "K@fatm2040qwerp"; // 15 bytes plus \x00 = 16 bytes, 128 bits unsigned char iv[] = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e"; // 15 bytes plus \x00 unsigned char shellcode[] = "\x2e\x51\x54\xf6\xa5\xbf\xb7\xe5\x06\x61\x62\x76\x4e\xe1\xa9\x01\xb9\x1f\x68\xfe\xb9"; // random number of bytes int shellcodeSize = sizeof(shellcode); shellcodeSize = shellcodeSize - 1; // need to do this to remove the last \x00 end of string byte unsigned char* paddedShellcode = padShellcode(shellcode, shellcodeSize); int paddingLength = calculatePaddingLen(shellcodeSize); int finalShellcodeLength = shellcodeSize + paddingLength; struct AES_ctx ctx; AES_init_ctx_iv(&ctx, key, iv); AES_CBC_encrypt_buffer(&ctx, paddedShellcode, finalShellcodeLength); printf("Encrypted buffer:\n"); for (int i = 0; i < finalShellcodeLength; i++) { printf("\\x%02x", paddedShellcode[i]); } // remember to free the allocated memory bruvv free(paddedShellcode);
All done! We can now just copy the padded and printed bytes and paste it into our malware injector which decrypts and injects the shellcode.
Just for completion's sake, the decryption code would look something like:
unsigned char key2[] = "K@fatm2040qwerp"; // 15 bytes plus \x00 = 128 bits unsigned char iv2[] = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e"; // 15 bytes plus \x00 unsigned char shellcode_enc[] = "\x3d\x78\x6c\x5d\xa0\x55\xe5\xe1\x14\xb0\x51\xd7\x65\x6b\x4c\x20\x77\x7f\x07\x22\xcc\xa4\xec\xf7\x5e\xc8\x09\xdd\x97\x71\xa6\x64"; // This is the encrypted shellcode from our code above. int encShellcodeSize = sizeof(shellcode_enc) - 1; struct AES_ctx ctx2; AES_init_ctx_iv(&ctx2, key2, iv2); AES_CBC_decrypt_buffer(&ctx2, shellcode_enc, encShellcodeSize); printf("Decrypted buffer:\n"); for (int i = 0; i < encShellcodeSize; i++) { printf("\\x%02x", shellcode_enc[i]); }
Make sure the key and IV of both the encryption and decryption code is the exact same, otherwise the encryption and decryption won't occur properly.
And there we go. We now have a clear understanding of encryption and decryption using the `tiny-aes-c` library with all the gotchas and its fixes.
The full code can be found in this gist: https://gist.github.com/emalp/124e71c5b0497110368a78fcc46863ea