Статья Атаки на невидимые сетевые службы через анализ временных задержек

1770921224323.webp


Знаешь, есть такое особенное чувство, когда прижимаешься щекой к холодному металлу сейфа. Не потому, что тебе холодно, а потому, что ты пытаешься услышать то, что не предназначено для человеческого уха. Щелчок сувальды, шелест пружины, едва уловимый звук - и ты понимаешь: код подходит. Ты не видишь замок изнутри, ты не знаешь комбинацию, но ты слышишь отклик механизма. Ты используешь не основной канал информации, а побочный - акустический.

Этот образ преследует меня уже несколько месяцев. Сначала он был просто метафорой, красивой картинкой для презентаций. А потом я понял: это не метафора. Это точное описание того, чем мы занимаемся каждый день. Только вместо сейфа - сетевой стек операционной системы, вместо сувальд - обработчики прерываний, а вместо акустики - наносекунды.

Я хочу, чтобы ты закрыл глаза и представил. Где-то, возможно за тысячу километров от тебя, стоит сервер. На нём крутится критический сервис - SSH, VPN, панель управления АЭС, биржа, да что угодно. Администратор, умный и осторожный, сделал так, чтобы этот сервис был «невидимым». Он настроил firewall на DROP, а не на REJECT. Он поставил port knocking. Он, может быть, даже использует Single Packet Authorization с 2048-битным RSA. С точки зрения любого сканера портов, любой IDS, любого «этереального» сниффера - на этом IP-адресе нет ничего, кроме веб-сервера. Тишина. Покой.

Но я слышу щелчок.

Почему меня бесит термин «невидимая служба»

Потому что это маркетинговый новояз. Это попытка выдать желаемое за действительное. В физическом мире не бывает невидимых объектов. Бывают объекты, которые не отражают свет в видимом диапазоне, но они всё равно взаимодействуют с окружающей средой - отражают радиоволны, поглощают тепло, создают гравитационное поле. Компьютер, включённый в сеть, даже если он не отвечает на пакеты, всё равно их принимает. Всё равно тратит на них энергию. Всё равно переводит свой сетевой контроллер из одного состояния в другое. И все эти взаимодействия оставляют следы.

Мейнстримная безопасность построена на логических абстракциях: «есть ответ - порт открыт, нет ответа - порт закрыт». Эта бинарная модель удобна для софта, для баз данных уязвимостей, для отчётности перед заказчиком. Но она не соответствует реальности. Реальность аналоговая. Между «открыто» и «закрыто» лежит целый спектр состояний, которые различаются временем отклика, джиттером, использованием кэша, энергопотреблением, электромагнитным излучением.

Мы привыкли думать, что компьютер - это строгая детерминированная машина. Подал на вход 0 и 1 - получил на выходе 0 и 1. Но это иллюзия, которую поддерживает многослойная абстракция. Сними один слой - и ты увидишь хаос: конкуренцию за шину, очереди прерываний, кэш-промахи, троттлинг частоты из-за перегрева. В этом хаосе скрытый сервис оставляет отпечаток пальца. Нам нужно только правильно осветить поверхность.

Что мы будем делать в этой статье

Мы не будем пересказывать документацию. Мы не будем повторять прописные истины. Мы залезем в такие дебри, куда нормальные админы заходят только с валидолом. Мы будем измерять наносекунды, писать код, взаимодействующий напрямую с DMA-кольцами сетевых карт, и строить вероятностные модели.

Я дам тебе инструменты - от древнего hping3 до современных обвязок вокруг eBPF и DPDK. Я покажу, как превратить обычный ноутбук в осциллограф для сетевого трафика. Я расскажу, где искать утечки, которые разработчики скрытых сервисов забыли заткнуть.

Но главное - я попытаюсь передать тебе способ мышления. Способность видеть за бинарным ответом «DROP» непрерывную аналоговую величину «время обработки». Это как научиться различать оттенки серого там, где обычный человек видит только чёрное и белое.

С порога

Прежде чем мы нырнём, я обязан сказать несколько неприятных вещей.

Во-первых, 99% систем, которые ты будешь тестировать, не имеют скрытых портов. Ты потратишь кучу времени, настраивая оборудование, отлаживая код, а в ответ получишь только равномерный белый шум. Это нормально. Это не значит, что метод не работает. Это значит, что защитник либо действительно ничего не прячет, либо использует продвинутые методы маскировки (например, XDP). Но даже отрицательный результат - это результат.

Во-вторых, точность требует жертв. Ты не сможешь проводить такие атаки через спутниковый интернет, LTE в движущемся поезде или публичный Wi-Fi в торговом центре. Джиттер там перекроет любые полезные сигналы. Тебе нужна либо локальная сеть, либо хороший прямой канал с минимальной буферизацией. Если у тебя нет доступа к такой среде - не отчаивайся, просто воспринимай статью как теоретический базис.

В-третьих, это не для скрипт-кидди. Я не дам тебе одну команду, которая «взламывает всё». Я дам тебе леску, крючок и покажу, где водится рыба. Насаживать наживку и вытаскивать улов придётся самостоятельно.

Сейф ждёт

Перед тобой закрытая дверь. За ней - данные, доступ, контроль. Дверь не подаёт признаков жизни. Ни огонька, ни скрипа. Но ты знаешь: за ней - сложный механизм, и у него есть сувальды. Приложи стетоскоп. Слушай.

Время - это наша игла.

Поехали.

Глава 1: Доктрина «Невидимости» и ее фатальный изъян​

Давай сразу определимся с терминами. Я пишу это не для учебника, а для нас с тобой.

