В продолжение предыдущей статьи (Так ли мёртв emotet?!) хотели бы рассказать о технических особенностях процесса распаковки основной нагрузки ВПО семейства «Еmotet».
Напоминаем, основная нагрузка представляет собой «Dll» файлы, запускаемые через системную утилиту regsvr32.exe.
В библиотеке есть несколько экспортируемых функций, основные используемые:
«DllMain» – вызывается при использовании библиотеки, как правило, при событии DLL_PROCESS_ATTACH, в контексте работы данного ВПО восстанавливает основную нагрузку в памяти процесса;
«DllRegisterServer» – вызываемая утилитой regsvr32.exe при регистрации библиотеки. Основная задача – передать управление на восстановленный код в памяти.
Процесс восстановления основной нагрузки в функции «DllMain» делится на два этапа.
Первый этап восстановления основной нагрузки.
На первом этапе восстанавливается бинарный код. Исходный бинарный код записывается в стек и имеет размер 0xbd9.
Для восстановления, исходные байты бинкода проходят операцию xor фиксированным ключом, по три байта за цикл. В качестве ключа используется следующая строка:
«N1kj^H<M1vf@$_yiXP+o*hH*fZQl5vC5qjfXErgxjcCb4v_e75<edkge!z$U9k+h»
Указанная выше строка является идентификатором экземпляра, может изменяться от версии к версии и используется далее для получения кода основной нагрузки.
После восстановления бинкода ему передаётся управление. В качестве параметра передаётся указатель на ключ – идентификатор экземпляра.
Бинкод использует интересный способ получения адресов функций, проще говоря, собственную реализацию системной функции «GetProcAddress». Схожий алгоритм был обнаружен в «payload WarzoneRAT».
Первый этап работы функции заключается в получении структуры PPE_LDR_DATA их PEB (Process envoriment block). Из структуры PPE_LDR_DATA получается указатель на начало связного списка импортированных модулей, структура LDR_DATA_TABLE_ENTRY.
Полную информацию об указанных структурах можно найти в официальной документации:
(https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data)
Функция использует два поля из структуры LDR_DATA_TABLE_ENTRY:
- «DllBase» – базовый адрес загрузки модуля в память.
- «FullDllName» – поле, в которое записывается имя импортируемого модуля. Функция парсит
MZ-PE заголовок модуля, и проверяет его на наличие ExportTable. Если модуль содержит таблицу экспорта, то переходит к следующему этапу.
Второй этап восстановления основной нагрузки.
Следующий этап работы функции заключается в построении кодового значения по имени модуля.
Каждый байт циклически сдвигается вправо на 0xd, полученное значение добавляется к результату. Также используется корректировка на регистр букв в имени модуля. У байт, которые соответствуют нижнему регистру, отнимается значение 0x20, что приводит к соответствующему символу в нижнем регистре. То есть, код от NTDLL.DLL и ntdll.dll будет одинаковый.
Далее считываются имена экспортируемых функций модуля по PE заголовку. Для каждого имени функция по схожему алгоритму строит кодовое значение. Однако в алгоритме расчёта кода для имени функции нет коррекции на регистр символов. Код имени модуля и код имени функции суммируются между собой и сравниваются с аргументом функции. Если они совпадают, то возвращается адрес функции.
Первые две импортируемые функции бинкодом – это недокументированные функции «ntdll – LdrLoadDll» – загрузка модуля в память процесса; «LdrGetProcedureAddress» – получение адреса функции из модуля. Далее бинкод получает адреса основных функций – «VirtualAlloc», «VirtualProtect», «FlushInstructionCahe», «GetNativeSystemInfo», «Sleep», «RtlAddFunctionTable», «LoadLibraryA», «FindResourceW», «LoadResource», «SizeofResource», «LockResource», «FreeResource».
Бинкод, используя последовательность функции работы с ресурсами приложения «FindResourceW», «LoadResource», «SizeofResource», – получается доступ к байтам ресурса.
Исходный код основной нагрузки проходит операцию xor с ключом-идентификатором экземпляра для расшифровки данных.
Корректность расшифровки проверяется через magic значение PE заголовка.
ВПО делает следующие проверки:
- поле Machine заголовка file проверяется на соответствие значению 0x8664 (флаг x64).
- Поле sectionaligment проверяется на соответствие значению 0x1000.
- количество секций проверяется п оусловию больше 0.
Память выделяется по смещению 0x180000000. Этот адрес соответствует значению из «ImageBase» библиотеки. Таким способом ВПО пытается скрыть основную нагрузку. В область памяти 0x180000000 посекционно переносится код основной нагрузки. Очищает часть MZ-PE заголовка.
Бинкод и функция «DllMain» завершают свою работу. Управление передаётся на функцию «DllRegisterServer» основная функция которой – парсинг заголовка MZ-PE основной нагрузки. Базовый адрес загрузки передаётся первым аргументом.
«DllRegisterServer» возвращает адрес экспортируемой функции, имя которой передаётся вторым аргументом.
Проанализировав алгоритм распаковки основной нагрузки ВПО семейства «Emotet» можем сделать вывод, что разработчики данного ВПО используют следующие тактики:
— Native API (https://attack.mitre.org/techniques/T1106/);
— Dynamic-link Library Injection (https://attack.mitre.org/techniques/T1055/001/).
Национальный центр реагирования на компьютерные инциденты (CERT.BY) благодарит за взаимодействие контактных лиц и оперативное предоставление информации!!!
Для удобства и своевременного оповещения о новостях подписывайтесь на нас в социальных сетях: