Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
EDRSandblast — Превращает уязвимые подписанные драйверы в оружие для обхода обратных вызовов ядра EDR, обратных вызовов объектов, провайдера ETW TI и хуков пользовательского режима с целью дампа памяти LSASS и извлечения учетных данных. | Kitploit
Инструменты/GitHubGitHub/wavestone-cdt/edrsandblast
Оборонительные ИнструментыПовышение привилегийЭксплуатацияТестирование на ПроникновениеRed Teaming
GitHubwavestone-cdt/edrsandblast

EDRSandblast

Превращает уязвимые подписанные драйверы в оружие для обхода обратных вызовов ядра EDR, обратных вызовов объектов, провайдера ETW TI и хуков пользовательского режима с целью дампа памяти LSASS и извлечения учетных данных.

Репозиторий
1.8k3202 лет назадПроверено Kitploit

Популярное

Смотреть все →

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

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

EDRSandBlast

EDRSandBlast — это инструмент, написанный на C, который использует уязвимый подписанный драйвер для обхода обнаружения EDR (обратных вызовов Notify Routine, объектных обратных вызовов и провайдера ETW TI) и защиты LSASS. Также реализованы несколько методов снятия перехватчиков в пользовательском режиме для обхода мониторинга на уровне пользователя.

На момент выпуска для дампа памяти LSASS под наблюдением EDR использовалась комбинация методов пользовательского режима (--usermode) и режима ядра (--kernelmode), при этом не было ни блокировок, ни событий, связанных с «OS Credential Dumping», в (облачной) консоли продукта. Тесты проводились на 3 различных продуктах EDR и были успешны в каждом случае.

Описание

Обход EDR через удаление Notify Routines ядра

Продукты EDR используют обратные вызовы «Notify Routines» ядра в Windows, чтобы ядро уведомляло их о системной активности, такой как создание процессов, потоков и загрузка образов (exe / DLL).

Эти обратные вызовы ядра определяются на уровне ядра, обычно в драйвере, реализующем такие вызовы, с помощью ряда документированных API (nt!PsSetCreateProcessNotifyRoutine, nt!PsSetCreateThreadNotifyRoutine и т.д.). Эти API добавляют предоставленные драйвером функции обратного вызова в недокументированные массивы процедур в пространстве ядра:

  • PspCreateProcessNotifyRoutine — для создания процессов
  • PspCreateThreadNotifyRoutine — для создания потоков
  • PspLoadImageNotifyRoutine — для загрузки образов

EDRSandBlast перечисляет процедуры, определённые в этих массивах, и удаляет любую функцию обратного вызова, связанную с заданным списком драйверов EDR (поддерживается более 1000 драйверов продуктов безопасности; см. раздел об обнаружении драйверов EDR). Перечисление и удаление становятся возможными благодаря эксплуатации примитива произвольного чтения/записи памяти ядра, обеспечиваемого эксплуатацией уязвимого драйвера (см. раздел об уязвимых драйверах).

Смещения указанных массивов восстанавливаются с использованием нескольких методов; см. раздел о смещениях.

Обход EDR через удаление объектных обратных вызовов

Продукты EDR (и даже EPP) часто регистрируют «объектные обратные вызовы» с помощью API ядра nt!ObRegisterCallbacks. Эти обратные вызовы позволяют продукту безопасности получать уведомления при каждой генерации дескриптора для определённых типов объектов (теперь Windows поддерживает объектные обратные вызовы, связанные с процессами, потоками и рабочими столами). Генерация дескриптора может происходить при открытии объекта (вызов OpenProcess, OpenThread и т.д.), а также при дублировании дескриптора (вызов DuplicateHandle и т.д.).

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

При каждой регистрации обратного вызова с помощью ObRegisterCallbacks новый элемент добавляется в двусвязный список CallbackList, находящийся в объекте _OBJECT_TYPE, который описывает тип объекта, на который влияет обратный вызов (процесс, поток или рабочий стол). К сожалению, эти элементы описываются структурой, которая не документирована и не опубликована Microsoft в файлах символов. Однако её изучение в различных версиях ntoskrnl.exe показывает, что структура не менялась, по крайней мере, между сборками Windows 10 10240 и 22000 (с 2015 по 2022 год).

Упомянутая структура, представляющая регистрацию объектного обратного вызова, выглядит следующим образом:```C 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;

root@kitploit:~
Структура `OB_CALLBACK`, упомянутая выше, также не документирована и определяется следующим образом:```C
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;

Для отключения обратных вызовов объектов, зарегистрированных EDR, в EDRSandblast реализовано три техники; однако на данный момент включена только одна.

Использование поля Enabled в OB_CALLBACK_ENTRY

Это техника по умолчанию, включенная в EDRSandblast. Чтобы обнаружить и отключить обратные вызовы объектов, связанные с EDR, просматривается список CallbackList, расположенный в объектах _OBJECT_TYPE, привязанных к типам Process и Thread. Оба _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 выглядит следующим образом:```C struct _OBJECT_TYPE { LIST_ENTRY TypeList; UNICODE_STRING Name; [...] _OBJECT_TYPE_INITIALIZER TypeInfo; [...] LIST_ENTRY CallbackList; }

root@kitploit:~
Задание указателей `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` которого определяет, поддерживает ли описанный тип объекта (Процесс, Поток, Рабочий стол, Токен, Файл и т.д.) регистрацию объектных обратных вызовов или нет. Как уже упоминалось, только типы объектов Process, Thread и Desktop поддерживают эти обратные вызовы в установке Windows на момент написания.

Поскольку бит `SupportsObjectCallbacks` проверяется функциями `ObpCreateHandle` или `ObDuplicateObject` еще до чтения `CallbackList` (и, конечно, до выполнения обратных вызовов), переключение этого бита во время выполнения ядра эффективно отключает выполнение всех объектных обратных вызовов.

Основной недостаток метода заключается в том, что *KPP* («*PatchGuard*») контролирует целостность некоторых (всех?) структур `_OBJECT_TYPE` и вызывает [`0x109 Bug Check`](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/bug-check-0x109---critical-structure-corruption) с параметром 4, равным `0x8`, что означает, что структура типа объекта была изменена.

Однако, если выполнить отключение/повторное включение (и «вредоносное» действие между ними) достаточно быстро, этого должно быть достаточно, чтобы «обогнать» *PatchGuard* (если только вам не повезет и периодическая проверка не будет выполнена как раз в неподходящий момент).

### Обход EDR путем отключения обратных вызовов мини-фильтров

Система диспетчера фильтров 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 и, следовательно, не будет обнаружения на основе самого файла.

### Обход EDR путем деактивации поставщика ETW Microsoft-Windows-Threat-Intelligence

Поставщик `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`](https://public.cnotools.studio/bring-your-own-vulnerable-kernel-driver-byovkd/exploits/data-only-attack-neutralizing-etwti-provider), поставщик `ETW TI` можно полностью отключить, пропатчив в памяти ядра его атрибут `ProviderEnableInfo` на `0x0`. Обратитесь к упомянутому отличному сообщению блога для получения дополнительной информации о методе.

Аналогично удалению обратных вызовов ядра, необходимые смещения `ntoskrnl.exe` (`nt!EtwThreatIntProvRegHandleOffset`, `GuidEntry` структуры `_ETW_REG_ENTRY` и `ProviderEnableInfo` структуры `_ETW_GUID_ENTRY`) вычисляются в файле `NtoskrnlOffsets.csv` для ряда версий ядра Windows.

### Обход EDR через обход перехвата в пользовательском режиме

#### Как работает перехват в пользовательском режиме

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

Когда процесс загружается Windows, и до его фактического запуска, EDR может внедрить некоторую пользовательскую DLL в адресное пространство процесса, которая содержит его логику мониторинга. При загрузке эта DLL внедряет «*хуки*» в начало каждой функции, которая должна отслеживаться EDR. Во время выполнения, когда отслеживаемые функции вызываются процессом под наблюдением, эти хуки перенаправляют поток управления к некоторому коду наблюдения, присутствующему в DLL EDR, что позволяет ему проверять аргументы и возвращаемые значения этих вызовов.

В большинстве случаев отслеживаемыми функциями являются системные вызовы (такие как `NtReadVirtualMemory`, `NtOpenProcess` и т.д.), реализации которых находятся в `ntdll.dll`. Перехват вызовов функций `Nt*` позволяет продуктам быть как можно ближе к границе пользовательского режима / режима ядра (оставаясь в пользовательском режиме), но также могут отслеживаться функции из некоторых DLL более высокого уровня.

Ниже приведены примеры одной и той же функции до и после перехвата продуктом EDR:```assembly
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			

