Введение

В современных условиях потребители ИБ-услуг предъявляют повышенные требования к качеству телеметрии, которая служит основой для построения детектирующей логики в решениях типа EDR, XDR, SIEM, SOAR и других. Однако часто у заказчиков отсутствует четкое понимание, какую именно телеметрию и на каких уровнях операционной системы необходимо собирать для эффективного обнаружения различных видов атак.

В статье будут подробно рассмотрены события, которые можно получать на каждом уровне ОС — начиная с наиболее базовых, таких как журналы аудита или Sysmon, и углубляясь до более низких уровней, как ETW-провайдеры (при этом выделим наиболее полезные из них). Поговорим о том, как современные EDR могут перехватывать системные вызовы.

Дополнительно рассмотрим возможности обхода аудита на каждом из рассмотренных уровней.

Базовые утилиты для мониторинга Windows (Windows EventLog/Sysmon)

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

  • Windows EventLog.
  • Sysmon.

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

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

Принцип работы Windows EventLog

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

Глобально логи делятся на:

  • Windows Logs — стандартные логи аудита Windows, их источником в основном является само ядро ОС.
  • Applications and Services Logs — логи, источник событий которых находится в пользовательском пространстве (приложения, сервисы, программы).
Список журналов Windows EventLog
Список журналов Windows EventLog

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

Далее предлагаем вместе с нами рассмотреть весь путь генерации события из журналов Windows Logs и Applications. В качестве примера возьмем из этих журналов по одному из наиболее популярных событий, которые часто используются в качестве телеметрии для SIEM:

  • Windows Logs => Security => EventId 4688 (A new process has been created) — событие создания нового процесса.
  • Applications and Services Logs => Microsoft => Windows => PowerShell => Operational => 4104 (PowerShell scriptblock logging) — событие, которое генерируется при исполнении PowerShell-скрипта.

ETW 101

Для того чтобы понимать, о чем пойдет речь в дальнейшем, напомним, что такое ETW (Event Tracing for Windows).

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

Схема работы Event Tracing for Windows
Схема работы Event Tracing for Windows

В рамках работы ETW существуют следующие ключевые сущности:

  • Провайдеры
  • Контроллеры
  • Сессии
  • Потребители

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

Контроллер — сущность, которая отвечает за управление ETW-сессией (включение, выключение, создание). К примеру, встроенная утилита logman исполняет роль контроллера.

Сессия — пересылает события от поставщика к потребителю с помощью буфера.

Потребитель — приложение, которое получает события из сессий, например, служба EventLog.

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

В случае провайдеров режима ядра источником событий является само ядро.

Также у провайдеров есть:

  • KeyWords — битовая маска, определяющая категории событий внутри провайдера.
  • Уникальный GUID. Однозначный идентификатор определенного провайдера.
Список провайдеров в системе
Список провайдеров в системе
KeyWords провайдера Microsoft-Windows-DotNetRuntime
KeyWords провайдера Microsoft-Windows-DotNetRuntime

Генерация события запуска процесса

Для того чтобы понять, где именно генерируется событие запуска процесса, посмотрим на полную цепочку действий при генерации события:

Полный цикл получения события
Полный цикл получения события от запуска процесса и генерации события в ETW до получения его службой EventLog

Шаг 1–4: стандартный запуск процесса в UserMode. Вызов функции-обертки CreateProcessW() в kernel32.dll, которая, в свою очередь, вызывает соответствующую функцию в kernelbase.dll. Здесь происходит подготовка аргументов, а также различные проверки. Дальше идем к границе с KernelMode, в функцию NtCreateUserProcess() библиотеки ntdll.dll, которая вызывает инструкцию системного вызова (прерывания) с соответствующим номером syscall (SSN).

Шаг 5: инструкция syscall вызывает ядерную функцию KiSystemCall64(), которая принимает в регистре RAX номер нужного системного вызова (SSN).

Шаг 6: KiSystemCall64() обращается к таблице SSDT (System Service Descriptor Table) и сопоставляет номер системного вызова и смещение. По этому смещению и будет находится сама реализация системного вызова, в нашем случае — функция запуска процесса.

Шаг 7: ядерная функция NtCreateUserProcess() является самой главной функцией при запуске процесса. Она отвечает за вызовы всех остальных нужных функций ядра, которые необходимы для запуска процесса (создание структур _KPROCESS, _KTHREAD, их заполнение, создание секций в памяти).

В этой же функции можно найти вызов:

    
inserted = PspInsertProcess(v86, v93, v68, a7, *((_QWORD *)&v118[8] + 1), v89, v70, v119);
    

Эта функция отвечает за добавление процесса в глобальный список процессов ядра.

В начале этой функции происходит проверка того, включен ли аудит для этой категории событий (запуск процессов):

    
if ( (unsigned __int8)SeAuditingWithTokenForSubcategory(134LL, 0LL) )
    SeAuditProcessCreation(BugCheckParameter1);
    

Если аудит включен, то вызывается функция SeAuditProcessCreation().

Далее вызывается цепочка функций, которая в итоге формирует событие и передает все в конечную функцию EtwWriteKMSecurityEvent(), которая отправляет событие в ETW-провайдер Microsoft-Windows-Security-Auditing.

SepAdtLogAuditRecord() => SepQueueWorkItem() => SepRmCallLsa() => AdtpWriteToEtw() => EtwWriteKMSecurityEvent()

    
v5 = EtwWriteKMSecurityEvent(&v25, v8, v22[0], (unsigned __int64)v32 & -(__int64)(v22[0] != 0));
    

Описанной выше цепочкой вызовов формирование события в ядре заканчивается. Теперь рассмотрим процесс получения событий службой EventLog.

EventLog get Security Events (получение событий службой EventLog)

Что мы знаем в теории?

  • Для того чтобы собирать события с провайдера, нужно подписаться на одну из его сессий.
  • У каждого провайдера есть свой уникальный GUID.
  • Предположение о том, что сам EventLog подписывается на провайдера.

Начнем с GUID провайдера Microsoft-Windows-Security-Auditing: {54849625-5478-4994-a5ba-3e3b0328c30d}.

Если перевести этот GUID в HEX-формат, то можно получить последовательность байтов: 25 96 84 54 78 54 94 49 a5 ba 3e 3b 03 28 c3 0d.

Если поискать по последовательности байтов в исполняемом файле службы EventLog (wevtsvc.dll), то увидим, что данный GUID в нем действительно существует:

GUID провайдера Microsoft-Windows-Security-Auditing в wevtsvc.dll
GUID провайдера Microsoft-Windows-Security-Auditing в wevtsvc.dll

По адресу в секции .rdata можно найти переменную SecurityProviderGuid:

Переменная SecurityProviderGuid в секции .rdata
Переменная SecurityProviderGuid в секции .rdata
Перекрестная ссылка по переменной SecurityProviderGuid
Перекрестная ссылка по переменной SecurityProviderGuid

Построив перекрестную ссылку, находим, что переменная используется в единственной функции — GetLegacyChannel().

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

Условие выбора журнала событий
Условие выбора журнала событий

Действительно, для нашего SecurityProvider выбирается журнал Security.

Саму функцию GetLegacyChannel() вызывает функция ProcessEvent(), которой на вход передается структура _EVENT_RECORD:

Структура _EVENT_RECORD как параметр функции ProcessEvent
Структура _EVENT_RECORD как параметр функции ProcessEvent

Если проследовать по цепочке вызовов, то в конечном счете можно прийти к функции EtwEventCallback(), которая и является точкой входа для получения событий с ETW-провайдера (callback-функцией, обрабатывающей полученное событие):

Функция EtwEventCallback
Функция EtwEventCallback

Чем обрабатывается пришедшее событие, мы нашли. Но есть ли момент подписи на провайдера? Для этого обратимся к ETW-API-функциям, которые реализованы в NTDLL и поищем использование этих функций в нашей службе.

Находим вызов функции OpenTraceW() в методе StartProcessingEvents().

https://learn.microsoft.com/en-us/windows/win32/api/evntrace/nf-evntrace-opentracew

Данная функция буквально открывает дескриптор ETW-сеанса:

    
ETW_APP_DECLSPEC_DEPRECATED PROCESSTRACE_HANDLE WMIAPI OpenTraceW(
  [in, out] PEVENT_TRACE_LOGFILEW Logfile
);
    
Наполнение полей PEVENT_TRACE_LOGFILEW LogFile
Наполнение полей PEVENT_TRACE_LOGFILEW LogFile

На вход функции OpenTraceW() передается структура PEVENT_TRACE_LOGFILEW, в которой можно указать callback-функцию обработчика пришедших событий в поле EventCallback. Это как раз наш обработчик EtwEventCallback(struct _EVENT_RECORD), который мы нашли ранее.

    
typedef struct _EVENT_TRACE_LOGFILEW {
  LPWSTR                        LogFileName;
  LPWSTR                        LoggerName;
  LONGLONG                      CurrentTime;
  ULONG                         BuffersRead;
  union {
    ULONG LogFileMode;
    ULONG ProcessTraceMode;
  } DUMMYUNIONNAME;
  EVENT_TRACE                   CurrentEvent;
  TRACE_LOGFILE_HEADER          LogfileHeader;
  PEVENT_TRACE_BUFFER_CALLBACKW BufferCallback;
  ULONG                         BufferSize;
  ULONG                         Filled;
  ULONG                         EventsLost;
  union {
    PEVENT_CALLBACK        EventCallback;
    PEVENT_RECORD_CALLBACK EventRecordCallback;
  } DUMMYUNIONNAME2;
  ULONG                         IsKernelTrace;
  PVOID                         Context;
} EVENT_TRACE_LOGFILEW, *PEVENT_TRACE_LOGFILEW;

    

Обработчик должен принимать на вход структуру _EVENT_RECORD, что и происходит в нашем случае.

Таким образом мы поняли, что сама служба EventLog подписывается на провайдера Microsoft-Windows-Security-Auditing и в дальнейшем занимается обработкой его событий (в том числе событий запуска процесса).

Генерация события PowerShell ScriptBlock

В отличии от события запуска процесса, журнал, который содержит события запуска ScriptBlock, находится по пути Applications and Services Logs => Microsoft => Powershell.

Из этого можно сделать вывод, что провайдер, скорее всего, является провайдером пользовательского режима и события генерируются в данном случае не в ядре, а в UserMode-приложении.

Для начала найдем уникальный GUID провайдера, используя утилиту ETW Explorer:

GUID провайдера Microsoft-Windows-Powershell
GUID провайдера Microsoft-Windows-Powershell
События провайдера Microsoft-Windows-Powershell
События провайдера Microsoft-Windows-Powershell

Видим искомое нами событие 4104 в провайдере Microsoft-Windows-PowerShell. GUID — {A0C1853B-5C40-4B15-8766-3CF1C58F985A}.

Также существует возможность просмотра того, для какого провайдера процесс является поставщиком. Это делается с помощью встроенной утилиты Windows — logman:

    
logman query providers -pid <process pid>
    
Провайдеры, в которые поставляет события Powershell.exe
Провайдеры, в которые поставляет события Powershell.exe

Запустим процесс powershell.exe и посмотрим, каким провайдерам он поставляет события. Провайдер Microsoft-Windows-PowerShell присутствует в этом списке.

Провайдер Microsoft-Windows-PowerShell существенно отличается от провайдера Microsoft-Windows-Security-Auditing, ведь поставщиком событий в данном случае является пользовательский процесс, а не само ядро, как в случае с событием 4688.

При таком варианте провайдера путь генерации события и его доставки в EventLog выглядит следующим образом:

Упрощенная схема работы провайдера Microsoft-Windows-Powershell
Упрощенная схема работы провайдера Microsoft-Windows-Powershell
  1. При старте процесс powershell.exe вызывает цепочку ETW-API-функций, в итоге приходя в ntdll:EtwNotificationRegister() для того, чтобы зарегистрировать себя как поставщика событий для нужных ему провайдеров. Среди этих провайдеров есть искомый нами Microsoft-Windows-PowerShell. Если посмотреть в определение функции, то мы увидим, что первым аргументом как раз является уникальный GUID провайдера, в который процесс хочет отправлять события:
    
EtwNotificationRegister (
    LPCGUID Guid,
    ULONG Type,
    PETW_NOTIFICATION_CALLBACK Callback,
    PVOID Context,
    REGHANDLE *RegHandle);
    

Теперь можно найти момент регистрации powershell.exe как поставщика, установив точку останова на входе в функцию EtwNotificationRegister() и проверив значения, на которые указывает регистр RCX, поставив точку останова с условием проверки первых байтов по указателю. Если перевести GUID {A0C1853B-5C40-4B15-8766-3CF1C58F985A} в HEX, получим: 3b85c1a0405c154b87663cf1c58f985a:

 GUID провайдера Microsoft-Windows-Powershell в аргументах EtwNotificationRegister
