В предыдущей статье мы рассмотрели где хранить полезную нагрузку (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;
}
Формат ключа шифрования/дешифрования
Имейте в виду, что внедрение открытого текстового ключа в бинарный файл считается плохой практикой и может быть легко извлечено при анализе вредоносного ПО. В будущих статьях будут представлены решения, обеспечивающие невозможность легкого извлечения ключа.
Различные методы представления ключа
При реализации процесса шифрования важно правильно представить ключ шифрования. Ключ может быть представлен разными способами в зависимости от конкретных требований к безопасности, удобства и других аспектов.
- Прямое представление ключа в виде строки: Простейший и наиболее понятный способ. Однако, такое представление может быть легко обнаружено и извлечено из исполняемого файла при анализе.
unsigned char* key = "ru-sfera.pw"; - Представление ключа в виде массива шестнадцатеричных байтов: Это немного менее очевидно для человеческого глаза при просмотре исходного кода, но остается простым для анализа.
unsigned char key[] = {
0x6D, 0x61, 0x6C, 0x64, 0x65, 0x76, 0x31, 0x32, 0x33
}; - Представление ключа в виде шестнадцатеричной строки (экранированной последовательностью): Похоже на предыдущий метод, но представляет собой строку.
unsigned char* key = "\x6D\x61\x64\x65\x76\x31\x32\x33"; - Представление ключа через стек строк: Этот метод маскирует ключ, представляя его в виде массива символов. Это может создать дополнительный уровень сложности для автоматических инструментов анализа.
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.
Пример расшифровки данных
Ниже представлена основная функция, которая используется для выполнения процедуры дешифрования.
Для процедуры дешифрования требуются ключ дешифрования, инициализирующий вектор (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;
}
Недостатки библиотеки bCrypt
Одним из основных недостатков использования описанного выше метода для реализации шифрования AES является то, что использование криптографических функций WinAPI приводит к их появлению в таблице импорта адресов (IAT) бинарного файла.
Решения безопасности могут обнаруживать использование криптографических функций, сканируя IAT, что может потенциально указывать на вредоносное поведение или вызвать подозрения.
Скрытие WinAPI в IAT возможно и будет обсуждаться в следующих статьях.
На изображении ниже показана IAT бинарного файла, использующего Windows API для шифрования AES. Использование библиотеки crypt.dll и криптографических функций явно видно.
Обход статического анализа Microsoft Defender
Пример использования алгоритмов шифрования XOR, RC4 и AES для обхода статического анализатора Microsoft Defender.
На этом этапе учебного модуля полезная нагрузка (payload) не выполняется, она просто выводится на консоль. Поэтому в этом модуле основное внимание уделяется исключительно обходу статического анализа и сигнатурному обнаружению.
Каждый из примеров кода использует шелл-код Msfvenom.
Необработанный шелл-код (Raw Shellcode) - обнаружен Defender'ом.
XOR-шифрованный шелл-код - успешно обходит Defender.
AES-шифрованный шелл-код - успешно обходит Defender.
RC4-шифрованный шелл-код - успешно обходит Defender.
Последнее редактирование: