Исследовательская заметка N.12 · 2026-10-16

Как бы мы охотились на себя.

Каждый имплант подчиняется закону сохранения: исполнение оставляет улики где-то, а уклонение лишь выбирает — где. Эта заметка переворачивает нашу обычную точку зрения. Вместо того, как агент выживает в стеке, — как бы мы на него охотились: сигнатуры в памяти, периодичность маяков, гигиена именованных каналов, — и пробелы в телеметрии, которые мы оставляем сознательно, потому что продукт, утверждающий, что не оставляет ни одного, — продукт, который лжёт.

ВРЕМЯ ЧТЕНИЯ ≈ 8 МИН АВТОР КОМАНДА AXIS

Закон сохранения

Детект-инжиниринг против C2 в памяти начинается с одной аксиомы: ничто не невидимо; улики сохраняются. Обход не удаляет сигнал — он переносит сигнал в слой, который сенсор не наблюдает, и в этом весь аргумент предыдущей заметки. Поэтому первый вопрос охотника — не есть ли имплант, а в каком слое этот имплант решил платить. Четыре охотничьих угодья покрывают бо́льшую часть реестра: форма памяти, ритм времени, таблица именованных каналов и провод. Пройдём каждое так, как прошли бы против самих себя — что мы там тратим, что выживает в любом случае, и какое правило мы бы написали.

Память: охотьтесь на форму, не на байты

Байтовые сигнатуры против нашего агента умирают молодыми, и почему — мы знаем лучше всех: в покое он шифрует и отмапливает себя — зарегистрированные регионы XOR-ятся ключом на один сон, переводятся в PAGE_NOACCESS, ключ затирается при пробуждении; модули приходят с ключами на билд и при выгрузке затираются, а не освобождаются. Поэтому мы бы не сканировали байты. Мы бы сканировали форму укрытия: приватные регионы PAGE_NOACCESS внутри процесса, который также держит исполняемую память без файловой подложки; закоммиченные приватные страницы, не отображающиеся ни на один модуль на диске; регистрации в очередях таймеров у процесса, которого их никогда не было; поток, чей стартовый адрес больше не резолвится ни во что; затёртый DOS-заголовок — стирание MZ удаляет сигнатуру и устанавливает аномалию, а аномалии достойны правил. Ничто из этого не обвиняет в одиночку. Всё это кластеризуется — а кластеризация и есть детект-инжиниринг.

Зашифрованные байты — безопасные байты. Скрытая форма — не скрытая вещь.

Периодичность: джиттер проигрывает статистике

Фольклор говорит, что джиттер побеждает охотника. Он побеждает секундомер; он не побеждает распределение. Маяк с щедрым джиттером всё равно кластеризуется вокруг своего среднего; сессия всё равно открывается рукопожатием характерного размера; таскинг всё равно приходит всплесками, выверенными по рабочему дню оператора, а не по браузингу человека. Имея месяц netflow, мы бы никогда не искали интервал — мы бы искали автокорреляцию: хост, который держит время как машина, каким бы размытым ни был метроном. Что мы тратим против этого: трафик-профиль на билд, ротацию идентичности сессии по фиксированной каденции и архитектуру с двумя пулами, разделяющую таскинг и доставку полезной нагрузки. Что выживает в любом случае: ротация сбрасывает идентичность импланта, но не его поведение — каденция байт-счётчиков и ритм сессии переживают любое переименование. Следите за формой сессии, а не за именем импланта.

Секундомер охотникаСтатусЧто переживает джиттер
Таймеры фиксированного интерваламёртвРаспределение интервалов вокруг среднего
Известные плохие адресатымёртвБайт-счётчики рукопожатий и сессий
Идентичность импланта, на сессиюмёртвРитм сессии сквозь ротации
Статический TLS-фингерпринтпотраченВсплески таскинга против человеческих часов
Рис. A — секундомер на пенсииДжиттер — костюм; распределение — лицо

Именованные каналы: таблица, которую все забывают

SMB-пивоты живут в таблице каналов, а таблица каналов — чтение публичное. Каждый SMB-маяк — именованный канал; каждый именованный канал — строка, которую операционная система с радостью перечислит любому защитнику, спросившему. Мы бы спросили. Охота пишет себя сама: имена каналов с сгенерированной энтропией — или имена, имитирующие общеизвестные системные каналы, но принадлежащие не тому процессу; каналы, созданные юзермодными процессами без служебной причины; ACL, затянутые туже, чем требует мнимое назначение канала; времена жизни каналов, коррелирующие с окнами сессий. Наша собственная доктрина — гигиена: имена из профиля, тугие ACL, демонтаж по завершении задачи, — и мы честны насчёт того, что покупает гигиена: она сжимает строку; удалить её она не может. Базовая линия имён каналов на роль хоста, диффуемая ежедневно, ловит класс. Имплант может быть вежлив в этом. Отсутствующим он быть не может.

Пробелы, которые мы оставляем сознательно

Вот часть, которую большинство вендоров пропускает. Резидентный агент обязан держать память где-то. C2 обязан двигать байты. У сессии есть первый пакет. У пивота есть процесс, открывший хендл, который ему никогда раньше не требовался. Мы не утверждаем обратного — ни в панели, ни в документации, ни здесь. Что мы выбираем — это где живут наши улики: прочь от слоёв, обвиняющих по умолчанию, — хукнутые юзермодные API, неподписанные бинари на диске, родословная дочерних процессов, — и в слои, требующие охотника с бюджетом и терпением: аудиты VAD, статистический анализ трафика, базовые линии таблицы каналов. Этот выбор и есть продукт. Остальное — прилагательные.

СлойМы тратимМы признаём
Юзермодный APIНепрямые сисколлы, маршрут вокруг хуковЯдро всё равно журналирует вызов
Память в покоеШифрование, отмапливание, затёртый ключСилуэт укрытия
ВремяДжиттер, трафик-профили на билдРаспределение под ним
ИдентичностьРотация идентичности сессииФорма сессии сквозь ротации
ПивотыИменованные, ACL-нутые, демонтируемые каналыСтрока существует, пока живёт
Рис. B — реестр уступокОпубликован, чтобы защитники могли оценить нас, а покупатели — проверить нашу математику

Угол защитника

Веди мы охоту, в блокноте было бы четыре вопроса, в этом порядке, — и мы публикуем их, потому что клиент, способный проверить наши утверждения, стоит десятерых, кто им верит:

блокнот охотника — четыре вопроса, по порядкууровень категорий, как всегда
// 1. где этот процесс держит свои секреты?
vad_audit(host)  // приватные NOACCESS + RX без подложки, очереди таймеров, сироты-потоки
// 2. держит ли этот хост время как машина?
autocorr(sessions, 30d)  // распределения, не интервалы; каденция байт-счётчиков
// 3. кто владеет этим каналом, и с каких пор?
pipe_baseline(role)  // новые владельцы, имитирующие имена, слишком тугие ACL
// 4. что замолчало, когда это началось?
silence_corr(etw, sysmon)  // отсутствующая телеметрия — тоже телеметрия: см. N.09, N.11

Запишите эти четыре как регулярные охоты — и реестр уступок выше станет вашим списком покупок. Мы предпочитаем быть пойманными командами, ведущими этот блокнот, чем услышанными через прилагательные — и предпочитаем, чтобы наша детект-поверхность была документированным инженерным компромиссом, а не маркетинговым секретом. Вот что значит измерено, а не прорекламировано, когда предложение обязано пережить встречу с людьми, которые на нас охотятся.

Следующая заметка: Восемь способов пережить перезагрузку, с ценниками. Аудит персистентности — восемь локаций автозапуска, которые мы поставляем, сколько каждая стоит под современной телеметрией и почему Run и RunOnce считаются одной строкой.