Free Porn Storage: передаём мемы в TLS-трафике, не привлекая внимания санитаров

от автора

Если нужно спрятать дерево, прячьте его в лесу. Лес интернета состоит из TLS-соединений (>70% трафика, если верить статистике): вкладок браузера, WebSocket-потоков, долгоживущих HTTPS-запросов, клиентов игр, мобильных приложений, телеметрии, видеоплееров, чатов и тысяч спокойных TCP/443-соединений, которые выглядят настолько обычно, что именно это и делает их интересной средой для, кхм, исследования.

Очевидно, единственная и конечная цель существования интернета — быть распределённым хранилищем порно, так почему бы не использовать его по прямому назначению и не хранить смешные картинки прямо в трафике?

Тем не менее, как ни странно, Free Porn Storage, или FPS, о котором пойдёт речь в статье — не о порно (а жаль); так сказать, удачи с поиском. FPS — это экспериментальный фреймворк для построения скрытого L3 TUN-туннеля внутри живых TLS-сессий.

В статье предлагается разбор одной архитектурной идеи (или семейства идей) в гонке фильтрации и, скажем, “расширения доступа”: если фильтрующие системы становятся достаточно хороши в распознавании протоколов, которые только притворяются TLS, можно ли начать с настоящей TLS-сессии обычного приложения, выполнить отложенную аутентификацию и уже после этого спрятать канал с полезной нагрузкой внутри байтового потока TLS?

Оглавление

Лестница фильтрации

(Довольно бесполезный раздел в текущих реалиях, ибо всё это давно известно читателю, но пусть будет.) Системы фильтрации почти всегда начинают с простых мер, потому что простые меры дешевле, понятнее в эксплуатации и, как ни странно, работают, и дают приемлемый результат. DNS можно исказить/подменить. Блокировка IP и CIDR грубее и даёт больше побочных эффектов, зато работает до того, как прикладной протокол вообще успел сказать хоть что-то. Политика по протоколам и портам поднимает стоимость ещё на одну ступень: TCP/443 может проходить, а UDP/443, UDP/53, типичный UDP-трафик VPN или на неизвестные порты — отваливаться.

Следующие уровни уже смотрят на поведение приложения. Пока Encrypted ClientHello (ECH) не стал повсеместной реальностью, TLS ClientHello способен раскрыть имя хоста через SNI RFC 6066, поэтому промежуточный фильтр может разрешать IP-адрес, но рвать соединение с выбранными доменами. За пределами SNI остаётся ещё больше сигналов: порядок наборов шифров, расширения TLS, ALPN, настройки HTTP/2, размеры первых записей, тайминги рукопожатия, TCP-поведение и реакция сервера (см. JA3/JA4). Если соединение утверждает, что оно браузерное, но ведёт себя как от маленькой утилиты на Go с необычным набором TLS расширений — выглядит грешновато.

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

Лестница фильтрации трафика

Лестница фильтрации трафика

Для инструментов расширения доступа это важно, потому что многие из них на момент создания проектировались под более ранние слои этой лестницы. Сначала было достаточно зашифровать трафик. Потом стало важно, чтобы он был похож на TLS. Затем понадобился fallback на реальный HTTPS-сайт под active probing, и браузероподобные отпечатки TLS-клиента. Следующая комплексная задача — не быть классифицированным в начале соединения, потому что именно начало соединения даёт фильтрующим системам самый структурированный и дешёвый для анализа материал.

Что видно в TLS на уровне байтового потока

TLS удобно делить на две большие части: протокол рукопожатия и протокол записей. Рукопожатие аутентифицирует стороны, согласует параметры и приводит их к общим секретам. Протокол записей режет дальнейший трафик на записи с небольшими видимыми заголовками и непрозрачной полезной нагрузкой. В TLS 1.3 после рукопожатия прикладные данные идут в записях с внешним типом содержимого application_data, значение 23 RFC 8446. Это немного упрощенная модель, но достаточная для понимания.

Для промежуточного фильтра на пути трафика открытие TLS-соединения особенно полезно, потому что оно структурировано. ClientHello содержит поля, расширения и порядок этих расширений; часто содержит SNI и ALPN; его можно сравнивать с известными реализациями браузеров и библиотек; даже после начала шифрования размеры и тайминги первых нескольких раундтрипов часто сохраняют узнаваемый рисунок. Поэтому «мимикрия под TLS» хрупка: можно слушать 443-й порт и всё равно палиться в деталях.

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

Мимикрируем

Один из ранних примеров сетевой стеганографии в контексте компьютерных сетей — работа Manfred Wolf «Covert Channels in LAN Protocols» из материалов LANSEC ’89, где скрытый канал рассматривался внутри поведения LAN-протоколов. Современная экосистема практических средств расширения доступа намного шире, но базовый вопрос тот же: где можно спрятать канал связи так, чтобы не нарушить статистические и семантические ожидания протокола-носителя?

Trojan работает внутри TLS и использует fallback к настоящему хосту для неаутентифицированных клиентов, что делает его относительно простым для использования и идейно понятным. Xray/VLESS/XTLS Vision и связанные конфигурации дают зрелую прокси-экосистему с идентификацией по UUID, TLS-подобным поведением, выравниванием пакетов и fallback-механикой. NaïveProxy идёт прагматичным путём и использует настоящий сетевой стек Chromium вместо ручной имитации браузерного отпечатка. Cloak использует стеганографию и шифрование вокруг HTTPS-подобного трафика, Zero-RTT-аутентификацию, fallback и формИрование (shaping/шейпинг, то есть дробление/агрегирование и изменение профиля таймингов) трафика. Наконец, Balboa особенно интересен как исследовательская система, которая туннелирует данные работающих приложений, переписывая трафик, совпадающий с заранее согласованной моделью.

Экспроприируем

FPS можно считать идейным развитием упомянутых протоколов и фреймворков. Но вместо того, чтобы сразу после старта (или непосредственно вместо старта) TCP/TLS проводить аутентификацию и определение “свой-чужой”, FPS сначала прозрачно проксирует настоящую TLS-сессию браузера или другого приложения. Носителем (carrier) может быть долгоживущее HTTPS-соединение (keep-alive), WebSocket, долгий опрос (long polling), игровой клиент или любой другой достаточно живой TLS-поток.

Только после того, как настоящие TLS-записи носителя уже появились, FPS пытается выполнить позднее Zero-RTT-переключение (upgrade) внутри правдоподобной записи TLS Application Data. Попытка привязана к ближайшему контексту сессии-носителя, включая хэш предыдущей TLS-записи. Если переключение не распознано, запись считается обычным трафиком-носителем и проксируется дальше. Если переключение успешно, обе стороны переходят в режим зашифрованных конвертов (FPS envelops): на проводе всё ещё видны записи TLS Application Data, но их полезная нагрузка содержит зашифрованные, понятные только клиенту и серверу FPS, данные.

Поток TLS-записей после FPS-переключения

Хаха, даже тут уже видно, что фреймы FPS жирненькие

Практическое следствие — фильтрующей системе уже недостаточно посмотреть только на первые пакеты, рукопожатие и реакцию на активное зондирование. Для обобщённого обнаружения FPS нужно продолжать анализировать L4-поток и границы TLS-записей после того, как реальная TLS-сессия уже началась, а это дороже, шумнее и, теоретически, даёт больше ложных срабатываний. В предельном варианте, при хорошем шейпинге трафика, выравнивании размеров и выборе потоков-носителей, FPS стремится к неотличимости от обычного прикладного TLS-трафика. Текущая бета этого не обещает, но архитектура построена именно в этом направлении.

Архитектура реализации

На данный момент в Linux-сборке FPS есть два приложения-демона:

  • fps_client, который работает рядом с пользователем;

  • fps_server, который работает рядом с операторским сервером/origin.

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

