Использование IDA+IDAPython+Xdbg при восстановлении обфусцированного семпла

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

При изучении вредоносных программ динамического анализа в изолированных системах-песочницах в большинстве случаев достаточно для того, чтобы выявить природу той или иной угрозы и на основе полученной информации пополнить вирусную базу антивируса. Однако иногда в вирусную лабораторию попадают образцы, требующие глубокого понимания непосредственного алгоритма их работы. И тогда без статического анализа уже не обойтись. Например, для бэкдоров может понадобиться разбор протокола общения с C&C-сервером или алгоритма генерации доменных имен. А при анализе шифровальщиков необходимо выяснять, каким алгоритмом и какими ключами шифровались файлы. При этом часто такие троянские приложения запакованы или обфусцированы, и потому статический анализ может быть осложнен. Пример разбора такой сложной угрозы мы сегодня и рассмотрим. 

Итак, перед нами некая вредоносная программа для платформы Windows. Первым делом загрузим ее в IDA Pro Disassembler и попытаемся выполнить автоанализ. Как ни странно, это нам не удается, поскольку тот спотыкается уже в самом начале — на функции Main. Это значит, что для разбора образца нам потребуется приложить определенные усилия.

Рисунок 1 — Ошибка автоанализа.
Рисунок 1 — Ошибка автоанализа.

Анализ ассемблерного листинга расставит все на свои места

 Переоткроем файл в IDA с выключенным анализатором и приступим к разметке инструкций вручную. Перед нами предстает обфускация с перепрыгиванием между мусорными участками кода — так называемая jmp-вермишель. Рассмотрим алгоритм этой обфускации. Для наглядности фрагмент соответствующего кода показан на изображении ниже.

Рисунок 2 — Логика обфускации в исследуемом образце.
Рисунок 2 — Логика обфускации в исследуемом образце.

Итак, мы видим, что регистр ESP уменьшается на заданное значение, затем следуют 2 или 3 инструкции, задающие некие значения регистрам с последующим их сравнением. Далее идет условный переход, который на самом деле является безусловным, поскольку значения сравниваемых величин не вычисляются, а прописаны непосредственно в коде. Вслед за этим переходом регистр ESP возвращается к исходному значению. Затем инструкция CALL записывает в стек адрес возврата (в данном случае это адрес 4142D1), а вызванная функция увеличивает это значение. После исполнения инструкции RET указатель инструкций имеет значение 414306. И по адресу 414306 мы видим начало перехода на точно такой же шаблон перепрыгивания. Подобными кусками бессмысленного кода наполнен весь файл. Поэтому пока мы не избавимся от этого мусора, о статическом анализе можно забыть.

Убираем jmp-вермишель

Чтобы разобраться с обфускацией, мы воспользуемся IDAPython — встроенным в IDA Pro интерпретатором Python. С его помощью напишем специальный скрипт поиска выявленного нами шаблона обфускации и пропатчим образец так, чтобы «нейтрализовать» jmp-вермишель.

Назовем один из методов созданного нами скрипта SizeOfObfuscatedCode. В этом методе с помощью idaapi и функций по декодированию инструкций реализуем поиск нужного нам шаблона. На входе каждая инструкция будет брать предполагаемый адрес мусорного блока и число инструкций, которые будут идти после первого регистра SUB ESP. Напомним, что в мусорном блоке задействуется 2 или 3 инструкции по записи неких значений регистров и их сравнению.

Рисунок 3 —Метод SizeOfObfuscatedCode, помогающий искать мусорные участки кода, участвующие в обфускации.
Рисунок 3 —Метод SizeOfObfuscatedCode, помогающий искать мусорные участки кода, участвующие в обфускации.

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

 Другой метод (назовем его CleanCode) будет брать диапазон адресов исполняемой секции, где фактически расположен весь код программы, и выполнит поиск в нем байтовой последовательности 83 EC. Эта последовательность означает инструкцию SUB ESP и, собственно, начало мусорного блока. Для найденного адреса будет вызываться метод SizeOfObfuscatedCode. Если в результате исполнения возвратится ненулевой результат (обнаруживается целевой шаблон), мусорный блок заполняется байтами 0x90, то есть инструкциями NOP.

Рисунок 4 — Метод CleanCode
Рисунок 4 — Метод CleanCode

 Запускаем наш скрипт, ждем окончания его работы и сохраняем пропатченный файл, в котором фрагменты кода с обфускацией фактически обнулились. Если теперь открыть образец в 16-ричном редакторе, в коде перед нами предстанут последовательности из NOP-инструкций. Мы успешно избавились от мусора. 

Рисунок 5 — фрагменты мусорного кода с обфускацией заменены последовательностью NOP-инструкций.
Рисунок 5 — фрагменты мусорного кода с обфускацией заменены последовательностью NOP-инструкций.

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

Рисунок 6 — Корректная работа декомпилятора с пропатченным файлом.
Рисунок 6 — Корректная работа декомпилятора с пропатченным файлом.

 При беглом осмотре декомпилированных функций находим секцию, наиболее вероятно отвечающую за получение адресов WinAPI-функций. Здесь одна и та же функция для различных DWORD (контрольных сумм) возвращает определенные значения. Запускаем отладку файла и обнаруживаем, что полученная таблица импорта не содержит никаких адресов. Копаем немного глубже и понимаем, что значения из этой таблицы проходят через функцию sub_40DEA0 непосредственно перед вызовом каждой функции из таблицы. Фактически sub_40DEA0 используется для раскодирования реального адреса вызова. Ей отдается кодированное значение целевой WinAPI-функции, она возвращает ее реальный адрес, после чего функция вызывается по этому адресу.