ВХОД:```assembly 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

root@kitploit:~
#### Обнаружение хуков
Пользовательские хуки имеют «слабость» — они находятся в пользовательской памяти, что означает их
прямую наблюдаемость и модифицируемость со стороны отслеживаемого процесса. Для автоматического обнаружения
хуков в адресном пространстве процесса основная идея заключается в сравнении различий между
оригинальной DLL на диске и библиотекой, находящейся в памяти, которая потенциально могла быть
изменена EDR. Для выполнения этого сравнения EDRSandblast выполняет следующие шаги:
* Список всех загруженных DLL перечисляется с помощью `InLoadOrderModuleList`, расположенного
  в `PEB` (чтобы избежать вызова API, которые могут отслеживаться и вызывать подозрения)
* Для каждой загруженной DLL читается её содержимое на диске и разбираются её заголовки. Соответствующая
  библиотека, находящаяся в памяти, также анализируется для выявления секций, экспортов
  и т.д.
* Релокации DLL разбираются и применяются с учётом базового адреса
  соответствующей загруженной библиотеки. Это позволяет содержимому как библиотеки в памяти,
  так и DLL с диска быть полностью идентичным (на секциях, где применяются релокации),
  что делает сравнение надёжным.
* Экспортируемые функции перечисляются, и сравниваются первые байты «в памяти» и «на диске».
  Любое различие указывает на изменение, произошедшее после загрузки DLL, и, следовательно,
  с высокой вероятностью является хуком EDR.

Примечание: Процесс можно обобщить для поиска различий в любых незаписываемых секциях,
а не только в начале экспортируемых функций, например, если продукты EDR начнут
устанавливать хуки в середине функции :) Поэтому, хотя этот подход не используется в инструменте,
он реализован в `findDiffsInNonWritableSections`.


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

#### Обход хуков с помощью ... снятия хуков
Самый интуитивный способ обхода мониторинга на основе хуков — удалить
хуки. Поскольку хуки находятся в памяти, доступной самому процессу, для
удаления хука процесс может просто:
* Изменить права доступа на страницу, где расположен хук (RX -> RWX или RW)
* Записать оригинальные байты, известные благодаря содержимому DLL на диске
* Вернуть права доступа обратно на RX

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

Однако у него есть два основных недостатка. EDR, вероятно, отслеживает использование
`NtProtectVirtualMemory`, поэтому его использование для изменения прав доступа к странице, где
установлены хуки, (по крайней мере, концептуально) плохая идея. Кроме того, если EDR
запускает поток и периодически проверяет целостность хуков, это также может
вызвать обнаружение.

За деталями реализации обращайтесь к пути кода функции `unhook()`, когда `unhook_method` равен
`UNHOOK_WITH_NTPROTECTVIRTUALMEMORY`.

**Важное примечание: для простоты этот метод реализован в EDRSandblast как
базовый метод, используемый для *демонстрации* других методов обхода; каждый из них показывает,
как получить неотслеживаемую версию `NtProtectVirtualMemory`, но выполняет ту же самую
операцию после этого (снятие конкретного хука).**

#### Обход хуков с помощью пользовательского трамплина
Чтобы обойти конкретный хук, можно просто «перепрыгнуть» через него и выполнить остальную часть
функции как есть. Сначала нужно восстановить из файла DLL оригинальные байты отслеживаемой функции,
которые были перезаписаны EDR для установки хука. В нашем предыдущем примере кода это были бы байты, соответствующие следующим
инструкциям:```assembly
mov r10, rcx
mov eax, 50h

Определение этих байтов — простая задача, поскольку мы можем выполнить чистый diff как памяти, так и дисковых версий библиотеки, как описано ранее. Затем мы собираем инструкцию перехода, которая перенаправляет поток управления на код, следующий непосредственно после хука, по адресу `NtProtectVirtualMemory + sizeof(overwritten_instructions)````assembly jmp NtProtectVirtualMemory+8

root@kitploit:~
Наконец, мы объединяем эти опкоды, сохраняем их в (заново) выделенной исполняемой памяти и сохраняем
указатель на них. Этот объект называется "*trampoline*" (трамплин) и затем может использоваться как указатель
на функцию, строго эквивалентный исходной функции `NtProtectVirtualMemory`.

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

Что касается деталей реализации, обратитесь к пути выполнения функции `unhook()`, когда `unhook_method` имеет значение
`UNHOOK_WITH_INHOUSE_NTPROTECTVIRTUALMEMORY_TRAMPOLINE`. Пожалуйста, помните, что эта техника
показана только в нашей реализации и, в конечном счете, используется для **удаления** хуков из
памяти, как и все техники ниже.

#### Обход хука с использованием собственного трамплина EDR
Для того чтобы хук EDR работал, продукт EDR должен где-то в памяти сохранить опкоды,
которые он удалил. Хуже (*или «лучше», с точки зрения атакующего*), для
эффективного использования исходных инструкций EDR, вероятно, выделил себе
*trampoline* (трамплин) где-то для выполнения исходной функции после перехвата вызова.

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

Чтобы найти трамплин в памяти, мы просматриваем всё адресное пространство с помощью `VirtualQuery`,
ища зафиксированную и исполняемую память. Для каждой такой области памяти мы сканируем её
на предмет инструкции перехода, которая указывает на адрес, следующий за перезаписанными
инструкциями (`NtProtectVirtualMemory+8` в нашем предыдущем примере). Затем трамплин можно
использовать для вызова хуковой функции без срабатывания хука.

Эта техника работает на удивление хорошо, так как она восстанавливает почти все трамплины на тестированных
EDR. Что касается деталей реализации, обратитесь к пути выполнения функции `unhook()`, когда
`unhook_method` имеет значение `UNHOOK_WITH_EDR_NTPROTECTVIRTUALMEMORY_TRAMPOLINE`.

#### Обход хука с использованием дублирующей DLL
Другой простой способ получить доступ к неотслеживаемой версии функции `NtProtectVirtualMemory`
— загрузить дублирующую копию библиотеки `ntdll.dll` в адресное пространство процесса.
Поскольку две одинаковые DLL могут быть загружены в один и тот же процесс, при условии, что они
имеют разные имена, мы можем просто скопировать легитимный файл `ntdll.dll` в другое место,
загрузить его с помощью `LoadLibrary` (или перереализовать процесс загрузки) и получить доступ к функции,
например, используя `GetProcAddress`.

Эта техника очень проста для понимания и реализации и имеет неплохие шансы на
успех, так как большинство продуктов EDR не устанавливают хуки повторно на вновь загруженные 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`.

