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

194 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
date: 2026-06-07
tags:
- mtproto
- mtproxy
- dpi
- tspu
- ja4
- sni
- faketls
aliases:
- Кто может менять JA4 и SNI
- Почему обход MTProxy клиентский
- JA4/SNI client-side
- Детекция MTProto июнь 2026
link: https://gist.github.com/Flowseal/de630dd9d9ddaa86cc6bed9b473fae0c
description: "Почему смена JA4 и ротация SNI против детекции MTProxy работают только на стороне клиента Telegram, а серверный прокси изменить их не может."
---
> [!mirror] Резервное зеркало
> Актуальная версия этой страницы — на основной вики: [wiki.zapret.moe/mtproxy/ja4-sni-client-side](https://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](https://gist.github.com/Flowseal/de630dd9d9ddaa86cc6bed9b473fae0c).
4. Серверный прокси (mtproto.zig/telemt/teleproxy) может бить только по третьему условию — «залп на один ip:port» (pacing, разные порты/IP).
5. На **Zig это реализуемо** — но как отдельный **клиентский** инструмент, не как серверный бинарь mtproto.zig.
---
## Сама детекция (наблюдения, июнь 2026)
Блокировка включается, когда одновременно:
1. ClientHello несёт **JA4 Telegram**`t13d1516h2_8daaf6152771_d8a2da3f94cd` (мимикрия под Chrome 134/macOS; этот почерк протух, разбор #30733 — в [[Zapret/mtproto/10-telemt-logs-dpi|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](https://gist.github.com/Flowseal/de630dd9d9ddaa86cc6bed9b473fae0c) режим `--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 на Хабре](https://habr.com/ru/articles/1044396/)).
> [!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|разбор #30733]]).
> [!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|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|TCPMSS в mtproto.zig]] (там 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|tdlib-obf — клиентский TDLib с маскировкой JA4]] — готовое клиентское воплощение этого тезиса: форк библиотеки клиента строит свежий браузерный ClientHello (PQ-профили) внутри себя, без отдельного relay
- [[mtproxy/tsrman-tg-android-faketls|tsrman/tg — Telegram для Android со сменой JA4]] — то же воплощение в виде **готового приложения** (форк официального Telegram-Android): меняет JA4 на Firefox-подобный и разносит коннекты джиттером
- [[mtproxy/telegram-wss-transport|WSS: MTProto внутри WebSocket]] — третий путь мимо спора о почерке: соединение уходит не на адрес датацентра, а на веб-релей Telegram по 443, где TLS-рукопожатие уже настоящее
- [[mtproxy/mtproto-zig|MTProxy и mtproto.zig]] — как устроен FakeTLS-прокси, почему почерк фиксирован и узнаваем
- [[mtproxy/faketls-relay-diagnosis|Релей или клиент: диагностика FakeTLS]] — измеренные требования релеев к ClientHello (длина, метка времени, повторы) и как проверить их снаружи
- [[mtproxy/mtproto-zig-setup|Настройка mtproto.zig (runbook)]] — серверные меры (TCPMSS, SYN-ACK, домен)
- [[Zapret/mtproto/10-telemt-logs-dpi|Чтение логов и лимит SYN-ACK]] — детект DPI по логам, per-port pacing
- [[Zapret/mtproto/11-telemt-server-setup|telemt в продакшн]] — `client_mss="tspu"` (MSS=92) и UFW rate-limit как практический пример этих мер
- [[Zapret/mtproto/05-censorship|ТСПУ: каскад детекции MTProto]]
- [[VLESS/dpi-tls-june-2026|Сибирская схема: подсеть + фингерпринт + частота]] — та же логика «И трёх условий» и про uTLS/свежесть почерка
- 🔗 [Тестовый клиентский relay (gist, Flowseal)](https://gist.github.com/Flowseal/de630dd9d9ddaa86cc6bed9b473fae0c) — смена JA4 + ротация SNI
- 🔗 [tdesktop#30733](https://github.com/telegramdesktop/tdesktop/issues/30733) — протухший фингерпринт клиента