Что такое «Невидимая» служба?
Это не «отсутствие службы». Это служба, которая:
  1. Не отвечает на стандартный SYN-запрос на 22-й порт (потому что пакет просто дропается фаерволом).
  2. Не анонсирует себя.
  3. Пробуждается только при получении специфической последовательности пакетов (например, SYN 1000, SYN 2000, SYN 3000).
  4. В продвинутых реализациях использует криптографию (SPA) для подтверждения авторизации.
Со стороны сканера, будь то банальный nmap -sS или продвинутый masscan, это выглядит как фильтр «blackhole». Пакет ушел в пустоту и сгинул во времени. filtered. dropped. Конец истории. Для вендора DPI и для системы SIEM - это тишина. Алгоритмы победили.

Фатальный изъян.
Система, которая игнорирует входящий пакет, и система, которой этого пакета не существует вовсе, обязаны вести себя абсолютно идентично. Не на 99%. Не на 99.99%. На 100%. С точностью до наносекунды.

Любое, даже самое маленькое отклонение во времени обработки этого пакета - это сигнал. Физика противна идеальному сокрытию. Даже если твой knockd написан на голом C и работает в нулевом кольце защиты, он должен принять решение: «DROP or ACCEPT?» Принятие решения занимает время. В то время как полное отсутствие обработчика - это просто сброс пакета на уровне кольцевой DMA-буфера или драйвера.

Суть атаки (Executive Summary для тех, кто листает):
Мы измеряем время отклика (RTT) на серии легитимных запросов к открытым сервисам (например, 80 порт), вставляя в эту серию «зондирующие» пакеты на скрытый порт. Анализируя флуктуации времени доставки легитимных ответов (или специально сконструированных ICMP-запросов), мы детектируем момент, когда ядро или демон «отвлеклось» на обработку нашего скрытого пакета. Мы не видим ответа. Мы видим, что ответ на соседний запрос пришел на 3 микросекунды позже.

Это не баг. Это фича. Это физика.

Глава 2: Математика микроскопических остановок.​

Прежде чем ты полезешь в терминал, мы должны быть честны друг с другом. Это хардкор. Если ты надеешься найти тут одну команду timing_attack --target 1.1.1.1 --port 22 --stealth, то я тебя разочарую. Мы будем строить снаряд с нуля.

Переменные, с которыми мы играем:
  1. RTT (Round Trip Time): Базовое время протухания пакета туда-обратно. Наша шкала.
  2. Jitter (Джиттер): Вариация RTT. Наш главный враг и главный друг одновременно. Если джиттера нет (идеальный канал), мы видим иголку. Если джиттер дикий (Wi-Fi в час пик), мы вынуждены прибегать к статистике и увеличивать количество проб.
  3. Processing Delay (Задержка обработки): Время, которое тратит хост на то, чтобы понять, что делать с пакетом.
    • Легитимный порт (80): ~100-500 нс (если железная разгрузка) - 10 мкс (софтовый стек).
    • Скрытый порт (DROP): 0 нс (пакет умер в драйвере) или 5-20 мкс (пакет прошел до netfilter и был сопоставлен с правилом).
  4. Interference (Интерференция): Наши легитимные пакеты, которые мы используем как осциллограф.
Почему это работает? (Уровень ядра)
Рассмотрим два сценария на Linux-машине:

Сценарий А (Нет службы):
NIC; DMA; IRQ; Driver; netif_receive_skb -> ip_rcv -> tcp_v4_rcv -> Поиск сокета не найден -> tcp_v4_send_reset / или DROP по iptables. Даже на DROP мы проходим длинный путь до nf_hook_slow, если правило висит во входной цепочке. Это стоит микросекунд. Но главное - это последовательная обработка. Ядро не многозадачно в рамках одного ядра CPU в данный такт. Оно занято.

Сценарий Б (Скрытая служба + Knocking):
NIC ... tcp_v4_rcv -> Поиск сокета. Сокет есть, но он в режиме TCP_LISTEN с дополнительным условием (например, pcap-фильтр или userspace-демон). Пакет копируется в буфер сокета. Процесс knockd просыпается (контекст-свитч). Вот оно. Контекст-свитч. Это не микросекунды. Это наносекунды или даже микросекунды простоя для этого ядра CPU. Если в этот момент приходит пакет на 80-й порт, он будет ждать в очереди, пока ядро переключится обратно или пока другой CPU его обработает.

Наша задача: Заметить момент, когда ядро «споткнулось».

Глава 3: Инструментарий старого образца. Тренируем руку.​

Забудь про Python-скрипты с time.time(). Точности не хватит. Даже time.perf_counter() в нативном коде - это минимум 50-100 нс оверхеда, если повезет. Мы идем на уровень ниже.

Инструмент 1: hping3 с предельным терпением.
Самый грязный, самый надежный, самый «дедовский» способ. Позволяет слать кастомные TCP-пакеты и замерять RTT с точностью до микросекунд (через -i u1).
  • Суть метода: Запускаем бесконечный цикл пингов (ICMP) на жертву. Одновременно слаймом посылаем SYN на спрятанный порт. Смотрим на график пингов.
  • Команда-примитив:
    hping3 -S -p 80 -c 1000 --fast target.com (базовый замер)
    hping3 -S -p 31337 -c 1 target.com (зонд)
  • Почему это плохо: Слишком низкое разрешение. hping3 не синхронизирует таймеры идеально. Но если скрытый порт тормозит на 50+ мкс, ты это увидишь.
Инструмент 2: nping (Nmap suite) с --delay.
Более культурный, чем hping3. Позволяет делать замеры с миллисекундной точностью, что для нашей задачи - как лопата для нейрохирургии. Используем для калибровки.

Инструмент 3: Собственный сниффер/инжектор на libpcap (C или Rust).
Вот тут мы начинаем разговаривать серьёзно. Когда ты управляешь отправкой на уровне пакета и ловишь ответ в ту же наносекунду, снимая аппаратные штампы времени (HW timestamps), ты переходишь в высшую лигу.
  • Фишка: Современные сетевые карты (Intel I350, X550 и выше) умеют ставить timestamp на пакет прямо в момент прихода сигнала на PHY.
  • Linux API: SO_TIMESTAMPING.
  • Пример на Си (скелет):

    C:
    // Открываем RAW сокет
    // Включаем SO_TIMESTAMPING
    int enabled = SOF_TIMESTAMPING_RX_HARDWARE | SOF_TIMESTAMPING_TX_HARDWARE | SOF_TIMESTAMPING_RAW_HARDWARE;
    setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &enabled, sizeof(enabled));
    
    // Отправляем пакет на открытый порт (HTTP), засекаем время отправки
    // Получаем ответ SYN-ACK, читаем контрольное сообщение с timestamp
    
    // Между ними отправляем пакет на скрытый порт.
    // Анализируем разницу во времени между приходами ACK на HTTP-запросы.

    Точность этого метода - до 10-20 наносекунд. Это уровень аппаратуры.
Момент истины:
Тебе понадобится сетевуха, поддерживающая аппаратные штампы времени. Виртуалка типа VMWare или VirtualBox срет в тайминги. Они эмулируют время, и джиттер там такой, что можно прятать не то что порт - целый датацентр. Для отработки атаки тебе нужен либо реальный «bare metal» сервер рядом с тобой (желательно через свитч без буферизации), либо дорогой VPS с прямым доступом к NIC. Смирись.

Глава 4: Взлом порт-нокинга через джиттер. Практический воркшоп.​

Давай представим цель. Стандартный knockd конфиг. Парень захотел спрятать SSH. Последовательность: TCP 1000, TCP 2000, TCP 3000. Только после этого порт 22 открывается на 10 секунд.

Вопрос: Как понять, что последовательность началась правильно, не имея обратной связи?

Ответ: Смотреть на нагрузку процессора сетевого стека через временной анализ.

Практический план (для тестового стенда):
  1. Фаза 1: Калибровка базы.
    Мы замеряем время отклика от HTTP-сервера (порт 80) жертвы. Посылаем SYN, получаем SYN-ACK. Делаем это 10 000 раз. Строим гистограмму. Запоминаем mean и std_dev. Допустим, среднее RTT = 2.100 мс, стандартное отклонение = 0.010 мс (10 мкс). Это наш чистый канал + нагрузка легитимного веб-сервера.
  2. Фаза 2: Зондирование первого удара.
    Шлем SYN на порт 1000 (первый нок).
    Сразу после этого, с минимальной задержкой (например, через 10 мкс - usleep(10)), шлем SYN на порт 80.
    Измеряем RTT этого HTTP-запроса.
  3. Фаза 3: Сравнение.
    Отклонение RTT_зонда - RTT_базовое. Если отклонение стабильно превышает 2-3 сигмы (например, RTT = 2.150 мс вместо 2.100 мс) только когда мы стучим в порт 1000, значит, мы нашли «чувствительную» точку.
    Порт 1000 не просто дропается. Он приводит к пробуждению knockd, который тратит циклы на логирование (syslog), на сверку по времени, на запись в статус-файл.
Усложняем: Ложные срабатывания.
«Но, 0x7ch47, - скажешь ты. - В сети же постоянно что-то происходит. Может, это cron запустился? Или сосед порно смотрит?».
Поэтому мы не ищем одно отклонение. Мы ищем корреляцию.
Мы строим атаку так: [HTTP] [SYN:1000] [HTTP] [SYN:2000] [HTTP] [SYN:3000] [HTTP].
И мы ожидаем увидеть кумулятивный эффект. Каждый последующий «нок» вызывает чуть большую задержку, потому что knockd хранит состояние. Если состояние хранится в хеш-таблице, доступ к ней на третьем ударе может быть чуть медленнее, чем на первом. Крейзи? Да. Но это работает.

Глава 5: Эмпатия к защитнику (или почему это сложно фиксить)​

Я не циник. Я понимаю админа, который настраивает knockd. Он пытается защитить ресурсы от масс-сканирования и ботов. Это правильная идея. security through obscurity - это не всегда зло, если это один из слоев «луковой» защиты. Но я ненавижу, когда мне впаривают это как панацею.

Почему это нельзя просто «починить»?
Можно, конечно, попытаться внедрить случайные задержки (jitter injection). Например, при получении пакета на скрытый порт делать udelay(random() % 5000), чтобы сбить статистику.
  • Проблема 1: Ты замедляешь работу сервера для всех, включая легитимных пользователей, которые знают нок.
  • Проблема 2: Если случайная задержка меньше, чем максимальное стандартное отклонение сети, мы все равно увидим пик. Нам достаточно факта наличия задержки, а не ее точной величины.
Настоящий фикс:
Единственный способ защититься от темпорального анализа -это сделать так, чтобы решение о DROP принималось на том же аппаратном уровне, что и полное отсутствие правила. То есть зашить ACL в микросхему сетевого контроллера (SmartNIC, FPGA). Если пакет умирает на входе, не тревожа CPU, - мы ничего не измерим. Но кто из вас, ребята, юзает SmartNIC на домашнем сервере? То-то же.

Глава 6: Хардкор. Атака на Tor и скрытые службы.​

Поднимем ставки. Мы говорили о простом port knocking. Но принцип работает и на более высоких уровнях абстракции.

Скрытые службы Tor (Onion Services).
Рендеву-точки, интро-поинты. Сервис скрыт. IP-адрес неизвестен. Но он общается через цепочку нод.
В 2015-2016 годах были работы (и реальные эксплойты), которые позволяли детектировать, является ли конкретный релей сервером скрытой службы, по времени ответа на специфические запросы ячеек.

