Вам не нужен BloodHound

от автора

Изначально цели у меня свергнуть с пьедестала популярные сетевые инструменты типа BloodHound и иже с ними не было. Нет ее и сейчас. У них было, есть и будет заслуженное место в арсенале redteam и blueteam‑команд. Все нижеописанное можно воспринимать с легкой иронией, как необычный побочный эффект моих изысканий.

В процессе копания во внутренностях подсистемы COM (Component Object Model, компонентная объекта модель в Windows) на свет вылезает очень много тонкостей, которые Microsoft по вполне понятной причине старается лишний раз не светить. Одна из таких тонкостей лежит в особенностях программного управления сетью на основе Active Directory (AD).

Вопрос был у меня простой — какие компоненты подсистемы COM лежат в основе AD? Если вкратце, то Windows управляет AD через ADSI — Active Directory Service Interfaces. Это довольно замороченная COM‑абстракция над LDAP, которую использует сама Windows, когда компоненты, подключенные к домену, запрашивают каталог. Её используют процессы групповой политики, оснастки MMC и так далее

И, не сказать, чтобы прям совсем неожиданно, как побочный эффект ковыряния, вдруг обнаружилось, что вам не нужно самому пытаться построить топологию локальной сети под управлением AD — Windows это уже сделала за вас. И попросить у Windows эти результаты может кто угодно. Даже простой пользователь, без повышенных прав. Нужно просто попросить.

BloodHound основан на платформе.NET. Он генерирует LDAP‑трафик по шаблонам, которые не встречаются ни на одной легитимной рабочей станции в домене. Решения EDR обнаруживают его, и не потому, что он использует какие‑либо уязвимости, а потому, что его поведенческую сигнатуру можно определить с закрытыми глазами. Та же история с ADRecon, PowerView и большинством альтернатив на основе Python, а все почему — Runtime, среда выполнения, для EDR/SIEM светится как новогодняя елка.

Для меня это был как некий взгляд на проблему с той стороны, откуда пока никто не смотрел. Понять, как строится и управляется сеть на уровне низкоуровневых сетевых API в Windows. И главным, повторюсь, побочным эффектом оказалась скрытность — вы получаете готовые результаты максимально тихо и незаметно для средств SIEM/EDR. Вы не сканируете сеть прямо, вы не коннектитесь к удаленной машине, не шумите, как делают это большинство утилит, нет. Суть всего скана основывается на служебном общении между пользовательской машиной и контроллером домена, и генерируемый трафик неотличим от обычной активности домена, то есть обычной активности машин в сети под управлением AD. На уровне системных COM API. На том, что нельзя запретить, ибо слетит логика управления AD со всем своими политиками, правами и так далее

Так родился Kestrel. Написанный на чистом C побочный ребёнок излишне любопытного ресёрча.

Итак, что лежит под капотом.

