Группа NightEagle (в трекинге также APT-Q-95) с 2023 года работала по организациям Азии, а в последней кампании развернула прицел на предприятия России. Расследование инцидентов провела команда экстренного реагирования Kaspersky, и по нему видна не громкая уязвимость, а неприятная закономерность: у группы почти нет ничего своего, и именно это делает её опасной. Бэкдор на почтовом сервере склеен из нескольких чужих open-source-проектов. Канал наружу – штатный механизм самой Microsoft. Повышение привилегий – семилетний баг. И на этой полностью заёмной оснастке операторы дошли до контроллера домена и всей Active Directory жертвы.
Хакерский калибр здесь не в новизне инструмента, а в дисциплине сборки. NightEagle не изобретает – она компонует публичный код и легитимные сервисы так, что в сети не остаётся ни командного сервера, ни собственной инфраструктуры, ни авторского почерка, за который её можно было бы зацепить. И каждый шаг этого пути – от чужой учётки в VPN до мимикрии под контроллер домена – отдельное решение о том, где смолчать и каким чужим инструментом ударить, и оплачено оно наблюдаемостью.
Первичный доступ NightEagle берёт не эксплойтом, а логином. В большинстве инцидентов операторы заходили в корпоративный VPN под скомпрометированными, но действительными учётными записями – с точки зрения периметра это легитимная аутентификация, а не атака, и в журналах она выглядит как обычный вход сотрудника. Показателен выбор точек выхода: VPN-подключения шли с адресов российского сегмента, привязанных к туннелям Cloudflare WARP, и с адресов европейских провайдеров виртуальной инфраструктуры. WARP отдаёт российский IP поверх чужого транзита, европейская виртуалка даёт адрес, неотличимый от рядового удалённого работника. Ни то ни другое не всплывает в блоклистах и правилах гео-аномалий, на которые обычно ловят вход из неожиданной страны.
Это первое сильное решение оператора, и взвесить его стоит трезво. Валидные креды вместо эксплойта на входе – размен наблюдаемости на разведку: за краденую учётку не платят ни одним сетевым алертом, зато платят предварительной работой по её добыче, которую отчёт не восстанавливает. Альтернатива – ломиться публичным эксплойтом периметрового сервиса – дала бы вход быстрее и без чужой учётки, но зажгла бы IDS на первом же пакете и оставила бы в логах характерную сигнатуру. NightEagle выбрала тишину на входе ценой подготовки, и по остальному киллчейну видно, что это её системная привычка, а не разовая удача.
Закрепление на Microsoft Exchange – ядро кампании. Точный способ первичной доставки восстановить не удалось, но с высокой уверенностью его относят к уже знакомой технике. Оператор вытаскивает из конфигурации ASP.NET криптографические ключи, которыми сервер подписывает VIEWSTATE, перезаписывает этот параметр своей полезной нагрузкой и получает исполнение в рабочем процессе сервера. Ключевое следствие: бэкдор поднимается прямо в памяти Exchange, не ложась на диск отдельным исполняемым файлом, – антивирусу нечего сканировать на файловой системе, потому что там ничего и нет.
Механику этого шага стоит развернуть, потому что на ней держится вся скрытность закрепления. VIEWSTATE – это сериализованное состояние ASP.NET-страницы, которое сервер подписывает ключом validationKey из машинного ключа, чтобы клиент не мог его подделать. Вся защита держится ровно на секретности этого ключа: получив его, атакующий формирует собственный VIEWSTATE, кладёт внутрь сериализованный .NET-объект и подписывает валидной подписью – сервер принимает его как свой и десериализует, а десериализация управляемого объекта и есть исполнение кода. Именно этот класс уязвимости несёт CVE-2020-0688: Exchange исторически поставлялся с предсказуемым машинным ключом, и подделка VIEWSTATE давала RCE. NightEagle не гадает ключ, а достаёт настоящий прямо из конфигурации скомпрометированного сервера – и получает то же исполнение, но уже гарантированно валидной подписью, без единого странного запроса на входе.
Сам бэкдор называется GhostContainer, и его внутренности разбирали отдельным исследованием ещё в 2025 году, когда группа била по Азии. Это .NET-сборка App_Web_Container_1.dll размером 32.8 КБ, у которой даже поле времени компоновки обнулено в 1970-01-01. Имя выбрано под маскировку: файл выглядит рядовым скомпилированным контейнером ASP.NET-страницы, каких на живом Exchange десятки. Внутри – три рабочих класса и один вспомогательный, и вся конструкция собрана из чужого открытого кода.
Первым отрабатывает класс Stub – парсер и диспетчер команд. Он начинает с того, что ослепляет защиту: перезаписывает адреса в amsi.dll и ntdll.dll, снимая проверку через Antimalware Scan Interface и журналирование событий Windows разом. Дальше достаёт из конфигурации ASP.NET validation key – тот самый ключ, которым сервер подписывает VIEWSTATE, – причём код добычи ключа скопирован из публичного проекта machinekeyfinder-aspx. Ключ хешируется SHA-256 до ровно 32 байт и становится ключом AES, которым шифруется весь обмен между оператором и сервером: даже если трафик перехватят, без ключа сервера он нечитаем.
Команду Stub принимает не на отдельном порту и не по своему домену, а внутри обычного веб-запроса к Exchange. Она приезжает в заголовке x-owa-urlpostdata и разбирается по строгой цепочке: снять Base64, расшифровать AES, где первые 16 байт данных служат вектором инициализации, распаковать результат, а первый его байт взять за код операции. Если текущий пользователь не системный, Stub не пасует, а подменяет личность, беря токен из хранилища домена приложения, – и исполняет команду уже под нужной учёткой. Набор команд покрывает всё, что нужно для полного контроля над сервером:
0 – узнать разрядность (x86 или x64)
1 – исполнить полученные данные как шелл-код
2 – выполнить команду в командной строке
3 – загрузить .NET-байткод в отдельном потоке
4 – отправить GET-запрос
5 – скачать и сохранить файл
6 – записать переданные сырые данные в файл
7 – удалить файл
8 – прочитать содержимое файла
9 – выполнить .NET-программу с возвратом вывода
10 – вызвать инжектор виртуальных страниц
11 – обойти каталог и удалить файлы с App_Global в имени
14 – параллельно бить HTTP POST по нескольким адресам
Ответ на каждую команду упаковывается в XML, и его значение всегда начинается с зашитой строки /wEPDwUKLTcyODc4 – оператор получает результат обратно тем же каналом, спрятанным в поле __VIEWSTATE ответной страницы:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTcyODc4[BASE64_ENCODED_RESULT]" />
Та же строка-префикс встречается в публичном эксплойте ExchangeCmdPy.py под ту самую CVE-2020-0688 – и по совпадению структуры точки входа и ключевых строк код Stub связывают именно с этим проектом. То есть и разбор команд, и криптография, и обход AMSI не написаны, а склеены из готовых кусков. Сам разобранный образец известен точно – App_Web_Container_1.dll с MD5 01d98380dfb9211251c75c87ddb3c79c.
Два других класса GhostContainer превращают Exchange из жертвы в узел сети атакующего. App_Web_843e75cf5b63 – инжектор виртуальных страниц, построенный на открытом проекте PageLoad_ghostfile.aspx. Он принимает два параметра, fakePageName и fakePath, и создаёт через VirtualProvider призрачную ASPX-страницу, которой нет на диске: reflection-загрузчик зашит в бэкдор Base64-строкой исходника .aspx, а сам приём обходит проверки файлов, потому что проверять нечего – страница существует только в памяти. Её единственная работа – найти в текущем домене класс веб-прокси и вызвать его статический метод AppWebInit. Тонкость версионирования: у экземпляра прокси при создании фиксируется поле utcDate с текущим временем, и призрачная страница вызывает тот экземпляр, у которого utcDate максимальный, – так бэкдор всегда работает с самой свежей загруженной версией себя.
Этот веб-прокси – App_Web_8c9b251fb5b3, и он собран из Neo-reGeorg: функция StrTr с её собственным перекодированием строк перед Base64 в бэкдоре побайтово совпадает с оригиналом проекта. Прокси смотрит на заголовок запроса и по нему выбирает режим. Заголовок Qprtfva означает проксирование: расшифровать Base64 в целевой URL, склонировать исходный запрос без служебного заголовка и переслать по адресу, вернув ответ как локальный. Заголовок Dzvvlnwkccf означает работу с сокетом – долгоживущий TCP-туннель между интернетом и внутренней сетью. Имя сокета лежит в первых 22 байтах заголовка, сама команда начинается с 23-го байта, а самих сокет-команд четыре, и распознаются они по длинным зашитым строкам-меткам:
подключиться к IP:порт из шифрованного заголовка и сохранить сокет
1iGBIM1C5PmawX_1McmR7StamYn23jpfQoENPlm19cH42kceYkm8ch4x2
закрыть сокет и убрать его из глобального хранилища
vfhafFQZ4moDAvJjEjplaeySyMA
перегнать тело HTTP-запроса в сокет и отправить внутреннему хосту
M4LubGO0xaktF_YgZpsiH3v1cJ4dloAPOZKdG8AK4UxM
принять данные из внутренней сети, закодировать и вернуть оператору
NYIJVBf2PXRn7_BWxFyuheu1O0TuE9B0FtF0O
Если заголовок не тот и не другой, бэкдор отвечает безобидной строкой-заглушкой <!-- 5lxBk9Zh7MDCyVAaxD8 --> – с виду пустой HTML-комментарий, каких в ответах веб-приложений полно. Вспомогательный класс StrUtils при этом умеет больше, чем используется: помимо разбивки данных на части и обрезки хвоста пути, в нём лежат функции разбора и раскодирования XML, на которые в коде нет ни одной ссылки, – мёртвый задел, оставленный в сборке про запас или по невнимательности.
Здесь – самое сильное архитектурное решение всей операции, и назвать его надо прямо. У GhostContainer нет командного сервера вообще. Он не звонит наружу, не держит C2-домена, не открывает исходящих соединений, которые можно занести в блоклист. Оператор сам подключается снаружи к скомпрометированному Exchange, а команды прячет внутри штатных веб-запросов OWA – именно поэтому к кампании не удалось привязать ни одного IP или домена. Разница с обычным бэкдором принципиальна: имплант с исходящим C2 виден по адресату соединения, по домену, по периодике биконов – всё это ловит сетевой мониторинг. Бэкдор без C2 платит за невидимость управляемостью: оператор обязан сам держать доступ к серверу снаружи и не дотянется до цели, если периметр закрыли. NightEagle этот размен приняла осознанно, и на Exchange, который и так торчит наружу, он почти бесплатен: сервер по определению принимает веб-запросы, а значит, канал управления сливается с легитимным трафиком до неразличимости. Тот же принцип – жить на чужом легитимном, чтобы защитнику нечего было блокировать, – мы разбирали, когда APT28 держали командный центр на чужом бесплатном вебхуке; NightEagle доводит его до предела, отказавшись от командного канала как такового.
Получив на Exchange достаточно прав, оператор разворачивает горизонталь по RDP – и для этого тащит на хосты утилиты туннелирования. Способ хранения показателен: инструменты лежат в архивах на GitHub, в репозиториях с безобидными именами mirror-js и browserthemes, а сами архивы названы под легитимные пакеты – js-webpack.zip, resource-pack.zip. Внутри – исполняемые файлы, чьи имена косят под знакомый корпоративной машине софт: adobe_32.exe, AdobeSync.exe, trueconf.exe, 1cbroker.exe, 1c-office-plugin.exe, trueconf-broker.exe. Отсылки к 1С и TrueConf выбраны не наугад – в российской корпоративной среде эти имена в списке процессов вопросов не вызывают.
Туннель оператор строит связкой из двух инструментов. Первый – Microsoft dev tunnels, штатный механизм самой Microsoft для публикации локальных веб-служб наружу через домены *.devtunnels.ms; через него NightEagle выставляет в интернет порт 3389, то есть RDP скомпрометированной машины. Второй – rdp2tcp, публичная утилита для проброса TCP поверх установленного RDP-соединения, с серверной частью на цели и клиентской у оператора. Связка держит доступ к сети через легитимные сервисы, не открывая ни одного подозрительного порта, – трафик уходит на домены Microsoft, которые никто не станет блокировать.
У этого выбора есть цена, и она уже видна в артефактах. rdp2tcp работает через виртуальные каналы RDP, а открытие и закрытие канала штатно фиксируется в журнале Microsoft-Windows-RemoteDesktopServices-RdpCoreTS/Operational.evtx событиями с идентификаторами 132 и 148 – и в этих событиях остаётся имя канала. У легитимных каналов RemoteFX имена известные (cliprdr, XPSRD и подобные), а у rdp2tcp в поле имени стоит либо буквальное rdp2tcp, либо случайная цифро-буквенная строка, заданная оператором. То есть маскировка под легитимный сервис на уровне транспорта дала тишину в сети, но сам инструмент оставляет именную улику в журнале хоста – размен, зеркальный тому, что был на входе: там оператор спрятался в трафике, здесь засветился в логе. Помимо этого, для проброса портов через утилиту atexec из набора Impacket оператор ставит запланированные задачи, которые поднимают форвардинг штатным механизмом Windows:
netsh interface portproxy add v4tov4 listenport=443 connectaddress=10.0.12.101 connectport=445
Дальше NightEagle идёт вверх по привилегиям, эксплуатируя уязвимости Active Directory и подключаясь к внутренним системам через ранее проложенные туннели. В одном из инцидентов оператор применил CVE-2019-0708 – это BlueKeep, RCE в службах удалённого рабочего стола семилетней давности. Через уязвимый механизм он создал в системе локальную учётную запись и добавил её в группы администраторов и пользователей удалённого рабочего стола – то есть выписал себе легальный на вид админский доступ к хосту.
Выбор BlueKeep в 2026 году – тот случай, когда приём стоит взвесить на соответствие фронтиру. С одной стороны, это громко: у сетевого детекта на BlueKeep есть готовая сигнатура, эксплуатация оставляет характерные артефакты в дампе памяти, и на внешнем периметре такой залёт поймали бы сразу. С другой – оператор пускает его не с интернета, а из уже захваченной внутренней сети, по своему туннелю, против внутреннего хоста, который за семь лет так и не пропатчили. В этом контексте расход честный: незачем жечь свежий 0-day или редкий приватный эксплойт там, где рабочий 1-day семилетней выдержки решает задачу, а шум от него тонет во внутреннем сегменте, куда защита смотрит слабее, чем на край. Альтернатива – более тихий путь через кражу учёток и билетов без RCE – обошлась бы дешевле по следам, но медленнее и не всегда доводит до нужного хоста; NightEagle предпочла быстрый известный эксплойт, приняв его шум как терпимый внутри периметра.
Дальше в ход идёт Kerberos. Оператор запрашивает билеты с нестандартным набором флагов – Forwardable, Proxiable, Renewable, – а получив учётку с достаточными правами, пытается реплицировать объект Domain-Password из базы Active Directory, выдавая себя за контроллер домена. Это DCSync: рабочая станция прикидывается контроллером и просит у настоящего контроллера отдать ей хеши паролей, как при штатной репликации между контроллерами. На этом киллчейн смыкается – оператор закрепляется в инфраструктуре, снимает хеши доменных учёток, использует долгоживущие билеты Kerberos для легитимного с виду доступа к ресурсам и в итоге получает контроль над контроллерами домена и всей Active Directory жертвы.
Отказ от C2 и жизнь на легитимных сервисах убирают сетевую поверхность, но не делают операцию невидимой – метод оставляет следы на самих хостах, и отчёт эти швы называет прямо. Развёртывание GhostContainer через VIEWSTATE поднимает .NET-сборку прямо в рабочем процессе, а такая рефлективная загрузка, особенно инициированная из PowerShell, аномальна сама по себе и наблюдаема на конечной точке. Обход AMSI перезаписью памяти amsi.dll и ntdll.dll тоже не бесплатен: правка кода системных библиотек в памяти – классический индикатор, на который настроены поведенческие детекты.
Сетевой след, который оператор так старательно прятал, всё равно частично торчит. Эксплуатация BlueKeep распознаётся по сигнатуре в трафике – у неё есть узнаваемый рисунок обмена по RDP-каналу. Работа туннелей выдаёт себя обращениями к доменам вида *.devtunnels.ms, нехарактерными для внутреннего хоста, а rdp2tcp – уже упомянутыми именами каналов в событиях 132 и 148. Попытка DCSync видна как запрос репликации Domain-Password от машины, которая контроллером домена не является, – аномалия, которую отделяет от штатной репликации сам факт источника. Активность Impacket оставляет файловые артефакты создания запланированных задач и характерные следы инструментов дампа учёток.
Вывод для защиты из этого один и он инженерный: против оператора, который не оставляет сетевого адресата, бесполезно ждать плохого домена в блоклисте – ловить надо метод, а не инфраструктуру. Аномальная рефлективная загрузка сборки, правка памяти системных DLL, запрос репликации от не-контроллера, чужое имя RDP-канала в журнале – вот на чём NightEagle всё-таки проявляется, и всё это события хоста, а не сети. Оператор минимизировал именно ту поверхность, которую традиционно мониторят плотнее всего, – сетевую, – и тем сдвинул детект туда, где у многих защита реже настроена: на поведение процессов на самой конечной точке.
Сложим арсенал целиком. Обход AMSI – перезапись amsi.dll и ntdll.dll, приём общеизвестный. Добыча ключа Exchange – machinekeyfinder-aspx. Разбор команд и криптография Stub – ExchangeCmdPy.py под CVE-2020-0688. Инжектор страниц – PageLoad_ghostfile.aspx. Прокси и туннель – Neo-reGeorg. Внешний канал – Microsoft dev tunnels и rdp2tcp. Постэксплуатация – atexec из Impacket, BlueKeep 2019 года и DCSync. В этом списке нет ни одной строки, которую NightEagle написала бы с нуля.
Это и стало причиной, по которой атрибуция по коду здесь не работает: сборка из публичных проектов не несёт авторского почерка, который сравнением образцов привязали бы к конкретной группе, а инфраструктуры, по которой можно связать инциденты между собой, оператор наружу не выставил. Привязку к NightEagle дают открытые отчёты, а не улики в самом импланте – и это тоже часть замысла. Отсюда же и разброс целей: в 2025 году под GhostContainer лёг государственный орган и высокотехнологичная компания в Азии, в 2026-м та же оснастка развернулась против российских предприятий. Меняется география, тулкит остаётся тем же заёмным набором.
И в этом весь калибр операции. Слабый оператор, собрав то же самое из того же открытого кода, оставил бы за собой C2-домен, наследил бы исходящими соединениями и попался бы на первом же гео-алерте при входе. NightEagle не оставила ни командного сервера, ни собственной инфраструктуры, ни авторского кода – только краденые учётки на входе, легитимные сервисы в канале и чужие эксплойты в деле. Единственное, что она возит с собой честно своё, – это дисциплина: понимание, где Exchange принимает команды под видом OWA, где RDP-канал прячется под именем службы, а где семилетний баг всё ещё живёт непропатченным. Оснастку сегодня скачивает любой; профессионала отличает то, как он её собирает и где решает не светиться. Разбор кампании против российских целей и реверс GhostContainer выполнены командами реагирования и исследований Kaspersky.
Автор: hacker@shifry.local