Суть:
Сервер скрытой службы имеет постоянный кружок друзей (Introduction Points). Обычный релей просто ретранслирует трафик. Сервер скрытой службы генерирует трафик, подписывает дескрипторы, занимается криптографией. Криптография - это дорого по CPU. Измеряя RTT между отправкой ячейки и ответом от цепи, можно вычислить, где в этой цепи находится "тяжелый" узел, выполняющий асимметричные операции. Это называется "айсберг" атака.

Инструмент (теоретический):
Пропатченный клиент Tor, который умеет посылать CREATE_FAST и CREATE ячейки с замерами времени получения CREATED. Статистическая обработка. Соль. Перец.

Глава 7: Кухня. Пишем свой сканер «темпоральных призраков».​

Хватит теории. Открывай редактор. Будем писать PoC на Python + Scapy + raw sockets для максимальной точности.

Концепт кода (Упрощенный, для понимания):
Мы будем использовать мультиплексирование. Один поток/процесс лупит пингами (ICMP Echo) с интервалом 1 мс. Второй поток лупит SYN-сканами на подозрительные порты.

Задача: Найти порт, который при отправке на него SYN вызывает увеличение RTT пинга на 0.05 мс.

Python:
#!/usr/bin/env python3
# timing_knock_finder.py
# Требует: scapy, root, и хорошую сетевуху.

from scapy.all import *
import time
import threading
import numpy as np
import sys

target_ip = sys.argv[1]
base_port = 80
scan_ports = range(1000, 65535, 1000) # Сканируем редкие порты

class RTTMeter:
    def __init__(self, target):
        self.target = target
        self.rtt_samples = []
        self.lock = threading.Lock()
        self.stop_ping = False
        
    def ping_worker(self):
        # Отправляем ICMP Echo Request и меряем ответ
        while not self.stop_ping:
            pkt = IP(dst=self.target)/ICMP()
            start = time.perf_counter()
            reply = sr1(pkt, timeout=0.1, verbose=0)
            if reply:
                rtt = (time.perf_counter() - start) * 1000000 # в микросекундах
                with self.lock:
                    self.rtt_samples.append(rtt)
            time.sleep(0.001) # 1 мс, аккуратно с пермишенами
    
    def get_baseline(self):
        # Захватываем чистые сэмплы
        with self.lock:
            return np.mean(self.rtt_samples[-100:]), np.std(self.rtt_samples[-100:])

def probe_port(target, port, meter):
    # Шлем SYN на скрытый порт
    pkt = IP(dst=target)/TCP(dport=port, flags='S')
    send(pkt, verbose=0)
    # Даем время на обработку (1-2 мс)
    time.sleep(0.002)
    # Получаем свежий RTT от пингера
    mean, std = meter.get_baseline()
    return mean, std

def main():
    meter = RTTMeter(target_ip)
    ping_thread = threading.Thread(target=meter.ping_worker)
    ping_thread.daemon = True
    ping_thread.start()
    
    print("[*] Калибровка джиттера...")
    time.sleep(5) # Набираем базу
    base_mean, base_std = meter.get_baseline()
    print(f"[+] База: {base_mean:.2f} мкс, Стд.откл: {base_std:.2f} мкс")
    
    for port in scan_ports:
        print(f"[>] Зондирование порта {port}...")
        mean_after, _ = probe_port(target_ip, port, meter)
        delta = mean_after - base_mean
        if delta > (base_std * 5): # Порог в 5 сигм
            print(f"[!] Аномалия! Порт {port} вызывает задержку +{delta:.2f} мкс")
            # Здесь можно начать более глубокий анализ (повторные измерения)
    
    meter.stop_ping = True
    ping_thread.join()

if __name__ == "__main__":
    if os.geteuid() != 0:
        print("Нужен рут для raw сокетов.")
        sys.exit(1)
    main()

Почему этот код - дерьмо?
  1. time.perf_counter() имеет оверхед.
  2. scapy медленный. Для реальной атаки нужно писать на C или Rust.
  3. sleep(0.001) в Linux не дает гарантии 1 мс. Это минимум 10-20 мкс дрожания, а часто и больше.
  4. Нет аппаратных таймстемпов.
Этот код - иллюстрация идеи, а не боевой эксплоит. Но если ты запустишь его в хороших условиях (LAN), ты увидишь разницу.

1770921254550.webp


Глава 8: Калибровка ада. Строим систему сбора с точностью до такта​

Все предыдущие методы грешили одним: мы полагались на относительные измерения RTT от одного источника. А что, если мы можем получить абсолютную разницу во времени между событиями на разных машинах? Ведь скрытая служба, обрабатывая пакет, не только замедляет параллельные запросы. Она еще и отвечает чем-то? Нет. Но она может влиять на время отправки других пакетов, например, TCP-keepalive или фоновых DNS-запросов.

Встречайте: атака с использованием синхронизированных часов (Timing Synchronization Attack).

Концепция:

Мы заранее синхронизируем свои часы с часами жертвы. Не через NTP - это слишком шумно и грубо. Мы используем ICMP Timestamp Request (тип 13) или TCP Timestamp опции. Да, админ мог их отрубить, но часто они включены «для производительности». Получая timestamp от жертвы, мы можем подогнать локальное время с точностью до миллисекунды.

Зачем? Если мы знаем, в какой абсолютный момент жертва получила наш пакет на скрытый порт, мы можем анализировать корреляцию во времени с другими событиями. Например, мы можем мониторить broadcast-трафик, ARP-запросы, которые жертва отправляет во внешний мир. Если после нашего пакета на 31337-й порт жертва внезапно отправила ARP-запрос к своему шлюзу (потому что knockd решил зарезолвить имя хоста для логирования) - мы это увидим, даже не имея прямого ответа.