На участке fps_client <-> fps_server внешний наблюдатель должен видеть TCP-потоки, состоящие из записей TLS Application Data. Реальная TLS-сессия браузера и сервера-источника не терминируется FPS. До переключения FPS работает как прозрачный ретранслятор. После переключения реальные TLS-байты упаковываются в зашифрованные FPS-конверты и восстанавливаются перед тем, как попасть к настоящим TLS-узлам.

Архитектура FPS

Архитектура FPS

Сервер ответственен за адресацию. Клиент представлен одним секретным UUID, сервер держит UUID в списке разрешённых клиентов, назначает IPv4-адрес после аутентификации и отбрасывает TUN-пакеты клиента, если исходный IPv4-адрес не совпадает с выданным адресом. Несколько сессий-носителей одного клиентского процесса могут использоваться как общий пул носителей. Если другой активный процесс использует тот же UUID, новая сессия заменяет старую: шарить UUID между устройствами в текущей модели нельзя (пока).

Текущая реализация ориентирована на Linux и Docker. Android-клиента пока нет, но ядро кода разделено так, чтобы протокол, логика конвертов/сессий и упаковка TUN-пакетов в будущем могли работать за Android VpnService, например, а не за Linux TUN и ip.

Сердце FPS написано на C++20: он используется для двух основных демонов, fps_client и fps_server, а также для общего набора библиотек для криптографии, кодирования кадров, разбора TLS-записей, работы с TUN-интерфейсом, выдачи адресов и управления сессиями-носителями. Проект в основном опирается на Boost: Asio используется для сетевого ввода-вывода и таймеров, Boost.JSON — для чтения конфигурации, Boost.Log — через тонкую проектную обёртку для структурированных журналов, Boost.Program_options — для параметров командной строки, Boost.Test — для модульных тестов. Одним из архитектурных решений как раз является минимизация внешних необоснованных зависимостей, с чем проект, на мой взгляд, успешно справляется.

Размещение сервера перед reverse proxy

В чистой схеме self-hosted fps_server является публичным входным шлюзом перед следующими хопами к серверу-источнику. Он не терминирует TLS-сессию носителя; его роль ближе к предварительному обратному прокси или TCP-шлюзу.

Цепочка может выглядеть так:

Интернет -> fps_server -> L4/TCP-прокси или SNI-маршрутизатор -> обратный прокси <- приложение

Настоящий обратный прокси терминирует TLS и общается с бэкендом уже после FPS. Это принципиально: FPS должен видеть и сохранять байтовый поток TLS-сессии носителя. Обычная CDN-терминация или обратный прокси перед fps_server меняют модель, потому что сквозной байтовый поток носителя исчезает. FPS может работать в связках, где есть L4/TCP-проксирование, сквозной TLS (TLS passthrough) или SNI-маршрутизация, но, увы, это не «поставил за любой CDN и забыл».

Технические детали FPS

Поздняя аутентификация

FPS не обязан устанавливать соединение с первого байта carrier-сессии. Текущая конструкция использует X25519, HKDF-SHA256 и ChaCha20-Poly1305 через OpenSSL. Клиентский UUID детерминированно порождает внутреннюю пару ключей X25519; сервер имеет статическую пару ключей и список разрешённых клиентских UUID.

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

Есть зафиксированная проблема для следующих итераций исправлений: текущий Zero-RTT-кандидат (сообщение рукопожатия от клиента серверу) начинается с 32-байтного значения, похожего на открытый ключ, в фиксированной позиции. Оно задумано как сеансовое и одноразовое, но видимая структура повторяющегося блока сама по себе является более простой целью для классификатора, чем тайминговый анализ. Следующая версия должна заменить это другим механизмом, который так же позволит серверу быстро опознать вероятного клиента, не оставляя стабильного открытого идентификатора в начале полезной нагрузки.

Пул носителей

Любая успешно аутентифицированная TLS-сессия может переносить TUN-кадры. В FPS нет понятия единственной главной сессии, поэтому несколько вкладок браузера или прикладных потоков могут стать общим пулом носителей. Это полезно для пропускной способности, переподключения, будущего шейпинга трафика, и вообще делает FPS ближе к фреймворку, а не к одному фиксированному прокси-протоколу.

