Ослепить Sysmon. Выводим мониторинг из строя максимально незаметно
Получив доступ к системе, злоумышленник первым делом пытается ослепить средства аудита, чтобы как можно дольше оставаться незамеченным. В этой статье мы с тобой попробуем ослепить Sysmon и сделать это максимально незаметно для штатного аудита Windows. Достичь этого нам позволят манипуляции с хендлами и дескрипторами безопасности.
SYSMON INTERNALS
Сразу оговорюсь, что техники, описанные в этой статье, требуют, чтобы у атакующего были административные привилегии. Кто‑то скажет: зачем же нам углубляться во внутрянку Windows, если с правами админа можно остановить службу Sysmon или вообще удалить ее? Ответ прост. Во‑первых, при реализации традиционных техник, таких как остановка службы, завершение процесса или полное удаление Sysmon, штатный аудит Windows сформирует массу событий (начиная с EventID 4689 в журнале Security и заканчивая событием EventID 1 в журнале System от провайдера FilterManager). Во‑вторых, это просто полезное упражнение: мы должны как можно глубже разбираться в подсистеме безопасности любимой ОС, чтобы понимать техники, которыми пользуются хакеры.
Sysmon — это инструмент, предназначенный для углубленного мониторинга активности, происходящей в системе, который позволяет существенно расширить штатный аудит ОС Windows.
Вот как его работа выглядит изнутри.
Первая команда (search <ServicePattern>
) ищет зарегистрированные в системе службы и драйверы с помощью Win32-функции EnumServicesStatus(). Видно, что в системе действуют две сущности: служба режима пользователя — Sysmon64 и драйвер SysmonDrv. Обе сущности запускаются автоматически при старте системы (поле StartType
). Эту информацию мы получаем с помощью команды show all <ServiceName>
, которая последовательно вызывает QueryServiceStatus() (для получения текущего статуса работы — запущена или остановлена) и QueryServiceConfig() (для получения оставшейся информации).
Логично предположить, что события (или часть событий), которые пишет служба Sysmon64 в журнал Microsoft-Windows-Sysmon\Operational
, формируются модулем ядра SysmonDrv, а для взаимодействия службы и драйвера должно быть коммуникационное устройство (device). Чтобы проверить это, посмотрим на список хендлов, открытых службой Sysmon64 (процессом Sysmon64.exe).
Получим идентификатор процесса Sysmon64.exe (search name <ProcessPattern>
).
Команда search name <ProcessPattern>
работает через вызов CreateToolhelp32Snapshot(): функции Process32First() и Process32Next().
После того как мы получим ID процесса, посмотрим список его хендлов.
Из вывода видно, что у процесса Sysmon64.exe есть несколько открытых хендлов на устройства. Обрати внимание на устройство \Device\SysmonDrv
, одноименное драйверу. Скорее всего, именно с помощью этого устройства служба режима пользователя Sysmon64 получает информацию от драйвера и впоследствии формирует события аудита.
Команда show handles <PID> <Type>
реализует вызов NtQueryInformationProcess() с параметром ProcessHandleInformation
.
__kernel_entry NTSTATUS NtQueryInformationProcess(
[in] HANDLE ProcessHandle,
[in] PROCESSINFOCLASS ProcessInformationClass,
[out] PVOID ProcessInformation,
[in] ULONG ProcessInformationLength,
[out, optional] PULONG ReturnLength
);
Первый параметр (ProcessHandle
) — дескриптор процесса, хендлы которого мы хотим увидеть. Его можно получить при помощи функции OpenProcess() с параметром PROCESS_QUERY_INFORMATION
.
ProcessInformationClass
— это характер запрашиваемой информации. В нашем случае этот параметр равен ProcessHandleInformation
, то есть мы хотим получить информацию о хендлах процесса.
Следующим идет ProcessInformation
, указатель на память, в которую будет помещен результат нашего запроса — PPROCESS_HANDLE_SNAPSHOT_INFORMATION
, то есть указатель на структуру, содержащую список всех хендлов и их количество.
Четвертый и пятый параметры указывают размер имеющейся и требуемой памяти, в которую помещается возвращаемый функцией результат.
ЗАКРЫТИЕ ХЕНДЛА ЧЕРЕЗ ДУБЛИКАТ
Если наши предположения верны, то, чтобы Sysmon перестал формировать события аудита, атакующий может заставить процесс Sysmon64.exe закрыть хендл на это устройство.
Существует несколько способов это сделать, но мы воспользуемся самым, на мой взгляд, простым — с помощью вызова DuplicateHandle():
BOOL DuplicateHandle(
[in] HANDLE hSourceProcessHandle,
[in] HANDLE hSourceHandle,
[in] HANDLE hTargetProcessHandle,
[out] LPHANDLE lpTargetHandle,
[in] DWORD dwDesiredAccess,
[in] BOOL bInheritHandle,
[in] DWORD dwOptions
);
С помощью этой функции можно копировать хендлы между процессами, причем у нее есть интересная опция, которая позволяет при копировании закрывать исходный хендл. Этим мы и воспользуемся. Наш код будет выглядеть следующим образом:
DuplicateHandle( hProcess, hObject, GetCurrentProcess(), &hDupObject, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
Первый параметр (hProcess
) — дескриптор процесса, хендл из которого мы хотим скопировать закрыть. Тут важно не забыть, что передаваемый объект процесса hProcess
должен быть открыт с маской доступа PROCESS_DUP_HANDLE
.
Следующий параметр (hObject
) — сам закрываемый дескриптор.
Третий (GetCurrentProcess()
) и четвертый (&hDupObject
) параметры указывают на процесс‑приемник и сам хендл‑приемник соответственно.
Последний параметр содержит две константы: первая (DUPLICATE_SAME_ACCESS
) значит, что новый хендл должен иметь такую же маску доступа, как и исходный; вторая (DUPLICATE_CLOSE_SOURCE
) — что необходимо закрыть исходный хендл после операции дублирования. Отлично, это то, что нам нужно.
Итак, как мы видели ранее, у процесса Sysmon64.exe есть открытый описатель для объекта \Device\SysmonDrv
. Попробуем его закрыть.
Мы закрыли описатель с идентификатором 0x310, однако сразу после этого процесс Sysmon64.exe успешно открыл его повторно (0x4с4) и продолжил работать как ни в чем не бывало. Поэтому перейдем к тяжелой артиллерии, а эта техника нам еще понадобится позже.
ОБЪЕКТЫ ДРАЙВЕРОВ
Раз уж не получилось манипулировать с хендлом, попробуем поманипулировать с дескриптором безопасности (Security Descriptor), который определяет правила предоставления доступа к устройству \Device\SysmonDrv
. Но для начала давай вспомним, что такое объект драйвера (driver) и объект устройства (device), а также как устроена подсистема управления доступом в Windows. Если ты хорошо знаком с такими понятиями, как Driver Object, Device Object, Security Descriptor и Access Token, можешь смело пропускать этот и следующий разделы.
При загрузке драйвера в адресное пространство ядра система создает структуру _DRIVER_OBJECT
, то есть создает специальный объект типа драйвер (driver). Для наглядности воспользуемся отладчиком WinDbg и посмотрим на содержимое этой структуры.
По смещению 0x008 от начала структуры живет элемент DeviceObject
— указатель на структуру _DEVICE_OBJECT
— это другой объект ядра, который необходим для коммуникаций между процессом режима пользователя и самим драйвером. Иными словами, коммуникационное устройство обеспечивает передачу информации между пользовательским процессом и драйвером. Именно для этих целей драйверы создают объекты device, а пользовательские процессы могут взаимодействовать с ними.
Самый интересный элемент структуры — это SecurityDescriptor
. Именно там хранятся сведения о том, кому разрешено взаимодействовать с устройством и по какой маске доступа. Его содержимым мы и будем манипулировать.
КАК УСТРОЕНА БЕЗОПАСНОСТЬ В WINDOWS
В подсистеме управления доступом Windows есть три участника (роли):
Субъектом доступа принято называть поток (или процесс, в этой статье мы будем считать эти понятия синонимами), который пытается получить доступ к ресурсу (например, файлу или ключу реестра) — объекту доступа. Потоки исполняются в контексте определенного пользователя. Это означает, что процесс может получить ровно столько доступа, сколько предоставлено учетной записи, от имени которой запущен процесс.
За принадлежность потока к контексту конкретного пользователя отвечает структура ядра _Token
(Access Token, маркер доступа). Это своеобразный «паспорт субъекта», который система проверяет всякий раз, как только процесс затребует выполнение какой‑либо операции над объектом (чтение ключа реестра, запись в файл, чтение из коммуникационного устройства). Структура _Token
содержит следующие сведения:
Получим адрес Token
из структуры _EPROCESS
(это структура ядра, которая описывает объект процесса).
По факту Token
«указывает на указатель» _EX_FAST_REF
, а не на сам Token
. Причем _EX_FAST_REF
имеет тип union (в терминологии языка C) и включает в себя три элемента. Напомню, union предполагает, что все элементы типа совместно используют выделенную им память.
Элементы Object
и Value
— 8 байт, RefCnt
— 4 бита. Значение RefCnt
используется внутренними механизмами ядра Windows для подсчета количества ссылок. Таким образом, чтобы получить адрес указателя на структуру _Token
, нужно занулить последние 4 бита значения в элементе Object
.
Посмотреть на содержимое Access Token в удобном виде можно с помощью расширения !token
.
Видим, что Sysmon64.exe запущен от имени учетной записи Local System (S-1-5-18).
Объектом доступа называется любой элемент Windows, доступ к которому может быть произвольно ограничен. По своей сути Windows имеет объектную модель, то есть все ресурсы являются объектами. Файлы, ключи реестра, процессы, устройства ввода‑вывода — это всё объекты, доступ к которым контролируется средствами ОС. Сведения о том, кому можно получить доступ к объекту, а кому нет, содержатся в Security Descriptor (SD, дескриптор безопасности объекта, структура ядра _SECURITY_DESCRIPTOR
). Посмотрим на содержимое дескриптора безопасности для нашего устройства \Device\SysmonDrv
. Для этого проделаем следующий путь:
Посмотрим на содержимое дескриптора безопасности нашего объекта.
Именно список DACL содержит перечень учетных записей (идентификаторов), которым разрешено (или запрещено) получать доступ к объекту. При этом указывается метод доступа (маски, операции доступа).
У нас есть субъект доступа, изъявивший желание получить доступ к защищаемому ресурсу (субъект имеет свой Access Token, содержащий сведения об учетной записи пользователя, ее членстве в группах и привилегиях), и объект доступа (то есть сам защищаемый ресурс), имеющий Security Descriptor (сведения о том, кому и какой доступ разрешен). Теперь нам нужен независимый арбитр, который сможет на основе этой информации решить, предоставлять ли доступ.
В качестве такого независимого арбитра выступает компонент исполнительной подсистемы — Security Reference Monitor (SRM). Этот компонент отвечает за проверку того, может ли субъект получить доступ к объекту по запрашиваемому методу доступа на основе своих внутренних алгоритмов.
- субъект доступа — процесс Sysmon64.exe, запущенный от имени учетной записи Local System (S-1-5-18);
- Sysmon64.exe читает данные, формируемые драйвером SysmonDrv, с помощью коммуникационного устройства —
\Device\SysmonDrv
(объект доступа); - устройство
\Device\SysmonDrv
имеет свой Security Descriptor, который атакующий хочет модифицировать таким образом, чтобы субъекту (процессу Sysmon64.exe) было отказано в доступе.
ОСЛЕПЛЯЕМ SYSMON
Освежив знания о подсистеме управления доступом, приступим к нашей основной задаче — попробуем ослепить Sysmon. В этом разделе мы изменим Security Descriptor таким образом, чтобы процессу Sysmon64.exe было отказано в предоставлении доступа к устройству \Device\SysmonDrv
.
Посмотрим на содержимое дескриптора безопасности коммуникационного устройства. Для этого нам потребуется получить хендл на устройство с помощью функции CreateFile() и последующего вызова GetSecurityInfo(). Давай подробнее остановимся на этих функциях. CreateFile()
умеет получать описатели для множества объектов, в том числе и для коммуникационных устройств:
HANDLE CreateFileW(
[in] LPCWSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);
Давай подробнее разберем каждый из семи параметров.
lpFileName
принимает имя объекта, в нашем случае это\\.\SysmonDrv
— символическая ссылка на\Device\SysmonDrv
.dwDesiredAccess
— перечень флагов, описывающих запрашиваемый доступ (маска доступа). Подсистема безопасности Windows устроена таком образом, что перед тем, как выполнить операцию над объектом, надо указать перечень необходимых доступов. Чтобы посмотреть значения Owner, Group и список DACL, нужно указатьREAD_CONTROL
, а чтобы посмотреть или изменить список SACL — значениеACCESS_SYSTEM_SECURITY
(последний флаг требует наличия у вызывающего процесса включенных привилегийSE_SECURITY_NAME
, в противном случае получим ошибкуERROR_ACCESS_DENIED
).dwShareMode
определяет режим совместного использования. В нашем случае устанавливается 0.lpSecurityAttributes
принимает указатель на дескриптор безопасности, который будет ассоциирован с объектом. Этот параметр имеет значение только в случае создания нового объекта.dwCreationDisposition
определяет действие для существующих или несуществующих файлов или устройств, в нашем случае его значение будет равноOPEN_EXISTING
.dwFlagsAndAttributes
определяет перечень флагов и атрибутов, с которыми запрашивается доступ к объекту. Здесь мы укажемFILE_FLAG_BACKUP_SEMANTICS
, что сообщит подсистеме безопасности о необходимости получить доступ в обход существующего списка DACL (наличие данной опции требует у вызывающего процесса включенных привилегийSE_BACKUP_NAME
иSE_RESTORE_NAME
).hTemplateFile
— используется только при создании новых объектов.
Вот как будет выглядеть наш вызов CreateFile()
:
hDevice = CreateFile( lpDeviceName, READ_CONTROL | ACCESS_SYSTEM_SECURITY, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
После получения описателя для устройства \\.\SysmonDrv
воспользуемся функцией GetSecurityInfo(), которая позволит вытащить информацию из дескриптора безопасности:
DWORD GetSecurityInfo(
[in] HANDLE handle,
[in] SE_OBJECT_TYPE ObjectType,
[in] SECURITY_INFORMATION SecurityInfo,
[out, optional] PSID *ppsidOwner,
[out, optional] PSID *ppsidGroup,
[out, optional] PACL *ppDacl,
[out, optional] PACL *ppSacl,
[out, optional] PSECURITY_DESCRIPTOR *ppSecurityDescriptor
);
Первый параметр (handle
) принимает описатель нашего объекта, который мы получили с помощью CreateFile()
.
Далее нужно указать тип объекта (ObjectType
), дескриптор безопасности которого мы хотим посмотреть. Укажем SE_UNKNOWN_OBJECT_TYPE
.
В следующем аргументе (SecurityInfo
) мы указываем, какие именно поля дескриптора безопасности мы хотим получить. В нашем случае это вот такое объединение:
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION
Оставшиеся параметры указывают на структуры данных, в которые будет помещена требуемая нам информация.
В итоге вызов GetSecurityInfo()
имеет следующий вид:
GetSecurityInfo( hObject, SE_UNKNOWN_OBJECT_TYPE, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, &pOwner, &pGroup, &pDacl, &pSacl, &pSecurityDescriptor);
Посмотрим на дескриптор безопасности для \Device\SysmonDrv
. С помощью команд show owner <Device>
и show group <Device>
можно узнать хозяина объекта и группу, а командой show sd <Device>
получить полное содержание SD в формате SDDL.
Security Descriptor выглядит следующим образом:
O:SYG:SYD:(A;;0x1201bf;;;WD)(A;;FA;;;SY)(A;;FA;;;BA)(A;;0x1200a9;;;RC)S:AI
Мы остановимся лишь на одной его части — списке избирательного контроля доступа (DACL). Он имеет такой вид:
D:(A;;0x1201bf;;;WD)(A;;FA;;;SY)(A;;FA;;;BA)(A;;0x1200a9;;;RC)
Cписок избирательного контроля доступа (Discretionary Access Control List, DACL) состоит из отдельных записей (Access Control Entries, ACE). Каждая ACE оформляется в отдельных круглых скобках. Именно DACL хранит информацию о том, какому субъекту и какой именно доступ разрешен, а какой запрещен. Его мы разберем подробнее.
В глаза сразу бросается запись (A;;FA;;;SY)
, которая говорит о том, что учетной записи Local System (SY) возможно предоставлять (A) полный доступ (FA). Попробуем явно запретить любой доступ для этой учетной записи. Для этого символ A заменим D — (D;;FA;;;SY)
. Переписать Security Descriptor можно командой set sddldacl <DACL>
. Она реализована через вызов функции SetSecurityInfo(), которая принимает параметры, аналогичные GetSecurityInfo()
, поэтому не будем разбирать его подробно.
Проверим, что из этого выйдет, если попросить систему закрыть хендл на устройство с помощью уже известной нам техники — закрытие хендла через дубликат.
Видим, что ничего не изменилось и у процесса Sysmon64.exe осталось открытое устройство \Device\SysmonDrv
, но уже с другим описателем. Вероятно, мы чего‑то не учли! Вернемся к дескриптору безопасности.
Обратим внимание на первую запись в списке DACL — (A;;0x1201bf;;;WD)
, которая означает, что для учетной записи Everyone (WD) предоставлен (A) какой‑то довольно широкий доступ — 0x1201bf (присутствуют как минимум FILE_READ_DATA
и FILE_WRITE_DATA
). Уберем ее из списка DACL и повторим операцию по закрытию хендла на устройство.
«Ура! Сработало!» — скажет хакер и продолжит свои грязные делишки в нашей системе.
Ну а мы посмотрим в журнал Microsoft-Windows-Sysmon\Operational
и убедимся, что наш Sysmon, к сожалению, больше ничего не пишет.
Но не стоит отчаиваться, мы как безопасники получили «плюс один» детект для нашего SIEM:
Provider Name='Microsoft-Windows-Sysmon' and EventID='255' and Data Name.
Description='Failed to access the driver - Last error: Неверный дескриптор'
С позиции же «спецов по проникновению» здесь есть еще куда посмотреть, но, пожалуй, мы продолжим это увлекательное занятие в другой раз.