Инструмент: nping с ICMP Timestamp.

Bash:
nping --icmp-type 13 -c 1 --delay 1 target.com
# Ответ содержит originate, receive, transmit timestamps.
# С их помощью можно оценить смещение часов.

Но точность низкая. Пойдем дальше.

TCP Timestamp опции (RFC 1323):
Каждый TCP-пакет может содержать опцию TSval и TSecr. Если жертва имеет открытый TCP-сокет (например, тот же веб-сервер), мы можем в рукопожатии получить от неё timestamp. Это значение - миллисекунды (или чаще - тики, инкрементируемые раз в 1 мс или 10 мс). Собирая множество таких значений, мы строим линию тренда и экстраполируем время с точностью до миллисекунды.

Код на Python для извлечения TCP timestamp из захваченного пакета:

Python:
from scapy.all import TCP, rdpcap
packets = rdpcap('capture.pcap')
for pkt in packets:
    if TCP in pkt and pkt[TCP].options:
        for opt, val in pkt[TCP].options:
            if opt == 'Timestamp':
                tsval, tsecr = val
                print(f"Timestamp: {tsval}")

Синхронизировав часы, мы можем проводить кросс-корреляционный анализ между нашими зондирующими пакетами и любыми observable событиями от жертвы. Например, DNS-запросами (мы можем перехватывать их, если находимся в одной подсети), или даже просто изменением TTL в IP-пакетах (некоторые стеки меняют способ обработки, что влияет на IP ID или TTL).

Глава 9: Атака на Single Packet Authorization (SPA) - fwknop в деталях​

Хватит греть старичка knockd. Он слишком прост. Перейдем к тяжелой артиллерии - fwknop. Это уже не просто последовательность портов, а криптографически подписанный UDP-пакет, который несет в себе зашифрованные данные о клиенте и времени. Даже если мы увидим такой пакет, мы не сможем его подделать без ключа.

Вопрос: Можно ли обнаружить SPA-сервер, который в обычное время молчит и дропает все UDP на порту 62201?

Ответ: Да, и даже более эффективно, чем простой port knocking.

Почему SPA уязвим к временному анализу:
  1. Проверка подписи: Получив UDP-пакет, демон fwknop должен расшифровать его (используя AES), проверить HMAC, сверить timestamp (чтобы избежать повторного воспроизведения), сверить IP-адрес, возможно, сверить имя пользователя. Это - вычислительно затратная операция. Даже на современном CPU проверка HMAC-SHA1 занимает микросекунды, но это уже микросекунды простоя для одного ядра.
  2. Доступ к диску: По умолчанию fwknop пишет в syslog. Запись в syslog - это блокировка, очередь, иногда синхронная запись на диск. Это миллисекунды. Огромная задержка.
  3. Открытие порта: Если пакет валиден, fwknop выполняет iptables команду. Это системный вызов, загрузка модулей, изменение правил - десятки миллисекунд.
Сценарий атаки:
Мы отправляем на порт 62201 случайный UDP-мусор. Замеряем RTT до HTTP-сервера до и после. Если RTT стабильно увеличивается на 0.5–2 мс после нашего мусора - мы, возможно, попали в обработчик. Но это не точно: мусор просто дропается на ранней стадии (проверка длины пакета, проверка magic bytes). fwknop ожидает пакет с определенным заголовком (16 байт random data, затем данные). Если мы пошлем короткий пакет, он отбросится быстро.

Как найти точный триггер?
Методом перебора. Мы отправляем пакеты разной длины, с разным содержимым, и смотрим на корреляцию с джиттером. В какой-то момент мы попадем в «зону ответственности» криптографии. Самый большой всплеск задержки будет при отправке корректно сформированного, но невалидного (сломанная подпись) SPA-пакета. Потому что демон честно попытается его расшифровать и только потом поймет, что контрольная сумма не сходится.

Далее: fwknop имеет режим --foreground и --verbose. В реальных серверах он часто запущен как демон. Но админы могут включить подробное логирование. Каждая попытка расшифровки (даже неудачная) попадает в лог. Это дает нам дополнительный канал утечки: мы можем атаковать syslog-сервер или просто заметить увеличение времени ответа на syslog-запросы (если syslog удаленный).

Глава 10: Микроархитектурные утечки: когда процессор сам тебя сдаёт​

Мы рассматривали сеть как черный ящик. Теперь давай заглянем внутрь сервера. Потому что даже если сетевая карта и ядро работают бесшумно, процессор оставляет следы.

Cache-timing атаки известны давно (Spectre, Meltdown). Но обычно они требуют выполнения кода на целевой машине. А что, если мы можем влиять на кэш процессора удаленно через сетевые пакеты?

Оказывается, можно. Исследования 2019-2023 годов (например, NetCAT - Network Cache Attack) показали, что если у вас есть Intel DDIO (Data Direct I/O), сетевая карта может писать данные прямо в LLC (Last Level Cache) процессора, минуя DRAM. Это значит, что обработка входящего пакета может вытеснить кэш-линии, принадлежащие другим процессам. И время доступа к памяти для этих процессов изменится.

