Я начинаю первую неделю своей стажировки в спуктябре, погружаясь в сложную тему, которая пугала меня уже некоторое время: Ядро Windows.
1. KdPrint(“Hello, world!\n”);
Когда я закончил свою предыдущую стажировку, которая была посвящена обходу EDR и AV в пользовательском пространстве, мы шутили что следующей темой будет она же, но в пространстве ядра. На тот момент у меня совсем не было опыта работы с ядром Windows и всё это казалось очень сложным, выше моего уровня знаний. Сейчас, когда я пишу эту статью, должен признаться, что это было не так страшно и сложно, как я думал: C/C++ - это всё ещё C/C++, а ассемблерные инструкции хоть и вызывают головную боль, но они понятны при наличии ресурсов и времени.
В этой статье я изложу некоторые технические концепции и идеи, лежащие в основе моей стажировки и расскажу о шагах по успешному обходу/отключению известных антивирусов.
2. BugCheck?
Чтобы начать кататься на этих американских горках, настоятельно рекомендую ознакомиться с этой статьёй, в которой вкратце рассказано о User space (и Kernel space) и о том, как EDR/AVs взаимодействуют с ними.
Вкратце - Windows состоит из двух уровней - пространства пользователя и пространства ядра.
Пространство пользователя содержит Windows Native API:
Функции, содержащиеся в
Kernel space является самым нижним слоем между User space и железом и состоит из ряда различных элементов. Сердцем Kernel space является
В отличие от пользовательского пространства (где каждый процесс имеет своё собственное виртуальное адресное пространство) весь код в пространстве ядра имеет одно общее виртуальное адресное пространство. Это означает, что драйвер в режиме ядра может записывать/перезаписывать память, принадлежащую другому драйверу или самому ядру. Когда это произойдёт и приведёт к сбою драйвера, произойдёт сбой всей ОС.
В 2005 году в x64-битной версии Windows XP компания Microsoft представила новую функцию под названием Kernel Patch Protection (KPP), в просторечии известную как PatchGuard. PatchGuard отвечает за защиту целостности ядра Window путем хэширования его критических структур и выполнения сравнений через случайные промежутки времени. Когда PatchGuard обнаруживает изменения, он немедленно выполнит Bugcheck системы (
3. Битва на два фронта
Цель этой статьи - разработать драйвер ядра, который сможет отключить/обойти/ввести в заблуждение или иным образом помешать работать EDR/AV на целевой системе. Так что же такое драйвер и зачем он нужен?
Как сказано в документации, драйвер - это программное обеспечение, позволяющее операционной системе и устройству взаимодействовать друг с другом. Большинство из нас знакомы с термином "Драйвер видеокарты" - нам часто приходится обновлять его для поддержки новейших игр. Однако не все драйверы привязаны к аппаратному обеспечению, существует отдельный класс драйверов, называемых
Программные драйверы работают в режиме ядра и используются для доступа к защищённым данным, которые доступны только в режиме ядра. Чтобы понять зачем нам нужен драйвер, мы должны заглянуть в прошлое и рассмотреть как работают продукты EDR/AV.
Продукты EDR/AV адаптировались и эволюционировали с течением времени с увеличением сложности эксплойтов и атак. Общим способом обнаружения вредоносной активности является перехват функций WIN32 API в User space и передача выполнения на себя. Таким образом когда процесс вызывает функцию WIN32 API, она проходит через EDR/AV, чтобы её можно было проанализировать и либо разрешить, либо не разрешить выполнение. Авторы ВПО обходили эти хуки, напрямую вызывая функции Windows Native API (
Когда битва больше не могла вестись в User space (поскольку Windows Native API является самым низким уровнем), она перешла в Kernel space. Вместо того чтобы хукать функции Native API, EDR/AV начали патчить SSDT. Звучит знакомо? Когда выполнение из
С появлением PatchGuard компания Microsoft прервала эру патчинга SSDT в x64-битных версиях Windows. Вместо этого ввела новую функцию под названием Kernel Callbacks. Драйвер может зарегистрировать callback для своих нужд. Когда действие будет выполнено, драйвер получит уведомление либо до, либо после выполнения.
EDR/AV активно используют эти callback'и для своих проверок. Хорошим примером может служить
1. Когда пользовательское приложение хочет породить новый процесс, оно вызывает функцию
2. Тем временем драйвер EDR/AV подписался на callback
3. Ядро зарегистрировало адрес функции драйвера EDR/AV (0xFA7F) в массиве обратного вызова.
4. Когда ядро получает вызов создания процесса
5. Драйвер EDR/AV получает уведомление о создании процесса и выполняет назначенную ему функцию (0xFA7F).
6. Эта функция даёт указание приложению EDR/AV, работающему в User space, выполнить инжект в виртуальное адресное пространство пользовательского приложения, перехватить
С переходом продуктов EDR/AV в пространство ядра авторам ВПО пришлось последовать их примеру и создавать собственные драйверы чтобы вернуться в равные условия. Задача вредоносного драйвера довольно проста: устранить обратные вызовы к драйверу EDR/AV. Как же этого можно добиться?
1. Злому приложению в пространстве пользователя известно, что мы хотим запустить Mimikatz.exe (хорошо известный инструмент для извлечения из памяти паролей, хэшей, PIN-кодов и т.д.)
2. Злое приложение даёт указание злому драйверу отключить продукт EDR/AV.
3. Злой драйвер сначала найдёт и прочитает массив обратных вызовов, затем исправит все записи принадлежащие драйверам EDR/AV, заменив первую инструкцию в их функции обратного вызова (0xFA7F) на инструкцию возврата
4. Теперь Mimikatz.exe может быть запущен и вызовет
5. Ядро получает этот callback и посылает уведомление всем зарегистрированным драйверам в массиве обратных вызовов.
6. Драйвер EDR/AV получает уведомление о создании процесса и выполняет назначенную ему функцию (0xFA7F).
7. Функция драйвера EDR/AV (0xFA7F) выполняет инструкцию
8. Выполнение
4. Не изобретайте колесо
Вооружившись всеми этими знаниями, я решил применить теорию на практике. Наткнулся на статью Windows Kernel Ps Callback Experiments от @fdiskyou, в которой подробно объясняется, как он написал свой собственный драйвер и пользовательское приложение evilcli для отключения EDR/AV, как объяснялось выше. Для использования проекта вам понадобится Visual Studio 2019 и последние версии Windows SDK и WDK.
Я также установил две виртуальные машины, настроенные для удалённой отладки с помощью WinDbg
- Windows 10 build 19042
- Windows 11 build 21996
Включил следующие опции:
Чтобы скомпилировать и собрать проект, мне пришлось внести несколько изменений. Во-первых, цель сборки должна быть
Как только драйвер скомпилировался и был подписан тестовым сертификатом, я установил его на свою виртуальную машину Windows 10 с удаленно подключенным WinDbg. Чтобы увидеть отладочные сообщения в WinDbg, я обновил маску до 8:
Используя приложение evilcli.exe с флагом -l, я могу перечислить все зарегистрированные callback'и из массива обратных вызовов о создании процессов и потоков. Когда я впервые попробовал это сделать, то сразу же получил BSOD с сообщением "Page Fault in Non-Paged Area".
5. Загадка трех байтов
BSOD сообщает, что я пытаюсь получить доступ к non-committed памяти, что является немедленным bugcheck. Причина по которой это произошло связана с версионностью Windows и способом нахождения массива обратных вызовов в памяти.
Нахождение массива обратных вызовов в памяти вручную является тривиальной задачей и может быть выполнено с помощью WinDbg или любого другого отладчика ядра. Сначала мы разбираем функцию
Далее мы дизассемблируем функцию
Затем проверяем адрес памяти, который
Чтобы просмотреть различные драйверы в этом массиве, нам нужно выполнить операцию
Драйвер работает примерно так же: чтобы найти массив обратных вызовов в памяти, вычисляются смещения инструкций (которые мы искали вручную) относительно адреса функции
Здесь следует обратить внимание на конструкции
Чтобы разгадать загадку BSOD, я сравнил вывод с ручными расчётами и выяснил, что мой драйвер искал OPCODE
После корректировки массива OPCODE и функции, отвечающей за вычисление индекса из номера сборки Windows, драйвер заработал как надо.
6. Драйвер против антивируса
Чтобы протестировать драйвер, я установил его на виртуальную машину Windows 11 вместе с антивирусом. После исправления процедур обратного вызова AV-драйвера в массиве обратных вызовов файл mimikatz.exe был успешно выполнен.
После возврата AV-драйвера в исходное состояние, файл mimikatz.exe был обнаружен и заблокирован при выполнении.
7. Заключение
Мы начали эту статью с рассмотрения пространства пользователя и пространства ядра и того, как EDR/AV взаимодействуют с ними. Поскольку нашей целью является разработка драйвера ядра для блокировки EDR/AV, мы обсудили концепцию драйверов и обратных вызовов, а также то, как они используются антивирусными решениями. В качестве практического примера мы использовали evilcli. В результате Mimikatz запустился и остался незамеченным.
---
Оригинал статьи: https://blog.nviso.eu/2021/10/21/kernel-karnage-part-1/
Переведено специально для xss.pro. Спасибо Azrv3l за наводку.
Это цикл статей. Будет обновление этого топика по мере перевода мной оставшихся частей.
1. KdPrint(“Hello, world!\n”);
Когда я закончил свою предыдущую стажировку, которая была посвящена обходу EDR и AV в пользовательском пространстве, мы шутили что следующей темой будет она же, но в пространстве ядра. На тот момент у меня совсем не было опыта работы с ядром Windows и всё это казалось очень сложным, выше моего уровня знаний. Сейчас, когда я пишу эту статью, должен признаться, что это было не так страшно и сложно, как я думал: C/C++ - это всё ещё C/C++, а ассемблерные инструкции хоть и вызывают головную боль, но они понятны при наличии ресурсов и времени.
В этой статье я изложу некоторые технические концепции и идеи, лежащие в основе моей стажировки и расскажу о шагах по успешному обходу/отключению известных антивирусов.
2. BugCheck?
Чтобы начать кататься на этих американских горках, настоятельно рекомендую ознакомиться с этой статьёй, в которой вкратце рассказано о User space (и Kernel space) и о том, как EDR/AVs взаимодействуют с ними.
Вкратце - Windows состоит из двух уровней - пространства пользователя и пространства ядра.
Пространство пользователя содержит Windows Native API:
ntdll.dll, подсистему WIN32: kernel32.dll, user32.dll, advapi.dll, все пользовательские процессы и приложения. Когда приложениям нужен доступ к аппаратным устройствам, памяти и т.д. - они используют ntdll.dll для общения с ядром.Функции, содержащиеся в
ntdll.dll, загружают число, называемое "Номером системной службы" в регистр EAX, затем выполняют инструкцию syscall (x64-bit), которая начинает переход в режим ядра. Диспетчер системных служб выполняет поиск в Таблице дескрипторов системных служб (SSDT), используя в качестве индекса номер из регистра EAX. Затем происходит переходит к соответствующей системной службе и по завершении выполняется возврат в режим пользователя.Kernel space является самым нижним слоем между User space и железом и состоит из ряда различных элементов. Сердцем Kernel space является
ntoskrnl.exe или, как мы его называем, ядро. Этот исполняемый файл содержит наиболее важный код ОС, такой как планирование потоков, обработка прерываний и исключений, а также различные примитивы. Оно также содержит различные менеджеры, такие как менеджер ввода/вывода, менеджер памяти. Рядом с самим ядром находятся драйверы, которые представляют собой загружаемые модули. Я буду возиться в основном с ними, поскольку они полностью работают в режиме ядра. Помимо самого ядра и драйверов, в Kernel space также находится Слой аппаратных абстракций (HAL), win32k.sys, который в основном работает с пользовательским интерфейсом (UI), и различные системные и подсистемные процессы (Lsass.exe, Winlogon.exe, Services.exe и т.д.), но они менее важны для EDR/AV.В отличие от пользовательского пространства (где каждый процесс имеет своё собственное виртуальное адресное пространство) весь код в пространстве ядра имеет одно общее виртуальное адресное пространство. Это означает, что драйвер в режиме ядра может записывать/перезаписывать память, принадлежащую другому драйверу или самому ядру. Когда это произойдёт и приведёт к сбою драйвера, произойдёт сбой всей ОС.
В 2005 году в x64-битной версии Windows XP компания Microsoft представила новую функцию под названием Kernel Patch Protection (KPP), в просторечии известную как PatchGuard. PatchGuard отвечает за защиту целостности ядра Window путем хэширования его критических структур и выполнения сравнений через случайные промежутки времени. Когда PatchGuard обнаруживает изменения, он немедленно выполнит Bugcheck системы (
KeBugCheck(0x109);), что приведёт к появлению печально известного Синего экрана смерти (BSOD) с сообщением: "CRITICAL_STRUCTURE_CORRUPTION".
3. Битва на два фронта
Цель этой статьи - разработать драйвер ядра, который сможет отключить/обойти/ввести в заблуждение или иным образом помешать работать EDR/AV на целевой системе. Так что же такое драйвер и зачем он нужен?
Как сказано в документации, драйвер - это программное обеспечение, позволяющее операционной системе и устройству взаимодействовать друг с другом. Большинство из нас знакомы с термином "Драйвер видеокарты" - нам часто приходится обновлять его для поддержки новейших игр. Однако не все драйверы привязаны к аппаратному обеспечению, существует отдельный класс драйверов, называемых
Программные драйверы.
Программные драйверы работают в режиме ядра и используются для доступа к защищённым данным, которые доступны только в режиме ядра. Чтобы понять зачем нам нужен драйвер, мы должны заглянуть в прошлое и рассмотреть как работают продукты EDR/AV.
Сразу оговорюсь: я ни в коем случае не являюсь экспертом и большая часть информации, использованной для написания этой статьи, получена из источников, которые могут быть или не быть достоверными, полными или точными.
Продукты EDR/AV адаптировались и эволюционировали с течением времени с увеличением сложности эксплойтов и атак. Общим способом обнаружения вредоносной активности является перехват функций WIN32 API в User space и передача выполнения на себя. Таким образом когда процесс вызывает функцию WIN32 API, она проходит через EDR/AV, чтобы её можно было проанализировать и либо разрешить, либо не разрешить выполнение. Авторы ВПО обходили эти хуки, напрямую вызывая функции Windows Native API (
ntdll.dll), оставляя функции WIN32 API нетронутыми. Естественно, продукты EDR/AV адаптировались и начали хукать и функции Windows Native API. Авторы ВПО использовали несколько методов обхода этих хуков, применяя такие техники, как прямые вызовы системы, анхукинг и другие. Рекомендую ознакомиться со статьёй "A tale of EDR bypass methods" от @ShitSecure.Когда битва больше не могла вестись в User space (поскольку Windows Native API является самым низким уровнем), она перешла в Kernel space. Вместо того чтобы хукать функции Native API, EDR/AV начали патчить SSDT. Звучит знакомо? Когда выполнение из
ntdll.dll переходит к диспетчеру системных служб, поиск в SSDT приведет к адресу, принадлежащему функции EDR/AV, а не оригинальной системной службе. Такая практика рискованна, поскольку она затрагивает всю ОС и если что-то пойдёт не так, это приведет к BSOD.С появлением PatchGuard компания Microsoft прервала эру патчинга SSDT в x64-битных версиях Windows. Вместо этого ввела новую функцию под названием Kernel Callbacks. Драйвер может зарегистрировать callback для своих нужд. Когда действие будет выполнено, драйвер получит уведомление либо до, либо после выполнения.
EDR/AV активно используют эти callback'и для своих проверок. Хорошим примером может служить
PsSetCreateProcessNotifyRoutine():1. Когда пользовательское приложение хочет породить новый процесс, оно вызывает функцию
CreateProcessW() из kernel32.dll, которая затем запускает callback создания нового процесса, сообщая ядру, что новый процесс вот-вот будет создан.2. Тем временем драйвер EDR/AV подписался на callback
PsSetCreateProcessNotifyRoutine() и назначил одну из своих функций (0xFA7F) для него.3. Ядро зарегистрировало адрес функции драйвера EDR/AV (0xFA7F) в массиве обратного вызова.
4. Когда ядро получает вызов создания процесса
CreateProcessW(), оно посылает уведомление всем зарегистрированным драйверам из массива обратных вызовов.5. Драйвер EDR/AV получает уведомление о создании процесса и выполняет назначенную ему функцию (0xFA7F).
6. Эта функция даёт указание приложению EDR/AV, работающему в User space, выполнить инжект в виртуальное адресное пространство пользовательского приложения, перехватить
ntdll.dll для передачи выполнения самому себе.
С переходом продуктов EDR/AV в пространство ядра авторам ВПО пришлось последовать их примеру и создавать собственные драйверы чтобы вернуться в равные условия. Задача вредоносного драйвера довольно проста: устранить обратные вызовы к драйверу EDR/AV. Как же этого можно добиться?
1. Злому приложению в пространстве пользователя известно, что мы хотим запустить Mimikatz.exe (хорошо известный инструмент для извлечения из памяти паролей, хэшей, PIN-кодов и т.д.)
2. Злое приложение даёт указание злому драйверу отключить продукт EDR/AV.
3. Злой драйвер сначала найдёт и прочитает массив обратных вызовов, затем исправит все записи принадлежащие драйверам EDR/AV, заменив первую инструкцию в их функции обратного вызова (0xFA7F) на инструкцию возврата
RET (0xC3).4. Теперь Mimikatz.exe может быть запущен и вызовет
ReadProcessMemory(), чтобы "активировать" callback.5. Ядро получает этот callback и посылает уведомление всем зарегистрированным драйверам в массиве обратных вызовов.
6. Драйвер EDR/AV получает уведомление о создании процесса и выполняет назначенную ему функцию (0xFA7F).
7. Функция драйвера EDR/AV (0xFA7F) выполняет инструкцию
RET (0xC3).8. Выполнение
ReadProcessMemory() возобновляется, вызывается NtReadVirtualMemory(), которая выполнит системный вызов и перейдёт в режим ядра для чтения памяти процесса lsass.exe.
4. Не изобретайте колесо
Вооружившись всеми этими знаниями, я решил применить теорию на практике. Наткнулся на статью Windows Kernel Ps Callback Experiments от @fdiskyou, в которой подробно объясняется, как он написал свой собственный драйвер и пользовательское приложение evilcli для отключения EDR/AV, как объяснялось выше. Для использования проекта вам понадобится Visual Studio 2019 и последние версии Windows SDK и WDK.
Я также установил две виртуальные машины, настроенные для удалённой отладки с помощью WinDbg
- Windows 10 build 19042
- Windows 11 build 21996
Включил следующие опции:
Код:
bcdedit /set TESTSIGNING ON
bcdedit /debug on
bcdedit /dbgsettings serial debugport:2 baudrate:115200
bcdedit /set hypervisorlaunchtype off
Чтобы скомпилировать и собрать проект, мне пришлось внести несколько изменений. Во-первых, цель сборки должна быть
Debug - x64. Затем я преобразовал текущий драйвер в примитивный драйвер, изменив файл evil.inf в соответствии с моими требованиями.
Код:
;
; evil.inf
;
[Version]
Signature="$WINDOWS NT$"
Class=System
ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318}
Provider=%ManufacturerName%
DriverVer=
CatalogFile=evil.cat
PnpLockDown=1
[DestinationDirs]
DefaultDestDir = 12
[SourceDisksNames]
1 = %DiskName%,,,""
[SourceDisksFiles]
[DefaultInstall.ntamd64]
[Standard.NT$ARCH$]
[Strings]
ManufacturerName="<Your manufacturer name>" ;TODO: Replace with your manufacturer name
ClassName=""
DiskName="evil Source Disk"
Как только драйвер скомпилировался и был подписан тестовым сертификатом, я установил его на свою виртуальную машину Windows 10 с удаленно подключенным WinDbg. Чтобы увидеть отладочные сообщения в WinDbg, я обновил маску до 8:
kd> ed Kd_Default_Mask 8.
Код:
sc create evil type= kernel binPath= C:\Users\Cerbersec\Desktop\driver\evil.sys
sc start evil
Используя приложение evilcli.exe с флагом -l, я могу перечислить все зарегистрированные callback'и из массива обратных вызовов о создании процессов и потоков. Когда я впервые попробовал это сделать, то сразу же получил BSOD с сообщением "Page Fault in Non-Paged Area".
5. Загадка трех байтов
BSOD сообщает, что я пытаюсь получить доступ к non-committed памяти, что является немедленным bugcheck. Причина по которой это произошло связана с версионностью Windows и способом нахождения массива обратных вызовов в памяти.
Нахождение массива обратных вызовов в памяти вручную является тривиальной задачей и может быть выполнено с помощью WinDbg или любого другого отладчика ядра. Сначала мы разбираем функцию
PsSetCreateProcessNotifyRoutine() и ищем первую инструкцию CALL (0xE8).
Далее мы дизассемблируем функцию
PspSetCreateProcessNotifyRoutine() пока не найдём инструкцию LEA (0x4C 0x8D 0x2D) (Load Effective Address).
Затем проверяем адрес памяти, который
LEA помещает в регистр r13. Это массив callback'ов в памяти.
Чтобы просмотреть различные драйверы в этом массиве, нам нужно выполнить операцию
Логическое И с адресом в массиве и 0xFFFFFFFFFFFFF8.
Драйвер работает примерно так же: чтобы найти массив обратных вызовов в памяти, вычисляются смещения инструкций (которые мы искали вручную) относительно адреса функции
PsSetCreateProcessNotifyRoutine(), который мы получим с помощью функции MmGetSystemRoutineAddress().
C++:
ULONG64 FindPspCreateProcessNotifyRoutine()
{
LONG OffsetAddr = 0;
ULONG64 i = 0;
ULONG64 pCheckArea = 0;
UNICODE_STRING unstrFunc;
RtlInitUnicodeString(&unstrFunc, L"PsSetCreateProcessNotifyRoutine");
//obtain the PsSetCreateProcessNotifyRoutine() function base address
pCheckArea = (ULONG64)MmGetSystemRoutineAddress(&unstrFunc);
KdPrint(("[+] PsSetCreateProcessNotifyRoutine is at address: %llx \n", pCheckArea));
//loop though the base address + 20 bytes and search for the right OPCODE (instruction)
//we're looking for 0xE8 OPCODE which is the CALL instruction
for (i = pCheckArea; i < pCheckArea + 20; i++)
{
if ((*(PUCHAR)i == OPCODE_PSP[g_WindowsIndex]))
{
OffsetAddr = 0;
//copy 4 bytes after CALL (0xE8) instruction, the 4 bytes contain the relative offset to the PspSetCreateProcessNotifyRoutine() function address
memcpy(&OffsetAddr, (PUCHAR)(i + 1), 4);
pCheckArea = pCheckArea + (i - pCheckArea) + OffsetAddr + 5;
break;
}
}
KdPrint(("[+] PspSetCreateProcessNotifyRoutine is at address: %llx \n", pCheckArea));
//loop through the PspSetCreateProcessNotifyRoutine base address + 0xFF bytes and search for the right OPCODES (instructions)
//we're looking for 0x4C 0x8D 0x2D OPCODES which is the LEA, r13 instruction
for (i = pCheckArea; i < pCheckArea + 0xff; i++)
{
if (*(PUCHAR)i == OPCODE_LEA_R13_1[g_WindowsIndex] && *(PUCHAR)(i + 1) == OPCODE_LEA_R13_2[g_WindowsIndex] && *(PUCHAR)(i + 2) == OPCODE_LEA_R13_3[g_WindowsIndex])
{
OffsetAddr = 0;
//copy 4 bytes after LEA, r13 (0x4C 0x8D 0x2D) instruction
memcpy(&OffsetAddr, (PUCHAR)(i + 3), 4);
//return the relative offset to the callback array
return OffsetAddr + 7 + i;
}
}
KdPrint(("[+] Returning from CreateProcessNotifyRoutine \n"));
return 0;
}
Здесь следует обратить внимание на конструкции
OPCODE_*[g_WindowsIndex], которые определяются так:
C++:
UCHAR OPCODE_PSP[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8 };
//process callbacks
UCHAR OPCODE_LEA_R13_1[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c };
UCHAR OPCODE_LEA_R13_2[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d };
UCHAR OPCODE_LEA_R13_3[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d };
// thread callbacks
UCHAR OPCODE_LEA_RCX_1[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48 };
UCHAR OPCODE_LEA_RCX_2[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d };
UCHAR OPCODE_LEA_RCX_3[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d };
g_WindowsIndex - это индекс, основанный на версии Windows (osVersionInfo.dwBuildNumer).Чтобы разгадать загадку BSOD, я сравнил вывод с ручными расчётами и выяснил, что мой драйвер искал OPCODE
0x00 вместо OPCODE 0xE8 (CALL) для получения адреса функции PspSetCreateProcessNotifyRoutine(). Первый найденный им OPCODE 0x00 расположен со смещением в 3 байта от OPCODE 0xE8, в результате чего функция memcpy() получает неверное смещение.После корректировки массива OPCODE и функции, отвечающей за вычисление индекса из номера сборки Windows, драйвер заработал как надо.
6. Драйвер против антивируса
Чтобы протестировать драйвер, я установил его на виртуальную машину Windows 11 вместе с антивирусом. После исправления процедур обратного вызова AV-драйвера в массиве обратных вызовов файл mimikatz.exe был успешно выполнен.
После возврата AV-драйвера в исходное состояние, файл mimikatz.exe был обнаружен и заблокирован при выполнении.
7. Заключение
Мы начали эту статью с рассмотрения пространства пользователя и пространства ядра и того, как EDR/AV взаимодействуют с ними. Поскольку нашей целью является разработка драйвера ядра для блокировки EDR/AV, мы обсудили концепцию драйверов и обратных вызовов, а также то, как они используются антивирусными решениями. В качестве практического примера мы использовали evilcli. В результате Mimikatz запустился и остался незамеченным.
---
Оригинал статьи: https://blog.nviso.eu/2021/10/21/kernel-karnage-part-1/
Переведено специально для xss.pro. Спасибо Azrv3l за наводку.
Это цикл статей. Будет обновление этого топика по мере перевода мной оставшихся частей.
Последнее редактирование: