todo/mtproxy/ja4-sni-client-side.md
loop-uh 0ad0bcc8dd
Some checks failed
Published content check / validate (push) Failing after 5s
seo+зеркало: [!mirror]-плашка со ссылкой на вики и description во frontmatter всех заметок
Каждая публикуемая заметка получила callout-шапку со ссылкой на свою страницу
wiki.zapret.moe (на самой вики она вырезается транформером RemoveMirrorCallout,
видна только на зеркале Obsidian Publish и в Forgejo) и SEO-поле description —
1–2 предложения для meta description обоих сайтов.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 22:05:57 +03:00

26 KiB
Raw Permalink Blame History

date tags aliases link description
2026-06-07
mtproto
mtproxy
dpi
tspu
ja4
sni
faketls
Кто может менять JA4 и SNI
Почему обход MTProxy клиентский
JA4/SNI client-side
Детекция MTProto июнь 2026
https://gist.github.com/Flowseal/de630dd9d9ddaa86cc6bed9b473fae0c Почему смена JA4 и ротация SNI против детекции MTProxy работают только на стороне клиента Telegram, а серверный прокси изменить их не может.

[!mirror] Резервное зеркало Актуальная версия этой страницы — на основной вики: wiki.zapret.moe/mtproxy/ja4-sni-client-side

🪪 Кто может менять JA4/SNI и почему обход MTProxy — клиентский

[!info] О чём заметка Разбор архитектурного факта, который определяет всю борьбу с новой детекцией MTProto: TLS-почерк (JA4) и имя домена (SNI) задаёт клиент Telegram, а не сервер-прокси. Поэтому чистые способы обхода (смена JA4, ротация SNI) работают только на стороне клиента, до цензора. Серверные прокси (mtproto.zig, telemt, teleproxy) изменить их физически не могут.

[!warning] Статус данных Параметры детекции — наблюдения сообщества (реверс-инжиниринг, июнь 2026), а не опубликованная спецификация ТСПУ. Числа и условия читай как «по наблюдениям», у разных операторов они могут отличаться. Архитектурный вывод (ClientHello генерит клиент) — это свойство самого TLS, оно не зависит от наблюдений.


Термины (чтобы заметка читалась без контекста)

  • MTProxy / FakeTLS — прокси для Telegram, маскирующий трафик под обычный HTTPS: первый пакет выглядит как TLS-рукопожатие к популярному сайту.
  • ClientHello — самый первый, ещё не зашифрованный пакет TLS-рукопожатия, который шлёт клиент. В нём открытым текстом: список шифров, расширения и SNI (имя домена, к которому якобы идёт подключение).
  • JA4 — отпечаток ClientHello. Формат t13d1516h2_…_…: первая часть — метаданные (t=TLS-over-TCP, 13=TLS 1.3, d=есть SNI, 15=число шифров, 16=число расширений, h2=ALPN) — она не хешируется; а две следующие — хеши шифров и расширений, отсортированных перед хешированием (GREASE при этом выкидывается). Сортировка делает JA4 устойчивым к перетасовке порядка, в отличие от старого JA3. По отпечатку DPI понимает, какая программа сгенерировала пакет.
  • SNI (Server Name Indication) — поле в ClientHello с именем домена.
  • ТСПУ — Технические Средства Противодействия Угрозам, DPI-оборудование у российских операторов.

TL;DR

  1. Новая детекция (примерно с начала июня 2026, по сообщениям сообщества) блокирует соединение, когда совпадают три условия: почерк JA4 Telegram + один и тот же SNI + несколько ClientHello одновременно на один ip:port. Слом любого одного условия — обход.
  2. JA4 и SNI лежат в ClientHello, а его шлёт клиент Telegram. Цензор видит этот пакет по пути client→server. Серверный прокси получает его уже после цензора — изменить не может.
  3. Поэтому смена JA4 и ротация SNI делаются только клиентским relay (локально, до цензора) — как в тестовом relay Flowseal.
  4. Серверный прокси (mtproto.zig/telemt/teleproxy) может бить только по третьему условию — «залп на один ip:port» (pacing, разные порты/IP).
  5. На Zig это реализуемо — но как отдельный клиентский инструмент, не как серверный бинарь mtproto.zig.

Сама детекция (наблюдения, июнь 2026)