GUID провайдера Microsoft-Windows-Powershell в аргументах EtwNotificationRegister
  1. EtwNotificationRegister() вызывает функцию EtwpRegisterProvider(), которая в свою очередь вызывает NtTraceControl().
  2. NtTraceControl() совершает системный вызов:
    
NtTraceControl (
    ULONG FunctionCode, 
    PVOID InBuffer, 
    ULONG InBufferLen, 
    PVOID OutBuffer, 
    ULONG OutBufferLen, 
    ULONG *ReturnSize);
    
Вызов функции NtTraceControl()
Вызов функции NtTraceControl()

Обратим внимание на первый аргумент — FunctionCode. В ядре на основе FuncitonCode через конструкцию switch-case выбирается действие:

Вызов функции EtwpRegisterUMGuid()
Вызов функции EtwpRegisterUMGuid()
  1. Функция EtwpRegisterUMGuid() в свою очередь регистрирует UserMode-поставщика событий (процесс powershell.exe) и отождествляет его с общим провайдером Microsoft-Windows-PowerShell.
  2. Теперь любой процесс, который захочет получать события от провайдера Microsoft-Windows-PowerShell, может открыть сессию и получать события со всех инстансов поставщиков этого провайдера (например, с нескольких процессов powershell.exe). После регистрации поставщика функция EtwNotificationRegister() возвращает указатель на RegHandle. Этот указатель является дескриптором зарегистрированного поставщика событий. В нашем случае поставщик — сам процесс powershell.exe.

Далее сервис EventLog по аналогии с Security-провайдером открывает сессию с ETW-провайдером Microsoft-Windows-PowerShell, получая события со всех поставщиков, которые были ассоциированы с этим провайдером.

Сами процессы powershell.exe генерируют события и отправляют их через системный вызов EtwWriteTransfer(), используя RegHandle, полученный ранее. Получается, что в случае с UserMode-провайдерами им тоже требуется проделать путь через ядро, чтобы событие было ассоциировано с определенным провайдером. После чего потребители вроде EventLog могут подключаться к сессиям и получить события.

    
NTSTATUS EtwWriteTransfer(
  [in]           REGHANDLE              RegHandle,
  [in]           PCEVENT_DESCRIPTOR     EventDescriptor,
  [in, optional] LPCGUID                ActivityId,
  [in, optional] LPCGUID                RelatedActivityId,
  [in]           ULONG                  UserDataCount,
  [in, optional] PEVENT_DATA_DESCRIPTOR UserData
);
    
Функция EtwWriteTransfer() для отправки событий от поставщиков

Принцип работы Sysmon

Sysmon — это утилита, которая значительно расширяет возможности стандартного аудита Windows. На первый взгляд, она также может получать события запусков процессов, создания файлов, изменений в реестре, но у Sysmon и EventLog есть существенные отличия в принципе работы.

Sysmon состоит из двух компонентов:

  • Служба Sysmon.exe (usermode-компонент);
  • Драйвер SysmonDrv.sys (kernelmode-компонент).

Далее рассмотрим его принцип работы.

Регистрация событий Sysmon от ядерных callback-функций

В Windows существуют специальные функции-регистраторы callback-функций на определенные события. В случае запуска процесса регистратором является функция PsSetCreateProcessNotifyRoutine():

    
NTSTATUS PsSetCreateProcessNotifyRoutine(
  [in] PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,
  [in] BOOLEAN                        Remove
);
    
Определение функции PsSetCreateProcessNotifyRoutine()

NotifyRoutine — точка входа в callback-функцию, которая отработает при запуске процесса;

Remove — булево значение, определяющее операцию с callback-функцией (если false — добавляется в список, если true — удаляется).

Данные callback-функции регистрируются драйверами режима ядра. В случае Sysmon это SysmonDrv.sys.

Если посмотреть в драйвер Sysmon и погрузиться в то, что происходит после DriverEntry(), мы дойдем до момента регистрации callback-функции на запуск процесса:

    
ProcessNotifyRoutine = PsSetCreateProcessNotifyRoutine(NotifyRoutine, a1 == 0);
    
Регистрация callback-функции на запуск процесса

    
void __fastcall NotifyRoutine(HANDLE ParentId, HANDLE ProcessId, __int64 Create)
{
  NotifyRoutineInternal(ParentId, ProcessId, Create, 0LL);
}
    
NotifyRoutine — обертка логики callback-функции NotifyRoutineInternal

Далее функция NotifyRoutineInternal заполняет структуру события, получая информацию о процессе, и передает событие в функцию ProcessingEvents():

    
void __fastcall NotifyRoutineInternal(void *a1, void *a2, char a3, __int64 a4)
{.......
	ZwQueryInformationProcess(ProcessHandle, ProcessSessionInformation, &ProcessInformation, 4u, 0LL);
.......
		*v28 = 3;
        v28[1] = v26;
        v28[6] = ProcessInformation;
        v28[8] = (_DWORD)a2;
        *((_QWORD *)v28 + 5) = MEMORY[0xFFFFF78000000014];
        sub_180007120(&v36, 1LL, v28 + 14, v28 + 12);
        ProcessingEvents(v28);
........
}
    
Обогащение события информацией

Внутри ProcessingEvents() драйвер Sysmon заполняет очередь событий EventsQueue. Очередь представляет собой Circular Linked List и заполняется новыми событиями следующим образом:

    
void __fastcall ProcessingEvents (char *P, __int64 a2, char a3)
{.....
if ( v12 != &EventsQueue )
        ExReleaseFastMutex(&stru_180027E40);
      ExFreePoolWithTag(v15[3], 0);
      ExFreePoolWithTag(v15, 0);
    }
    v25 = (PVOID **)v12[1];
   if ( *v25 == v12 )
    {
      *v14 = v12; // v14 - новое событие
      v14[1] = (PVOID *)v25; // помещаем в конец кольцевого листа и настраиваем указатели
      *v25 = (PVOID *)v14;
      v12[1] = v14;
      ExReleaseFastMutex(v9);
      return;
    }
.....
}
    
Алгоритм заполнения буфера событий Circular Linked List

На этом обработка событий от коллбэков закончена, остается забрать события из драйвера в UserMode.

Регистрация событий мини-фильтра Sysmon

События файловой системы в Sysmon (как и события, связанные с пайпами) собираются с помощью мини-фильтра файловой системы. Подробнее о самой концепции фильтров файловой системы можно почитать здесь.

Если коротко: зарегистрировав специальным образом созданный драйвер мини-фильтра в ОС Windows, можно использовать его для анализа операций на файловой системе.

Рассмотрим, как устроена регистрация и принцип работы фильтра файловой системы в Sysmon.

За подготовку к регистрации фильтра отвечает следующая функция:

Функция регистрации мини-фильтра в Sysmon
Функция регистрации мини-фильтра в Sysmon

В строке 20 вызывается функция FltRegisterFilter(), в параметрах которой находится указатель на структуру FLT_REGISTRATION *Registration. Внутри этой структуры располагается указатель на массив структур FLT_OPERATION_REGISTRATION, которые, в свою очередь, указывают на то, как должна обрабатываться та или иная операция.

Структура FLT_OPERATION_REGISTRATION
Структура FLT_OPERATION_REGISTRATION

