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

Для решения этой проблемы хакеры используют техники упаковки, шифрования и мутации кода. Такие техники часто реализуют отдельные инструменты — «крипторы» (crypters) или просто «пакеры». В этой статье на примере банковского трояна RTM мы рассмотрим, какие «пакеры» могут использовать злоумышленники и как эти «пакеры» осложняют обнаружение ВПО.

Полная версия данного исследования доступна по ссылке.


Packer-as-a-service

Хакерская группа, стоящая за распространением RTM, до конца 2020 года регулярно проводила массовые фишинговые рассылки с вредоносными вложениями. Этот процесс, по всей видимости, происходил автоматически.

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

Пример архива RTM
Пример архива RTM

Подобная особенность — естественное следствие применения «крипторов». Первоначально группа, стоящая за RTM, использовала свой собственный уникальный «криптор», однако в течение 2020 года дважды его сменила.

При исследовании по-новому упакованных образцов нам удалось обнаружить множество другого ВПО, которое было защищено аналогичным образом. Пересечения с другими вредоносами с учетом автоматизации процесса упаковки, на наш взгляд, позволяют говорить об использовании злоумышленниками модели packer-as-a-service. В этой модели упаковка вредоносных файлов делегируется специальному сервису, которым управляет третья сторона. Доступ к таким сервисам часто продается на хакерских форумах.

Rex3Packer

Первое использование этого пакера группой RTM, которое нам удалось обнаружить, относится к ноябрю 2019 года. Активное же его применение, по нашим данным, приходится на период апрель—май 2020 года.

Фишинговое письмо RTM, январь 2021
Фишинговое письмо RTM, январь 2021

Нам не удалось связать этот упаковщик с каким-либо из ранее описанных публично, поэтому мы дали ему свое название по трем особенностям его устройства: наличию рекурсии (recursion), реверса битов (reverse) и рефлективной загрузки PE-файлов (reflection) — Rex3Packer.

Алгоритм распаковки

Общий алгоритм извлечения полезной нагрузки выглядит так:

  1. С помощью VirtualAlloc выделяется заранее определенное количество памяти с правами на чтение, запись и исполнение.

  2. В выделенный буфер копируется содержимое образа текущего процесса в памяти (в частности, секция .text).

  3. Управление передается на функцию внутри буфера.

  4. Вычисляется разница между положением одних и тех же данных в буфере и в образе PE-файла (разность между адресами в буфере и виртуальными адресами в образе). Эта разность заносится в регистр ebx. Все обращения к виртуальным адресам в коде проиндексированы содержимым этого регистра. За счет этого везде, где это необходимо, к адресам из PE-образа добавляется поправка, которая позволяет получить соответствующий адрес в буфере.

  5. Выделяется еще один буфер под упакованные данные.

  6. Через вызов VirtualProtect устанавливаются права RWX на весь регион памяти с образом PE-файла.

  7. Упакованные данные копируются в свой буфер.

  8. Происходит декодирование упакованных данных.

  9. Регион памяти с образом PE заполняется нулевыми байтами.

  10. Декодированные данные представляют собой исполняемый файл — PE-нагрузку. Эта полезная нагрузка рефлективно загружается на место исходного PE-образа, а управление передается на ее точку входа.

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

Непосредственно упакованным данным предшествует заголовок размером 16 байт, который содержит 4 поля по 4 байта:

  • размер самого заголовка,

  • размер исходных данных (PE-нагрузки),

  • позиция в исходных данных (*), по которой происходит их разделение,

  • режим кодирования (1, 2, либо 4).

Декодирование выполняется следующим образом:

  1. Внутри каждого байта выполняется реверс порядка битов (к примеру, 10011000 становится 00011001).

  2. В зависимости от режима кодирования (1, 2, 4), данные разбиваются на блоки размером N = 9, 5, либо 3 байта соответственно. Результат декодирования блока — это (N – 1) байт (то есть 8, 4, или 2).

  3. В первых N-1 байтах блока отсутствует часть битов: их значения всегда равны нулю. Чтобы восстановить оригинальные байты, с помощью масок вида 00000001, 00010001 или 01010101 из последнего байта блока извлекаются недостающие биты. При этом для каждого следующего байта маска сдвигается. То есть последний байт блока фактически составлен из объединенных логической операцией OR битов, которые извлечены из предыдущих байтов.

Например, в режиме 4 последний байт состоит из четных битов первого байта блока и нечетных битов второго байта блока. В результате возврата этих битов в первый и второй байты получается оригинальная последовательность из двух байт.

Схема получения исходных байтов в режиме 4
Схема получения исходных байтов в режиме 4

4. После операции по восстановлению битов во всех блоках полученные данные представляют собой исходный PE-файл, который был разделен по позиции (*) на две части. Эти части, в свою очередь, были переставлены между собой. Обратная перестановка с учетом значения (*) позволяет получить оригинальный файл.

Обфускация

Чтобы усложнить анализ кода, в пакере применяется различного рода запутывание:

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

                Примеры вызова функций WinAPI
                Примеры вызова функций WinAPI
  • Характерная особенность данного пакера — наличие циклов (не выполняющих полезных операций), которые реализуются через рекурсивную функцию.