### Эксплуатация уязвимых драйверов
Как уже упоминалось, каждое действие, требующее чтения или записи в память ядра, полагается на
уязвимый драйвер, предоставляющий эту примитивную операцию. В EDRSanblast добавление поддержки нового
драйвера, предоставляющего примитив чтения/записи, может быть выполнено «легко»; нужно реализовать только три
функции:
* Функция `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` должен быть обновлен, если используемый
уязвимый драйвер необходимо изменить, или если реализуется новый.```C
#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

В настоящее время используется несколько методов для определения, принадлежит ли конкретный драйвер или процесс продукту EDR или нет.

Во-первых, для этой цели можно просто использовать имя драйвера. Действительно, Microsoft выделяет конкретные номера, называемые «Altitude», для всех драйверов, которым необходимо вставлять обратные вызовы в ядро. Это обеспечивает детерминированный порядок выполнения обратных вызовов, не зависящий от порядка регистрации, а основанный только на использовании драйвера. Список (вендоров) драйверов, зарезервировавших конкретные altitude, можно найти на MSDN. Как следствие, Microsoft предлагает почти полный список имен драйверов безопасности, связанных с продуктами безопасности, в основном в списках «FSFilter Anti-Virus» и «FSFilter Activity Monitor». Эти списки имен драйверов встроены в EDRSandblast, а также дополнительные данные.

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

Также драйверы должны быть напрямую подписаны Microsoft, чтобы их можно было загружать в пространство ядра. Хотя поставщик драйвера не является непосредственным подписантом самого драйвера, кажется, что имя поставщика всё же включено в атрибут подписи; тем не менее, этот метод обнаружения ещё предстоит исследовать и реализовать.

Наконец, при столкновении с неизвестным для EDRSandblast EDR, наилучшим подходом является запуск инструмента в режиме «audit» и проверка списка драйверов, зарегистрировавших обратные вызовы ядра; затем имя драйвера можно добавить в список, перекомпилировать инструмент и запустить снова.

Обход RunAsPPL

Механизм Защита локальной системы безопасности (LSA), впервые представленный в 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.

Обход Credential Guard

Microsoft Credential Guard — это технология изоляции на основе виртуализации, представленная в Microsoft Windows 10 (Enterprise edition), которая предотвращает прямой доступ к учетным данным, хранящимся в процессе LSASS.

Когда активирован Credentials Guard, создается процесс LSAIso (LSA Isolated) в Virtual Secure Mode — функции, использующей расширения виртуализации ЦП для обеспечения дополнительной безопасности данных в памяти. Доступ к процессу LSAIso ограничен даже для доступа с контекстом безопасности NT AUTHORITY\SYSTEM. При обработке хеша процесс LSA выполняет вызов RPC к процессу LSAIso и ожидает результат LSAIso, чтобы продолжить. Таким образом, процесс LSASS не будет содержать никаких секретов, а вместо этого будет хранить LSA Isolated Data.

Как указано в оригинальном исследовании, проведенном N4kedTurtle: «Wdigest может быть включен в системе с Credential Guard путем патча значений g_fParameter_useLogonCredential и g_IsCredGuardEnabled в памяти». Активация Wdigest приведет к сохранению учетных данных в открытом виде в памяти LSASS для любых новых интерактивных входов в систему (без необходимости перезагрузки системы). Обратитесь к оригинальному посту в блоге с исследованием для получения более подробной информации об этой технике.

EDRSandBlast просто делает оригинальный PoC немного более дружественным с точки зрения OpSec и предоставляет поддержку для ряда версий wdigest.dll (через вычисленные смещения для g_fParameter_useLogonCredential и g_IsCredGuardEnabled).

Получение смещений

Чтобы надежно выполнять операции обхода мониторинга ядра, EDRSandblast должен точно знать, где читать и писать в память ядра. Это делается с использованием смещений глобальных переменных внутри целевого образа (ntoskrnl.exe, wdigest.dll), а также смещений конкретных полей в структурах, определения которых опубликованы Microsoft в файлах символов. Эти смещения специфичны для каждой сборки целевых образов и должны быть получены хотя бы один раз для конкретной версии платформы.

Выбор использования «жестко закодированных» смещений вместо поиска по шаблону для поиска структур и переменных, используемых EDRSandblast, оправдан тем, что недокументированные API, отвечающие за добавление/удаление обратных вызовов ядра, могут измениться, и любая попытка чтения или записи памяти ядра по неправильному адресу может (и часто будет) привести к Bug Check (Синему экрану смерти). Краш машины неприемлем как в сценариях red-teaming, так и в обычных пентестах, поскольку машина, которая крашится, хорошо видна защитникам и потеряет любые учетные данные, которые всё ещё находились в памяти на момент атаки.

Для получения смещений для каждой конкретной версии 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 Symbol Server, извлекать требуемые смещения и даже обновлять соответствующие .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, который реализует примитивы чтения и записи памяти ядра, используемые EDRSanblast. Используя эти хеши, образцы драйверов можно легко найти в Интернете, особенно на https://www.loldrivers.io.

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

Быстрое использование```

Usage: EDRSandblast.exe [-h | --help] [-v | --verbose] <audit | dump | cmd | credguard | firewall | load_unsigned_driver> [--usermode] [--unhook-method ] [--direct-syscalls] [--add-dll ]* [--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>]

root@kitploit:~
### Параметры```
-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 Version: 10.0.19041.0 и Plateform Toolset: Visual Studio 2019 (v142)).

Использование ExtractOffsets.py

Обратите внимание, что 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.

root@kitploit:~
## Обнаружение
С точки зрения защитника (вендора EDR, Microsoft, SOC-аналитиков, анализирующих телеметрию EDR и т.д.) существует множество индикаторов, которые могут быть использованы для обнаружения или предотвращения подобных техник.

