Инструмент для исследования и тестирования детект-механизмов ядра Windows, используемых средствами EDR, в контексте авторизованных red team проверок.
Фреймворк для исследования генерации и подписи полезной нагрузки — изучает, как средства защиты Windows реагируют на …
Инструмент для исследования и работы с механизмом Protected Process Light (PPL) в Windows в контексте авторизованного …
Инструмент для извлечения паролей, хешей, PIN-кодов и Kerberos-тикетов из памяти Windows. Поддерживает Pass-the-Hash, Pass-the-Ticket, Golden/Silver Ticket, …
Кросс-платформенный инструмент для извлечения сохранённых паролей из десятков приложений: браузеры, почтовые клиенты, SSH-клиенты, мессенджеры, БД, VPN, …
# Prerequisites: # - Visual Studio 2019 or later # - Windows SDK Version: 10.0.19041.0 or later # - Platform Toolset: Visual Studio 2019 (v142) or later # Build steps: # 1. Clone the repository # 2. Open the solution file in Visual Studio # 3. Select the Release configuration and x64 platform # 4. Build the solution # Required files (obtain separately): # - Vulnerable driver (e.g., gdrv.sys, RTCore64.sys, DBUtil_2_3.sys) # - Offset CSV files: NtoskrnlOffsets.csv, FltmgrOffsets.csv, WdigestOffsets.csv, CiOffsets.csv
EDRSandBlast — это инструмент, написанный на C, который использует уязвимый подписанный драйвер для обхода обнаружения EDR (callback-ы Notify Routine, Object Callbacks и провайдер ETW TI), а также защиты LSASS. Реализованы несколько техник удаления хуков в пользовательском режиме, чтобы избежать мониторинга в userland.
На момент релиза комбинация техник в пользовательском режиме (--usermode) и в режиме ядра (--kernelmode) использовалась для дампа памяти LSASS под наблюдением EDR без блокировки или генерации событий, связанных с "OS Credential Dumping", в консоли продукта (облачной). Тесты проводились на 3 различных EDR-продуктах и были успешными в каждом случае.
Продукты EDR используют callback-ы "Notify Routines" в ядре Windows, чтобы получать уведомления от ядра о системной активности, например о создании процессов и потоков, а также о загрузке изображений (exe / DLL).
Эти callback-ы ядра определяются из режима ядра, обычно из драйвера, реализующего callback-ы, с использованием ряда документированных API (nt!PsSetCreateProcessNotifyRoutine, nt!PsSetCreateThreadNotifyRoutine и т. д.). Эти API добавляют функции обратного вызова, предоставленные драйвером, в недокументированные массивы функций в пространстве ядра:
- PspCreateProcessNotifyRoutine — для создания процессов
- PspCreateThreadNotifyRoutine — для создания потоков
- PspLoadImageNotifyRoutine — для загрузки изображений
EDRSandBlast перечисляет рутины, определённые в этих массивах, и удаляет любые callback-ы, связанные с заранее определённым списком драйверов EDR (поддерживается более 1000 драйверов продуктов безопасности, см. раздел Обнаружение драйверов и процессов EDR).
Перечисление и удаление становятся возможными благодаря эксплуатации примитива произвольного чтения/записи памяти ядра, предоставляемого эксплуатацией уязвимого драйвера (см. раздел Обнаружение уязвимых драйверов).
Смещения упомянутых массивов восстанавливаются с использованием нескольких техник, см. раздел Смещения.
Продукты EDR (и даже EPP) часто регистрируют "Object callbacks" с помощью API ядра nt!ObRegisterCallbacks. Эти callback-ы позволяют продукту безопасности получать уведомления при каждом создании дескриптора для определённых типов объектов (на данный момент Windows поддерживает callback-ы для объектов, связанных с процессами, потоками и рабочими столами). Создание дескриптора может происходить при открытии объекта (вызов OpenProcess, OpenThread и т. д.), а также при дублировании дескриптора (вызов DuplicateHandle и т. д.).
Получая уведомления от ядра о каждой из этих операций, продукт безопасности может анализировать легитимность создания дескриптора (например, неизвестный процесс пытается открыть LSASS), а также блокировать его, если обнаружены угрозы.
При каждой регистрации callback-ов с помощью ObRegisterCallbacks в двусвязный список CallbackList, присутствующий в объекте _OBJECT_TYPE, описывающем тип объекта, на который распространяется callback (процесс, поток или рабочий стол), добавляется новый элемент.
К сожалению, эти элементы описываются структурой, которая не документирована и не публикуется Microsoft в символах. Однако изучение различных версий ntoskrnl.exe показывает, что структура не изменялась между сборками Windows 10 от 10240 до 22000 (с 2015 по 2022 год).
Упомянутая структура, представляющая регистрацию callback-ов объектов, выглядит следующим образом:
typedef struct OB_CALLBACK_ENTRY_t {
LIST_ENTRY CallbackList; // linked element tied to _OBJECT_TYPE.CallbackList
OB_OPERATION Operations; // bitfield : 1 for Creations, 2 for Duplications
BOOL Enabled; // self-explanatory
OB_CALLBACK* Entry; // points to the structure in which it is included
POBJECT_TYPE ObjectType; // points to the object type affected by the callback
POB_PRE_OPERATION_CALLBACK PreOperation; // callback function called before each handle operation
POB_POST_OPERATION_CALLBACK PostOperation; // callback function called after each handle operation
KSPIN_LOCK Lock; // lock object used for synchronization
} OB_CALLBACK_ENTRY;
Структура OB_CALLBACK, упомянутая выше, также не документирована и определяется следующим образом:
typedef struct OB_CALLBACK_t {
USHORT Version; // usually 0x100
USHORT OperationRegistrationCount; // number of registered callbacks
PVOID RegistrationContext; // arbitrary data passed at registration time
UNICODE_STRING AltitudeString; // used to determine callbacks order
struct OB_CALLBACK_ENTRY_t EntryItems[1]; // array of OperationRegistrationCount items
WCHAR AltitudeBuffer[1]; // is AltitudeString.MaximumLength bytes long, and pointed by AltitudeString.Buffer
} OB_CALLBACK;
Для отключения callback-ов объектов, зарегистрированных EDR, в EDRSandBlast реализованы три техники, однако на данный момент включена только одна.
Enabled структуры OB_CALLBACK_ENTRYЭто техника по умолчанию, включённая в EDRSandBlast. Для обнаружения и отключения callback-ов объектов, связанных с EDR, просматривается список CallbackList, расположенный в объектах _OBJECT_TYPE, связанных с типами Процесс и Поток. Оба объекта _OBJECT_TYPE указываются общедоступными глобальными символами в ядре: PsProcessType и PsThreadType.
Предполагается, что каждый элемент списка соответствует структуре OB_CALLBACK_ENTRY, описанной выше (предположение, которое, похоже, выполняется хотя бы во всех сборках Windows 10 на момент написания). Функции, определённые в полях PreOperation и PostOperation, проверяются
если они принадлежат драйверу EDR, то в таком случае колбэки просто отключаются переключением флага Enabled.
Хотя это довольно безопасная техника, у неё есть недостаток: она зависит от недокументированной структуры. Чтобы снизить риск небезопасной манипуляции с этой структурой, выполняются базовые проверки, подтверждающие, что некоторые поля имеют ожидаемые значения:
* Enabled — это либо TRUE, либо FALSE (не смейтесь, BOOL — это int, поэтому он может быть чем угодно, кроме 1 или 0);
* Operations — это OB_OPERATION_HANDLE_CREATE, OB_OPERATION_HANDLE_DUPLICATE или оба;
* ObjectType указывает на PsProcessType или PsThreadType.
CallbackList для потоков и процессовДругая стратегия, не зависящая от недокументированной структуры (и, следовательно, теоретически более устойчивая к изменениям в ядре NT), — это удаление всего списка CallbackList для процессов и потоков. Объект _OBJECT_TYPE выглядит следующим образом:
struct _OBJECT_TYPE {
LIST_ENTRY TypeList;
UNICODE_STRING Name;
[...]
_OBJECT_TYPE_INITIALIZER TypeInfo;
[...]
LIST_ENTRY CallbackList;
}
Если указатели Flink и Blink структуры LIST_ENTRY списка CallbackList указывают на саму структуру LIST_ENTRY, то список становится пустым. Поскольку структура _OBJECT_TYPE опубликована в символах ядра, техника не зависит от жёстко закодированных смещений/структур. Однако у неё есть недостатки.
Первый заключается в том, что невозможно отключить только колбэки EDR: техника затрагивает все колбэки объектов, которые могли быть зарегистрированы «легитимным» ПО. Тем не менее стоит отметить, что на момент написания ни один из предварительно установленных компонентов Windows 10 не использует колбэки объектов, поэтому их отключение не должно влиять на стабильность системы (тем более, если отключение временное).
Второй недостаток связан с тем, что операции с дескрипторами процессов или потоков происходят очень часто (почти непрерывно) в нормальной работе ОС. Если используемая примитивная запись в ядро не может выполнить запись QWORD «атомарно», то велика вероятность, что указатель _OBJECT_TYPE.CallbackList.Flink будет использован ядром в процессе его перезаписи. Например, уязвимый драйвер MSI RTCore64.sys может выполнять запись только DWORD за раз, поэтому для перезаписи указателя потребуется 2 отдельных IOCTL, между которыми ядро с высокой вероятностью использует его (что приведёт к краху). С другой стороны, уязвимый драйвер DELL DBUtil_2_3.sys может выполнять записи произвольного размера за один IOCTL, поэтому использование этого метода с ним не рискует вызвать сбой.
Ещё одна найденная нами техника — полное отключение поддержки колбэков объектов для потоков и процессов. В структуре _OBJECT_TYPE, соответствующей типам процессов и потоков, есть поле TypeInfo, следующее за документированной структурой _OBJECT_TYPE_INITIALIZER. Последняя содержит битовое поле ObjectTypeFlags, флаг SupportsObjectCallbacks которого определяет, поддерживает ли описанный тип объекта (Процесс, Поток, Рабочий стол, Токен, Файл и т. д.) регистрацию колбэков объектов. Как было указано ранее, на момент написания только типы объектов Процесс, Поток и Рабочий стол поддерживают эти колбэки в Windows.
Поскольку бит SupportsObjectCallbacks проверяется функциями ObpCreateHandle или ObDuplicateObject ещё до чтения CallbackList (и, конечно, до выполнения колбэков), изменение этого бита во время работы ядра эффективно отключает выполнение всех колбэков объектов.
Основной недостаток метода заключается в том, что KPP («PatchGuard») контролирует целостность некоторых (всех?) структур _OBJECT_TYPE и инициирует ошибку 0x109 с параметром 4, равным 0x8, что означает изменение структуры типа объекта.
Однако, если отключение/включение (и «злонамеренное» действие между ними) выполняется достаточно быстро, это может быть достаточно, чтобы «обогнать» PatchGuard (если только вам не повезёт, и периодическая проверка не будет выполнена в неподходящий момент).
Система Filter Manager в Windows позволяет EDR загружать драйвер «мини-фильтра» и регистрировать колбэки, чтобы получать уведомления об операциях ввода-вывода, таких как открытие, чтение, запись файлов и т. д.
Вот краткое описание различных внутренних структур, используемых менеджером фильтров:
- Менеджер фильтров устанавливает «фрейм» (_FLTP_FRAME) в качестве корневой структуры;
- Структура "тома" (_FLT_VOLUME) создаётся для каждого "диска", которым управляет
Менеджер фильтров (это могут быть разделы, теневые копии или специальные тома, соответствующие
именованным каналам или удалённым файловым системам);
- Каждому зарегистрированному драйверу мини-фильтра соответствует структура "фильтра" (_FLT_FILTER),
описывающая различные свойства, такие как поддерживаемые операции;
- Эти мини-фильтры не все подключены к каждому тому; для обозначения ассоциаций
фильтр<->том создаётся структура "экземпляра" (_FLT_INSTANCE);
- Мини-фильтры регистрируют функции обратного вызова, которые должны выполняться до и/или после
определённых операций (открытие файла, запись, чтение и т. д.). Эти функции обратного вызова описаны в
структурах _CALLBACK_NODE и могут быть доступны разными способами:
- Массив всех _CALLBACK_NODE, реализованных экземпляром мини-фильтра,
можно найти в структуре _FLT_INSTANCE; массив индексируется по коду "основной функции" IRP,
константе, представляющей операции, обрабатываемые функциями обратного вызова (IRP_MJ_CREATE, IRP_MJ_READ и т. д.).
- Также все _CALLBACK_NODE, реализованные экземплярами, связанными с определённым томом,
группируются в связанные списки, которые хранятся в массиве _FLT_VOLUME.Callbacks.OperationLists,
индексированном по кодам основных функций IRP.
Эти различные структуры просматриваются EDRSandblast для обнаружения фильтров, связанных с драйверами EDR,
и перечисляются узлы обратного вызова, содержащие функции мониторинга. Чтобы отключить их действие,
узлы удаляются из своих списков, временно делая их невидимыми для менеджера фильтров.
Таким образом, в течение заданного периода EDR может полностью не замечать никаких операций с файлами. Простой пример — создание файла дампа памяти lsass на диске, который не вызовет никакого анализа со стороны EDR, а значит, не приведёт к обнаружению на основе самого файла.
Провайдер ETW Microsoft-Windows-Threat-Intelligence регистрирует данные об использовании некоторых API Windows,
часто применяемых со злым умыслом. Сюда входит API nt!MiReadWriteVirtualMemory, вызываемое nt!NtReadVirtualMemory
(которое используется для дампа памяти LSASS) и отслеживаемое функцией nt!EtwTiLogReadWriteVm.
Продукты EDR могут потреблять журналы, создаваемые провайдером ETW TI, через службы или процессы, работающие с правами,
соответственно, SERVICE_LAUNCH_PROTECTED_ANTIMALWARE_LIGHT или PS_PROTECTED_ANTIMALWARE_LIGHT,
и связанные с драйвером Early Launch Anti Malware (ELAM).
Как было опубликовано slaeryan в блоге CNO Development Labs,
провайдер ETW TI можно полностью отключить, исправив в памяти ядра его атрибут ProviderEnableInfo на 0x0.
Ознакомьтесь с упомянутой статьёй для получения дополнительной информации о технике.
Аналогично удалению ядерных обратных вызовов, необходимые смещения в ntoskrnl.exe
(nt!EtwThreatIntProvRegHandleOffset, GuidEntry структуры _ETW_REG_ENTRY и
ProviderEnableInfo структуры _ETW_GUID_ENTRY) вычисляются в файле
NtoskrnlOffsets.csv для ряда версий ядра Windows.
Чтобы легко отслеживать действия, выполняемые процессами, продукты EDR часто используют механизм, называемый хуки в пользовательском режиме. Сначала EDR регистрируют ядерный обратный вызов (обычно обратные вызовы загрузки образа или создания процесса, см. выше), который позволяет им получать уведомления при каждом запуске процесса.
Когда Windows загружает процесс, но до его фактического запуска, EDR может внедрить специальную DLL в адресное пространство процесса, содержащую логику мониторинга. При загрузке эта DLL внедряет "хуки" в начало каждой функции, которую необходимо отслеживать EDR. Во время выполнения, когда отслеживаемые функции вызываются процессом под наблюдением, эти хуки перенаправляют поток управления на код наблюдения, находящийся в DLL EDR, что позволяет проверять аргументы и возвращаемые значения этих вызовов.
Чаще всего отслеживаемыми функциями являются системные вызовы (например, NtReadVirtualMemory,
NtOpenProcess и т. д.), реализации которых находятся в ntdll.dll. Перехват вызовов функций Nt*
позволяет продуктам находиться как можно ближе к границе между пользовательским режимом и режимом ядра.
граница (оставаясь в пользовательском пространстве), но функции из некоторых DLL более высокого уровня также могут отслеживаться.
Ниже приведены примеры одной и той же функции до и после установки хука продуктом EDR:
NtProtectVirtualMemory proc near
mov r10, rcx
mov eax, 50h
test byte ptr ds:7FFE0308h, 1
jnz short loc_18009D1E5
syscall
retn
loc_18009D1E5:
int 2Eh
retn
NtProtectVirtualMemory endp
NtProtectVirtualMemory proc near
jmp sub_7FFC74490298 ; --> "hook", jump to EDR analysis function
int 3 ; overwritten instructions
int 3 ; overwritten instructions
int 3 ; overwritten instructions
test byte_7FFE0308, 1 ; <-- execution resumes here after analysis
jnz short loc_7FFCB44AD1E5
syscall
retn
loc_7FFCB44AD1E5:
int 2Eh
retn
NtProtectVirtualMemory endp
Хуки в пользовательском пространстве имеют "слабость": они находятся в пользовательской памяти, что означает, что они напрямую наблюдаемы и изменяемы процессом, который исследуется. Чтобы автоматически обнаруживать хуки в адресном пространстве процесса, основная идея заключается в сравнении различий между оригинальной DLL на диске и библиотекой, находящейся в памяти, которая, возможно, была изменена EDR. Для выполнения этого сравнения EDRSandblast выполняет следующие шаги:
* Перечисляется список всех загруженных DLL благодаря InLoadOrderModuleList, расположенному в PEB (чтобы избежать вызовов любых API, которые могут отслеживаться и вызывать подозрения)
* Для каждой загруженной DLL считывается её содержимое на диске и анализируются её заголовки. Соответствующая библиотека в памяти также анализируется для идентификации секций, экспортов и т. д.
* Обрабатываются и применяются перемещения DLL с учётом базового адреса загруженной библиотеки. Это позволяет содержимому как библиотеки в памяти, так и DLL с диска иметь абсолютно одинаковое содержимое (в секциях, где применяются перемещения), что делает сравнение надёжным.
* Перечисляются экспортируемые функции, и сравниваются первые байты версий "в памяти" и "на диске". Любое различие указывает на изменение, сделанное после загрузки DLL, и, скорее всего, это хук EDR.
Примечание: Процесс можно обобщить для поиска различий где угодно в не записываемых секциях, а не только в начале экспортируемых функций, например, если продукты EDR начнут устанавливать хуки в середине функции :) Хотя это и не используется инструментом, такая возможность реализована в findDiffsInNonWritableSections.
Чтобы обойти мониторинг, выполняемый этими хуками, существует несколько методов, у каждого из которых есть свои плюсы и минусы.
Самый интуитивный способ обхода мониторинга на основе хуков — это удаление хуков. Поскольку хуки находятся в памяти, доступной самому процессу, для удаления хука процесс может просто: * Изменить права доступа к странице, где находится хук (RX → RWX или RW) * Записать оригинальные байты, известные из содержимого DLL на диске * Вернуть права доступа обратно к RX
Этот подход довольно простой и может использоваться для удаления всех обнаруженных хуков сразу. Если offensive-инструмент выполняет это в начале своей работы, то остальной код может полностью не знать о механизме хуков и работать нормально без мониторинга.
Однако у него есть два основных недостатка. EDR, скорее всего, отслеживает использование NtProtectVirtualMemory, поэтому его применение для изменения прав доступа к странице, где установлены хуки, (по крайней мере концептуально) — плохая идея. Также, если EDR выполняет поток, который периодически проверяет целостность хуков, это может вызвать обнаружение.
Для деталей реализации см. код функции unhook(), когда параметр unhook_method равен UNHOOK_WITH_NTPROTECTVIRTUALMEMORY.
Важное примечание: для простоты этот метод реализован в EDRSandblast как базовый метод для демонстрации других методов обхода; каждый из них показывает, как получить неотслеживаемую версию NtProtectVirtualMemory, но затем выполняет ту же операцию (удаление конкретного хука).
Чтобы обойти конкретный хук, можно просто "перепрыгнуть" через него и выполнить оставшуюся часть функции как есть. Сначала необходимо восстановить оригинальные байты отслеживаемой функции, которые были перезаписаны EDR при установке хука. В нашем предыдущем примере кода это были бы байты, соответствующие следующим инструкциям:
mov r10, rcx
mov eax, 50h
Определение этих байтов — простая задача, так как мы можем выполнить чистое сравнение (diff) версий библиотеки в памяти и на диске, как было описано ранее. Затем мы собираем инструкцию перехода, которая перенаправляет поток управления на код, следующий сразу за хуком, по адресу NtProtectVirtualMemory + sizeof(overwritten_instructions)
jmp NtProtectVirtualMemory+8
В конце концов, мы объединяем эти опкоды, сохраняем их в (новой) исполняемой памяти и сохраняем
указатель на них. Этот объект называется "трамплином" и может затем использоваться как указатель на функцию,
полностью эквивалентный оригинальной функции NtProtectVirtualMemory.
Основное преимущество этой техники, как и всех остальных, описанных ниже, заключается в том, что хук никогда не удаляется, поэтому любая проверка целостности хуков, выполняемая EDR, должна пройти успешно. Однако для этого требуется выделение памяти с правами на запись, а затем на выполнение, что типично для выделения памяти под шеллкод, что может привлечь внимание EDR.
Для получения дополнительных сведений об реализации см. код функции unhook(), когда параметр unhook_method равен
UNHOOK_WITH_INHOUSE_NTPROTECTVIRTUALMEMORY_TRAMPOLINE. Помните, что эта техника демонстрируется в нашей реализации и в итоге
используется для удаления хуков из памяти, как и все остальные техники, описанные ниже.
Продукт EDR, чтобы его хук работал, должен где-то в памяти сохранять опкоды, которые он удалил. Худший (или "лучший", с точки зрения нападающего) вариант: чтобы эффективно использовать оригинальные инструкции, EDR, вероятно, выделил себе где-то трамплин, чтобы выполнять оригинальную функцию после перехвата вызова.
Этот трамплин можно найти и использовать в качестве замены для захученной функции,
без необходимости выделять исполняемую память или вызывать какие-либо API, кроме VirtualQuery,
которая, скорее всего, не отслеживается, так как это безобидная функция.
Чтобы найти трамплин в памяти, мы просматриваем всё адресное пространство с помощью VirtualQuery,
ищем выделенные и исполняемые области памяти. Для каждой такой области мы сканируем её в поисках
инструкции перехода, которая указывает на адрес, следующий за перезаписанными инструкциями
(NtProtectVirtualMemory+8 в нашем предыдущем примере). Затем трамплин можно использовать для вызова захученной функции,
не активируя хук.
Эта техника работает удивительно хорошо, так как позволяет восстановить почти все трамплины на протестированных EDR.
Для получения дополнительных сведений об реализации см. код функции unhook(), когда параметр unhook_method равен
UNHOOK_WITH_EDR_NTPROTECTVIRTUALMEMORY_TRAMPOLINE.
Ещё один простой способ получить доступ к неотслеживаемой версии функции NtProtectVirtualMemory —
загрузить дубликат библиотеки ntdll.dll в адресное пространство процесса. Поскольку две идентичные DLL
могут быть загружены в один и тот же процесс, если у них разные имена, мы можем просто скопировать
легитимный файл ntdll.dll в другое место, загрузить его с помощью LoadLibrary (или перереализовать процесс загрузки)
и получить доступ к функции, например, через GetProcAddress.
Эта техника очень проста для понимания и реализации и имеет неплохие шансы на успех, так как большинство продуктов EDR не устанавливают хуки на newly загруженные DLL после запуска процесса. Однако основной недостаток заключается в том, что копирование подписанных Microsoft бинарных файлов под другим именем часто рассматривается EDR как подозрительное действие само по себе.
Тем не менее эта техника реализована в EDRSandblast. Для получения дополнительных сведений об реализации см.
код функции unhook(), когда параметр unhook_method равен UNHOOK_WITH_DUPLICATE_NTPROTECTVIRTUALMEMORY.
Чтобы использовать функции, связанные с системными вызовами, программа может перереализовать системные вызовы
(на ассемблере), чтобы вызывать соответствующие функции ОС, не затрагивая код в ntdll.dll,
который может отслеживаться EDR. Это полностью обходит любые хуки на уровне пользовательского пространства,
установленные на функции системных вызовов в ntdll.dll.
Однако у этого подхода есть некоторые недостатки. Во-первых, это требует знания номеров системных вызовов
для функций, которые нужны программе, а эти номера меняются в зависимости от версии Windows.
Эта проблема смягчается за счёт реализации нескольких эвристик, которые работают во всех предыдущих версиях Windows NT
(сортировка экспортов Zw* из ntdll, поиск инструкции mov rax, #syscall_number в соответствующей функции ntdll и т. д.),
и проверки, что все они возвращают один и тот же результат (см. Syscalls.c для получения дополнительных сведений).
Кроме того, функции, которые технически не являются системными вызовами (например, LoadLibraryX/LdrLoadDLL),
тоже могут отслеживаться, и их нельзя просто перереализовать с помощью системного вызова.
Техника прямых системных вызовов реализована в EDRSandblast. Как было указано ранее, она используется только для
безопасного выполнения NtProtectVirtualMemory и удаления всех обнаруженных хуков.
Для получения дополнительных сведений об реализации см. код функции unhook(), когда параметр unhook_method равен
UNHOOK_WITH_DIRECT_SYSCALL.
Как было упомянуто ранее, любое действие, требующее чтения или записи в память ядра, зависит от уязвимого драйвера, предоставляющего эту возможность. В EDRSandblast добавление поддержки нового драйвера, обеспечивающего примитивы чтения/записи, может быть выполнено «легко» — необходимо реализовать всего три функции:
* Функция ReadMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer), которая копирует Size байт из адреса ядра Address в пользовательский буфер Buffer;
* Функция WriteMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer), которая копирует Size байт из пользовательского буфера Buffer по адресу ядра Address;
* Функция CloseDriverHandle_DRIVERNAME(), которая гарантирует закрытие всех дескрипторов драйвера (необходимо перед удалением драйвера, которое на данный момент не зависит от конкретного драйвера).
Например, в настоящее время EDRSandblast поддерживает два драйвера: RTCore64.sys (SHA256: 01AA278B07B58DC46C84BD0B1B5C8E9EE4E62EA0BF7A695862444AF32E87F1FD) и DBUtils_2_3.sys (SHA256: 0296e2ce999e67c76352613a718e11516fe1b0efc3ffdb8918fc999dd76a73a5).
Следующий код в KernelMemoryPrimitives.h необходимо обновить, если требуется изменить используемый уязвимый драйвер или добавить новый.
#define RTCore 0
#define DBUtil 1
// Select the driver to use with the following #define
#define VULN_DRIVER RTCore
#if VULN_DRIVER == RTCore
#define DEFAULT_DRIVER_FILE TEXT("RTCore64.sys")
#define CloseDriverHandle CloseDriverHandle_RTCore
#define ReadMemoryPrimitive ReadMemoryPrimitive_RTCore
#define WriteMemoryPrimitive WriteMemoryPrimitive_RTCore
#elif VULN_DRIVER == DBUtil
#define DEFAULT_DRIVER_FILE TEXT("DBUtil_2_3.sys")
#define CloseDriverHandle CloseDriverHandle_DBUtil
#define ReadMemoryPrimitive ReadMemoryPrimitive_DBUtil
#define WriteMemoryPrimitive WriteMemoryPrimitive_DBUtil
#endif
В настоящее время используется несколько методов для определения, относится ли конкретный драйвер или процесс к продукту EDR.
Во-первых, для этой цели можно просто использовать имя драйвера. Действительно, Microsoft выделяет специальные номера, называемые «Altitudes» (уровни приоритета), для всех драйверов, которым необходимо вставлять обратные вызовы в ядро. Это позволяет определить детерминированный порядок выполнения обратных вызовов, независимый от порядка их регистрации, но основанный на назначении драйвера. Список (вендоров) драйверов, зарезервировавших определённые уровни приоритета, можно найти в документации MSDN. В результате Microsoft предоставляет почти полный список имён драйверов, связанных с продуктами безопасности, в основном в списках «FSFilter Anti-Virus» и «FSFilter Activity Monitor». Эти списки имён драйверов встроены в EDRSandblast, а также дополнены другими вкладками.
Кроме того, исполняемые файлы и DLL EDR почти всегда подписаны цифровой подписью с использованием сертификата подписи вендора. Таким образом, проверка подписанта исполняемого файла или DLL, связанной с процессом, может позволить быстро идентифицировать продукты EDR.
Также драйверы должны быть подписаны напрямую Microsoft, чтобы их можно было загружать в пространство ядра. Хотя сам вендор драйвера не является прямым подписантом драйвера, похоже, что имя вендора всё же включается в один из атрибутов подписи. Однако этот метод обнаружения ещё предстоит исследовать и реализовать.
Наконец, если EDRSandblast сталкивается с неизвестным EDR, лучший подход — запустить инструмент в режиме «аудита» и проверить список драйверов, зарегистрировавших обратные вызовы ядра. Затем имя драйвера можно добавить в список, пересобрать инструмент и запустить его снова.
Механизм Защита локальной системы безопасности (LSA Protection), впервые представленный в Windows 8.1 и Windows Server 2012 R2, использует технологию Protected Process Light (PPL) для ограничения доступа к процессу LSASS. Защита PPL регулирует и ограничивает операции, такие как инъекция памяти или дамп памяти защищённых процессов, даже для процесса, обладающего привилегией SeDebugPrivilege. В рамках модели защиты процессов только процессы с более высокими уровнями защиты могут выполнять операции над защищёнными процессами.
Структура _EPROCESS, используемая ядром Windows для представления процесса в памяти ядра, включает поле _PS_PROTECTION, определяющее уровень защиты процесса через его атрибуты Type (_PS_PROTECTED_TYPE) и Signer (_PS_PROTECTED_SIGNER).
Путём записи в память ядра процесс EDRSandblast может повысить собственный уровень защиты до PsProtectedSignerWinTcb-Light. Этот уровень достаточен для дампа памяти процесса LSASS, так как он «доминирует» над PsProtectedSignerLsa-Light — уровнем защиты процесса LSASS, работающего с механизмом RunAsPPL.
EDRSandBlast реализует самозащиту следующим образом:
- открывает дескриптор текущего процесса;
- получает все системные дескрипторы с помощью NtQuerySystemInformation, чтобы найти открытый дескриптор текущего процесса и адрес текущего процесса
Структура EPROCESS в памяти ядра.
- используйте уязвимость произвольного чтения/записи уязвимого драйвера, чтобы перезаписать поле _PS_PROTECTION текущего процесса в памяти ядра. Смещения поля _PS_PROTECTION относительно структуры EPROCESS (определяемые версией ntoskrnl) вычисляются в файле NtoskrnlOffsets.csv.
Microsoft Credential Guard — это технология изоляции на основе виртуализации, представленная в Windows 10 (Enterprise edition), которая предотвращает прямой доступ к учётным данным, хранящимся в процессе LSASS.
При активации Credential Guard создаётся процесс LSAIso (LSA Isolated) в режиме Virtual Secure Mode — функции, использующей расширения виртуализации процессора для обеспечения дополнительной безопасности данных в памяти. Доступ к процессу LSAIso ограничен даже для контекста безопасности NT AUTHORITY\SYSTEM. При обработке хэша процесс LSA выполняет вызов RPC к процессу LSAIso и ожидает результата, чтобы продолжить работу. Таким образом, процесс LSASS не содержит секретов, а вместо этого хранит LSA Isolated Data.
Как указано в оригинальном исследовании, проведённом N4kedTurtle: "Wdigest можно активировать на системе с Credential Guard, исправив значения g_fParameter_useLogonCredential и g_IsCredGuardEnabled в памяти". Активация Wdigest приведёт к хранению учётных данных в открытом виде в памяти LSASS для любых новых интерактивных входов в систему (без необходимости перезагрузки). Подробнее об этой технике см. в оригинальной статье в блоге.
EDRSandBlast просто делает исходный PoC более безопасным с точки зрения опсек и обеспечивает поддержку нескольких версий wdigest.dll (через вычисленные смещения для g_fParameter_useLogonCredential и g_IsCredGuardEnabled).
Чтобы надёжно выполнять операции обхода мониторинга ядра, EDRSandblast должен точно знать, куда читать и записывать память ядра. Это осуществляется с помощью смещений глобальных переменных внутри целевого образа (ntoskrnl.exe, wdigest.dll), а также смещений конкретных полей в структурах, определения которых публикуются Microsoft в файлах символов. Эти смещения уникальны для каждой сборки целевых образов и должны быть собраны хотя бы один раз для конкретной версии платформы.
Выбор использования "жёстко закодированных" смещений вместо поиска по шаблонам для определения структур и переменных, используемых EDRSandblast, обоснован тем, что недокументированные API, отвечающие за добавление/удаление обратных вызовов ядра, могут изменяться, и любая попытка чтения или записи памяти ядра по неправильному адресу может (и часто приведёт) к Bug Check (Синий экран смерти). Аварийное завершение работы машины недопустимо как в сценариях красных команд, так и в обычных тестах на проникновение, поскольку сбои высоко заметны для защитников, а также приводят к потере всех учётных данных, которые ещё находились в памяти на момент атаки.
Для получения смещений для каждой конкретной версии Windows реализованы два подхода.
Необходимые смещения для ntoskrnl.exe и wdigest.dll можно извлечь с помощью предоставленного скрипта на Python ExtractOffsets.py, который использует radare2 и r2pipe для загрузки и анализа символов из PDB-файлов и извлечения нужных смещений. Смещения затем сохраняются в CSV-файлах для последующего использования EDRSandblast.
Чтобы поддерживать "из коробки" широкий диапазон сборок Windows, многие версии бинарных файлов ntoskrnl.exe и wdigest.dll приведены в Winbindex, и их можно автоматически загрузить (и извлечь смещения) с помощью ExtractOffsets.py. Это позволяет извлекать смещения почти из всех файлов, когда-либо опубликованных в пакетах обновлений Windows (на данный момент доступно и предварительно вычислено более 450 версий ntoskrnl.exe и более 30 версий wdigest.dll).
В EDRSandBlast реализована дополнительная опция, позволяющая программе самостоятельно загружать необходимые .pdb-файлы с сервера символов Microsoft, извлекать требуемые смещения и даже обновлять соответствующие .csv-файлы, если они присутствуют.
Использование опции --internet делает выполнение инструмента намного проще, но вводит
дополнительный риск OpSec, так как файл .pdb загружается и сохраняется на диск в процессе работы.
Это требуется для функций dbghelp.dll, используемых для парсинга базы символов; однако в будущем может быть реализован полный парсинг PDB в памяти, чтобы устранить это требование и уменьшить следы инструмента.
EDRSandblast открыто поддерживает как минимум 3 уязвимых драйвера: gdrv.sys (по умолчанию), RTCore64.sys и DBUtil_2_3.sys. Используемый драйвер выбирается перед компиляцией инструмента (см. #define VULN_DRIVER <driver name> в includes/KernelMemoryPrimitive.h). Копия уязвимого драйвера должна быть загружена и предоставлена EDRSandblast для работы его ядерных операций.
Хеши протестированных драйверов указаны в начале каждого файла Driver<name>.c, реализующего примитивы чтения и записи ядерной памяти, используемые EDRSandblast. С помощью этих хешей образцы драйверов можно легко найти в интернете, особенно на сайте https://www.loldrivers.io.
Вот список поддерживаемых уязвимых драйверов с ссылками для загрузки:
| Поддерживаемый драйвер | Ссылка для загрузки | SHA256 |
|---|---|---|
GDRV.sys |
Ссылка LOLDrivers | 31f4cfb4c71da44120752721103a16512444c13c2ac2d857a7e6f13cb679b427 |
RTCore64.sys |
Ссылка LOLDrivers | 01aa278b07b58dc46c84bd0b1b5c8e9ee4e62ea0bf7a695862444af32e87f1fd |
DBUtil_2_3.sys |
Ссылка LOLDrivers | 0296e2ce999e67c76352613a718e11516fe1b0efc3ffdb8918fc999dd76a73a5 |
Usage: EDRSandblast.exe [-h | --help] [-v | --verbose] <audit | dump | cmd | credguard | firewall | load_unsigned_driver>
[--usermode] [--unhook-method <N>] [--direct-syscalls] [--add-dll <dll name or path>]*
[--kernelmode] [--dont-unload-driver] [--no-restore]
[--nt-offsets <NtoskrnlOffsets.csv>] [--fltmgr-offsets <FltmgrOffsets.csv>] [--wdigest-offsets <WdigestOffsets.csv>] [--ci-offsets <CiOffsets.csv>] [--internet]
[--vuln-driver <RTCore64.sys>] [--vuln-service <SERVICE_NAME>]
[--unsigned-driver <evil.sys>] [--unsigned-service <SERVICE_NAME>]
[--no-kdp]
[-o | --dump-output <DUMP_FILE>]
-h | --help Show this help message and exit.
-v | --verbose Enable a more verbose output.
Actions mode:
audit Display the user-land hooks and / or Kernel callbacks without taking actions.
dump Dump the process specified by --process-name (LSASS process by default), as '<process_name>' in the current directory or at the
specified file using -o | --output <DUMP_FILE>.
cmd Open a cmd.exe prompt.
credguard Patch the LSASS process' memory to enable Wdigest cleartext passwords caching even if
Credential Guard is enabled on the host. No kernel-land actions required.
firewall Add Windows firewall rules to block network access for the EDR processes / services.
load_unsigned_driver Load the specified unsigned driver, bypassing Driver Signature Enforcement (DSE).
WARNING: currently an experimental feature, only works if KDP is not present and enabled.
--usermode Perform user-land operations (DLL unhooking).
--kernelmode Perform kernel-land operations (Kernel callbacks removal and ETW TI disabling).
Hooking-related options:
--add-dll <dll name or path> Loads arbitrary libraries into the process' address space, before starting
anything.This can be useful to audit userland hooking for DLL that are not
loaded by default by this program. Use this option multiple times to load
multiple DLLs all at once.
Example of interesting DLLs to look at: user32.dll, ole32.dll, crypt32.dll,
samcli.dll, winhttp.dll, urlmon.dll, secur32.dll, shell32.dll...
--unhook-method <N> Choose the userland un-hooking technique, from the following:
0 Do not perform any unhooking (used for direct syscalls operations).
1 (Default) Uses the (probably monitored) NtProtectVirtualMemory function in ntdll to remove all
present userland hooks.
2 Constructs a 'unhooked' (i.e. unmonitored) version of NtProtectVirtualMemory, by allocating an executable trampoline jumping over the hook, and remove all present
userland hooks.
3 Searches for an existing trampoline allocated by the EDR itself, to get an 'unhooked'
(i.e. unmonitored) version of NtProtectVirtualMemory, and remove all present userland
hooks.
4 Loads an additional version of ntdll library into memory, and use the (hopefully unmonitored) version of NtProtectVirtualMemory present in this library to remove all
present userland hooks.
5 Allocates a shellcode that uses a direct syscall to call NtProtectVirtualMemory, and uses it to remove all detected hooks
--direct-syscalls Use direct syscalls to dump the selected process memory without unhooking unserland hooks.
BYOVD options:
--dont-unload-driver Keep the vulnerable driver installed on the host
Default to automatically unsinstall the driver.
--no-restore Do not restore the EDR drivers' Kernel Callbacks that were removed.
Default to restore the callbacks.
--vuln-driver <gdrv.sys> Path to the vulnerable driver file.
Default to 'gdrv.sys' in the current directory.
--vuln-service <SERVICE_NAME> Name of the vulnerable service to intall / start.
Driver sideloading options:
--unsigned-driver <evil.sys> Path to the unsigned driver file.
Default to 'evil.sys' in the current directory.
--unsigned-service <SERVICE_NAME> Name of the unsigned driver's service to intall / start.
--no-kdp Switch to g_CiOptions patching method for disabling DSE (default is callback swapping).
Offset-related options:
--nt-offsets <NtoskrnlOffsets.csv> Path to the CSV file containing the required ntoskrnl.exe's offsets.
Default to 'NtoskrnlOffsets.csv' in the current directory.
--fltmgr-offsets <FltmgrOffsets.csv> Path to the CSV file containing the required fltmgr.sys's offsets
Default to 'FltmgrOffsets.csv' in the current directory.
--wdigest-offsets <WdigestOffsets.csv> Path to the CSV file containing the required wdigest.dll's offsets
(only for the 'credguard' mode).
Default to 'WdigestOffsets.csv' in the current directory.
--ci-offsets <CiOffsets.csv> Path to the CSV file containing the required ci.dll's offsets
(only for the 'load_unsigned_driver' mode).
Default to 'WdigestOffsets.csv' in the current directory.
-i | --internet Enables automatic symbols download from Microsoft Symbol Server
If a corresponding *Offsets.csv file exists, appends the downloaded offsets to the file for later use
OpSec warning: downloads and drops on disk a PDB file for the corresponding image
Dump options:
-o | --dump-output <DUMP_FILE> Output path to the dump file that will be generated by the 'dump' mode.
Default to 'process_name' in the current directory.
--process-name <NAME> File name of the process to dump (defaults to 'lsass.exe')
EDRSandBlast (только x64) собирался в Visual Studio 2019 (Windows SDK версии: 10.0.19041.0 и набор инструментов платформы: Visual Studio 2019 (v142)).
Обратите внимание, что ExtractOffsets.py тестировался только на Windows.
# Installation of Python dependencies
pip.exe install -m .\requirements.txt
# Script usage
ExtractOffsets.py [-h] -i INPUT [-o OUTPUT] [-d] mode
positional arguments:
mode ntoskrnl or wdigest. Mode to download and extract offsets for either ntoskrnl or wdigest
optional arguments:
-h, --help show this help message and exit
-i INPUT, --input INPUT
Single file or directory containing ntoskrnl.exe / wdigest.dll to extract offsets from.
If in download mode, the PE downloaded from MS symbols servers will be placed in this folder.
-o OUTPUT, --output OUTPUT
CSV file to write offsets to. If the specified file already exists, only new ntoskrnl versions will be
downloaded / analyzed.
Defaults to NtoskrnlOffsets.csv / WdigestOffsets.csv in the current folder.
-d, --download Flag to download the PE from Microsoft servers using list of versions from winbindex.m417z.com.
С точки зрения защитника (вендор EDR, Microsoft, аналитики SOC, изучающие телеметрию EDR и т. д.), для обнаружения или предотвращения таких техник можно использовать несколько индикаторов.
Поскольку все действия, выполняемые инструментом в памяти ядра, зависят от уязвимого драйвера для чтения/записи произвольного содержимого, события загрузки драйверов должны тщательно контролироваться продуктом EDR (или аналитиками SOC), и любая загрузка необычного драйвера должна вызывать оповещение, а то и блокировать известные уязвимые драйверы. Последний подход даже рекомендуется самой Microsoft: любое устройство Windows с включенной HVCI (Защита целостности кода на уровне гипервизора) содержит список блокировки драйверов, и это постепенно станет поведением по умолчанию в Windows (уже реализовано в Windows 11).
Поскольку атакующий всё ещё может использовать неизвестный уязвимый драйвер для выполнения тех же действий в памяти, драйвер EDR мог бы периодически проверять, что его ядерные колбэки всё ещё зарегистрированы, напрямую проверяя память ядра (как это делает данный инструмент) или просто инициируя события (создание процесса, создание потока, загрузка образа и т. д.) и проверяя, что функции колбэков действительно вызываются исполнительным ядром.
Кстати, такие структуры данных могли бы защищаться с помощью недавнего механизма Защита данных ядра (KDP), который опирается на Virtual Based Security, чтобы сделать массив ядерных колбэков неперезаписываемым без вызова соответствующих API.
Тот же принцип можно применить к чувствительным переменным ETW, таким как ProviderEnableInfo, которые используются этим инструментом для отключения генерации событий угрозой разведки ETW.
Первый индикатор того, что процесс активно пытается избежать хукинга на уровне пользовательского режима, — это обращения к файлам DLL, соответствующим загруженным модулям. В нормальном выполнении процесс в пользовательском режиме редко нуждается в чтении файлов DLL за пределами вызова LoadLibrary, особенно ntdll.dll.
Чтобы защитить хуки API от обхода, продукты EDR могли бы периодически проверять, что хуки не изменены в памяти внутри каждого контролируемого процесса.
Наконец, для обнаружения обхода хукинга (с использованием трамплина, прямых системных вызовов и т. д.), который не подразумевает удаление хуков, продукты EDR могли бы потенциально полагаться на ядерные колбэки, связанные с используемыми системными вызовами (например, PsCreateProcessNotifyRoutine для системного вызова NtCreateProcess, ObRegisterCallbacks для системного вызова NtOpenProcess и т. д.), и выполнять анализ стека вызовов в пользовательском режиме, чтобы определить, был ли системный вызов инициирован из нормального пути (kernel32.dll -> ntdll.dll -> системный вызов) или аномального (например, program.exe -> прямой системный вызов).
Micro-Star MSI Afterburner:
https://github.com/Barakat/CVE-2019-16098/Wdigest через исправление памяти LSASS:
https://teamhydra.blog/2020/08/25/bypassing-credential-guard/Томас ДИО (Qazeer) Максим МЕНЬЯН (themaks)
g_CiOptions) и поддержку драйвера GDRV.sysЛицензия CC BY 4.0 - https://creativecommons.org/licenses/by/4.0/