Рисунок 7 —  Фрагмент кода, вероятно отвечающий за формирование таблицы импорта.
Рисунок 7 —  Фрагмент кода, вероятно отвечающий за формирование таблицы импорта.

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

Рисунок 8  —  Фрагмент кода с примером вызова WinAPI-функции.
Рисунок 8  —  Фрагмент кода с примером вызова WinAPI-функции.

Восстанавливаем таблицу импорта

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

При этом обратите внимание, что в найденной нами таблице адресов значение адреса одной из функций, как ни странно, оказалось реальным. Можно предположить, что троян использует его без предварительного раскодирования. Соответственно, при восстановлении таблицы импорта этот адрес следует пропустить.

Рисунок 9 —  Таблица с закодированными адресами, среди которых один — реальный.
Рисунок 9 —  Таблица с закодированными адресами, среди которых один — реальный.

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

Рисунок 10 — Функция, с помощью которой мы восстанавливаем таблицу импорта.
Рисунок 10 — Функция, с помощью которой мы восстанавливаем таблицу импорта.

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

Рисунок 11 — Таблица с раскодированными адресами WinAPI-функций.
Рисунок 11 — Таблица с раскодированными адресами WinAPI-функций.

Теперь при помощи утилиты Scylla собираем целевые адреса из отлаживаемого образца, создаем дамп процесса и на основе этих данных реконструируем таблицу импорта.

Рисунок 12 — Восстановление таблицы импорта с использованием Scylla.
Рисунок 12 — Восстановление таблицы импорта с использованием Scylla.

 Успешно восстановленная таблица представлена на скриншоте ниже.

Рисунок 13 —  Реконструированная таблица импорта.
Рисунок 13 —  Реконструированная таблица импорта.

На основании информации о полученных адресах мы можем предположить, что перед нами вредоносная программа-вымогатель, а именно — шифровальщик. Так, здесь присутствуют функции FindFisrt* и FindNext* для обхода дисков и дерокторий, MoveFile, которую энкодеры обычно используют для переименования зашифрованных файлов, а также функции BCrypt* для генерации криптографических ключей и непосредственно выполнения шифрования.

 В пропатченном нами файле остаются теперь уже лишние вызовы функции sub_DEA0:

Однако необходимости в ее вызове больше нет, ведь мы получили реальные адреса. Вновь воспользуемся IDAPython. Напишем небольшой скрипт, который для заданного адреса пропатчит программу так, чтобы MOV ECX поменялся на MOV EAX, а вызов sub_136DEA0 вообще исчез. Для этого нам необходимо встать на начало этой ненужной функции, с помощью метода CodeRefsTo получить все адреса, откуда данная функция вызывается, и передать их в метод PatchCall скрипта.

Наш скрипт:

А это — результат его работы:

Как мы видим, вызовы пропатчены, и теперь IDA Pro понимает, что именно вызывается при выполнении инструкции CALL EAX.

В итоге после финальной декомпиляции файла в IDA Pro в функциях можно отчетливо увидеть все признаки трояна-шифровальщика:

  • Обход директорий.

  • Сбрасывание флага Read-only у файла, подлежащего шифрованию.

  • Создание потока для шифрования целевого файла.

  • Использование криптографических функций для генерации ключей шифрования.

Рисунок 17 — Обход директорий.
Рисунок 17 — Обход директорий.
Рисунок 18 — Сбрасывание Read-only флага у файла и создание потока для его шифрования .
Рисунок 18 — Сбрасывание Read-only флага у файла и создание потока для его шифрования .
Рисунок 19 —  Использование криптографических функций для генерации ключей шифрования
Рисунок 19 —  Использование криптографических функций для генерации ключей шифрования

Наконец-то мы можем приступить непосредственно к анализу алгоритма работы шифровальщика. 

Источник: https://habr.com/ru/companies/drweb/articles/748822/


Интересные статьи

Интересные статьи

Автор статьи: Артем Михайлов Микросервисная архитектура – одна из самых популярных подходов к разработке современных приложений. Благодаря ее гибкости и масштабируемости, разработчики могут лег...
В предыдущей статье мы закончили на том, что реализовали полностью законченное подмножество нашего учебного языка, в котором есть целые и вещественные числа, функции и множество управляющих конструкци...
Terraform позволяет организовывать свой код так, как вам хочется.Это обеспечивает большую гибкость и позволяет легко начать работу, просто поместив несколько ресурсов в файл и запустив terraform apply...
Привет! Меня зовут Иван, я занимаюсь бэкенд-разработкой в Ozon: пишу микросервисы на Go для личного кабинета продавца. В прошлом году мы запустили новый процесс регистрации продавцов, в котором задейс...
В рамках проекта Фото-Географического Атласа России (photogeomap.ru) мы собрали ряд фотографий различных ландшафтов страны. Многие из них сделаны в достаточно труднодосту...