Применение к скрытым службам:
  1. Мы находим легитимный сервис на жертве, который имеет предсказуемый паттерн доступа к памяти (например, Nginx, использующий sendfile для отдачи статики).
  2. Мы отправляем пакеты на скрытый порт. Демон knockd (или SPA) начинает обрабатывать пакет, читает свою структуру данных из памяти, которая находится в LLC.
  3. Из-за DDIO эти структуры данных могут попасть в LLC и вытеснить кэш Nginx.
  4. Мы отправляем HTTP-запрос к Nginx и тщательно измеряем время получения первого байта (Time To First Byte). Это время напрямую зависит от того, попали ли данные Nginx в кэш L3 или их пришлось читать из DRAM.
  5. Разница между чтением из LLC и DRAM может составлять десятки наносекунд. Но в LAN с низким джиттером эта разница измерима.
Инструмент: Специализированный HTTP-клиент с точным измерением TTFB.

C:
// Псевдокод на C с использованием clock_gettime(CLOCK_MONOTONIC_RAW)
// и TCP_CORK для минимизации накладных расходов.
int sock = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(sock, SOL_TCP, TCP_CORK, &one, sizeof(one));
connect(sock, ...);
send(sock, "GET / HTTP/1.0\r\n\r\n", ...);
struct timespec t1, t2;
clock_gettime(CLOCK_MONOTONIC_RAW, &t1);
recv(sock, buf, sizeof(buf), 0);
clock_gettime(CLOCK_MONOTONIC_RAW, &t2);
// t2 - t1 = TTFB
close(sock);

Повторяем это 10 000 раз, чередуя с отправкой пакетов на скрытый порт. Используем статистический тест (t-тест Стьюдента) для проверки гипотезы, что TTFB значимо увеличивается после пакетов на подозрительный порт.

Это уже не просто хак. Это наука.

Глава 11: Промышленный шпионаж на джиттере: Как я нашел бэкдор в сетевом оборудовании​

(Реальная история, слегка приукрашенная для драматизма)

Пару лет назад меня позвали разобраться с одной коробкой. Умный сетевой фильтр, китайского производства, стоял в банковской сети. По документации - он только логгирует трафик и режет P2P. Никаких удаленных интерфейсов управления. Ни порта 22, ни 80, ни 443. Вендор клялся, что устройство полностью пассивное и не имеет даже стека TCP/IP для управленческого трафика.

Но были странности: иногда в часы низкой нагрузки коробка генерировала микро-задержки на проходящих пакетах. Банк думал, что это проблемы с линией.

Я приехал на место. С собой - ноутбук с Intel I210, патченный драйвер для аппаратных таймстемпов, и терпение.

Методика:
Я пропустил через коробку тестовый поток UDP (чтобы минимизировать влияние TCP-стеков) и снимал таймстемпы отправки и приема через GPS-синхронизированные сетевухи. Разница во времени - это задержка, вносимая коробкой.

Затем я начал посылать на IP-адрес коробки (у нее был IP для обслуживания, хотя порты были закрыты) различные ICMP-пакеты. Никакого ответа. Но задержка на сквозном трафике начинала ритмично колебаться с частотой примерно 1 Гц. Я записал несколько минут такого поведения и построил спектрограмму.

Находка:
В спектре задержек четко виднелись пики на частотах, соответствующих последовательности нажатий клавиш. Кто-то (вероятно, вендор) встроил в прошивку скрытый telnet-сервер, который слушал определенную последовательность TCP SYN-пакетов на порт 12345. Но он не отвечал. Он просто активировал интерфейс отладки. Как я это понял? Я отправил пакет на порт 12345 и увидел, что задержка проходящего трафика резко упала (видимо, CPU переключился на обслуживание консоли и меньше обрабатывал основной поток). После отправки правильной последовательности (которую я подобрал эмпирически) задержка стала скачкообразно меняться.

Это был бэкдор. Невидимый для сканеров. Живущий только во временной области.

Мораль: Все, что имеет CPU и включено в сеть, может быть обнаружено. Даже если это «пассивный байпасс».

Глава 12: Защита от темпоральных атак (снифферский взгляд)​

Мы, как хакеры, обязаны предупредить: знание оружия не делает тебя убийцей. Оно делает тебя ответственным. Поэтому - глава для админов, которые хотят спрятать свои службы так, чтобы их не нашли даже с лазерным микрофоном.

Методы противодействия, от бесполезных к реальным:

1. Случайная задержка (Randomized Delay) - БЕСПОЛЕЗНО.

Добавить usleep(rand()%10000) перед дропом пакета. Почему бесполезно? Потому что мы не измеряем точную задержку одного пакета. Мы измеряем статистическое распределение. Если средняя задержка на порт-призрак будет 5 мкс, а на пустой порт - 0.1 мкс, добавление случайности только увеличит дисперсию, но среднее останется смещенным. Нам нужно именно среднее. Случайная задержка просто заставит нас послать больше проб.

2. Постоянная задержка (Constant Delay) - УМЕНЬШАЕТ ЭФФЕКТИВНОСТЬ.
Добавить фиксированную задержку, например 10 мс, на каждый дропнутый пакет. Тогда обработчик пакета на скрытом порте будет тратить столько же времени, сколько и пустой порт. Но ты убиваешь производительность сервера. Каждый мусорный пакет будет вешать CPU на 10 мс. Это DoS-вектор.

3. Сброс на уровне железа - ЕДИНСТВЕННОЕ РЕШЕНИЕ.
Использовать SmartNIC с возможностью загрузки BPF-программ прямо на сетевую карту (NVIDIA BlueField, Netronome, Intel FPGA). Правила фильтрации применяются до того, как пакет попадет в основную память. CPU даже не просыпается. Разницы во времени между пакетом, сброшенным на NIC, и пакетом, который даже не пришел (его просто не существует), - нет. С точки зрения времени ответа соседних потоков, все выглядит одинаково.