В самом первом модуле, скана ADWS, который я взял со своего первого проекта NetEnum реализовано пять независимых сканирований со своими флагами:

  • проба порта 9389 (доступен ли веб‑сервисный (SOAP/WS‑*);

  • реконструирование топологии сети: перечисляем компы по Service Principal Name, видим, кто контроллер домена, кто SQL, кто файловый сервер.

  • находим делегирование в сети: читаем биты userAccountControl, msDS‑AllowedToDelegateTo, msDS‑AllowedToActOnBehalfOfOtherIdentity.

  • проверяем наличие ms‑Mcs‑AdmPwd/Windows LAPS в схеме и, главное, кто вправе их читать.

  • Находим STALL‑машины, учётки машины в AD, которые выглядят брошенной: машина давно не аутентифицировалась в домене. Объект в каталоге есть, а живого хоста за ним, судя по всему, уже нет (по lastLogonTimestamp/pwdLastSet; время хранится как FILETIME).

Все пять сканов это варианты одного приёма: LDAP‑запрос к каталогу и проверка нужных атрибутов. Меняются только Base DN и список атрибутов.

В модуле KestrelACL проверяем рёбра прав. Подтягиваем nTSecurityDescriptor каждого объекта через низкоуровневый интерфейс IDirectoryObject, отдающий сырые байты дескриптора (дружелюбный интерфейс IADs может их искажать).

В коде заложены план «A» и план «B»: сначала привязка к объекту и чтение дескриптора, при «отказано в доступе» — откат на прямое чтение колонки по LDAP, причём запрашивается только DACL (DACL_SECURITY_INFORMATION == 0×4), без SACL (0×7), посольку для SACL нужна привилегия, которой у обычного пользователя нет. Каждое интересное ACE (GenericAll, WriteDacl, WriteOwner, право на member, расширенные права DCSync) становится ребром «принципал → объект».

В модуле KestrelGroup строим вложенность групп. Транзитивное членство через правило LDAP_MATCHING_RULE_IN_CHAIN (OID 1.2.840.113556.1.4.1941) заставляет сам DC размотать всю цепочку вложенных групп. Реализован ключевой момент — опознавать привилегированные группы по Well‑Known RID + SID домена, а не по sAMAccountName.

В KestrelReport строим граф. Модуль не делает новых LDAP‑запросов, он вытаскивает готовые результаты (KESTREL_ACL_SCAN_RESULT, KESTREL_GROUP_SCAN_RESULT, делегирование) и сшивает в один типизированный граф, где узлы — объекты по SID, рёбра — права/членство/делегирование. Поиск узла по SID — через хеш‑таблицу с открытой адресацией (ключ сразу даёт номер ячейки; если занято, то берётся соседняя). Дальше сериализация: KestrelEmitJson/KestrelEmitYaml и диспетчер KestrelWriteReportAuto, выбирающий формат по расширению, плюс самодостаточный HTML с силовым графом на D3.js.

В модуле KestrelPath анализируем пути по графу. То есть проводим симметричный BFS (поиск в ширину: расходится слоями, даёт кратчайший путь по числу шагов) поверх того самого графа из KestrelReport. Два режима: прямой (‑from <принципал> — что эта нода может скомпрометировать) и обратный (по умолчанию — кто дотянется до целей нулевого уровня). KestrelTagHighValue помечает well‑known SID‑ы (админы домена/леса, DC, krbtgt). Внутри лежит CSR‑представление смежности и ограничители вывода (1000 путей всего, 100 на цель), чтобы на большом домене не утонуть.

Модуль KestrelPolicy — незаконнорожденный не‑LDAP отщепенец, которому тоже нашлось место. Тут организовано чтение настроек политик — есть хитрый прием прочитать файлы SYSVOL, модуль читает их по SMB («\\домен\SYSVOL»), а не по LDAP, разбирает бинарный Registry.pol и атрибут dSHeuristics, ищет уязвимые места: LLMNR, NBT‑NS, WDigest, NTLMv1, отсутствие LDAP signing.

Как‑то так.

В планах, наверное, подтянуть изучение объектов trustedDomain из раздела Configuration (направление, тип, включена ли фильтрация SID) и пассивный детект ESC1–5/9 (чтение атрибутов шаблонов (msPKI‑Certificate‑Name‑Flag, EKU, msPKI‑Enrollment‑Flag) и их ACL), с пометкой дефектов. В общем, посмотрим.

Как я говорил выше, вам не нужно лезть в сеть, чтобы получить все эти сведения — попросите DC, он добрый, он отдаст.

Я не утверждаю, что решение на 100% невидимо.

Каждая операция чтения это легитимная, аутентифицированная операция LDAP. Ровно такая же, какую контроллер домена видит с обычных рабочих станций в течение всего дня. Это обходит стороной идентификацию по времени выполнения/поведению (нет сборки.NET, нет хоста PowerShell, нет инструментов для выявления аномалий на диске).

Но, админам сети кое‑что я подскажу. Во‑первых, контроллер домена с аудитом доступа к службе каталогов (событие 4662) или журналированием запросов LDAP записывает операции чтения, как и любой другой клиент LDAP. Во‑вторых, некоторые запросы являются уникальными по содержимому (например, фильтр LDAP_MATCHING_RULE_IN_CHAIN ​​или получение nTSecurityDescriptor), поэтому среда, которая устанавливает базовые шаблоны запросов, не только время выполнения, все еще способна их выявлять.

P. S. Я не ставил задачу сделать решение заточенным по red‑ или blueteam команды. Оно максимально пассивно и нейтрально по своей логике. Еще раз повторюсь, изначально это было как доказательство возможного. Генерация отчетов была честно стырена в сети. Скелет кода писал сам (мое пробное решение NetEnum), причесывал и исправлял ошибки вместе с Claude, куда уж без него.

ссылка на оригинал статьи https://habr.com/ru/articles/1046387/