Остановимся подробнее на самых важных элементах структуры:

  1. MajorFunction — тип обрабатываемого запроса (IRP — I/O request packet, подробнее смотрите тут.
  2. PreOperation — callback-функция, которая обрабатывает запрос перед его выполнением.
  3. PostOperation — callback-функция, которая обрабатывает запрос после его выполнения.

В Sysmon массив структур FLT_OPERATION_REGISTRATION выглядит следующим образом:

Массив структур FLT_OPERATION_REGISTRATION в драйвере Sysmon
Массив структур FLT_OPERATION_REGISTRATION в драйвере Sysmon

На скриншоте можно увидеть, что обрабатываются следующие операции: IRP_MJ_CREATE, IRP_MJ_WRITE, IRP_MJ_CLEANUP, IRP_MJ_SET_INFORMATION, IRP_MJ_CLOSE, IRP_MJ_CREATE_NAMED_PIPE.

Из интересного — все функции PreOperation/PostOperation одинаковы для каждого типа операций, каждый запрос обрабатывается в формате одного большого switch-case по MajorFunction:

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

Получение событий в UserMode

UserMode-агент — это служба Sysmon.exe, которая отправляет запросы в драйвер на получение накопленных событий (IRP-пакет) через функцию DeviceIoControl с уникальным кодом, который соответствует коду операций в обработчике драйвера Sysmon. Этот код операции буквально значит «отдай мне все накопленные события от коллбэков и мини-фильтра»:

    
if ( DeviceIoControl(hDevice, 0x83400004, 0LL, 0, v1, 0x40000u, &BytesReturned, &Overlapped) )
    {
LABEL_39:
      if ( BytesReturned >= 0x358 )
        EventProcessing(v1);
    
Вызов операции в драйвере Sysmon от службы Sysmon в UserMode

    
case 0x83400004:
      v17 = &EventsQueue;
      v18 = &stru_180027E40;
      v19 = &dword_1800278B0;

......

v20 = *v17;     // Pointer to EventsQueue
// заполнение буфера который будет передан в UserMode
BufferFuller(SystemBuffer, *((char **)v20 + 3), (unsigned int)v20[4]);

......
// сигнал usermode, что irp-запрос выполнен и можно получать буфер
IofCompleteRequest(a2, 0)
    
Заполнение буфера событий по запросу в драйвер

Далее служба Sysmon в UserMode обрабатывает события и записывает их в ETW-провайдере Microsoft-Windows-Sysmon, который предварительно зарегистрировала на этапе установки Sysmon.

EventRegister из advapi32.dll реализует цепочку вызовов: advapi32:EventRegister() => ntdll:EtwEventRegister() => ntdll:EtwNotificationRegister(). Механизм регистрации точно такой же, как был ранее рассмотрен для провайдера Microsoft-Windows-PowerShell:

    
EventRegister = (__int64 (__fastcall *)(_QWORD, _QWORD, _QWORD, _QWORD))GetProcAddress(ModuleHandleW, "EventRegister");

EventRegister(&sysmonProviderGUID, 0LL, 0LL, &RegHandleSysmon);
    
Регистрация ETW-провайдера Sysmon

    
EventWrite(RegHandleSysmon, *((_QWORD *)a1 + 1), *a1, &EventBlock);
    
Запись событий в провайдер после получения из драйвера

Провайдер Microsoft-Windows-Sysmon в ETW Explorer
Провайдер Microsoft-Windows-Sysmon в ETW Explorer

Также в ResourceHacker можно увидеть, что в сам бинарный файл Sysmon.exe зашит манифест ETW:

Манифест ETW внутри исполняемого файла Sysmon
Манифест ETW внутри исполняемого файла Sysmon

Помимо записи в ETW-провайдер Sysmon будет писать события напрямую в журнал EventLog, в случае если версия Windows ниже, чем Vista. Журнал событий создается на этапе установки Sysmon:

    
.data:00000001402F4108 lpDisplayName   dq offset aSysmon       ; DATA XREF: sub_1400E3CC0+BF↑r
.data:00000001402F4108                                         ; sub_140105AA0+2C↑r ...
.data:00000001402F4108                                         ; "Sysmon"

hEventLog = RegisterEventSourceW(0LL, lpDisplayName);
    
Регистрация журнала EventLog службой Sysmon

    
ReportEventW(hEventLog, 4u, 0, *((_DWORD *)a1 + 1), lpUserSid, wNumStrings, 0, Strings, 0LL)
    
Запись в EventLog-журнал напрямую из службы Sysmon

Таким образом, Sysmon получает основной поток событий из kernelmode-компонента и пишет их в ETW-провайдер (или в журнал напрямую в качестве legacy).

На данном этапе схема получения события в Sysmon выглядит следующим образом:

Упрощенная схема работы Sysmon
Упрощенная схема работы Sysmon

Однако у Sysmon существуют и другие источники телеметрии помимо собственного драйвера. К событиям из других источников относятся:

  • события сетевых соединений;
  • события DNS-запросов;
  • события WMI.

Регистрация сетевых событий и событий DNS

События сетевых соединений и DNS-запросов Sysmon получает в своей агентской части, то есть в службе Sysmon.exe. В ходе работы служба создает две ETW-сессии:

  • SYSMON TRACE;
  • SysmonDnsEtwSession.
ETW-сессии, созданные Sysmon
ETW-сессии, созданные Sysmon

Эти сессии связаны с двумя провайдерами:

  • Microsoft-Windows-DNS-Client {1C95126E-7EEA-49A9-A3FE-A378B03DDB4D};
  • NT Kernel Logger {9E814AAD-3204-11D2-9A82-006008A86939}.
Описание сессии SysmonDnsEtwSession
Описание сессии SysmonDnsEtwSession
Описание сессии SYSMON TRACE
Описание сессии SYSMON TRACE

Microsoft-Windows-DNS-Client — отвечает за регистрацию событий, связанных со службой клиента системы доменных имен (DNS). Эта служба обрабатывает преобразование доменных имен в IP-адреса путем запроса локальных кешей или удаленных DNS-серверов.

NT Kernel Logger — специальный ETW-провайдер ядра Windows, который предоставляет множество полезной телеметрии. Именно из-за его «специфики» в свойствах самой ETW-сессии не отображается его GUID (его мы найдем уже в самой реализации сбора событий).

В события, которые собирает NT Kernel Logger, входят:

  • registry — все события взаимодействия с реестром;
  • process — события запуска и завершения процессов;
  • image_load — события загрузки образов в память и их маппинг;
  • network — входящие/исходящие сетевые соединения;
  • driver — события загрузки драйверов;
  • file — события файловых операций;
  • handles — события создания новых дескрипторов.

В случае Sysmon Microsoft-Windows-DNS-Client используется для получения событий DNS-запросов/ответов, а в случае NT Kernel Logger — для получения событий сетевых соединений (Sysmon EventID 3).

В целом взаимодействие с ETW API (создание сессий, ее активация, открытие сессии и связывание callback-функции с полученным событием) точно такое же, как и в случае с любым ETW-провайдером.

Регистрация сетевых событий

Для того чтобы зарегистрировать сетевые события, требуется:

  • Создать сеанс трассировки с провайдером Nt Kernel Logger.
  • Указать, какие события мы хотим получать.
  • Активировать сессию и связать получение событие с обратным вызовом.

Ниже представлена реализация описанного алгоритма в Sysmon:

Получение адресов функций ETW API
Получение адресов функций ETW API
Наполнение структуры PEVENT_TRACE_PROPERTIES (Properties)
Наполнение структуры PEVENT_TRACE_PROPERTIES (Properties)
Регистрация и запуск сеанса отслеживания событий через StartTraceW()
Регистрация и запуск сеанса отслеживания событий через StartTraceW()
Функция OpenTrace()
Функция OpenTrace() открывает дескриптор обработки трассировки ETW для использования событий из сеанса трассировки ETW в режиме реального времени, связывая пришедшее событие с обратным вызовом EventCallback.
    
ULONG WMIAPI StartTraceW(
            CONTROLTRACE_ID         *TraceId,
  [in]      LPCWSTR                 InstanceName,
  [in, out] PEVENT_TRACE_PROPERTIES Properties
);
    
Определение функции StartTraceW()

Из скриншотов видно, что перед созданием сессии в структуре PEVENT_TRACE_PROPERTIES значение поля EnableFlags выставляется в 0x10000. Это и есть keyword, с помощью которого указывается, какие события хочет получать сессия. Сами keywords можно найти в свойствах провайдера:

Свойства провайдера Windows Kernel Trace
Свойства провайдера Windows Kernel Trace

Значению 0x10000 соответствуют события типа net — Network TCP/IP.

Переходим к тому, как реализовано получение событий DNS.

Регистрация событий DNS

События DNS собираются с ETW-провайдера Microsoft-Windows-DNS-Client, и принцип их сбора очень похож на предыдущий.

Заполнение структуры PEVENT_TRACE_PROPERTIES
Заполнение структуры PEVENT_TRACE_PROPERTIES
Регистрация и запуск сеанса отслеживания событий через StartTraceW()
Регистрация и запуск сеанса отслеживания событий через StartTraceW(), а также активация провайдера DNS в сеансе через EnableTraceEx2()

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

    
ULONG WMIAPI EnableTraceEx2(
                 CONTROLTRACE_ID          TraceId,
  [in]           LPCGUID                  ProviderId,
  [in]           ULONG                    ControlCode,
  [in]           UCHAR                    Level,
  [in]           ULONGLONG                MatchAnyKeyword,
  [in]           ULONGLONG                MatchAllKeyword,
  [in]           ULONG                    Timeout,
  [in, optional] PENABLE_TRACE_PARAMETERS EnableParameters
);
    
Определение функции EnableTraceEx2()

В параметре Level указывается значение — 1, это тип событий TRACE_LEVEL_INFORMATION. MatchAnyKeyword и MatchAllKeyword установлены в значения 0. Это показывает, что сессия принимает события, не фильтруя их по ключевым словам. А параметр ProviderId задает тот самый провайдер Microsoft-Windows-DNS-Client, который будет включен в сессию.

Регистрация провайдера NT Kernel Logger в Sysmon значительно отличается от регистрации провайдера DNS. По той причине, что NT Kernel Logger является системным провайдером.

Далее мы открываем сессию и начинаем получать с нее события, обрабатывая их в указанном коллбэке:

Вызов OpenTraceW() для начала получения событий и указания callback-функции обработчика событий
Вызов OpenTraceW() для начала получения событий и указания callback-функции обработчика событий

В итоге справедливо будет добавить к общей картине работы Sysmon еще 2 источника событий в виде провайдеров NT Kernel Logger и DNS.

Регистрация событий WMI

Sysmon способен регистрировать события закрепления в системе через механизм WMI Subscription (события 19–21). Далее рассмотрим принцип генерации этих событий.

В начале Sysmon, используя механизмы COM, создает подключение к интерфейсу WMI ROOT\\Subscription:

Интерфейс ROOT/Subscription и его классы, которые могут быть использованы для закрепления
Интерфейс ROOT/Subscription и его классы, которые могут быть использованы для закрепления

Он делает это, используя COM-объект WBEMComLocator (CLSID: 4590F811-1D3A-11D0-891F-00AA004B2E24) и запрашивая его интерфейс IWbemLocator {IID: DC12A681-737F-11CF-884D-00AA004B2E24}:

Создание объекта WBEMComLocator и получение указателя на его интерфейс IWbemLocator
Создание объекта WBEMComLocator и получение указателя на его интерфейс IWbemLocator
Вызов метода ConnectServer()
Вызов метода ConnectServer()

У интерфейса IWbemLocator есть единственный метод: IWbemLocator::ConnectServer():

    
HRESULT ConnectServer(
  [in]  const BSTR    strNetworkResource,
  [in]  const BSTR    strUser,
  [in]  const BSTR    strPassword,
  [in]  const BSTR    strLocale,
  [in]  long          lSecurityFlags,
  [in]  const BSTR    strAuthority,
  [in]  IWbemContext  *pCtx,
  [out] IWbemServices **ppNamespace
);
    

Метод создает подключение через DCOM к пространству имен WMI, указанному в strNetworkResource, и возвращает в последнем аргументе указатель на объект IWbemServices. Интерфейс IWbemServices используется клиентами и поставщиками для доступа к службам WMI.

Далее создается stub-объект для маршалинга callback-функции. В результате мы получаем указатель на реализацию интерфейса IWbemObjectSink, внутри которого реализована callback-функция на получаемые события от WMI:

Создание stub-объекта и получение указателя на интерфейс IWbemObjectSink
Создание stub-объекта и получение указателя на интерфейс IWbemObjectSink

Следующий шаг — формирование фильтра WMI, на который будет срабатывать callback-функция:

WMI-запрос на получение событий
WMI-запрос на получение событий

Этот запрос получает события:

  • операций над WMI Event Consumers;
  • операций над WMI Event Filters;
  • операций над связями Filter-Consumer.

(те самые события 19, 20, 21 Sysmon)

В итоге мы получили:

  • указатель на интерфейс с реализацией callback-функции;
  • подключение к пространству имен WMI ROOT\\Subscription (IWbemServices);
  • сформированный фильтр, описывающий то, какие события мы хотим получать.

Теперь связываем все это с помощью вызова метода ExecNotificationQueryAsync() интерфейса IWbemServices.

    
HRESULT ExecNotificationQueryAsync(
  [in] const BSTR      strQueryLanguage,
  [in] const BSTR      strQuery,
  [in] long            lFlags,
  [in] IWbemContext    *pCtx,
  [in] IWbemObjectSink *pResponseHandler
);
    
Определение метода ExecNotificationQueryAsync()

Регистрация callback-функции для событий WMI по фильтру в пространстве имен ROOT\Subscription
Регистрация callback-функции для событий WMI по фильтру в пространстве имен ROOT\Subscription

Таким образом, Sysmon получает события создания WMI Event Filters, WMI Event Consumers и создание Filter-Consumer Binding для отслеживания техники закрепления атакующих через механизмы WMI.

Итоговая схема потребления Sysmon событий выглядит следующим образом:

Схема сбора событий Sysmon
Схема сбора событий Sysmon

Техники обхода стандартного аудита

В данном разделе будут приведены примеры обхода стандартного аудита Windows (EventLog / Sysmon).

EventLog Evasion

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

Теперь, когда мы узнали, каким образом EventLog собирает события из провайдеров, рассмотрим некоторые техники обхода логирования.

Dummy EventLog Evasion

Здесь перечислены верхнеуровневые способы отключения стандартного аудита Windows.

Events Location Tampering

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

Критичной для отслеживания в данном случае является ветка реестра:

    
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog
    

И ключ File, в котором хранится путь до файла .evtx, где будут записаны события определенного поставщика:

Ветка реестра, содержащая путь до журнала .evtx
Ветка реестра, содержащая путь до журнала .evtx

Если злоумышленнику удастся переписать данный ключ, события будут записываться не по стандартному пути \Windows\System32\winevt\Logs\Security.evtx, а в произвольный указанный evtx файл, что позволит обойти некоторые системы логирования, которые опираются на файлы журналов, а также скрыть forensic-артефакты.

Чтобы отслеживать подобные попытки изменения пути, следует настроить мониторинг ветки реестра HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog и отслеживать изменения ключа File:

    
title: 4RAYS_WIN_RegistryEventLogModified_rule
id: efb4f83e-1810-4800-9a09-636547928490
status: stable
description: Detects a change to the standard path where .evtx logs are saved
author: SOLAR 4RAYS
date: 2025-09-18
modified: 2025-09-18
logsource:
    category: registry_set
    product: windows
detection:
    RegistrySet:
        RegKey|re:
            - .*\\system\\(currentcontrolset|controlset001)\\services\\eventlog.*\\file.*
    condition: RegistrySet
tags:
    - attack.defense-evasion
    - attack.t1562.002
level: medium
falsepositives:
    - no known false-positives
    

Disabling log-channels via registry

В реестре присутствует значение, которое отвечает за включение/выключение определенного журнала событий. Найти данную настройку можно, к примеру, с помощью ProcMon. При отключении журнала сбора событий WMI, можно увидеть, что сервис EventLog изменяет значение реестра HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Channels\Microsoft-Windows-WMI-Activity/Operational\Enabled на 0.

Процесс отключения журнала событий WMI-Activity
Процесс отключения журнала событий WMI-Activity

Следовательно, злоумышленник может попытаться отключить таким образом определенный журнал событий. Детектируется подобная активность правилом на изменение ветки реестра:

    
title: 4RAYS_WIN_RegistryEvenLogChannelDisable_rule
id: 70f2181b-417e-4239-b77f-2f50613b952d
status: experimental
description: Detects disabling of the EventLog channel in the registry
author: SOLAR 4RAYS
date: 2025-09-22
modified: 2025-09-22
logsource:
    category: registry_set
    product: windows
detection:
    RegistrySet:
        RegKey|re:
            - .*\\Microsoft\\Windows\\CurrentVersion\\WINEVT\\Channels\\.*\\Enabled
        RegValue:
            - dword (0x00000000)
    condition: RegistrySet
tags:
    - attack.defense-evasion
    - attack.t1562.002
level: medium
falsepositives:
    - possible legitimate event channel disabling
    

EventLog Service Stop

Самое очевидное поведение атакующих: попытка напрямую отключить службу EventLog через sc stop.

    
C:\Windows\system32>sc stop eventlog
    
    
title: 4RAYS_WIN_EventLogStopViaSCutil_rule
id: d415c397-d40e-4f8e-a8ea-31b045df8081
status: stable
description: Detects Eventlog disable via sc utility
author: SOLAR 4RAYS
date: 2025-09-22
modified: 2025-09-22
logsource:
    category: process_creation
    product: windows
detection:
    SCutilDisableEventLog:
      Image|endswith: \sc.exe
      CommandLine|re: .*stop.*eventlog
    condition: SCutilDisableEventLog
tags:
    - attack.defense-evasion
    - attack.t1562.002
level: high
falsepositives:
    - no known false-positives
    

Advanced EventLog Evasion

Существует известная техника отключения потока событий в службе EventLog. Суть техники состоит в следующем:

  1. Мы находим хост-процесс, который отвечает за службу EventLog (svchost.exe).
  2. Открываем адресное пространство этого процесса.
  3. Манипулируем дескрипторами или потоками (закрываем/приостанавливаем, etc.).
  4. В итоге получается, что служба работает, а события не поступают.

На основе этой техники работают 2 существующих инструмента: Invoke-Phant0m и EvtPsst.

Invoke-Phant0m

Принцип работы утилиты Invoke-Phant0m
Принцип работы утилиты Invoke-Phant0m

Эта утилита требует прав локального администратора и работает по следующему алгоритму:

  1. С помощью WMI-запроса осуществляется поиск хост-процесса EventLog:
    
$Process in (Get-Process -Id (Get-WmiObject -Class win32_service -Filter "name = 'eventlog'" | select -exp ProcessId)
    
  1. Для найденного процесса открывается его дескриптор с правами PROCESS_ALL_ACCESS (0x1F0FFF):
    
$ProcessHandle = $Kernel32::OpenProcess(0x1F0FFF, $false, $Process.Id
    
  1. Для полученного дескриптора вызывается функция SymInitialize(), тем самым загружая в процесс PDB. Таким образом, появляется возможность оперировать не только адресами функций, но и их именами:
    
$Dbghelp::SymInitialize($ProcessHandle, $null, $false)
    
  1. Далее инструмент проходится по всем потокам внутри процесса, открывая их дескрипторы и вызывая функцию StackWalk64(), тем самым получая адреса функций из стека вызовов в рамках потока:
    
do {  # Get Stackframe
    if (!$Dbghelp::StackWalk64($ImageType, $ProcessHandle, $hThread, $lpStackFrame, $lpContextRecord, $null, $FunctionTableAccess, $GetModuleBase, $null)) {
        Write-Error "Unable to get stackframe for thread $ThreadId."
    }
    $StackFrame = [Runtime.InteropServices.Marshal]::PtrToStructure($lpStackFrame, [Type]$STACKFRAME64)
....
    
  1. Для каждого полученного адреса из стек-фрейма вызывается функция Get-SymbolFromAddress(), которая возвращает имя функции по ее адресу:
    
$Symbol = Get-SymbolFromAddress -ProcessHandle $ProcessHandle -Address $StackFrame.AddrPC.Offset
    
  1. На выходе получается набор следующих структур:
    
$Properties = @{
ProcessId  = $ProcessId # procid
ThreadId   = $ThreadId # threadid
AddrPC     = $StackFrame.AddrPC.Offset # адрес текущей инструкции
AddrReturn = $StackFrame.AddrReturn.Offset # адрес возврата
Symbol     = $SymbolName # имя функции по адресу
MappedFile = $MappedFile # файл модуля к которому замаплена функция
}
    
  1. Двигаясь в цикле по каждой такой структуре, Invoke-Phant0m ищет потоки, отвечающие за EventLog по следующей логике: замапленный файл для функции содержит подстроку 'evt’:
Потоки, созданные модулем wevtsvc.dll
Потоки, созданные модулем wevtsvc.dll
    
$eventLogThreads = $ReturnedObjects | Where-Object {$_.MappedFile -like '*evt*'} | %{$_.ThreadId }
    
  1. После чего с помощью функции TerminateThread() инструмент завершает все полученные потоки:
    
$kill = $Kernel32::TerminateThread($getThread, 1)
    
Потоки, созданные модулем wevtsvc.dll завершены
Потоки, созданные модулем wevtsvc.dll завершены

Таким образом, мы получаем работающую службу EventLog, которая фактически перестала выполнять свою основную функцию: сбор и запись событий ОС.

Явных следов своего исполнения, помимо самого запуска Invoke-Phant0m, утилита не оставляет. Отсутствуют события:

  • завершения службы;
  • очистки журнала.

Однако при работе утилиты имеет место отслеживание факта доступа к процессу svchost.exe с повышенными привилегиями (так как в ходе работы присутствует вызов OpenProcess()). Детект можно реализовать с помощью Sysmon:

    
title: 4RAYS_WIN_SvchostSuspiciousAccess_rule
id: 11e17f5f-4208-4177-8cb7-175bcdaafd34
status: stable
description: Detects suspicious access to svchost.exe. This may be a sign of running the Invoke-Phant0m utility to terminate ETW threads in the EventLogs service.
author: SOLAR 4RAYS
date: 2025-09-19
modified: 2025-09-19
logsource:
    category: process_access
    product: windows
    definition: This is a behavioral rule for Invoke-Phant0m and similar tools. Detection requires updating the Sysmon configuration.
detection:
    AccessToSvchostWithPROCESSALLACCESS:
        TargetImage|endswith: \System32\svchost.exe
        CallTrace|contains: unknown
        AccessMask: '0x1F3FFF'
    condition: AccessToSvchostWithPROCESSALLACCESS
tags:
    - attack.defense-evasion
    - attack.t1562.002
    - tool.Invoke-Phant0m
level: high
falsepositives:
    - Unknown
    

Также мы можем отследить подозрительный WMI-запрос на поиск процесса службы EventLog. Отслеживать WMI-запросы предлагается с помощью непосредственной подписки на ETW-провайдера Microsoft-Windows-WMI-Activity.

Теоретически это можно сделать с помощью стандартного EventLog, ведь в нем содержится журнал событий WMI:

Журнал WMI-Activity
Журнал WMI-Activity

Однако в журнале Operational, который включен по умолчанию, записывается очень скудное количество событий, которых часто недостаточно для того, чтобы увидеть всю WMI-активность:

  • Event ID 5857: This event indicates that a new consumer has been registered to receive WMI events.
  • Event ID 5858: This event signifies that a new event filter has been registered.

Есть еще Trace, туда пишутся события напрямую с ETW-провайдера, в нем мы уже сможем поймать WMI-query. Но из-за того что это отладочный журнал, отфильтровать события в нем будет проблематично, что может привести к высокому EPS.

В качестве демонстрации получения событий из провайдера Microsoft-Windows-WMI-Activity мы используем утилиту SilkETW. Пример события поиска процесса службы EventLog из провайдера Microsoft-Windows-WMI-Activity:

    
{
  "ProviderGuid": "1418ef04-b0b4-4623-bf7e-d74ab47bbdaa",
  "YaraMatch": [],
  "ProviderName": "Microsoft-Windows-WMI-Activity",
  "EventName": "EventID(11)",
  "Opcode": 0,
  "OpcodeName": "Info",
  "TimeStamp": "2025-09-19T12:59:14.9490692+03:00",
  "ThreadID": 10540,
  "ProcessID": 2096,
  "ProcessName": "svchost",
  "PointerSize": 8,
  "EventDataLength": 434,
  "XmlEventData": {
    "FormattedMessage": "CorrelationId = {1DF192FD-292E-0001-C9F6-F11D2E29DC01}; GroupOperationId = 10 482; OperationId = 10 483; Operation = Start IWbemServices::ExecQuery - root\\cimv2 : select * from win32_service where name = 'eventlog'; ClientMachine = DESKTOP-PKUMUTT; User = DESKTOP-PKUMUTT\\user; ClientProcessId = 2 308; NamespaceName = 134 027 490 711 288 204 ",
    "PID": "2096",
    "ActivityID": "4fbf831b76894b1a95ebc831d1f35663",
    "Operation": "Start IWbemServices::ExecQuery - root\\cimv2 : select * from win32_service where name = 'eventlog'",
    "User": "DESKTOP-PKUMUTT\\user",
    "MSec": "9515,8373",
    "NamespaceName": "\\\\.\\root\\cimv2",
    "IsLocal": "True",
    "OperationId": "10 483",
    "ClientMachineFQDN": "DESKTOP-PKUMUTT",
    "GroupOperationId": "10 482",
    "ClientProcessCreationTime": "134 027 490 711 288 204",
    "ClientMachine": "DESKTOP-PKUMUTT",
    "TID": "10540",
    "ProviderName": "Microsoft-Windows-WMI-Activity",
    "PName": "",
    "ClientProcessId": "2 308",
    "EventName": "EventID(11)",
    "CorrelationId": "{1DF192FD-292E-0001-C9F6-F11D2E29DC01}"
  }
}
    
    
title: 4RAYS_WIN_ETW_SearchPidOfEventLogService_rule
id: 535a026f-30e3-484c-a30a-1d950c61bfad
status: experimental
description: Detects the search for the EventLog service pid that was requested via WMI classes.
author: SOLAR 4RAYS
date: 2025-07-24
modified: 2025-07-24
logsource:
    product: windows
    service: Microsoft-Windows-WMI-Activity
    definition: In order to collect these events, you need to activate the Microsoft-Windows-WMI-Activity provider with GUID {1418ef04-b0b4-4623-bf7e-d74ab47bbdaa} and collect WMI Query events.
detection:
    suspiciouswmiquery:
      EventName: EventID(11)
      Operation|re: .*ExecQuery.*where name \= 'eventlog'.*
    condition: suspiciouswmiquery
tags:
    - attack.defense-evasion
    - attack.t1562.002
    - tool.Invoke-Phant0m
level: high
falsepositives:
    - no known false-positives
    

EvtPsst

Этот инструмент также создан для того, чтобы отключать сбор событий в EventLog, однако его принцип работы несколько отличается от Invoke-Phant0m, поскольку с его помощью закрываются дескрипторы, а не останавливаются потоки.

Принцип работы EvtPsst:

Принцип работы EvtPsst
  1. С помощью WMI-запроса инструмент перечисляет все процессы на системе и получает из них PID трех процессов:
  • EventLog — хост-процесс службы EventLog;
  • RpcSs — хост-процесс службы RpcSs (диспетчер служб для COM- и DCOM-серверов). Она выполняет запросы активации объектов, разрешение экспортера объектов и распределенный сбор мусора для этих серверов;
  • ProfSvc — хост-процесс службы ProfSvc (Эта служба отвечает за загрузку и выгрузку профилей пользователей).
  1. Получает дескриптор процесса RpcSs с правами PROCESS_QUERY_LIMITED_INFORMATION:
    
hProcessRPCSslow = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwPidRPCSs);
    
  1. Имперсонируется под SYSTEM с помощью токена, извлеченного из процесса ProfSvc:
    
hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwPID);
dwSuccess = OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE, &hToken);
bSuccess = ImpersonateLoggedOnUser(hToken);
    

SYSTEM-привилегии в данном случае нужны для получения токена из RPCs:

    
dwSuccess = OpenProcessToken(hProcessRPCSslow, TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, &hTokenRPCSs);
    
  1. У нас есть токен процесса RPCs. Имперсонируемся под RPCs и совершаем Handle Elevate (дублируем дескриптор и повышаем ему маску доступа, в данном случае с помощью имперсонации под RPCs у нас есть возможность сделать так с дескриптором его процесса). Право PROCESS_DUP_HANDLE позволяет дублировать дескрипторы этого процесса:
    
dwSuccess = ImpersonateLoggedOnUser(hTokenRPCSs);
dwSuccess = DuplicateHandle(GetCurrentProcess(), hProcessRPCSslow, GetCurrentProcess(), &hProcessRPCSsduplicate, PROCESS_DUP_HANDLE, FALSE, 0);
    
  1. В цикле мы перебираем все дескрипторы токенов, которые есть в процессе RPCs (нам заранее известно, что в нем есть токен процесса EventLog). Впоследствии находим дескриптор токена EventLog и дублируем его с правами TOKEN_ALL_ACCESS:
    
for (DWORD dwCounterHandle = 0; dwCounterHandle < dwHandleCount; dwCounterHandle++) {
	PSYSTEM_HANDLE pSystemHandle = NULL;
	pSystemHandle = &pHandleInfo->Handles[dwCounterHandle];
	if (pSystemHandle->GrantedAccess == 0xf01ff && pSystemHandle->ProcessId == (ULONG)dwPIDRPCSs && pSystemHandle->ObjectTypeNumber == bHandleTypeNumberToken) {
		DWORD dwSuccess = FAIL;
		HANDLE hToken = NULL;
		
		dwSuccess = DuplicateHandle(hProcessRPCSsduplicate, (HANDLE)(pSystemHandle->Handle), GetCurrentProcess(), &hToken, TOKEN_ALL_ACCESS, FALSE, 0);
		if (dwSuccess == FAIL) {
			printf("[-] Could not duplicate token\n");
			continue;
		}
		else {
			dwSuccess = checkTokenGroups(hToken);
			if (dwSuccess == SUCCESS) {
				*hTokenEvtLog = hToken;
				return SUCCESS;
			}
			else {
				if (hToken) {
					CloseHandle(hToken);
				}
				continue;
			}
		}

	}
    
  1. В этом же процессе RPCs находится и дескриптор процесса службы EventLog, который нам нужно найти точно так же, как и токен EventLog. Нам также известно, что этот дескриптор процесса EventLog у RPCs имеет права только на SYNCHRONIZE.
  2. Для того чтобы найти дескриптор процесса EventLog, нужно пройтись по всем дескрипторам процессов внутри RPCs и, используя ранее полученный дескриптор токена EventLog, пытаться осуществить DuplicateHandle() для него. Если это получилось, значит, мы получили дескриптор процесса EventLog с правами службы EventLog:
    
for (DWORD dwCounterHandle = 0; dwCounterHandle < dwHandleCount; dwCounterHandle++) {
	PSYSTEM_HANDLE pSystemHandle = NULL;
	pSystemHandle = &pHandleInfo->Handles[dwCounterHandle];
	if (pSystemHandle->GrantedAccess == SYNCHRONIZE && pSystemHandle->ProcessId == (ULONG)dwPidRPCSs && pSystemHandle->ObjectTypeNumber == bHandleTypeNumberProcess) {
		DWORD dwSuccess = FAIL;

		HANDLE hProcessRemote = NULL;
		hProcessRemote= (HANDLE)pSystemHandle->Handle;
		HANDLE hProcesssynchronize = NULL;
		dwSuccess = RevertToSelf();
		if (dwSuccess == FAIL) {
			printf("[-] Could not Revert the Token\n");
		}
		dwSuccess = ImpersonateLoggedOnUser(hTokenRPCSs);
		if (dwSuccess == FAIL) {
			printf("[-] Could not Impersonate RPCSs Token\n");
		}
		dwSuccess = DuplicateHandle(hProcessRPCSsduplicate, hProcessRemote, GetCurrentProcess(), &hProcesssynchronize, SYNCHRONIZE, FALSE, 0);
		if (dwSuccess == FAIL) {
			printf("[-] Could not duplicate process handle\n");
			continue;
		}
		else {
			dwSuccess = RevertToSelf();
			if (dwSuccess == FAIL) {
				printf("[-] Could not Impersonate RPCSs Token\n");
			}
			dwSuccess = ImpersonateLoggedOnUser(hTokenEvtLog);
			if (dwSuccess == FAIL) {
				printf("[-] Could not Impersonate EventLog Token\n");
			}
			HANDLE hProcessqueryduplicate = NULL;
			dwSuccess = DuplicateHandle(GetCurrentProcess(), hProcesssynchronize, GetCurrentProcess(), &hProcessqueryduplicate, PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION, FALSE, 0);
			if (dwSuccess == FAIL){
				//Wrong Process, cleanup and move to the next
				CloseHandle(hProcesssynchronize);
			}
			else{
				if (dwPidEventlog == GetProcessId(hProcessqueryduplicate)) {
					CloseHandle(hProcesssynchronize);
					*phProcessEventlogduplicatequery = hProcessqueryduplicate;
					return SUCCESS;
				}
				else {
					CloseHandle(hProcessqueryduplicate);
					CloseHandle(hProcesssynchronize);
				}
			}

	}

}
    
  1. На этом этапе мы получили дескриптор процесса EventLog без использования OpenProcess()-функции. Теперь мы проходимся по всем дескрипторам процесса EventLog и по характерной маске 0x400 находим EtwConsumer-дескриптор, после чего вызываем для каждого из них DuplicateHandle() с флагом DUPLICATE_CLOSE_SOURCE. Этот флаг дублирует дескриптор к нам в процесс и закрывает оригинальный, таким образом останавливая поток событий в EventLog:
    
for (DWORD dwCounterHandle = 0; dwCounterHandle < dwHandleCount; dwCounterHandle++) {
	PSYSTEM_HANDLE pSystemHandle = NULL;
	pSystemHandle = &pHandleInfo->Handles[dwCounterHandle];
	if (pSystemHandle->GrantedAccess == (ACCESS_MASK)0x400 && pSystemHandle->ProcessId == (ULONG)dwPidEventlog) {
		DWORD dwSuccess = FAIL;
		HANDLE hEtwConsumer = NULL;
		dwtotal++;
		//Kills ETW Consumer Handles
		dwSuccess = DuplicateHandle(hProcessEventlogduplicatequery, (HANDLE)(pSystemHandle->Handle), GetCurrentProcess(), &hEtwConsumer, 0x400, FALSE, DUPLICATE_CLOSE_SOURCE);
		if (GetLastError() == 50) {
			dwSuccess = SUCCESS;
		}
		if (dwSuccess == FAIL) {
			printf("[-] Could not close ETW Consumer Handle: %x\n", pSystemHandle->Handle);
		}
		else {
			dwSuccessfull++;
			printf("[+] Successfully closed ETW Consumer Handle: %x\n", pSystemHandle->Handle);
			CloseHandle(hEtwConsumer);		
		}

	}
    
Закрытие дескрипторов ETW-Consumer
Закрытие дескрипторов ETW-Consumer

Видно, что по своей задумке техники отключения событий очень похожи, однако в EvtPsst мы закрываем дескрипторы, а не потоки, как это реализовано в Invoke-Phant0m. При этом не используется функция OpenProcess(), что уменьшает вероятность обнаружения.

Детектирующую логику в случае EvtPsst можно построить на отслеживании вызова WINAPI DuplicateHandle(), где в качестве 7-го параметра указана опция DUPLICATE_CLOSE_SOURCE, а также в 5-м параметре — характерный DesiredAccess для дескриптора ETWConsumer.

Вызов функции DuplicateHandle() с флагом DUPLICATE_CLOSE_SOURCE
Вызов функции DuplicateHandle() с флагом DUPLICATE_CLOSE_SOURCE

Sigma:

    
title: 4RAYS_WIN_WinAPI_PossibleCloseEventLogConsumersHandle_rule
id: 34f465b2-7bbd-4583-a6f3-0b3b228db16e
status: experimental
description: Detects mass closing of descriptors with a mask characteristic of EtwConsumer during the process
references:
- https://nothingspecialforu.github.io/EvtPsstBlog/
author: SOLAR 4RAYS
date: 2025-09-19
modified: 2025-09-19
correlation:
    type: event_count
    rules:
        - 0e0385c6-ad20-4b50-8b63-87829375e815
    group-by:
        - Computer
        - ProcessName
    timespan: 10 s
    condition:
      gte: 4
tags:
    - attack.defense-evasion
    - attack.t1562.002
level: medium
falsepositives:
    - Possible legitimate handle duplication in svchost processes
---
title: 4RAYS_WIN_WinAPI_DuplicateAndCloseHandleSvchost_rule
name: 4RAYS_WIN_WinAPI_DuplicateAndCloseHandleSvchost_rule
id: 0e0385c6-ad20-4b50-8b63-87829375e815
status: experimental
description: Detects duplicate close handle operation with EtwConsumer Descriptor
author: SOLAR 4RAYS
date: 2025-09-19
modified: 2025-09-19
logsource:
    service: WinAPI
    product: windows
    definition: In order to detect this activity, it is necessary to monitor the NtDuplicateObject system call.
detection:
      CloseEtwConsumerHandle:
        FunctionName: NtDuplicateObject
        DesiredAccess: 0x400
        Options|contains: DUPLICATE_CLOSE_SOURCE
      condition: CloseEtwConsumerHandle
falsepositives:
    - this is enrich rule
level: informational
    

Sysmon Evasion

Unload Sysmon Driver

Одним из наиболее тривиальных способов прекращения потока событий от Sysmon является выгрузка его драйвера из ядра Windows.

Сам драйвер Sysmon считается драйвером мини-фильтра, поэтому им можно управлять с помощью fltMC.exe, встроенной утилиты WIndows для управления драйверами мини-фильтров.

Чтобы посмотреть, какие драйверы мини-фильтров присутствуют в системе, запускаем fltMC:

Для выгрузки драйвера добавляем флаг unload:

Итак, драйвер мини-фильтра выгружен, но, как мы и показывали на схеме ранее, Sysmon получает некоторые события из ETW, поэтому часть событий, которые не завязаны на callback-функциях ядра и обработке событий мини-фильтром, продолжают поступать.

Продолжают поступать следующие события:

  • сетевые соединения;
  • DNS-запросы;
  • WMI-запросы.
Поток сетевых событий из ETW
Поток сетевых событий из ETW

Подобная манипуляция позволит избежать дальнейшего обнаружения на уровне:

  • запусков процессов;
  • загрузки образов;
  • создания/удаления файлов;
  • операций реестра.

Подобные попытки выгрузки Sysmon-драйвера предлагаем детектировать следующим образом:

  1. Событие журнала System от провайдера Microsoft-Windows-FilterManager:
  1. Событие от драйвера Sysmon (ошибка, характерная для выгрузки драйвера):
  1. Характерный командлайн запуска утилиты fltMC.exe:

WMI Persistence in Unusual Namespace

Для начала посмотрим на то, как выглядит стандартная техника закрепления через WMI Subscription в логах Sysmon. В конфигурационном файле настраиваются события WMI (просто получаем все события):

    
 <WmiEvent onmatch="exclude"></WmiEvent>
    
Конфигурация Sysmon для получения событий WMI

Скриптов и фреймворков, которые реализуют данное закрепление, очень много. Для примера возьмем PowerShell-скрипт WMI-Persistence.ps1:

    
PS C:\me\Wmi-Persistence> Install-Persistence -Trigger Startup -Payload "c:\windows\notepad.exe"
Event Filter Dcom Launcher successfully written to host
Event Consumer Dcom Launcher successfully written to host
Filter To Consumer Binding successfully written to host
    
Запуск скрипта WMI-Persistence.ps1

Процесс закрепления состоит в следующем:

  • Создание Filter на определенные события, например на запуск процесса с определенным именем.
  • Создание Consumer — описание логики того, что произойдет в момент сработки фильтра.
  • Создание связи (bind) между Filter и Consumer.
Создание filter, consumer и binding в скрипте
Создание filter, consumer и binding в скрипте

Заметим, что все сущности создаются внутри пространства имен root/subscription, что, как мы увидим далее, Sysmon успешно детектирует.

Смотрим в Autoruns:

Закрепление WMI в Autoruns
Закрепление WMI в Autoruns

Закрепление прошло успешно, теперь посмотрим, какие события мы получили от Sysmon:

События WMI в журнале Sysmon
События WMI в журнале Sysmon
Событие создания WMI-фильтра
Событие создания WMI-фильтра
Событие создания WMI-Consumer
Событие создания WMI-Consumer
Событие создания связи между Consumer и Filter
Событие создания связи между Consumer и Filter

Все как по учебнику. Просто берем события и пишем детектирующую логику. Но каким образом можно обойти логирование WMI в Sysmon?

Вспомним, что подписка на события в недрах Sysmon осуществляется только на пространство имен ROOT\\Subscription. Однако существует еще одно пространство имен, в котором возможно создавать filters, consumers и связи между ними: ROOT\\Default.

Наличие класса CommandLineEventConsumer в пространстве имен ROOT\\Default
Наличие класса CommandLineEventConsumer в пространстве имен ROOT\\Default

Попробуем создать в нем filter, consumer и binding. Изменим название нашего фильтра в коде, также поменяем payload на mimikatz.exe и изменим пространство имен на root\Default:

Замена названий фильтров
Замена названий фильтров
Замена пространства имен на root/default
Замена пространства имен на root/default/figcaption>

Запускаем скрипт:

Запуск процесса закрепления в пространстве имен root/default

Закрепление прошло успешно. Появился второй WMI Consumer. Однако Sysmon не детектирует данную активность.

ETW как самостоятельный источник телеметрии

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

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

Useful ETW Providers

В Windows существует огромное количество ETW-провайдеров:

Перечисление ETW-провайдеров в Windows утилитой ETW-Explorer
Перечисление ETW-провайдеров в Windows утилитой ETW-Explorer

Однако не все из этого огромного количества могут быть полезны для детектирования атак или расследования инцидентов. Мы решили выделить и рассмотреть повнимательнее некоторые из «полезных» провайдеров.

ETW .NET

Microsoft-Windows-DotNETRuntime (GUID: E13C0D23-CCBC-4E12-931B-D9CC2EEE27E4). Этот провайдер генерирует различные события из среды выполнения .NET, включая сборщик мусора, JIT, выгрузку/загрузку сборок. С его помощью этого провайдера можно собирать события, которые помогут потенциально отследить различные атаки / эксплуатируемые уязвимости с помощью .NET.

В качестве примера рассмотрим несколько атак.

VIEWSTATE deserialization

Принцип действия данной атаки довольно глубоко разобран в блоге 4RAYS. Напомним, как это происходит:

  1. Крадутся ключи шифрования от веб-страниц IIS.
  2. С помощью ysoserial и ключей генерируется payload, который можно прокинуть во VIEWSTATE.
  3. В VIEWSTATE прокидывается метод загрузки .NET-сборки и байткод веб-шелла (сама .NET-сборка).
  4. Сборка выполняется в памяти процесса w3wp.exe.

На самом последнем шаге в событиях провайдера Microsoft-Windows-DotNETRuntime мы можем отследить загрузку .NET-сборки в память процесса. В качестве примера для источников событий использована утилита SilkETW:

Событие загрузки неподписанной сборки в память процесса w3wp.exe
Событие загрузки неподписанной сборки в память процесса w3wp.exe
Событие загрузки модуля без полного пути до него на диске
Событие загрузки модуля без полного пути до него на диске

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

ToolShell

Нашумевшая уязвимость в SharePoint.

Описание хода эксплуатации:

  1. Злоумышленник отправляет специально созданный POST-запрос, содержащий в поле Referrer ссылку на aspx-страницу, не требующую аутентификации (например, стандартная страница signout.aspx), по адресу: https://<TARGET>/_layouts/15/ToolPane.aspx?DisplayMode=Edit&a=/ToolPane.aspx.
  2. Далее при эксплуатации недостатков механизма десериализации данных внутри запроса можно воспользоваться функциональностью ToolPane.aspx и записать на диск вредоносную aspx-страницу. Для этого в исходном запросе злоумышленники помещают сериализованную полезную нагрузку в поля:
    MSOTlPn_Uri (поле Control source path),
    MSOTlPn_DWP (поле Web partial configuration).
  3. Созданная в результате предыдущего этапа aspx-страница подгружает в память вредоносную .NET-сборку, используемую злоумышленниками для кражи ASP.NET-ключей.
  4. Далее, имея ключи ASP.NET, злоумышленники сериализуют полезную нагрузку при помощи утилиты ysoserial. Таким образом, эксплуатируя недостатки механизма десериализации данных VIEWSTATE на SharePoint, они получают возможность выполнения произвольного кода без аутентификации.

Здесь провайдер Microsoft-Windows-DotNETRuntime может помочь увидеть эксплуатацию на третьем шаге. Дело в том, что каждая aspx-страница в итоге компилируется в .NET-сборку и при первом своем использовании на сервере она будет скомпилирована и загружена в память до перезапуска процесса w3wp.exe.

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

Новая aspx-страница в виде сборки в процессе w3wp.exe
Новая aspx-страница в виде сборки в процессе w3wp.exe
Событие загрузки новой сборки в провайдере Microsoft-Windows-DotNETRuntime
Событие загрузки новой сборки в провайдере Microsoft-Windows-DotNETRuntime

.NET Assembly Injection

Злоумышленники могут попытаться осуществить инъекцию вредоносной .NET-сборки в неуправляемый (unmanaged) CLR-процесс, что позволит им провести fileless-атаку, так как сборка загружается прямо в память и не записывается на диск. К тому же это усложняет обнаружение, так как инъекция затрагивает нативные процессы.

Подобную технику реализует модуль Cobalt Strike execute-assembly.

Вредоносная .NET-сборка внутри процесса notepad.exe
Вредоносная .NET-сборка внутри процесса notepad.exe

Эту активность можно обнаружить с помощью события провайдера Microsoft-Windows-DotNETRuntime — MethodLoadVerbose, которое регистрируется при первом вызове метода в момент его JIT-компиляции и показывает NameSpace вызываемого метода, а также его имя. Это позволит осуществлять детектирование на основе известных реализаций версий вредоносных сборок по их уникальным именам классов и методов.

Событие Method/LoadVerbose в ходе инъекции .NET-сборки в память notepad.exe
Событие Method/LoadVerbose в ходе инъекции .NET-сборки в память notepad.exe

ETW RPC

Remote Procedure Call, или удаленный вызов процедур, представляет собой технологию межпроцессного взаимодействия IPC. С помощью данной технологии один процесс может вызывать функции/процедуры у другого процесса удаленно, обращаясь к интерфейсам.

В Windows эта технология используется повсеместно: удаленное создание задачи, репликация контроллеров домена, удаленное создание новых значений в реестре, связь с COM и т. д.

По умолчанию в стандартных журналах Windows нет возможности мониторинга RPC-вызовов. Здесь нам поможет ETW-провайдер, а именно провайдер с GUID: 6ad52b32-d609-4be9-ae07-ce8dae937e3 — Microsoft-Windows-RPC.

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

DCSync

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

Стандартные методы детектирования DCSync обычно строятся на событиях доступа к объекту AD domainDNS с правами на репликацию, которые нетрудно детектировать, используя соответствующие GUID:

  • 1131f6aa-9c07–11d1-f79f-00c04fc2dcd2 Replicating Directory Changes;
  • 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 Replicating Directory Changes All;
  • 89e95b76-444d-4c62-991a-0facbeda640c Replicating Directory Changes In Filtered Set.
Событие 4662. Доступ к объекту domainDNS с правом Replicating Directory Changes
Событие 4662. Доступ к объекту domainDNS с правом Replicating Directory Changes

Однако с помощью провайдера Microsoft-Windows-RPC можно найти альтернативный подход к обнаружению данной активности.

Дело в том, что за репликацию в Active Directory отвечает определенный RPC-интерфейс drsuapi. Возможности данного интерфейса позволяют контроллерам домена выполнять репликацию. Точно такой же принцип работает при использовании утилит вроде mimikatz.

GUID интерфейса drsuapi
GUID интерфейса drsuapi

Методы, с помощью которых можно детектировать запросы на репликацию через провайдера RPC:

  • IDL_DRSBind (создание дескриптора контекста интерфейса) OpNum 0;
  • IDL_DRSGetNCChanges (метод который производит репликацию) OpNum 3,
    
{
   "FormattedMessage":"Client RPC call started. InterfaceUuid: e3514235-4b06-11d1-ab04-00c04fc2dcd2 
   OpNum: 0 
   Protocol: TCP NetworkAddress dc1.test.local Endpoint  Binding Options NULL Authentication Level Packet Privacy Authentication Service Negotiate Impersonation Level Default ",
   "Protocol":"TCP",
   "NetworkAddress":"dc1.test.local",
   "ActivityID":"70a91031521e431c9733826862a55b09",
   "Endpoint":"",
   "MSec":"11984.7642",
   "AuthenticationLevel":"Packet Privacy",
   "AuthenticationService":"Negotiate",
   "ImpersonationLevel":"Default",
   "InterfaceUuid":"e3514235-4b06-11d1-ab04-00c04fc2dcd2",
   "PID":"11900",
   "ProcNum":"0",
   "TID":"21036",
   "ProviderName":"Microsoft-Windows-RPC",
   "PName":"",
   "Options":"NULL",
   "EventName":"RpcClientCall/Start"
}
    
Вызов метода IDL_DRSBind при атаке DCSync. Событие из SilkETW

    
{
   "FormattedMessage":"Client RPC call started. InterfaceUuid: e3514235-4b06-11d1-ab04-00c04fc2dcd2 
   OpNum: 3 Protocol: TCP NetworkAddress dc1.test.local Endpoint 6405 Binding Options NULL Authentication Level Packet Privacy Authentication Service Negotiate Impersonation Level Default ",
   "Protocol":"TCP",
   "NetworkAddress":"dc1.test.local",
   "ActivityID":"ffdd2c7ae5c84e2290d835d9594eb096",
   "Endpoint":"6405",
   "MSec":"11985.8920",
   "AuthenticationLevel":"Packet Privacy",
   "AuthenticationService":"Negotiate",
   "ImpersonationLevel":"Default",
   "InterfaceUuid":"e3514235-4b06-11d1-ab04-00c04fc2dcd2",
   "PID":"11900",
   "ProcNum":"3",
   "TID":"21036",
   "ProviderName":"Microsoft-Windows-RPC",
   "PName":"",
   "Options":"NULL",
   "EventName":"RpcClientCall/Start"
}
    
Вызов метода IDL_DRSGetNCChanges при атаке DCSync. Событие из SilkETW

Atexec-pro

Atexec — известный скрипт, часть фреймворка Impacket, который позволяет создавать задачи на удаленном хосте и выполнять через них произвольные команды. В качестве способа доставки задачи используется протокол SMB и пайп ATSVC.

Стандартный сценарий атаки следующий:

  • Атакующий обращается по протоколу SMB к пайпу ATSVC и отправляет запрос на создание задачи с предоставленными параметрами.
  • На хосте создается задача, и при ее выполнении результат записывается в общую SMB-шару.
  • Злоумышленник считывает результат.
    
atexec.py admin2:<redacted>@10.199.49.37 'cmd /c whoami'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[!] This will work ONLY on Windows >= Vista
[*] Creating task \TQPYiPtF
[*] Running task \TQPYiPtF
[*] Deleting task \TQPYiPtF
[*] Attempting to read ADMIN$\Temp\TQPYiPtF.tmp
[*] Attempting to read ADMIN$\Temp\TQPYiPtF.tmp
nt authority\system
    
Запуск Atexec и получение результата выполнения задачи

Данная активность может быть успешно обнаружена с помощью отслеживания обращения к пайпу ATSVC (событие 5145), что происходит в продовой среде очень редко (в легитимных целях):

Обращение к пайпу ATSVC для создания задачи (стандартная работа Atexec)
Обращение к пайпу ATSVC для создания задачи (стандартная работа Atexec)

У Atexec есть модифицированный «аналог» — Atexec-pro, который обладает аналогичной функциональностью: создание задачи => выполнение => запись результата в общий пайп. Однако в принципах их работы есть отличие: atexec-pro создает задачу не через ATSVC, а через динамический эндпоинт сервиса Task Scheduler — ITaskSchedulerService. Эта особенность позволяет не использовать SMB-протокол и, соответственно, обходить обнаружение за счет обращения к ATSVC.

GUID Динамического RPC интерфейса ITaskSchedulerService
GUID Динамического RPC интерфейса ITaskSchedulerService

Для создания задачи Atexec-pro использует методы интерфейса ITaskSchedulerService, и на ключевом из них можно построить детект: SchRpcRegisterTask (метод регистрации задачи).

    
{
   "FormattedMessage":"Server RPC call started. InterfaceUuid: 86d35949-83c9-4044-b424-db363231fd0c 
   OpNum: 1 Protocol: TCP Endpoint 6403 Authentication Level Packet Privacy Authentication Service NTLM ",
   "Protocol":"TCP",
   "NetworkAddress":"NULL",
   "Endpoint":"6403",
   "MSec":"16979.5630",
   "AuthenticationLevel":"Packet Privacy",
   "AuthenticationService":"NTLM",
   "ImpersonationLevel":"Default",
   "InterfaceUuid":"86d35949-83c9-4044-b424-db363231fd0c",
   "PID":"1644",
   "ProcNum":"1",
   "TID":"24480",
   "ProviderName":"Microsoft-Windows-RPC",
   "PName":"",
   "Options":"NULL",
   "EventName":"RpcServerCall/Start"
}
    
Вызов метода SchRpcRegisterTask при использовании Atexec-Pro. Событие из SilkETW

Правда, у данных методик обнаружения, как в целом и у всей телеметрии, не специализированной под детектирование вредоносной активности, есть минусы:

  • Отсутствие базовых обогащений (хост источник, pid, username).
  • Большой EPS при ошибках в параметрах фильтрации (нужен очень узкий профиль).
  • Сложность в формулировке гипотез.

ETW TI

Microsoft-Windows-Threat-Intelligence — провайдер режима ядра, который был создан Microsoft для использования в качестве телеметрии различными доверенными вендорами, поэтому для того, чтобы иметь возможность получать с него события, нужно иметь подпись от Microsoft — чтобы запускаться в режиме PPL (AntiMalware). ETW TI доступен для чтения только из процессов PPL.

На данном провайдере мы немного заострим внимание, поскольку он считается менее инвазивным (в отличие от загрузки драйверов / установки хуков) методом получения низкоуровневой телеметрии для обеспечения безопасности конечных точек.

Microsoft-Windows-Threat-Intelligence-провайдер предоставляет множество полезных событий безопасности, которые могут быть применены для детектирования различных техник Process Injection.

В провайдере присутствуют события:

  • Выделения виртуальной памяти в процессе.
  • Создание APC-очереди.
  • Чтение памяти.
  • Остановка, запуск потоков.
  • Загрузка и выгрузка драйвера, etc.
    
PS C:\Users\user> logman.exe query providers Microsoft-Windows-Threat-Intelligence

Поставщик                                GUID
-------------------------------------------------------------------------------
Microsoft-Windows-Threat-Intelligence    {F4E1897C-BB5D-5668-F1D8-040F4D8DD344}

Значение            Ключевое слово       Описание
-------------------------------------------------------------------------------
0x0000000000000001  KERNEL_THREATINT_KEYWORD_ALLOCVM_LOCAL
0x0000000000000002  KERNEL_THREATINT_KEYWORD_ALLOCVM_LOCAL_KERNEL_CALLER
0x0000000000000004  KERNEL_THREATINT_KEYWORD_ALLOCVM_REMOTE
0x0000000000000008  KERNEL_THREATINT_KEYWORD_ALLOCVM_REMOTE_KERNEL_CALLER
0x0000000000000010  KERNEL_THREATINT_KEYWORD_PROTECTVM_LOCAL
0x0000000000000020  KERNEL_THREATINT_KEYWORD_PROTECTVM_LOCAL_KERNEL_CALLER
0x0000000000000040  KERNEL_THREATINT_KEYWORD_PROTECTVM_REMOTE
0x0000000000000080  KERNEL_THREATINT_KEYWORD_PROTECTVM_REMOTE_KERNEL_CALLER
0x0000000000000100  KERNEL_THREATINT_KEYWORD_MAPVIEW_LOCAL
0x0000000000000200  KERNEL_THREATINT_KEYWORD_MAPVIEW_LOCAL_KERNEL_CALLER
0x0000000000000400  KERNEL_THREATINT_KEYWORD_MAPVIEW_REMOTE
0x0000000000000800  KERNEL_THREATINT_KEYWORD_MAPVIEW_REMOTE_KERNEL_CALLER
0x0000000000001000  KERNEL_THREATINT_KEYWORD_QUEUEUSERAPC_REMOTE
0x0000000000002000  KERNEL_THREATINT_KEYWORD_QUEUEUSERAPC_REMOTE_KERNEL_CALLER
0x0000000000004000  KERNEL_THREATINT_KEYWORD_SETTHREADCONTEXT_REMOTE
0x0000000000008000  KERNEL_THREATINT_KEYWORD_SETTHREADCONTEXT_REMOTE_KERNEL_CALLER
0x0000000000010000  KERNEL_THREATINT_KEYWORD_READVM_LOCAL
0x0000000000020000  KERNEL_THREATINT_KEYWORD_READVM_REMOTE
0x0000000000040000  KERNEL_THREATINT_KEYWORD_WRITEVM_LOCAL
0x0000000000080000  KERNEL_THREATINT_KEYWORD_WRITEVM_REMOTE
0x0000000000100000  KERNEL_THREATINT_KEYWORD_SUSPEND_THREAD
0x0000000000200000  KERNEL_THREATINT_KEYWORD_RESUME_THREAD
0x0000000000400000  KERNEL_THREATINT_KEYWORD_SUSPEND_PROCESS
0x0000000000800000  KERNEL_THREATINT_KEYWORD_RESUME_PROCESS
0x0000000001000000  KERNEL_THREATINT_KEYWORD_FREEZE_PROCESS
0x0000000002000000  KERNEL_THREATINT_KEYWORD_THAW_PROCESS
0x0000000004000000  KERNEL_THREATINT_KEYWORD_CONTEXT_PARSE
0x0000000008000000  KERNEL_THREATINT_KEYWORD_EXECUTION_ADDRESS_VAD_PROBE
0x0000000010000000  KERNEL_THREATINT_KEYWORD_EXECUTION_ADDRESS_MMF_NAME_PROBE
0x0000000020000000  KERNEL_THREATINT_KEYWORD_READWRITEVM_NO_SIGNATURE_RESTRICTION
0x0000000040000000  KERNEL_THREATINT_KEYWORD_DRIVER_EVENTS
0x0000000080000000  KERNEL_THREATINT_KEYWORD_DEVICE_EVENTS
0x8000000000000000  Microsoft-Windows-Threat-Intelligence/Analytic
    
Типы событий провайдера Microsoft-Windows-Threat-Intelligence

Теперь рассмотрим, где именно формируется телеметрия от этого провайдера.

Генерация события выделения виртуальной памяти

Для того чтобы понять, где провайдер берет телеметрию, посмотрим в бинарный файл ядра Windows — ntoskrnl.exe.

В ETW Explorer можно посмотреть описание и манифест провайдера Microsoft-Windows-Threat-Intelligence:

GUID провайдера Microsoft-Windows-Threat-Intelligence
GUID провайдера Microsoft-Windows-Threat-Intelligence
Описание событий из манифеста Microsoft-Windows-Threat-Intelligence
Описание событий из манифеста Microsoft-Windows-Threat-Intelligence

Внутри ядра можно обнаружить, что GUID f4e1897c-bb5d-5668-f1d8-040f4d8dd344 соответствует константа ThreatIntProviderGuid:

Константа ThreatIntProviderGuid
Константа ThreatIntProviderGuid

Сделав перекрестную ссылку на эту константу, видно, что она вызывается единожды в функции EtwRegister():

Вызов функции EtwRegister()
Вызов функции EtwRegister()
    
NTSTATUS EtwRegister(
  [in]           LPCGUID            ProviderId,
  [in, optional] PETWENABLECALLBACK EnableCallback,
  [in, optional] PVOID              CallbackContext,
  [out]          PREGHANDLE         RegHandle
);
    
Определение функции EtwRegister()

По названию функций нетрудно догадаться, что она осуществляет регистрацию поставщика событий. Это аналог UserMode-функции EventRegister(). На выходе из функции мы получаем RegHandle, которым и будем оперировать в дальнейшем для совершения каких-либо действий над провайдером.

Аналогично, используя перекрестные ссылки, возможно найти места использования RegHandle Microsoft-Windows-Threat-Intelligence. В итоге мы попадем в функцию EtwTiLogAllocExecVm():

Перекрестная ссылка на RegHandle Microsoft-Windows-Threat-Intelligence
Перекрестная ссылка на RegHandle Microsoft-Windows-Threat-Intelligence

Функция EtwTiLogAllocExecVm() по сути и является той самой callback-функцией, которая расположена в реализациях системных вызовов.

Если посмотреть в конец реализации EtwTiLogAllocExecVm(), то можно увидеть, что осуществляется вызов функции EtwWrite(). Данная функция осуществляет запись события в указанный провайдер:

    
NTSTATUS EtwWrite(
  [in]           REGHANDLE              RegHandle,
  [in]           PCEVENT_DESCRIPTOR     EventDescriptor,
  [in, optional] LPCGUID                ActivityId,
  [in]           ULONG                  UserDataCount,
  [in, optional] PEVENT_DATA_DESCRIPTOR UserData
);
    
Определение функции EtwWrite()

Вызов функции EtwWrite() внутри функции EtwTiLogAllocExecVm()
Вызов функции EtwWrite() внутри функции EtwTiLogAllocExecVm()

В первом аргументе мы говорим буквально, в какой провайдер хотим записывать событие, это Microsoft-Windows-Threat-Intelligence, — здесь мы используем RegHandle. Второй аргумент — указатель на _EVENT_DESCRIPTOR, который описывает структуру отправляемого события:

    
typedef struct _EVENT_DESCRIPTOR {
  USHORT    Id;
  UCHAR     Version;
  UCHAR     Channel;
  UCHAR     Level;
  UCHAR     Opcode;
  USHORT    Task;
  ULONGLONG Keyword;
} EVENT_DESCRIPTOR, *PEVENT_DESCRIPTOR;
    
Структура _EVENT_DESCRIPTOR

Мы также можем посмотреть все варианты наполнения _EVENT_DESCRIPTOR для коллбэка EtwTiLogAllocExecVm():

Варианты наполнения структур _EVENT_DESCRIPTOR для внутри EtwTiLogAllocExecVm()
Варианты наполнения структур _EVENT_DESCRIPTOR для внутри EtwTiLogAllocExecVm()

Первое число в структуре — это hex-представление EventID, и если сравнить его с тем, что написано в манифесте провайдера, то все совпадает:

EventID событий выделения памяти в провайдере Microsoft-Windows-Threat-Intelligence
EventID событий выделения памяти в провайдере Microsoft-Windows-Threat-Intelligence

Мы уже поняли, как событие отправляется в провайдер, теперь же надо понять, где располагается коллбэк EtwTiLogAllocExecVm(). Смотрим перекрестные ссылки:

Перекрестная ссылка на EtwTiLogAllocExecVm()
Перекрестная ссылка на EtwTiLogAllocExecVm()

Сама callback-функция располагается во внутренней функции ядра MiAllocateVirtualMemory() — это низкоуровневая ядерная функция, в которой реализован механизм выделения виртуальной памяти:

Перекрестная ссылка на MiAllocateVitrualMemory()
Перекрестная ссылка на MiAllocateVitrualMemory()

Сама функция MiAllocateVirtualMemory() при выделении памяти вызывается реализацией системного вызова NtAllocateVirtualMemory(). В процессе своей работы MiAllocateVirtualMemory() вызывает EtwTiLogAllocExecVm() — callback, который логирует информацию о выделении исполняемой памяти в провайдер Microsoft-Windows-Threat-Intelligence.

Early Bird APC Injection

Рассмотрим вариант использования телеметрии провайдера Microsoft-Windows-Threat-Intelligence на примере варианта техники Process Injection: Early Bird APC Injection.

Общие шаги для техники:

  1. Создание легитимного процесса в состоянии CREATE_SUSPENDED или DEBUG_PROCESS.
  2. Выделение памяти для шелл-кода внутри созданного процесса.
  3. Запись шелл-кода в выделенную память.
  4. Объявление APC-процедуры, которая указывает на память, в которую записан шелл-код.
  5. Добавление APC-процедуры в очередь для main-потока.
  6. Вывод процесса из состояния CREATE_SUSPENDED. В этот момент поток возобновляет выполнение и очередь APC очищается, то есть выполняются все процедуры в очереди.

Стоит отметить, что данный способ инъекции в процесс позволяет обойти некоторые EDR-решения, так как при запуске процесса в состоянии CREATE_SUSPENDED его инициализация (маппинг в память системных dll, очистка очереди APC) еще не завершена. Окончательно это случится после вывода процесса из CREATE_SUSPENDED-состояния, когда в конце инициализации произойдет вызов функции NtTestAlert(), которая отвечает за очистку очереди APC.

Поэтому, если EDR-хуки устанавливаются в процессе после очистки APC очереди, EDR не сможет увидеть подозрительную активность при инициализации процесса.

Ход выполнения EarlyBird APC Injection
Ход выполнения EarlyBird APC Injection
Запуск regedt32.exe от notepad.exe в рамках атаки Early Bird Injection
Запуск regedt32.exe от notepad.exe в рамках атаки Early Bird Injection

С обнаружением данной техники нам может помочь событие THREATINT_QUEUEUSERAPC_REMOTE (EventID — 4) провайдера Microsoft-Windows-Threat-Intelligence, которое генерируется в момент инициализации APC-очереди в UserMode-потоке.

В качестве источника событий ETW-TI мы использовали open-source-инструмент SealighterTI.

    
{
    "header": {
        "activity_id": "{00000000-0000-0000-0000-000000000000}",
        "event_flags": 577,
        "event_id": 4,
        "event_name": "",
        "event_opcode": 0,
        "event_version": 1,
        "process_id": 4,
        "provider_name": "Microsoft-Windows-Threat-Intelligence",
        "task_name": "KERNEL_THREATINT_TASK_QUEUEUSERAPC",
        "thread_id": 6504,
        "timestamp": "2025-02-07 08:29:20Z",
        "trace_name": "Microsoft-Windows-Threat-Intelligence"
    },
    "properties": {
        "ApcArgument1": "0x251CCF00000",
        "ApcArgument1VadAllocationBase": "0x251CCF00000",
        "ApcArgument1VadAllocationProtect": 4,
        "ApcArgument1VadCommitSize": "0x1000"
        ………………….
        "OriginalProcessStartKey": 844424930133247,
        "RealEventTime": "2025-02-07 08:29:20Z",
        "TargetProcessCreateTime": "2025-02-07 08:29:20Z",
        "TargetProcessId": 2976,
        "TargetProcessProtection": 0,
        "TargetProcessSectionSignatureLevel": 0,
        "TargetProcessSignatureLevel": 0,
        "TargetProcessStartKey": 844424930133248,
        "TargetThreadAlertable": 0,
        "TargetThreadCreateTime": "2025-02-07 08:29:20Z",
        "TargetThreadId": 1136
    },
    "property_types": {
        …….
    },
    "stack_trace": [
        "0xFFFFF80776D5ABDC",
        "0xFFFFF8077710944A",
        "0xFFFFF80776CB8655",
        "0xFFFFF80776D55995",
        "0xFFFFF80776DFE938"
    ]
}
    
Событие создания APC-очереди от провайдера ETW-TI

Данное событие можно коррелировать с событиями запусков процессов и получать довольно точную логику обнаружения такого способа инъекции.

ETW Patching

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

Как уже говорилось ранее, глобально существуют 2 типа провайдеров:

  • Провайдеры, источником событий которых являются приложения из UserMode.
  • Провайдеры, источником событий которых является само ядро Windows.

В первом случае ход доставки события выглядит следующим образом:

Процесс передачи события в UserMode-провайдер
Процесс передачи события в UserMode-провайдер
  1. Происходит вызов функции EventWrite() из библиотеки advapi32.dll. Она является оберткой над функцией из ntdll:EtwEventWrite().
    
ULONG EVNTAPI EventWrite(
  [in]           REGHANDLE              RegHandle,
  [in]           PCEVENT_DESCRIPTOR     EventDescriptor,
  [in]           ULONG                  UserDataCount,
  [in, optional] PEVENT_DATA_DESCRIPTOR UserData
);
    
Определение функции EventWrite()

Функция EventWrite в advapi32.dll
Функция EventWrite в advapi32.dll
    
EtwEventWrite(
    __in REGHANDLE RegHandle,
    __in PCEVENT_DESCRIPTOR EventDescriptor,
    __in ULONG UserDataCount,
    __in_ecount_opt(UserDataCount) PEVENT_DATA_DESCRIPTOR UserData
    );
    
Определение функции EtwEventWrite()

  1. Функция EtwEventWrite() вызывает EtwpEventWriteFull(), которая обрабатывает аргументы и впоследствии вызывает NtTraceEvent(), которая в свою очередь осуществляет системный вызов к своей ядерной реализации.
  2. Ядро получает RegHandle и событие, благодаря чему оно «понимает», в какой провайдер следует записать конкретное событие.

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

Ниже представлен пример кода, который осуществляет патчинг первой инструкции функции EventWrite() в ntdll на инструкцию ret:

    
//pointer to EtwEventWrite()
void *EtwEventWritep = GetProcAddress(LoadLibraryA("ntdll"), "EtwEventWrite");

//add write access to page
VirtualProtect(EtwEventWritep , 1, PAGE_EXECUTE_READWRITE, &lastprot);

// Patch on ret instruction
mchar patch[] = { 0xc3 };
memcpy(EtwEventWritep, patch, 1);
    
Патчинг функции EtwEventWrite()

Для наглядности возьмем события провайдера Microsoft-Windows-DotNETRuntime в PerfView. Пробуем инжектировать .NET-сборку в процесс notepad.exe и смотрим, какие типы событий будут доступны до патчинга и после:

События Microsoft-Windows-DotNETRuntime до патчинга функции EtwEventWrite() при инъекции сборки
События Microsoft-Windows-DotNETRuntime до патчинга функции EtwEventWrite() при инъекции сборки
Отсутствие событий от Microsoft-Windows-DotNETRuntime после патчинга функции EtwEventWrite()
Отсутствие событий от Microsoft-Windows-DotNETRuntime после патчинга функции EtwEventWrite()

Заметим, что из скриншота выше видно, что благодаря патчингу вообще все UserMode-провайдеры перестали отдавать события (для процесса notepad.exe), остались только ядерные.

Теперь сравним, как выглядит функция EtwEventWrite() до патчинга и после:

EtwEventWrite() до патчинга
EtwEventWrite() до патчинга

EtwEventWrite() после патчинга
EtwEventWrite() после патчинга

В итоге злоумышленники могут таким образом отключать UserMode-ETW-провайдеры для указанных процессов.

Схема доставки событий после патчинга
Схема доставки событий после патчинга

Если говорить о варианте с попыткой ослепления Kernel-Mode-ETW-провайдеров, то злоумышленникам для этого требуется возможность изменять память самого ядра. Для этого придется воспользоваться, к примеру, техникой BYOVD. В противном случае остановить поток событий от Kernel Mode ETW будет невозможно.

Intercepting Windows system calls

Как происходит системный вызов в Windows

Как мы уже описывали выше, практически любое действие в usermode в ОС Windows проходит множество шагов перед тем, как будет выполнено на уровне ядра. Рассмотрим пример того, как происходит вызов функции CreateFileW() из kernel32.dll:

  1. Приложение вызывает функцию CreateFileW() из kernel32.dll.
  2. Далее вызывается функция CreateFileW() из библиотеки kernelbase.dll.
  3. Далее происходит вызов NtCreateFile() из ntdll.dll — последний вызов перед переходом в пространство ядра.
  4. С помощью операции syscall и передачи в нее номера вызываемой функции из SSDT (SSN) происходит переход в контекст ядра.
  5. В ядре выполняется функция NtCreateFile(), которая и производит все необходимые действия для создания файла.
WinAPI-call flow
WinAPI-call flow

Различные защитные решения (EDR/AV/EPP) могут перехватывать вызовы функций для того, чтобы получать важный поток телеметрии, позволяющий обнаружить атаки, которые непросто увидеть при наличии только описанной выше телеметрии.

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

Способы перехвата системного вызова

WinAPI hooking

Многие защитные решения реализуют технику сбора телеметрии с помощью перехвата библиотечных функций — WinAPI hooking. Принцип работы тут такой:

  1. Решение отслеживает все запускаемые на системе процессы (например, с помощью ядерной callback-функции).
  2. В запускаемый процесс внедряется библиотека, реализующая постановку перехватов в загруженные библиотеки (чаще всего в ntdll.dll).
  3. Далее каждый перехваченный вызов будет сначала проходить через обработчик защитного решения, генерируя телеметрию.

Как правило, перехват осуществляется путем изменения пролога перехватываемой функции на так называемый «трамплин», передающий управление на функцию-обработчик защитного решения. В обработчике аргументы вызова анализируются, генерируется соответствующее событие, после чего поток выполнения возвращается в перехваченную библиотечную функцию. Также применяется метод, называемый IAT (Import Address Table) hooking, при котором адреса интересующих функций в таблице импорта процесса подменяются на адреса функций в библиотеке-обработчике.

Hooked WinAPI-call flow
Hooked WinAPI-call flow

Пример того, как выглядит пролог функции до внедрения трамлина:

И после:

Evading Intercepting Syscalls

Злоумышленники, зная, что защитные решения часто нацелены на перехват библиотечных функций для построения детектов, используют различные техники, позволяющие им оставаться незамеченными. Рассмотрим три из них:

  1. Direct syscalls.
  2. Indirect syscalls.
  3. Ntdll remapping.

Direct syscalls

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

Алгоритм примерно следующий:

  1. Для интересующей функции из ntdll находится соответствующий ей SSN (System Service Number), который может меняться в Windows от версии к версии.
  2. Подготавливаются аргументы вызываемой функции.
  3. Вызывается оператор syscall.

Техника позволяет обходить поставленные перехваты в ntdll, поскольку поток выполнения не проходит через трамплины, установленные защитными решениями.

Схема выполнения функции CreateFileW() при использовании такой техники:

Direct Syscall
Direct Syscall

Indirect syscalls

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

  1. В памяти ntdll находится адрес функции, которая должна быть вызвана «скрытно».
  2. По сигнатуре или по смещению внутри этой функции ищется адрес инструкции syscall.
  3. Происходит подготовка аргументов по аналогии с Direct syscall.
  4. Производится прыжок (jmp) на адрес инструкции syscall, из-за чего происходит ее выполнение.

Таким образом, главное отличие от предыдущего метода заключается в том, что вызов syscall происходит «более правильно» с точки зрения операционной системы — то есть из памяти библиотеки ntdll. Использование данного метода позволяет обойти хуки WinAPI-функций, а также различные простые эвристики, основанные на стеке вызовов (call stack).

Indirect Syscall
Indirect Syscall

Ntdll remapping

Техника ntdll remapping значительно отличается по своей сути от двух описанных ранее. Защитные решения при внедрении своей библиотеки в анализируемый процесс изменяют прологи интересующих функций только в памяти процесса, оригинальная библиотека ntdll остается на файловой системе в «чистом» виде.

Алгоритм выполнения техники:

  1. Вредоносный процесс маппит в свою память неизмененную библиотеку ntdll.dll. В данный момент времени в памяти процесса будет две копии библиотеки: с перехватами защитного решения и без.
  2. Получает базовый адрес неизменной библиотеки.
  3. Получает адрес раздела .text неизмененной библиотеки.
  4. Повторяет шаги 2 и 3 для библиотеки ntdll, загруженной в память изначально (измененной версии).
  5. Перезаписывает секцию .text измененной библиотеки аналогичной секцией, загруженной на 1-м шаге библиотеки.

После выполнения техники поток выполнения функции CreateFileW() будет выглядеть следующим образом:

Ntdll Remapping
Ntdll Remapping

Evasion detect ideas

В общем виде обнаружение подобных техник обхода является крайне специфичной задачей, которая не может быть выполнена на различных стандартных средствах аудита (EventLog/Sysmon/ETW).

Далее предложим несколько вариантов того, как можно обнаружить описанные выше методы обхода перехвата WinAPI-функций.

CallStack anomalies detection

Для обнаружения Direct/Indirect syscalls можно использовать анализ стека вызовов функций. Стек вызовов содержит в себе цепочку вызовов, которые привели к текущему моменту выполнения потока:

Пример CallStack
Пример CallStack

Что именно может показаться подозрительным в стеке вызовов:

  1. Нестандартный путь выполнения API-функции.
  2. Вызов критичных функций напрямую из памяти процесса, минуя ntdll.dll.
  3. Несоответствие последней функции в стеке вызовов той функции, которая была вызвана на самом деле.

Ntdll remapping detection

Как было описано в алгоритме выполнения техники Ntdll remapping, суть заключается в том, что ntdll.dll маппится в память и перезаписывает ntdll.dll в памяти, в которой могут быть установлены хуки.

Один из возможных способов обнаружения данной активности можно построить именно на событиях, которые могут быть получены из установленных в ntdll.dll перехватах, а именно:

  1. NtCreateSection().
  2. NtCreateFile().

В данном случае есть два варианта:

  1. Менее точный, основанный только на событии чтения с диска файла ntdll.dll (NtCreateFile).
  2. Более точный, основанный на корреляции двух событий по значению handle: чтение с диска файла ntdll.dll (NtCreateFile) и маппинг этого файла в память (NtCreateSection).

В первом случае необходимо обратить внимание на значение ObjectAttributes -> ObjectName (ntdll.dll) в аргументах вызова NtCreateFile(), а также на маску доступа DesiredAccess (GENERIC_READ).

Во втором случае нужно учесть уже описанное событие, а также скоррелировать его с последующим событием NtCreateSection по аргументу FileHandle.

BYOVD and Malicious Drivers

Особняком в области обхода защитных решений (которые практически всегда присутствуют в пространстве ядра) стоит техника BYOVD (Bring Your Own Vulnerable Driver).

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

Какие действия можно выполнить на хосте, имея доступ к kernelmode:

  • Выгрузку драйвера защитного решения.
  • Завершение любых процессов защитных решений.
  • Отключение/удаление kernel callbacks.
  • Установку собственных хуков для сокрытия действий на хосте.

Microsoft активно пытается бороться с загрузкой потенциально вредоносных драйверов, но их список все время пополняется. Один из вариантов борьбы с таким типом атак — отслеживание загружаемых драйверов и сравнение их метаданных с уже известным списком.

Также для доступа к kernelmode злоумышленники могут использовать различные проблемы в ОС Windows. Об одной из них мы рассказывали в статье. Суть атаки заключалась в том, что на систему был доставлен вредоносный драйвер, подписанный просроченным сертификатом, но из-за политики подписи драйверов он все равно мог быть загружен:

Исключения из политики проверки подписи
Исключения из политики проверки подписи

Далее происходило ровно то, что уже было описано выше: отключение защитных решений, сокрытие активности и развитие атаки.

Выводы

Вы, возможно, задались вопросом: «А зачем мне нужна вся эта информация?» Дело в том, что злоумышленники пристально изучают работу средств защиты или аудита, постоянно пытаясь обнаружить их способы обхода, поэтому знание того, как именно работает тот или иной инструмент, поможет в поиске потенциальных «слепых пятен», которыми может воспользоваться злоумышленник.