4. Изоляция CPU - ВРЕМЕННОЕ РЕШЕНИЕ.
Закрепить обработку сетевых прерываний за одними ядрами, а обработку скрытого сервиса - за другими. Это уменьшит интерференцию, но не устранит ее полностью (общий кэш, кольцевые шины). При достаточно точных измерениях (аппаратные таймстемпы) мы все равно заметим разницу в доступе к памяти.

5. eBPF/XDP - ИНТЕРЕСНО, НО...
Современные ядра Linux позволяют загружать программы XDP (eXpress Data Path) прямо в драйвер. Они могут дропать пакеты на скорости линии, до выделения sk_buff. В идеальном случае это приближается к аппаратному дропу. Но XDP-программа все же исполняется на CPU (хотя и с минимальным оверхедом). К тому же, вызов XDP-программы требует выгрузки инструкций в кэш. Это микроархитектурный след, который может быть обнаружен через другие каналы. Однако для 99% атак XDP - достаточная защита.

Пример XDP-программы для дропа всего трафика на порт 22:

C:
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>

SEC("xdp_drop_ssh")
int xdp_drop_ssh_prog(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end) return XDP_PASS;
    if (eth->h_proto != __constant_htons(ETH_P_IP)) return XDP_PASS;
    
    struct iphdr *ip = data + sizeof(*eth);
    if ((void *)(ip + 1) > data_end) return XDP_PASS;
    if (ip->protocol != IPPROTO_TCP) return XDP_PASS;
    
    struct tcphdr *tcp = (void *)ip + (ip->ihl * 4);
    if ((void *)(tcp + 1) > data_end) return XDP_PASS;
    if (tcp->dest == __constant_htons(22)) return XDP_DROP;
    
    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

Загружаем: ip link set dev eth0 xdp obj xdp_drop_ssh.o. Теперь SYN на 22-й порт даже не доходит до TCP-стека. Джиттер на HTTP-запросах? Измеряем. Есть разница? Минимальная, но есть (вызов XDP все же происходит). Если мы дополнительно загрузим XDP-программу для всех пакетов, то выравняем latency для всех портов. Тогда обнаружить службу станет очень сложно.

Глава 13: Форензика времени. Как понять, что тебя атакуют?​

Если ты админ и боишься, что кто-то «стучит» по твоим скрытым портам, анализируя задержки, есть ли способы это заметить?

Да, косвенно.
  1. Резкое увеличение числа ICMP-запросов. Атакующие часто используют ICMP как метроном. Мониторь количество ICMP Echo Request от одного источника. Если один IP шлет пинги с частотой 1000 Гц - это подозрительно.
  2. Аномально точные RTT. Если RTT от клиента практически не меняется (сверхмалый джиттер), значит клиент использует аппаратные таймстемпы и находится в очень хороших сетевых условиях. Это не норма для обычного пользователя.
  3. Паттерны зондирования. Если ты видишь последовательности TCP SYN на разные порты, идущие строго через равные промежутки времени (например, каждые 2 мс), возможно это синхронный измеритель.
  4. Корреляция с нагрузкой CPU. Сними perf-события. Если при приходе пакета на определенный порт (даже дропнутого) у тебя возникает всплеск CPU на ядре, обрабатывающем прерывания - значит пакет дошел до обработчика. Современные системы (bpftrace) позволяют это отследить.
bpftrace скрипт для отслеживания XDP дропов:

Код:
kprobe:xdp_do_redirect
{
    @xdp_drops[pid, comm] = count();
}
tracepoint:net:netif_receive_skb
{
    @rx_packets++;
}

Если увидишь, что пакет прошел через netif_receive_skb, но потом был сброшен XDP - это потенциальный признак атаки на определение правил фильтрации.

Глава 14: Квантовый скачок? Будущее временных атак​

С каждым годом сетевые карты становятся умнее, CPU быстрее, но требования к латентности растут. Финансовый HFT-трейдинг требует наносекундной точности. Именно оттуда к нам приходят самые острые инструменты.

Что нас ждет в будущем:
  • Атаки на оптические коммутаторы. Время переключения длин волн в DWDM-системах можно измерять и использовать для определения трафика.
  • Атаки на 5G UPF. User Plane Function в 5G обрабатывает пакеты с жесткими SLA. Скрытые каналы управления могут быть обнаружены по микро-задержкам в потоке данных.
  • Атаки на TEE (Trusted Execution Environments). Intel SGX, AMD SEV - пытаются изолировать код. Но их взаимодействие с сетевым стеком все равно происходит через незащищенную память. Временные атаки могут выявить, обрабатывает ли enclave сетевые пакеты (значит, там есть сервис).
Аппаратные ускорители:
NVIDIA DOCA, Intel IPU, AMD Pensando - эти устройства берут на себя сетевую обработку. Они программируются на высоком уровне. Мы, хакеры, тоже должны учиться программировать их. Если защитник перенес свою скрытую службу в P4-программу на коммутаторе Tofino, наша атака через CPU жертвы не сработает. Но мы можем атаковать сам коммутатор: измерить задержку прохождения тестового потока через него и выявить, что при получении пакета с определенным полем коммутатор выполняет дополнительную логику. Это сложнее, но возможно.

Глава 15: Сообщество. Наши люди.​

Я пишу это не ради хайпа. Я пишу, потому что мы - сообщество. Мы не верим вендорам на слово. Мы проверяем. Мы ковыряем. Мы делаем мир прозрачнее.

Сколько раз ты находил «скрытую» консоль на сетевом принтере, который по документации «не имеет сетевого интерфейса управления»? Сколько раз ты видел, как разработчики встраивают бэкдоры «для отладки» и забывают их выпилить? Наши методы - единственный способ проверить реальную безопасность.

Поэтому - делись опытом. Пиши свои инструменты. Выкладывай их на Github (с оговоркой «только для educational purposes»). Пусть вендоры знают: если они встраивают черный ход, мы найдем его, даже если он не светится в netstat.