L3 TUN, а не только SOCKS

FPS переносит IP-пакеты. Поверх него можно поднять SOCKS для удобства — в документации есть пример с SOCKS-прокси Dante как надстройкой, — но сам скрытый канал является L3-туннелем. Это сложнее, чем обычный прокси, зато область применения шире: можно форвардить разные протоколы, не ограничиваясь только теми приложениями, которые умеют в SOCKS.

Пространство для шейпинга трафика

В бете уже есть фрагментация TUN-пакетов и точки расширения для выравнивания внутри конвертов. Будущий слой формирования трафика сможет решать, сколько скрытых байт добавлять в носитель, как резать пакеты, как ограничивать скрытый трафик и как распределять нагрузку между сессиями-носителями.

Семейство протоколов, а не только TLS

TLS выбран первым носителем как один из самых распространённых протоколов. Но сама идея шире: обычные зашифрованные прикладные потоки могут становиться средой-носителем. Похожим образом можно оседлать SSH, WebRTC, протоколы совместной работы, QUIC-подобные прикладные потоки и другие долгоживущие зашифрованные соединения.

FPS-каналы также можно наслаивать друг на друга. В принципе, один FPS-канал может нести трафик, который позже становится носителем для другого FPS-канала, если порядок оборачивания спланирован аккуратно, например C1 -> C2 -> S2 -> S1 для двух вложенных пар клиент/сервер. Это не протестированная пользовательская фича, но хороший способ думать о FPS как о семействе компонуемых протоколов-носителей.

Цена подхода

У FPS есть важные ограничения, и часть из них фундаментальна. Нужны живые сессии-носители: если клиент получил TUN-адрес, но все потоки-носители закрылись, VPN-трафик остановится. Как сказано выше, использование классических схем CDN и терминированием TLS несовместимо с FPS. UX пока сыроват: оператор имеет дело с Docker, JSON-конфигами, UUID, /etc/hosts, TUN, сетевыми правами контейнера, статус-сокетами и живучестью сессий-носителей.

Мобильного “однокнопочного” клиента, к сожалению, пока нет, но потенциально он реализуем. Наконец, FPS не является анонимайзером: он не заменяет Tor, не обещает глобальную невозможность связывать действия пользователя между собой, и должен рассматриваться как фреймворк скрытого транспорта/туннеля.

Сравнение с альтернативами

Trojan

Trojan работает внутри TLS и использует резервный fallback для неаутентифицированного трафика. FPS отличается поздним переключением внутри живого байтового потока носителя и тем, что после аутентификации продолжает переносить реальные TLS-байты носителя внутри конвертов вместе со скрытыми кадрами.

Xray / VLESS / XTLS Vision

VLESS использует идентификацию через UUID, а конфигурации, ориентированные на Vision/REALITY, являются частью зрелой прокси-экосистемы. FPS позаимствовал у них UX-урок использования UUID, потому что выдать клиенту один UUID проще, чем настраивать систему публичных/приватных ключей на обоих концах. Технически ставка XTLS/Vision — это прокси-поток с минимизацией лишних прослоек, копирования и характерных следов TLS-in-TLS, где клиентская аутентификация всё равно относится к началу прокси-сценария. FPS решает другую задачу: он не строит браузероподобный старт соединения сам, а сначала пропускает реальную TLS-сессию, поздно переключает уже существующий поток и переносит не прокси-запросы, а L3 TUN-кадры через пул сессий-носителей.

NaïveProxy

Сильная идея NaïveProxy — использовать сетевой стек Chromium. Это практичный способ наследовать поведение “похожее на браузер” целиком. FPS не пытается быть Chrome; он, опять-таки, пытается использовать настоящие TLS-сессии Chrome или других приложений как носители.

Cloak

Cloak концептуально близок: Zero-RTT-аутентификация, резервное поведение для посторонних клиентов, стеганографическая упаковка и идеи формирования трафика важны и там. Главное отличие FPS — в модели поздней аутентификации.