Функция с рекурсивным вызовом (вариант без обфускации)
Функция с рекурсивным вызовом (вариант без обфускации)
  • Для дополнительного запутывания в исполняемый файл добавляется несколько десятков случайно сгенерированных функций. Они могут ссылаться друг на друга, но в процессе работы ни одна из них не получает управления.

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

Кроме экземпляров RTM, мы обнаружили использование Rex3Packer для упаковки различного ВПО, в основном из стран СНГ.

Семейство ВПО

SHA256

Phobos Ransomware

6e9c9b72d1bdb993184c7aa05d961e706a57b3becf151ca4f883a80a07fdd955

Zeppelin Ransomware

8d44fdbedd0ec9ae59fad78bdb12d15d6903470eb1046b45c227193b233adda6

Raсcoon Stealer

3be91458baa365febafb6b33283b9e1d7e53291de9fec9d3050cd32d98b7a039

KPOT Stealer

9b6af2502547bbf9a64ccfb8889ee25566322da38e9e0ccb86b0e6131a67df1e

Predator The Thief

d1060835793f01d1e137ad92e4e38ef2596f20b26da3d12abcc8372158764a8f

QakBot

18cc92453936d1267e790c489c419802403bb9544275b4a18f3472d2fe6f5dea

Также мы отметили использование пакера для упаковки экземпляров ВПО из семейств Nemty, Pony, Amadey.  

HellowinPacker

В мае 2020 группа RTM переключилась на использование нового упаковщика — HellowinPacker, который продолжала активно использовать до начала 2021 года. Ключевой особенностью этого пакера является два уровня мутации кода. Первый из них существенно меняет структуру кода распаковки, делая различные образцы не похожими друг на друга.

Сравнение кода в двух экземплярах разной структуры
Сравнение кода в двух экземплярах разной структуры

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

Сравнение кода в двух экземплярах одной структуры
Сравнение кода в двух экземплярах одной структуры

Так же, как и в случае с Rex3Packer, мы имеем дело с массовым использованием HellowinPacker для упаковки различного ВПО. При этом вредоносное ПО из одного семейства, как правило, имеет в упакованном виде одну и ту же структуру. Это можно пронаблюдать, по крайней мере, на протяжении некоторого времени — затем структура может измениться.

Алгоритм распаковки

Одни из первых действий, которые встречаются во всех упакованных файлах — это попытки открыть ключ реестра HKEY_CLASSES_ROOT\Interface\{b196b287-bab4-101a-b69c-00aa00341d07} (регистр символов в конкретном случае может отличаться) и запросить в нем значение по умолчанию (Default). От успешности этих операций в некоторых модификациях генерируемого кода зависит корректное продолжение работы программы.

GUID интерфейса в разных случаях также может отличаться.

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

Этот блок начинается с четырехбайтного числа, которое хранит размер исходных данных (тех, которые будут получены после декодирования). Вызовом VirtualAlloc под расшифрованные данные выделяется блок памяти нужного размера с правами RWX. В выделенную память блоками по X байт копируются зашифрованные данные. При этом в оригинальном файле между этими блоками располагаются пропуски длиной Y байт.

Схема копирования данных в HellowinPacker
Схема копирования данных в HellowinPacker

Затем происходит процесс дешифровки блоками по 4 байта:

  • очередной блок интерпретируется как целое число (DWORD),

  • к его значению прибавляется индекс первого байта в блоке,

  • выполняется операция xor между полученным значением и суммой индекса и фиксированного ключа, числа Z.

Обфускация

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

Точка входа в одной из упакованных библиотек
Точка входа в одной из упакованных библиотек

Дополнительным эффектом от такого использования WinAPI становится невозможность детектирования по списку импортируемых функций и imphash.

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

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

HellowinPacker существует по крайней мере с 2014 года. За это время он был использован в различном массовом вредоносном ПО. Вот лишь несколько примеров:

Семейство ВПО

SHA256

Cerber Ransomware

1e8b814a4bd850fc21690a66159a742bfcec212ccab3c3153a2c54c88c83ed9d

ZLoader

44ede6e1b9be1c013f13d82645f7a9cff7d92b267778f19b46aa5c1f7fa3c10b

Dridex

f5dfbb67b582a58e86db314cc99924502d52ccc306a646da25f5f2529b7bff16

Bunitu

54ff90a4b9d4f6bb2808476983c1a902d7d20fc0348a61c79ee2a9e123054cce

QakBot

c2482679c665dbec35164aba7554000817139035dc12efc9e936790ca49e7854

Заключение

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

Описанные нами упаковщики, конечно же, далеко не единственные на рынке. При этом они хорошо демонстрируют общие свойства подобных инструментов: в результате их работы получается исполняемый файл с обфусцированным полиморфным кодом распаковщика и шифрованной полезной нагрузкой. Мутации в коде и использование одних и тех же крипторов делают практически невозможным статическое детектирование полезной нагрузки. Однако, поскольку эта нагрузка так или иначе расшифровывается в память и начинает свою вредоносную деятельность, поведенческий анализ с использованием песочниц (таких, как PT Sandbox) позволяет обнаруживать ВПО и выносить точные вердикты даже для упакованных файлов. Следует также отметить, что упаковщики никак не влияют на взаимодействие вредоносов с управляющими серверами. Это дает возможность определять присутствие ВПО в сети, используя инструменты анализа трафика — такие, как PT Network Attack Discovery.

Автор: Алексей Захаров