### Белый список драйверов
Поскольку каждое действие, выполняемое инструментом в памяти режима ядра, основано на уязвимом драйвере для чтения/записи произвольного контента, события загрузки драйверов должны тщательно анализироваться продуктами EDR (или SOC-аналитиками). При нестандартной загрузке драйвера следует поднимать тревогу или даже блокировать известные уязвимые драйверы. Последний подход даже [рекомендуется самой Microsoft](https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/microsoft-recommended-driver-block-rules): любое устройство Windows с включённой HVCI (*целостностью кода, защищённой гипервизором*) содержит блок-лист драйверов, и постепенно это становится поведением по умолчанию в Windows (в Windows 11 уже так).

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

Как примечание, подобные структуры данных могут быть защищены недавним механизмом [Kernel Data Protection (KDP)](https://www.microsoft.com/security/blog/2020/07/08/introducing-kernel-data-protection-a-new-platform-security-technology-for-preventing-data-corruption/), который опирается на виртуальную безопасность (VBS), чтобы массив колбэков ядра был недоступен для записи без вызова соответствующих API.

Та же логика может применяться к чувствительным переменным ETW, таким как `ProviderEnableInfo`, используемая этим инструментом для отключения генерации событий ETW Threat Intelligence.

### Обнаружение в пользовательском режиме
Первый индикатор того, что процесс активно пытается обойти перехват на уровне пользователя — это обращения к файлам каждой DLL, соответствующей загруженным модулям; при нормальном выполнении процесс редко читает DLL-файлы вне вызова `LoadLibrary`, особенно `ntdll.dll`.

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

Наконец, для обнаружения обхода перехвата (злоупотребление трамплином, использование прямых системных вызовов и т.п.), не связанного с удалением хуков, продукты EDR потенциально могут полагаться на колбэки ядра, связанные с используемыми системными вызовами (например, `PsCreateProcessNotifyRoutine` для вызова `NtCreateProcess`, `ObRegisterCallbacks` для вызова `NtOpenProcess` и т.д.), и выполнять анализ стека вызовов в пользовательском режиме, чтобы определить, был ли системный вызов выполнен из нормального пути (`kernel32.dll` -> `ntdll.dll` -> системный вызов) или аномального (например, `program.exe` -> прямой системный вызов).

## Благодарности

- Перечисление и удаление колбэков ядра:
  https://github.com/br-sn/CheekyBlinder

- Примитивы чтения/записи памяти ядра через уязвимый
  драйвер `Micro-Star MSI Afterburner`:
  https://github.com/Barakat/CVE-2019-16098/

- Отключение провайдера ETW Threat Intelligence:
  https://public.cnotools.studio/bring-your-own-vulnerable-kernel-driver-byovkd/exploits/data-only-attack-neutralizing-etwti-provider

- Установка / удаление драйвера: https://github.com/gentilkiwi/mimikatz

- Первоначальный список имён драйверов EDR:
  https://github.com/SadProcessor/SomeStuff/blob/master/Invoke-EDRCheck.ps1

- Обход Credential Guard путём повторного включения `Wdigest` через
  патч памяти `LSASS`: https://teamhydra.blog/2020/08/25/bypassing-credential-guard/

## Авторы

[Тома Дио (Qazeer)](https://github.com/Qazeer/)
[Максим Меньян (themaks)](https://github.com/themaks)

## Благодарность участникам
- [v1k1ngfr](https://github.com/v1k1ngfr): за обход проверки подписи драйверов (через патч `g_CiOptions`) и поддержку драйвера GDRV.sys
- [Windy Bug](https://github.com/0mWindyBug): за обход проверки подписи драйверов, совместимый с KDP (через *подмену колбэка*), и их значительный вклад в функцию обхода минифильтра

## Лицензия

Лицензия CC BY 4.0 — https://creativecommons.org/licenses/by/4.0/
Скачать инструмент
Поддерживаемый драйверСсылка для скачиванияSHA256
GDRV.sysСсылка на LOLDrivers31f4cfb4c71da44120752721103a16512444c13c2ac2d857a7e6f13cb679b427
RTCore64.sysСсылка на LOLDrivers01aa278b07b58dc46c84bd0b1b5c8e9ee4e62ea0bf7a695862444af32e87f1fd
DBUtil_2_3.sysСсылка на LOLDrivers0296e2ce999e67c76352613a718e11516fe1b0efc3ffdb8918fc999dd76a73a5