Закон сохранения
Детект-инжиниринг против C2 в памяти начинается с одной аксиомы: ничто не невидимо; улики сохраняются. Обход не удаляет сигнал — он переносит сигнал в слой, который сенсор не наблюдает, и в этом весь аргумент предыдущей заметки. Поэтому первый вопрос охотника — не есть ли имплант, а в каком слое этот имплант решил платить. Четыре охотничьих угодья покрывают бо́льшую часть реестра: форма памяти, ритм времени, таблица именованных каналов и провод. Пройдём каждое так, как прошли бы против самих себя — что мы там тратим, что выживает в любом случае, и какое правило мы бы написали.
Память: охотьтесь на форму, не на байты
Байтовые сигнатуры против нашего агента умирают молодыми, и почему — мы знаем лучше всех: в покое он шифрует и отмапливает себя — зарегистрированные регионы XOR-ятся ключом на один сон, переводятся в PAGE_NOACCESS, ключ затирается при пробуждении; модули приходят с ключами на билд и при выгрузке затираются, а не освобождаются. Поэтому мы бы не сканировали байты. Мы бы сканировали форму укрытия: приватные регионы PAGE_NOACCESS внутри процесса, который также держит исполняемую память без файловой подложки; закоммиченные приватные страницы, не отображающиеся ни на один модуль на диске; регистрации в очередях таймеров у процесса, которого их никогда не было; поток, чей стартовый адрес больше не резолвится ни во что; затёртый DOS-заголовок — стирание MZ удаляет сигнатуру и устанавливает аномалию, а аномалии достойны правил. Ничто из этого не обвиняет в одиночку. Всё это кластеризуется — а кластеризация и есть детект-инжиниринг.
Зашифрованные байты — безопасные байты. Скрытая форма — не скрытая вещь.
Периодичность: джиттер проигрывает статистике
Фольклор говорит, что джиттер побеждает охотника. Он побеждает секундомер; он не побеждает распределение. Маяк с щедрым джиттером всё равно кластеризуется вокруг своего среднего; сессия всё равно открывается рукопожатием характерного размера; таскинг всё равно приходит всплесками, выверенными по рабочему дню оператора, а не по браузингу человека. Имея месяц netflow, мы бы никогда не искали интервал — мы бы искали автокорреляцию: хост, который держит время как машина, каким бы размытым ни был метроном. Что мы тратим против этого: трафик-профиль на билд, ротацию идентичности сессии по фиксированной каденции и архитектуру с двумя пулами, разделяющую таскинг и доставку полезной нагрузки. Что выживает в любом случае: ротация сбрасывает идентичность импланта, но не его поведение — каденция байт-счётчиков и ритм сессии переживают любое переименование. Следите за формой сессии, а не за именем импланта.
| Секундомер охотника | Статус | Что переживает джиттер |
|---|---|---|
| Таймеры фиксированного интервала | мёртв | Распределение интервалов вокруг среднего |
| Известные плохие адресаты | мёртв | Байт-счётчики рукопожатий и сессий |
| Идентичность импланта, на сессию | мёртв | Ритм сессии сквозь ротации |
| Статический TLS-фингерпринт | потрачен | Всплески таскинга против человеческих часов |
Именованные каналы: таблица, которую все забывают
SMB-пивоты живут в таблице каналов, а таблица каналов — чтение публичное. Каждый SMB-маяк — именованный канал; каждый именованный канал — строка, которую операционная система с радостью перечислит любому защитнику, спросившему. Мы бы спросили. Охота пишет себя сама: имена каналов с сгенерированной энтропией — или имена, имитирующие общеизвестные системные каналы, но принадлежащие не тому процессу; каналы, созданные юзермодными процессами без служебной причины; ACL, затянутые туже, чем требует мнимое назначение канала; времена жизни каналов, коррелирующие с окнами сессий. Наша собственная доктрина — гигиена: имена из профиля, тугие ACL, демонтаж по завершении задачи, — и мы честны насчёт того, что покупает гигиена: она сжимает строку; удалить её она не может. Базовая линия имён каналов на роль хоста, диффуемая ежедневно, ловит класс. Имплант может быть вежлив в этом. Отсутствующим он быть не может.
Пробелы, которые мы оставляем сознательно
Вот часть, которую большинство вендоров пропускает. Резидентный агент обязан держать память где-то. C2 обязан двигать байты. У сессии есть первый пакет. У пивота есть процесс, открывший хендл, который ему никогда раньше не требовался. Мы не утверждаем обратного — ни в панели, ни в документации, ни здесь. Что мы выбираем — это где живут наши улики: прочь от слоёв, обвиняющих по умолчанию, — хукнутые юзермодные API, неподписанные бинари на диске, родословная дочерних процессов, — и в слои, требующие охотника с бюджетом и терпением: аудиты VAD, статистический анализ трафика, базовые линии таблицы каналов. Этот выбор и есть продукт. Остальное — прилагательные.
| Слой | Мы тратим | Мы признаём |
|---|---|---|
| Юзермодный API | Непрямые сисколлы, маршрут вокруг хуков | Ядро всё равно журналирует вызов |
| Память в покое | Шифрование, отмапливание, затёртый ключ | Силуэт укрытия |
| Время | Джиттер, трафик-профили на билд | Распределение под ним |
| Идентичность | Ротация идентичности сессии | Форма сессии сквозь ротации |
| Пивоты | Именованные, ACL-нутые, демонтируемые каналы | Строка существует, пока живёт |
Угол защитника
Веди мы охоту, в блокноте было бы четыре вопроса, в этом порядке, — и мы публикуем их, потому что клиент, способный проверить наши утверждения, стоит десятерых, кто им верит:
// 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 считаются одной строкой.