Balboa

Balboa, вероятно, самый близкий исследовательский родственник по духу. Это фреймворк обфускации соединения, который туннелирует данные через существующие приложения, перехватывая исходящий трафик и переписывая совпадающие с моделью фрагменты. Для TLS его цель — сделать поведение приложения эквивалентным приложению без Balboa, за вычетом небольших тайминговых различий. FPS отличается моделью развёртывания и формой протокола. В каком-то смысле, конверты FPS проще и для реализации, но и для детектирования, так как в соединении есть чёткий момент апгрейда/аутентификации. Balboa же использует потоковую модель, вшивая полезную нагрузку напрямую в TLS-записи соединения-носителя (но, разумеется, точно так же, как и FPS, “ломая” TLS, делая необходимым наличие серверной части, восстанавливающей TLS-поток носителя).

Tor pluggable transports

Tor pluggable transports преобразуют трафик Tor, меняя его форму (видимый снаружи протокол): одни транспорты убирают узнаваемую структуру и сопротивляются активному пробированию, другие прячут соединение в HTTPS- или WebSocket-подобных потоках. FPS не является транспортом Tor и не даёт анонимности Tor, но так же использует мимикрию.

Shadowsocks

Shadowsocks — семейство защищённых прокси, где современные режимы AEAD делают поток похожим на случайные байты. FPS выбирает другую цель маскировки: не просто случайные байты, а записи TLS Application Data.

Как проект разрабатывался

Да, FPS сделан с активным использованием ИИ. И, знаете ли, это был интересный архитектурный вызов: ИИ резко удешевляет генерацию кода, одновременно заставляя сосредотачиваться на тактических и стратегических ревью и планировании.

В проекте есть актуальная спецификация протокола, журнал работ с решениями и ошибками, AGENTS.md* с правилами для будущих агентов, юнит- и интеграционные тесты, симуляции Docker, прогоны санитайзеров/Valgrind, проверки покрытия, короткие smoke-тесты libFuzzer и CI по GCC, clang, Ubuntu и Alpine-образам Docker.

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

Планы

Ближайшая цель FPS — дорасти до стадии твёрдой беты, и набрать мясца для более-менее удобного использования. В практическом плане это означает независимое ревью протокола, повторяемые тесты устойчивости, более строгий процесс релизов, подписанные образы, улучшение операторской диагностики, исправление видимого Zero-RTT-префикса, проектирование Android-клиента через VpnService, реалистичные эксперименты по формированию трафика и улучшения UX.

Время тестов

Если проект вызвал интерес, мне было бы приятно получить обратную связь. Да, после ревью я с большой вероятностью закину её в горнило ИИ на реализацию, ибо современные проблемы требуют современных решений.

Ломайте FPS, заводите задачи и замечания. Гоните его, насмехайтесь над ним. Снимайте pcap. Пытайтесь классифицировать потоки по сетевым характеристикам. Проводите ревью протокола. Ищите UX-шероховатости. Пишите, где протокол наивен, где реализация переусложнена, а где документация недоговаривает. Именно так подобный проект может стать полезным. Ну, или хотя бы вдохновить, и подкинуть новых идей — сети чуть проще, чем кажутся, а не все роутеры одинаково полезны.

Дисклеймер

FPS создан как экспериментальный исследовательский и инженерный проект, а не как инструмент для вреда сетям, людям и нелюдям. Его цель — исследовать современные сетевые архитектуры, маскировку протоколов, разработку с активным использованием ИИ и плотными тестами, а также практические пределы построения скрытых каналов связи поверх распределённой инфраструктуры.

Используйте и тестируйте его только там, где у вас есть на это право. Не злоупотребляйте чужими сервисами как серверами-носителями. Если разворачиваете FPS, используйте собственную инфраструктуру.

Выражаю благодарность Екб за лучшие завтраки, и Джону за отличную коллекцию бесплатного порно.

GitHub

https://github.com/mrcatnapper/fps

Ссылки

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