Глава 16: Терминальная одержимость. Эксперимент с лазером и вибрацией.​

Я обещал железо. Выполняю.

Помнишь, я говорил про звук конденсаторов? Это не шутка. В 2025 году, когда эта статья будет дописана (а я пишу ее в 2026, но эффект путешествия во времени нас не смущает), уже существуют методы перехвата данных через анализ вибрации сетевых кабелей с помощью лазерных дальномеров.

Но есть способ проще:
Современные SFP+ модули имеют цифровую диагностику (DOM - Digital Optical Monitoring). Они передают температуру, напряжение лазера, мощность сигнала. Эти параметры доступны через i2c. Если у тебя есть локальный доступ к коммутатору, ты можешь читать их.

Теперь представь: на другом конце оптоволокна висит сервер со скрытым SSH. Когда на сервер приходит пакет, его процессор потребляет больше энергии. Блок питания дергается, меняется нагрузка на линии электропитания. Это влияет на работу лазера SFP+ модуля - меняется его температура, мощность. И все это отображается в DOM твоего коммутатора.

Ты сидишь в своей подсети, имеешь доступ к SNMP коммутатора, и можешь по флуктуациям оптической мощности определить, обрабатывает ли сервер в данный момент что-то особенное.

Это уже не хак, это магия. Но она работает.

1770921269134.webp


Вернись к самому началу, к тому сейфу со стетоскопом. Знаешь, почему этот образ так въедается в подкорку? Потому что он про честность. Механика не врёт. Если сувальда щёлкнула - значит, она щёлкнула. Если сетевой стек споткнулся на полмикросекунды - значит, там было, обо что споткнуться. В мире, где вендоры рисуют красивые слайды с «полным спектром защиты», где regulatory compliance подменяет реальную безопасность, где CISO покупают очередной NGFW и верят, что теперь они неуязвимы, - временные атаки остаются последним полигоном для честных измерений. Мы не верим словам. Мы верим таймстемпам.

Смена парадигмы: от сигнатур к статистике

Инфосек застрял в сигнатурном мышлении. «Найди известный вредонос - вылечи». IDS/IPS ищут строку «cmd.exe» или определённую длину пакета. Но время - это аналоговая величина. Его нельзя описать простой строкой. Поэтому индустрия до сих пор игнорирует эту плоскость атаки. И это наше преимущество.

Я часто слышу от коллег: «Ну, это ж микросекунды, кто их там меряет?». А мы меряем. И не потому, что мы такие крутые, а потому что современное железо даёт нам эту возможность за 50 баксов. Сетевуха Intel I210 за 2 тысячи рублей на вторичке - и ты уже имеешь аппаратные таймстемпы с точностью 10-20 нс. Прибавь к этому открытый софт - DPDK, netmap, eBPF - и ты получаешь осциллограф для цифрового мира. Аналоговый век цифровых технологий.

Послесловие: маленький бонус, который не влез в основную часть

Я обещал железо. Вот тебе конкретный, проверенный рецепт, как поднять аппаратные таймстемпы на Linux с минимумом танцев с бубном. Это для тех, кто дочитал до конца и хочет не просто теории, а практики.

Берём любой современный Intel NIC (e1000e, igb, ixgbe). Убеждаемся, что драйвер загружен с параметром debug=1 (иногда нужен для включения timestamping). Далее, используя ethtool, включаем поддержку:

Bash:
ethtool -T eth0
# Смотрим, поддерживает ли карта hardware-transmit и hardware-receive

Если видишь hardware-transmit: on, значит, всё есть.

Теперь программа на C (или Rust). Используем recvmsg с флагом SO_TIMESTAMPING. Вот выжимка кода для захвата времени прихода пакета с точностью до наносекунд от PHY:

C:
#include <linux/net_tstamp.h>
#include <sys/socket.h>
#include <linux/if_packet.h>

int sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
int timestamp_flags = SOF_TIMESTAMPING_RX_HARDWARE |
                      SOF_TIMESTAMPING_RAW_HARDWARE |
                      SOF_TIMESTAMPING_SOFTWARE;
setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING, &timestamp_flags, sizeof(timestamp_flags));

char data[2048];
struct iovec iov = { .iov_base = data, .iov_len = sizeof(data) };
struct msghdr msg;
struct sockaddr_ll from;
char ctrl[256];

msg.msg_name = &from;
msg.msg_namelen = sizeof(from);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = ctrl;
msg.msg_controllen = sizeof(ctrl);

recvmsg(sock, &msg, 0);

struct cmsghdr *cmsg;
struct timespec *ts = NULL;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
    if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMPING) {
        struct scm_timestamping *tss = (struct scm_timestamping *)CMSG_DATA(cmsg);
        ts = &tss->ts[0]; // hardware timestamp, если доступен
        break;
    }
}
if (ts) {
    printf("Пакет принят в %ld.%09ld\n", ts->tv_sec, ts->tv_nsec);
}

Собери, запусти с sudo. Ты будешь получать время прихода каждого пакета с точностью, которую даёт твоя сетевая карта. На Intel I210 это ~20 нс, на X550 - ещё лучше. Подавай на вход такой программы пакеты, которые прошли через твою целевую систему, и ты сможешь строить графики задержек с точностью, достаточной для обнаружения большинства скрытых обработчиков.

Финальный аккорд

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

Я не знаю, встретимся ли мы с тобой на реальном проекте, в одной команде Red Team, или будем по разные стороны баррикад Blue Team. Но я знаю точно: пока есть такие, как ты, готовый копать вглубь, а не вширь, - у нашего ремесла есть будущее.

Не верь тишине. Верь измерениям.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab