
Автоматический распаковщик и регистратор для файлов, нацеленных на DotNet Framework
Автоматический распаковщик и логгер для файлов, нацеленных на DotNet Framework! Этот инструмент был представлен на Black Hat USA 2022. На Black Hat Asia 2023 были опубликованы DotDumperGUI и DotDumperNative, а также версия 1.1-stable DotDumper. Эти три инструмента предназначены для совместного использования: DotDumper 1.1-stable требует наличия DLL от DotDumperNative, а DotDumperGUI служит графическим интерфейсом для открытия и фильтрации JSON-вывода результатов работы DotDumper.
Автоматическое обнаружение и классификация любого файла надёжным способом часто считается Святым Граалем анализа вредоносного ПО. Трудностей и препятствий на пути к этому немало, поэтому создание такой системы высоко ценится. Когда речь идёт о двоичных файлах, ориентированных на DotNet, наш новый инструмент с открытым исходным кодом DotDumper призван помочь на нескольких ключевых этапах: логирование (в памяти) активности, дамп интересующих сегментов памяти и извлечение характеристик из заданного образца.
Коротко говоря, ручная распаковка — утомительный процесс, требующий от аналитиков непропорционально много времени. Обфусцированные двоичные файлы ещё больше увеличивают время, необходимое аналитику для распаковки конкретного файла. При масштабировании организациям требуется множество аналитиков, ежедневно препарирующих вредоносное ПО, вероятно, в сочетании с масштабируемой песочницей. Потерянное драгоценное время можно было бы использовать для изучения интересных кампаний или образцов с целью выявления новых угроз, а не для разбора массового повседневного вредоносного ПО. В конце концов, аналитики ищут иголки в стоге сена.
Итак, чем же DotDumper отличается? Запуск образца вредоносного ПО на базе DotNet с помощью DotDumper предоставляет файлы журналов ключевых, контекстуализирующих и общих вызовов функций в трёх форматах (читаемый человеком простой текст, JSON и XML), а также копии полезных сегментов памяти. Таким образом, аналитик может быстро просмотреть журнал вызовов функций. Кроме того, сброшенные файлы можно просканировать для их классификации, что даёт дополнительное понимание образца вредоносного ПО и содержащихся в нём данных. Это сокращает время, жизненно важное для процессов триажа и реагирования на инциденты, и высвобождает время аналитиков SOC и исследователей для более сложных задач анализа.
Для логирования и дампа контекстуализирующих вызовов функций и их результатов DotDumper использует смесь рефлексии и управляемых хуков, полностью написанных на чистом C#. Ниже будут выделены и подробно описаны ключевые возможности, в сочетании с выдержками из результатов работы DotDumper над упакованным образцом стилера AgentTesla, хеши которого приведены ниже.
| Тип хеша | Значение хеша |
|---|---|
| SHA-256 | b7512e6b8e9517024afdecc9e97121319e7dad2539eb21a79428257401e5558d |
| SHA-1 | c10e48ee1f802f730f41f3d11ae9d7bcc649080c |
| MD-5 | 23541daadb154f1f59119952e7232d6b |
DotDumper доступен через интерфейс командной строки с различными аргументами. Изображение ниже показывает меню справки. Обратите внимание, что будут обсуждены не все аргументы, а только наиболее часто используемые.

Минимальное требование для запуска образца — указать аргумент «-file» вместе с именем файла или путём к файлу. Если указан полный путь, он используется. Если указано только имя файла, проверяется текущий рабочий каталог, а также папка, в которой находится исполняемый файл DotDumper.
Если имя каталога не указано, имя папки «-log» устанавливается равным имени файла образца без расширения (если таковое имеется). Папка располагается в той же директории, что и DotDumper, и в ней будут сохранены журналы и сброшенные файлы.
В случае библиотеки или альтернативной точки входа в двоичный файл необходимо переопределить точку входа с помощью «-overrideEntry true». Кроме того, необходимо указать полностью квалифицированный класс, включая пространство имён, с помощью «-fqcn My.NameSpace.MyClass». Это сообщает DotDumper, какой класс выбрать, из которого будет извлечено указанное имя функции (с помощью «-functionName MyFunction»).
Если выбранная функция требует аргументов, необходимо указать количество аргументов с помощью «-argc» и количество требуемых аргументов. Типы и значения аргументов должны быть указаны в формате «string|myValue int|9». Обратите внимание, что если в значениях используются пробелы, аргумент в командной строке необходимо заключить в кавычки, чтобы он был передан как один аргумент.
Другие реже используемые опции, такие как «-raceTime» или «-deprecated», безопасны в настройках по умолчанию, но могут потребовать настройки в будущем из-за изменений в DotNet Framework. В настоящее время они доступны в интерфейсе командной строки, чтобы при необходимости можно было легко внести изменения, даже если вы используете старую версию DotDumper.
Логирование и дамп — две основные функции DotDumper. Чтобы минимизировать время анализа, логирование должно предоставлять аналитику контекст. Это достигается за счёт предоставления следующей информации для каждого зарегистрированного вызова функции:
Обратите внимание, что для каждого сброшенного файла имя файла равно его хешу SHA-256.
Чтобы прояснить вышесказанное, ниже приведён фрагмент журнала. В фрагменте показаны детали для упомянутого образца AgentTesla, где он загружает второй этап с помощью функции Assembly.Load из DotNet.
![Журнал для перехваченного вызова функции Assembly.Load(byte[] rawAssembly)](images/assembly_load.png)
Во-первых, указывается местное системное время вместе с типом возврата, именем и аргументами исходной функции. Во-вторых, приводится трассировка стека, показывающая, что главная функция образца ведёт к конструктору, инициализирует компоненты и вызывает две пользовательские функции. Функция Assembly.Load была вызвана из «NavigationLib.TaskEightBestOil.GGGGGGGGGGGGGGGGGGGG(String str)». Это даёт аналитику контекст для поиска кода вокруг этого вызова, если он представляет интерес.
Затем даётся информация о порядке вызовов сборок. Чем больше загружено этапов, тем сложнее понять, через какие этапы был выполнен вызов. Обычно ожидается, что один этап загружает следующий, но в некоторых случаях последующие этапы используют предыдущие в нелинейном порядке. Кроме того, предоставляется информация об исходной сборке для дальнейшего обогащения данных для аналитика.
Далее указывается родительский хеш. Родитель этапа — это предыдущий этап, который в данном примере ещё не представлен. У вновь загруженного этапа этот этап будет родительским. Это позволяет аналитику легче коррелировать события.
Наконец, сохраняется тип и значение возвращаемого значения функции, а также тип, имя и значение каждого аргумента, переданного в перехваченную функцию. Если какая-либо переменная превышает 100 байт, она сохраняется на диск. В журнал вставляется ссылка на файл, а не отображается значение. Порог установлен, чтобы избежать сбоев при выводе журнала, так как некоторые массивы имеют размер в тысячи элементов.
Согласно документации Microsoft, рефлексия лучше всего описывается как «[…] предоставляет объекты, инкапсулирующие сборки, модули и типы». Если кратко, это позволяет динамически создавать и вызывать классы и функции DotNet из образца вредоносного ПО. DotDumper содержит рефлексивный загрузчик, который позволяет аналитику загружать и анализировать как исполняемые файлы, так и библиотеки, если они основаны на DotNet Framework.
Чтобы использовать загрузчик, необходимо переопределить точку входа в интерфейсе командной строки, указать класс (включая пространство имён, в котором он находится) и имя функции в заданном файле. При желании можно передать аргументы указанной функции для всех встроенных типов и их массивов. Примерами встроенных типов являются int, string, char и такие массивы, как int[], string[] и char[]. Все аргументы должны быть указаны через интерфейс командной строки, где указывается и тип, и значение.
Если точка входа не переопределяется, используется точка входа по умолчанию. По умолчанию в главную функцию образца передаётся пустой массив строк, как если бы образец был запущен без аргументов.
Кроме того, загрузчики часто используют рефлексию для вызова определённой функции в определённом классе на следующем этапе. Иногда также передаются аргументы, которые позже используются для дешифрования ресурса. В упомянутом образце AgentTesla разворачивается именно такая ситуация. Соответствующие хуки DotDumper, связанные с вызовом, регистрируют эти действия, как показано ниже.

Имя функции в первой строке не является внутренней функцией DotNet Framework, а скорее вызовом определённой функции во втором этапе. Типы и имена трёх аргументов перечислены в сигнатуре функции. Их значения можно найти в разделе информации об аргументах функции. Это позволило бы аналитику загрузить второй этап в пользовательский загрузчик с заданными значениями аргументов или даже сделать это с помощью DotDumper, загрузив ранее сброшенный этап и предоставив аргументы.
Прежде чем перейти к управляемым хукам, нужно понять, как работают хуки. Здесь есть две основные переменные: целевая функция и контролируемая функция, называемая хуком. Проще говоря, память в целевой функции (например, Assembly.Load) изменяется так, чтобы вместо неё выполнялся переход к хуку. Таким образом, поток выполнения программы перенаправляется. Хук может выполнять произвольные действия, при необходимости вызывать исходную функцию, после чего возвращает управление вызывающему коду вместе с возвращаемым значением, если это необходимо. Диаграмма ниже иллюстрирует этот процесс.

Понимание того, что такое хуки, необходимо для понимания управляемых хуков. Управляемый код выполняется в виртуальной и управляемой среде, такой как среда выполнения DotNet или виртуальная машина Java. Получение адреса памяти, где находится управляемая функция, отличается от неуправляемого языка, такого как C. После получения правильных адресов памяти для обеих функций хук может быть установлен путём прямого доступа к памяти с использованием небезопасного C# вместе со службой взаимодействия DotNet для вызова функций нативного Windows API.
Начиная с версии DotDumper v1.1-stable, DotDumper также может перехватывать неуправляемые (или нативные) функции. Перенаправление функций может быть любой комбинацией между управляемыми и неуправляемыми функциями, с одним важным ограничением. Любая неуправляемая функция, использующая управляемый хук, не сможет правильно читать значения стека. Поэтому требуется нативный компонент (названный DotDumperNative). Этот компонент взаимодействует с самим DotDumper через именованный канал, используя его централизованную систему логирования, перехватывая неуправляемые вызовы с доступом к аргументам функций, найденным в стеке.
Поскольку DotDumper написан на чистом C# без внешних зависимостей, его можно легко расширить с помощью Visual Studio. Код документирован в этом блоге, на GitHub, а также в классах, функциях и встроенных комментариях в исходном коде. Это в сочетании с понятными именами позволяет любому модифицировать инструмент по своему усмотрению, минимизируя время и усилия, необходимые для его понимания. Вместо этого разработчики и аналитики могут сосредоточить свои усилия на улучшении инструмента.
Хотя функция поддержки тайм-аута песочницы не была изменена или модифицирована, она не была задокументирована ранее. Поскольку DotDumper выполняет переданный ему файл, его выполнение будет продолжаться до тех пор, пока образец не завершится. Вредоносное ПО часто переходит в состояние «ожидания», когда должно быть выполнено определённое условие для его реактивации, или вредоносное ПО продолжает выполнение в другом процессе (с помощью инъекции процессов). Например, новый созданный поток в поломанном процессе возвращается только после завершения.
Чтобы избежать зависания, DotDumper имеет обработчик застоя. Каждый раз, когда регистрируются результаты хука, счётчик увеличивается. Если этот счётчик не меняется после трёх последовательных 20-секундных интервалов, DotDumper считает, что наступило состояние застоя. В этом случае системное время устанавливается на 30.12.2200 12:00, чтобы принудительно вызвать тайм-аут песочницы, после чего уведомляет аналитика через логгер и завершает свою работу. Тайм-аут песочницы происходит, когда прошедшее время от текущей даты до новой установленной даты превышает обычные 5 или 10 минут. Даже если время выполнения песочницы установлено на часы или дни, оно будет превышено.
Причина сокращения анализа — экономия времени и ресурсов, так как задание с 10-минутным тайм-аутом может выполняться всего 2 минуты. Таким образом, с помощью обработчика застоя можно запускать несколько образцов за то же время. Обзор логики обработчика застоя показан ниже.

Теперь, когда цели и возможности DotDumper ясны, может показаться, что есть пересечение с известными общедоступными инструментами, такими как ILSpy, dnSpyEx, de4dot или pe-sieve. Обратите внимание, что нет намерения утверждать, что один инструмент лучше другого, а скорее показать, чем они отличаются.
Цель DotDumper — логирование и дамп ключевых, контекстуализирующих и общих вызовов функций из образцов, ориентированных на DotNet. ILSpy — это дизассемблер и декомпилятор DotNet, но он не позволяет выполнять файл. dnSpyEx (и его предшественник dnSpy) используют ILSpy в качестве компонента дизассемблера и декомпилятора, добавляя отладчик. Это позволяет вручную просматривать и изменять память. de4dot используется исключительно для деобфускации двоичных файлов DotNet, улучшая читаемость кода для человека. Последний инструмент в этом сравнении, pe-sieve, предназначен для обнаружения и дампа вредоносного ПО из запущенных процессов, независимо от используемого языка программирования. Таблица ниже даёт графическое представление вышеупомянутых инструментов.

DotDumper находится в постоянном процессе пересмотра и разработки, сосредоточенной на двух основных областях: исправление ошибок и добавление новых функций. Во время разработки код тестировался, но из-за внедрения хуков в функции DotNet Framework, которые могут меняться, вполне возможно наличие ошибок в коде. Любого, кто столкнулся с ошибкой, просят открыть тикет в репозитории GitHub, который будет рассмотрен. Предложения новых функций также можно вносить через репозиторий GitHub. Для тех, у кого есть учётная запись GitHub, или кто предпочитает не взаимодействовать публично, можете отправить мне личное сообщение в Twitter.
Разумеется, если вы использовали DotDumper во время анализа или использовали его творчески, не стесняйтесь связаться публично или лично! Нет ничего лучше, чем слышать об использовании собственного инструмента!
Для DotDumper запланировано ещё больше нововведений, и обновление будет отправлено сообществу, как только оно станет доступным!