Блокировка включается, когда одновременно:

  1. ClientHello несёт JA4 Telegramt13d1516h2_8daaf6152771_d8a2da3f94cd (мимикрия под Chrome 134/macOS; этот почерк протух, разбор #30733 — в Zapret/mtproto/10-telemt-logs-dpi);
  2. у нескольких соединений один и тот же SNI;
  3. они идут залпом на один ip:port (несколько ClientHello почти одновременно).

Это логическое «И» — сломай любое условие, и правило не срабатывает.

Что помогает (по тестам сообщества):

  • смена JA4 (почерк перестаёт совпадать с известным Telegram);
  • ротация SNI (нет «одинакового SNI» в залпе).

[!warning] SNI лучше брать резолвящийся (гипотеза по аналогии с REALITY) По наблюдениям для VLESS+REALITY цензор проверяет A-запись SNI (резолвится ли домен). Применяется ли такая же проверка к MTProto-FakeTLS — отдельно не подтверждено, так что это осторожная рекомендация, а не факт. Безопаснее ротировать по списку реальных резолвящихся доменов. Тонкость про случайные сабдомены: сабдомен резолвится, только если базовый домен имеет wildcard A-запись. В relay Flowseal режим --unique-sni клеит рандом к --sni-base; по умолчанию база — www.cloudflare.com (конкретный хост, не wildcard-зона), поэтому <hex>.www.cloudflare.com не резолвится — автор позиционирует дефолт как строгий тест «каждый SNI различается», а не как резолвящийся вариант. Чтобы сабдомены резолвились, нужно дать --sni-base свой домен с wildcard (это прямо предусмотрено в gist). Если проверка A-записи к MTProto всё же применяется, дефолтный --unique-sni — как раз потенциально рискованный режим.


Это та же «И трёх условий», что и «сибирская» схема для VLESS

Детект MTProto — не отдельное изобретение, а частный случай той же философии ТСПУ, что описана для VLESS+REALITY в VLESS/dpi-tls-june-2026 (первоисточник — статья @hyperion_cs на Хабре).

[!example] На пальцах Цензор перестал «открывать чемоданы» (вскрывать шифр — бесполезно, крипта цела) и начал смотреть на манеру пассажира: откуда приехал, во что одет, как себя ведёт. Блокировка включается, только когда совпали несколько признаков сразу (логическое «И»). Сломай любой один — правило не сработает. Это верно и для VLESS, и для MTProto — меняются лишь конкретные «признаки».

Отображение сигналов одной схемы на другую:

«Сибирская» схема (VLESS+REALITY) Детект MTProto (июнь 2026)
Подсеть сервера в списке подозрительных подсеть так же в игре (зарубежные ДЦ тоже в списке)
Фингерпринт uTLS (массовый chrome) JA4 Telegram t13d1516h2_8daaf6152771_… (мимикрия под Chrome 134)
Частота к одному SNI (>3 конн. <~350-400 мс / 60 с) залп ClientHello на один ip:port
Ключ агрегации частоты — SNI одинаковый SNI в залпе

[!note] Про «подсеть» в строке выше В первоисточнике сигнал подсети одинаков и для VLESS, и (по экстраполяции) для MTProxy: в список подозрительных попали и российские ДЦ (в статье поимённо — Selectel, Я.Облако, Cloud.ru), и зарубежные провайдеры (в статье — обобщённо «методы затрагивали только зарубежных»; по общему знанию это Hetzner/DigitalOcean/OVH, флагнуты ещё раньше). То есть «арендовать VPS за рубежом» само по себе подсеть не ослабляет — ослабляет лишь попадание в нефлагнутую подсеть (редкий чистый провайдер, residential, CDN) либо малый трафик. (Сам MTProxy в статье @hyperion_cs не разбирается — перенос на него сделан здесь по аналогии.)

В обоих случаях это «И»: сломай одно звено — обход. Поэтому серверные меры (pacing, разные порты/домены) бьют по «частоте/залпу», а чистый слом «фингерпринта» (JA4) остаётся #Почему JA4/SNI меняются только на стороне клиента.

[!important] Протухший пресет = сам по себе аномалия Развивая логику фингерпринта (тезис из VLESS/dpi-tls-june-2026, по данным Cloudflare Radar — не из статьи @hyperion_cs, там фингерпринты делятся по марке браузера): важна не только марка, но и свежесть пресета. К концу 2025 больше половины «человеческих» TLS-соединений несут post-quantum key_share (X25519MLKEM768) — он стал дефолтом в Chrome 131 и Firefox 132. Пресет без него, выдающий себя за свежий браузер, аномален сам по себе. Это можно трактовать как частный случай беды Telegram из #30733: клиент шлёт почерк протухшего Chrome 134/macOS. (В самом #30733 проблема описана как устаревший отпечаток в целом, без явного указания на отсутствие ML-KEM.) Лечится только обновлением почерка в клиенте (Zapret/mtproto/10-telemt-logs-dpi).

[!warning] «Ловушка для тех, кто дёргается» В «сибирской» схеме эскалация на ~600 с — это второй шаг, а не реакция на любую правку: сначала надо уже поймать первичную заморозку на 120 с (>3 параллельных конн. <~350-400 мс к одному SNI за 60 с), и только потом, если под этой заморозкой сменить фингерпринт, прилетает +600 с на весь TLS к узлу (независимо от почерка и SNI). Чистый перезапуск без залпа этого не вызывает. Вероятно, та же платформа ТСПУ обслуживает и MTProto, так что нервно крутить настройки под уже действующей блокировкой вредно: лучше переждать окно или сменить узел. (Точные тайминги и применимость к MTProto отдельно не подтверждены — это перенос логики со смежной схемы.)


Почему JA4/SNI меняются только на стороне клиента

[!example] На пальцах ClientHello — это визитка, которую показывает сам гость (приложение Telegram) на входе. Охранник (цензор ТСПУ) рассматривает визитку по дороге. Сервер-вышибала (прокси) получает гостя уже после охранника — переклеить визитку он не может, её давно увидели.

Telegram → [генерит ClientHello: JA4+SNI] → 👁 ЦЕНЗОР видит почерк → СЕРВЕР-прокси
                                                                         ▲
                                              сюда пакет приходит уже после цензора

ClientHello — первый пакет рукопожатия, клиент строит и отправляет его до любого ответа сервера. Значит JA4 и SNI определены приложением Telegram.

Серверный прокси не может задним числом переписать уже ушедший пакет.

Единственный способ изменить то, что видит цензор, — встать между Telegram и цензором, то есть на устройстве пользователя (локальный relay) или в его локальной сети. Именно поэтому тестовый relay из gist — локальный: Telegramконнектится к нему на localhost, relay строит свой свежий ClientHello (с ротацией SNI и новым JA4) и уже его отправляет наружу.

Действие Где возможно Серверный прокси
Сменить JA4 ClientHello только client-side (до цензора) невозможно
Ротировать SNI на каждое соединение только client-side (SNI зашит в ссылке рядом с ee-секретом = tls_domain прокси, не выводится из секрета) один неизменный tls_domain
Сломать «залп на один ip:port» можно и на сервере ⚠️ частично

Что МОЖЕТ серверный прокси против этой детекции

Только третье условие — «несколько ClientHello одновременно на один ip:port»:

  • Лимит SYN-ACK / pacing — растягивает «залп» по времени (разбор и per-port вариант — в Zapret/mtproto/10-telemt-logs-dpi).
  • Разные порты / IPv6-hopping — размывает «один ip:port».
  • Разные домены разным юзерам — размывает «один SNI» между пользователями (но это не ротация на одного юзера — у каждого ee-секрет фиксирует один SNI).

Это частичные меры: детект — «И» трёх условий, так что слом даже одного помогает.

Но чистый слом (JA4/SNI) серверу недоступен.


Можно ли на Zig

Да — но как отдельный клиентский relay, а не как серверный mtproto.zig. Zig для этого подходит: статический бинарь, лёгкая кросс-компиляция под Windows/Linux.

Такой relay должен:

  • слушать localhost, принимать соединение от клиента Telegram;
  • на каждое соединение строить свежий FakeTLS-ClientHello: GREASE, ML-KEM key_share (post-quantum — заодно лечит протухший почерк #30733), тасовка расширений, HMAC секрета в client_random;
  • ротировать SNI из списка реальных доменов (см. оговорку про A-запись выше);
  • форвардить на апстрим-MTProxy.

[!note] Переиспользуемый код в mtproto.zig В проекте mtproto.zig уже есть TLS-обвязка (src/protocol/tls.zig, генерация обфусцированного хендшейка e2e_obf_handshake_gen.zig) — из неё можно собрать такой клиентский relay. Но серверный бинарь применить это к входящему трафику не может — см. раздел выше про сторону клиента.

[!tip] Тот же принцип уже воплощён рядом — в VLESS Свой Zig-relay — не единственное воплощение идеи «почерк генерит клиент». Для VLESS уже есть инструменты, которые вместо имитации берут подлинный сетевой стек браузера (NaiveProxy на cronet) или вообще реально установленный браузер (XHTTP + Browser Dialer в Xray) — почерк там аутентичный и свежий, без uTLS-попугайства.

⚠️ Но это другой протокол-стек: они строят браузерный HTTPS/VLESS-ClientHello, а не FakeTLS-MTProto (нет HMAC секрета прокси в client_random), поэтому к MTProxy as-is не подключаются. Ценен здесь сам подход (подлинный почерк со стороны клиента), а не готовый MTProxy-клиент. Разбор и сравнение — VLESS/dpi-tls-june-2026#Альтернатива uTLS-пресетам: реальный стек Chromium (naive/cronet).


Миф: «в teleproxy JA4 решён»

teleproxy — серверный прокси (язык C), запускается на VPS, без клиентского компонента: пользователи подключаются к нему обычным клиентом по ссылке/QR. В его README речь идёт о статичном Chrome-профиле (517-байтный ClientHello, 15 расширений, GREASE, X25519, padding) на уровне JA3-имитации — ни статической, ни динамической работы именно с JA4, ни ротации SNI там нет. Есть только FakeTLS-камуфляж и форвард неизвестного SNI на реальный бэкенд (защита от активного зондирования).

Вывод: по архитектуре и собственной документации teleproxy не меняет и не ротирует входящий JA4 официального клиента Telegram — как и любой серверный прокси.

[!quote] Из доков самого teleproxy (docs/features/dpi-resistance.md) «The primary detection vector is the Telegram client's TLS fingerprint, which cannot be fixed server-side… The Telegram app controls the byte-for-byte content of the ClientHello. Server-side proxy code cannot alter what the client puts on the wire.»

Почему конфиг teleproxy всё же «работает» — и это НЕ смена JA4

Три причины, ни одна из которых не меняет почерк:

  1. Фрагментация ClientHello через MSS-clamp. teleproxy ставит TCP_MAXSEG=256 (src/net/net-events.c), чтобы клиент порезал ClientHello на несколько TCP-сегментов: ALPN и signature_algorithms (входы JA4) уезжают во 2-3 сегмент, и DPI, считающий JA4 из первого пакета, получает неверный хеш. Сам JA4 при этом не меняется — ТСПУ просто не может его извлечь. ⚠️ Против DPI с полной пересборкой TCP-потока это не спасёт (об этом прямо сказано в доках teleproxy). Это ровно тот же приём, что mtproxy/mtproto-zig-setup#Шаг 4. TCPMSS — дробление ClientHello (там MSS=88 — даже агрессивнее) и [[Zapret/mtproto/11-telemt-server-setup#Шаг 4. Конфиги инстансов|client_mss="tspu" в telemt]] (MSS=92).
  2. Чистая зарубежная подсеть VPS + малый трафик — остальные условия детекта не складываются (см. «И трёх условий» выше).
  3. SOCKS5_PROXY + DIRECT_MODE — маршрут исходящего трафика VPS→DC через Xray (egress), по обфусцированному MTProto-транспорту к дата-центрам (не TLS). Ко входящему ClientHello (где живёт JA4) отношения не имеет.

Итого «решение JA4 в teleproxy» = фрагментация + чистый IP + egress через Xray, а не смена почерка. Чистая смена/ротация JA4 и SNI остаётся клиентской задачей.


📚 См. также

  • mtproxy/tdlib-obf-client-side-stealth — готовое клиентское воплощение этого тезиса: форк библиотеки клиента строит свежий браузерный ClientHello (PQ-профили) внутри себя, без отдельного relay
  • mtproxy/tsrman-tg-android-faketls — то же воплощение в виде готового приложения (форк официального Telegram-Android): меняет JA4 на Firefox-подобный и разносит коннекты джиттером
  • mtproxy/telegram-wss-transport — третий путь мимо спора о почерке: соединение уходит не на адрес датацентра, а на веб-релей Telegram по 443, где TLS-рукопожатие уже настоящее
  • mtproxy/mtproto-zig — как устроен FakeTLS-прокси, почему почерк фиксирован и узнаваем
  • mtproxy/faketls-relay-diagnosis — измеренные требования релеев к ClientHello (длина, метка времени, повторы) и как проверить их снаружи
  • mtproxy/mtproto-zig-setup — серверные меры (TCPMSS, SYN-ACK, домен)
  • Zapret/mtproto/10-telemt-logs-dpi — детект DPI по логам, per-port pacing
  • Zapret/mtproto/11-telemt-server-setupclient_mss="tspu" (MSS=92) и UFW rate-limit как практический пример этих мер
  • Zapret/mtproto/05-censorship
  • VLESS/dpi-tls-june-2026 — та же логика «И трёх условий» и про uTLS/свежесть почерка
  • 🔗 Тестовый клиентский relay (gist, Flowseal) — смена JA4 + ротация SNI
  • 🔗 tdesktop#30733 — протухший фингерпринт клиента