354 lines
26 KiB
Markdown
354 lines
26 KiB
Markdown
# ZaStoGram — Telegram для Android с усиленной маскировкой MTProxy
|
||
|
||
<img width="1916" height="821" alt="image" src="https://github.com/user-attachments/assets/0850c5cd-6d7f-4304-9347-2cc54d5ba416" />
|
||
|
||
> Экспериментальный форк официального Telegram для Android. Цель форка —
|
||
> сделать подключение к MTProxy FakeTLS (`ee`-секрет) менее похожим на
|
||
> стандартный Telegram MTProxy-трафик и ближе к обычному браузерному HTTPS.
|
||
|
||
Это не новый мессенджер и не отдельный протокол. База остаётся официальным
|
||
Telegram Android, а изменения сосредоточены вокруг MTProxy/FakeTLS-пути.
|
||
|
||
## Простыми словами
|
||
|
||
- **MTProxy** — прокси для Telegram.
|
||
- **FakeTLS** — режим MTProxy, где начало соединения выглядит как TLS/HTTPS.
|
||
- **DPI / ТСПУ** — оборудование провайдера, которое классифицирует и режет
|
||
трафик по признакам на проводе.
|
||
- **JA4** — отпечаток TLS ClientHello. По нему можно отличить настоящий браузер
|
||
от синтетического FakeTLS, если ClientHello сделан плохо.
|
||
|
||
Проблема MTProxy не только в одном хеше. DPI может смотреть на TLS-рукопожатие,
|
||
временные интервалы, размеры записей, количество одновременных соединений и
|
||
поведение после рукопожатия. Поэтому здесь важна аккуратная маскировка без
|
||
поломки MTProto.
|
||
|
||
## Что сейчас реализовано
|
||
|
||
### Путь MTProxy FakeTLS от tsrman-коммита
|
||
|
||
В активном пути транспорта сохранено поведение, близкое к рабочему
|
||
`tsrman/tg`-варианту:
|
||
|
||
- ClientHello держится в отдельном буфере до полной отправки: если `send()`
|
||
отправил только часть данных, остаток досылается на следующем `EPOLLOUT`;
|
||
- фаза данных использует стабильное фиксированное ограничение `2878` байт;
|
||
- рискованные эксперименты с динамическим размером TLS-записей сейчас убраны из
|
||
активного пути.
|
||
|
||
Это важно для надёжности: сначала соединение должно стабильно работать, и только
|
||
потом можно снова усиливать маскировку фазы данных.
|
||
|
||
### Стабильный выбор TLS-профиля
|
||
|
||
Для MTProxy FakeTLS выбранный TLS-профиль теперь не меняется хаотично на каждое
|
||
соединение. Профиль выбирается стабильно по адресу прокси, секрету и локальной
|
||
соли.
|
||
|
||
В автоматическом пуле сейчас только Android-семейства:
|
||
|
||
- Android Chrome;
|
||
- Firefox Android;
|
||
- Android OkHttp.
|
||
|
||
Так разные установки и разные прокси могут получать разные сетевые отпечатки, но
|
||
один конкретный адрес прокси не прыгает между профилями каждую секунду.
|
||
Desktop Firefox и Yandex оставлены в коде как заготовки для ручных тестов, но
|
||
в авто-выбор временно не входят, пока мы добиваемся стабильного подключения.
|
||
|
||
В настройках списка прокси добавлен выбор `JA4 / TLS-профиль`.
|
||
|
||
- `Auto` — режим по умолчанию. Клиент сам выбирает стабильный профиль для
|
||
конкретного `адрес:порт:секрет`;
|
||
- `Chrome`, `Ffox A`, `OkHttp`, `Firefox`, `Yandex` — ручной режим для тестов.
|
||
В этом режиме один выбранный профиль принудительно используется для всех
|
||
FakeTLS-прокси, а служебная проверка прокси использует тот же профиль.
|
||
|
||
Это нужно, чтобы на одном APK быстро сравнивать, какой отпечаток лучше проходит
|
||
у конкретного провайдера или набора прокси, не пересобирая приложение.
|
||
|
||
### Защитные проверки ClientHello
|
||
|
||
Перед отправкой ClientHello проверяется на базовую совместимость с MTProxy:
|
||
|
||
- корректная TLS-запись и длины;
|
||
- ожидаемое смещение списка наборов шифров;
|
||
- первый шифр не из GREASE относится к TLS 1.3 `TLS_AES_*`;
|
||
- наличие SNI-домена из `ee`-секрета;
|
||
- размер в пределах серверного лимита.
|
||
|
||
Это защищает от красивого, но несовместимого ClientHello.
|
||
|
||
### Рандомизация внутри ClientHello
|
||
|
||
Оставлены низкорисковые элементы маскировки:
|
||
|
||
- случайные поля ClientHello;
|
||
- случайные ECH-данные там, где профиль их использует;
|
||
- случайная цель расширения заполнения вместо старого фиксированного размера;
|
||
- безопасная генерация GREASE-значений без выхода за границы массива.
|
||
|
||
### Управляемая мягкая фрагментация ClientHello
|
||
|
||
В настройках списка прокси добавлен переключатель
|
||
`Мягкая фрагментация ClientHello`. Он действует только на MTProxy FakeTLS
|
||
(`ee`-секреты).
|
||
|
||
Когда режим включён, уже собранный и проверенный ClientHello отправляется не
|
||
одним логическим залпом, а двумя небольшими TCP-отправками с коротким
|
||
неблокирующим интервалом. TLS-структура, SNI, HMAC и MTProto-трафик после
|
||
подключения не меняются.
|
||
|
||
Режим нужен для A/B-тестов против DPI: можно собрать один APK и сравнивать
|
||
прокси с фрагментацией и без неё прямо из GUI, не откатывая код. По умолчанию
|
||
режим выключен, чтобы рабочий транспорт оставался максимально близким к
|
||
стабильному пути.
|
||
|
||
### Планировщик MTProxy-подключений
|
||
|
||
Для `ee` FakeTLS добавлен контроллер допуска подключений к одному адресу прокси.
|
||
Он ограничивает не скорость уже установленного MTProto-трафика, а только залп
|
||
одинаковых попыток `connect -> ClientHello -> server_hello_hmac_ok` к одному
|
||
адресу прокси.
|
||
|
||
Ключ планировщика: `адрес прокси:порт:SNI`.
|
||
|
||
В текущей диагностической сборке активная очередь рукопожатий временно
|
||
выключена флагом `MT_PROXY_HANDSHAKE_ADMISSION_ENABLED = false`: настоящий
|
||
`connect()` снова запускается сразу, как в базовом транспортном пути. Это нужно,
|
||
чтобы отделить баги нашего admission-controller от внешнего freeze/блокировки
|
||
прокси.
|
||
|
||
Код планировщика оставлен для следующего этапа. Когда он включён, его задача:
|
||
|
||
- держать максимум одно активное FakeTLS-рукопожатие на адрес прокси;
|
||
- после недавних успешных `server_hello_hmac_ok` поднимать лимит до двух;
|
||
- ставить новые рукопожатия в очередь по приоритету:
|
||
`Generic/Temp -> GenericMedia -> Push -> Download -> Upload`;
|
||
- не пропускать служебную проверку прокси через жёсткую очередь, чтобы не
|
||
получать ложный результат "прокси не работает" из-за ожидания слота;
|
||
- освобождать слот сразу после `server_hello_hmac_ok`, чтобы загрузка и отправка
|
||
файлов после подключения шли параллельно как раньше.
|
||
|
||
Зависания в фазе `client_hello_sent` сейчас только логируются; штрафной
|
||
cooldown тоже временно выключен, чтобы клиент сам не переводил живые прокси в
|
||
долгое состояние "недоступен" во время диагностики блокировок.
|
||
|
||
Это сделано против узнаваемого залпового шаблона, когда клиент почти
|
||
одновременно открывает много одинаковых FakeTLS-соединений к одному прокси.
|
||
Жёсткий режим типа "один SYN в секунду" не используется: он слишком грубо
|
||
замедляет Telegram и сам может стать странной сигнатурой.
|
||
|
||
### Диагностика запуска подключения
|
||
|
||
Добавлены диагностические точки для MTProxy-подключения:
|
||
|
||
- `connect_start`;
|
||
- `socket_connected`;
|
||
- `client_hello_sent`;
|
||
- `server_hello_hmac_ok`;
|
||
- `server_hello_hmac_timeout`, если ServerHello структурно получен, но HMAC не
|
||
совпал за короткое диагностическое окно;
|
||
- `on_connected`;
|
||
- `first_tls_app_sent`, когда первый MTProto-пакет ушёл внутри TLS ApplicationData;
|
||
- `first_tls_app_recv`, когда от сервера пришла первая полезная TLS ApplicationData;
|
||
- `mtproxy_disconnect` с причиной, состоянием и флагами первой MTProto-активности
|
||
внутри TLS (`first_tls_sent`, `first_tls_recv`);
|
||
- `admission_grant`, `admission_queue`, `admission_release`,
|
||
`admission_disabled`, `admission_freeze_detected`,
|
||
`admission_freeze_observed`.
|
||
|
||
Это нужно, чтобы отличать DPI-обрыв от багов в нашем транспортном пути.
|
||
|
||
### Архитектура проверки прокси
|
||
|
||
Проверка прокси разделена на три слоя, чтобы GUI не запускал лишние соединения и
|
||
не спорил с native-состоянием:
|
||
|
||
- `ProxyCheckScheduler` в Java принимает ручные проверки из bottom sheet,
|
||
фоновые проверки из списка прокси и проверки ротации. Он держит один активный
|
||
check, склеивает одинаковые endpoint'ы, хранит владельцев проверки и отменяет
|
||
active check через `cancelProxyCheck()`.
|
||
- Native-слой `ConnectionsManager` завершает проверку через единый
|
||
`finishProxyCheck`: успех по `TL_pong`, сетевой обрыв, timeout, start-fail и
|
||
явная отмена проходят через одну точку очистки request/token/socket.
|
||
- `Tools/analyze_mtproxy_markers.py` сводит Java scheduler, native proxy-check,
|
||
rotation и FakeTLS-маркеры в один отчёт. Важный verdict:
|
||
`connected_without_socket_connected_marker` означает, что лог дошёл до
|
||
`on_connected`, но в этом срезе нет маркера `socket_connected`; такой случай
|
||
нельзя считать TCP-недоступностью.
|
||
|
||
В отчёте есть два разных среза:
|
||
|
||
- `FakeTLS endpoint phases` — фазы настоящего FakeTLS-подключения по адресу
|
||
прокси: TCP/connect, отправка ClientHello, проверка ServerHello/HMAC, первая
|
||
MTProto ApplicationData и поздний disconnect;
|
||
- `Plain MTProxy lifecycle` — обычные `dd`/legacy MTProxy-секреты отдельно от
|
||
FakeTLS. Этот срез группируется по `endpoint`, аккаунту, DC и типу соединения,
|
||
чтобы отличать "проверка прокси доступна" от ситуации, где, например,
|
||
`Generic` получает RPC-ответы, а `Download` много отправляет и почти ничего не
|
||
получает;
|
||
- `Native endpoint outcomes` — результат служебной проверки прокси. Значение
|
||
`fail:tcp_not_connected` относится к TCP/connect-слою, а
|
||
`fail:tcp_connected_no_pong` означает, что TCP открылся, но MTProxy ping не
|
||
завершился.
|
||
|
||
`Tools/collect_mtproxy_logs.ps1` строит основной `mtproxy_markers.txt` только по
|
||
текущему `logcat.txt`. Сырые device logs всё ещё подтягиваются в папку сессии,
|
||
но не смешиваются с live-статистикой, чтобы старые файлы не портили выводы.
|
||
|
||
### Единая карта ошибок прокси
|
||
|
||
Статус прокси теперь передаётся не числовыми кодами, а строковой фазой. Один и
|
||
тот же словарь используется в native-логе, Java scheduler, GUI и анализаторе:
|
||
|
||
- `tcp_not_connected` — TCP-соединение не открылось;
|
||
- `tcp_connected_no_pong` — TCP открылся, но MTProxy ping не завершился;
|
||
- `client_hello_sent_no_server_hello` — ClientHello отправлен, валидный
|
||
ServerHello не пришёл;
|
||
- `server_hello_hmac_mismatch` — ServerHello пришёл, но не прошёл HMAC-проверку
|
||
MTProxy;
|
||
- `post_handshake_no_appdata` — рукопожатие прошло, но первые MTProto-данные не
|
||
пришли;
|
||
- `dropped_after_appdata` — соединение поднялось и отвалилось уже после первых
|
||
MTProto-данных.
|
||
|
||
Единый источник GUI-текстов — `ProxyCheckDiagnostics`. Поэтому список прокси,
|
||
bottom sheet проверки и логи используют одинаковые имена фаз. Это нужно, чтобы
|
||
по экрану и по `logcat` было видно, где именно умирает прокси и похоже ли это
|
||
на недоступный сервер, несовместимость транспорта или фильтрацию пути.
|
||
|
||
### Корректная досылка неполной TLS-записи
|
||
|
||
Стартовый FakeTLS `ClientHello` отправляется через отдельный ожидающий буфер:
|
||
если неблокирующий `send()` принял только часть hello, остаток досылается на
|
||
следующем `EPOLLOUT`, а состояние `client_hello_sent` логируется только после
|
||
полной отправки.
|
||
|
||
Если peer закрыл TCP-соединение (`recv() == 0`), сокет закрывается сразу с
|
||
меткой `recv_eof`, а не ждёт общего таймаута подключения.
|
||
|
||
Если после полной отправки `ClientHello` нет корректного `server_hello_hmac_ok`
|
||
за время freeze-таймера, попытка закрывается с меткой
|
||
`server_hello_timeout_close`, чтобы не держать мёртвое рукопожатие до общего
|
||
таймаута.
|
||
|
||
Проверка HMAC для ServerHello теперь привязана к границам TLS-записей, а не к
|
||
случайному размеру одного `recv()`. Это сохраняет совместимость с официальным
|
||
MTProxy, где ответ состоит из `ServerHello + ChangeCipherSpec + ApplicationData`,
|
||
и не ломается на telemt-подобных ответах с дополнительными профилированными
|
||
ApplicationData-записями в серверном handshake flight.
|
||
|
||
После установленного FakeTLS-соединения reader принимает только полезные
|
||
`ApplicationData`-записи MTProto, но корректно пропускает служебный
|
||
`ChangeCipherSpec`, не отдаёт пустые TLS-записи в MTProto-парсер и закрывает
|
||
соединение с отдельной меткой `tls_alert`, если peer прислал TLS Alert.
|
||
|
||
Для обёрнутых TLS-записей добавлена очередь ожидающей отправки записи: если
|
||
`send()` отправил только часть TLS-записи, остаток досылается как продолжение той
|
||
же записи, а полезная нагрузка MTProto не выбрасывается раньше полной отправки.
|
||
Успешная досылка обновляет таймер активности соединения, чтобы активная отправка
|
||
не считалась простым бездействием.
|
||
|
||
## Сборка
|
||
|
||
Готового APK здесь намеренно нет. Собирай приложение из исходников.
|
||
|
||
1. Получи `api_id` и `api_hash` на [my.telegram.org](https://my.telegram.org).
|
||
2. Укажи свои значения в проекте Telegram Android.
|
||
3. Подставь свой ключ подписи релизной сборки.
|
||
4. Собери через Android Studio или GitHub Actions.
|
||
|
||
GitHub Actions настроен на ccache с `CCACHE_COMPILERCHECK=content`, чтобы
|
||
повторные сборки нативного кода были заметно быстрее после прогрева кэша.
|
||
|
||
На время диагностики MTProxy GitHub Actions включает сетевые логи Telegram прямо
|
||
в обычном артефакте `ZaStoGram-standalone-afat`. После установки такого APK можно
|
||
собрать маркеры:
|
||
|
||
```powershell
|
||
D:\bin\platform-tools\adb.exe devices -l
|
||
```
|
||
|
||
Из WSL:
|
||
|
||
```bash
|
||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$(wslpath -w Tools/collect_mtproxy_logs.ps1)" -Adb "D:\bin\platform-tools\adb.exe" -Package org.zastogram.messenger -Seconds 180
|
||
```
|
||
|
||
Во время записи надо открыть приложение и повторить подключение к MTProxy.
|
||
Главный файл для разбора: `mtproxy_markers.txt`.
|
||
Скрипт также создаёт `mtproxy_analysis.txt`: он группирует попытки по
|
||
`ConnectionSocket` и показывает, где остановилось подключение:
|
||
TCP/connect, отправка `ClientHello`, проверка `server_hello_hmac_ok`, переход в
|
||
`on_connected` или первые TLS `ApplicationData`-записи MTProto.
|
||
|
||
Для сравнения профилей и endpoint'ов рядом создаются CSV:
|
||
|
||
- `mtproxy_attempts.csv` — каждая FakeTLS-попытка: endpoint, профиль, фаза
|
||
отказа, задержка до HMAC, close/error и набор увиденных маркеров;
|
||
- `mtproxy_endpoint_profile_stats.csv` — агрегат по `endpoint + profile`:
|
||
количество попыток, процент успеха, топ отказов, медиана HMAC и максимальный
|
||
burst рукопожатий за 1 и 5 секунд.
|
||
|
||
Именно эти файлы нужны, чтобы отделить нашу ошибку транспорта от внешней
|
||
блокировки или нестабильного прокси.
|
||
Перед публичным релизом это нужно выключить обратно, чтобы не оставлять
|
||
подробные сетевые логи в обычной сборке.
|
||
|
||
## Как пользоваться
|
||
|
||
1. Подними MTProxy с FakeTLS вне зоны блокировки.
|
||
2. Используй `ee`-секрет с доменом.
|
||
3. В приложении добавь MTProto-прокси обычным способом:
|
||
**Настройки -> Данные и память -> Прокси -> Добавить прокси -> MTProto**.
|
||
|
||
Маскировка из этого форка применяется именно к FakeTLS/`ee`-секрету. Для обычных
|
||
`dd`-секретов и других вариантов без FakeTLS эти изменения не дают того же
|
||
эффекта.
|
||
|
||
## Честные ограничения
|
||
|
||
- SNI не ротируется сам по себе. Он берётся из `ee`-секрета.
|
||
- Фаза данных пока не имитирует настоящий HTTP/2 или HTTP/1.1.
|
||
- Динамический размер TLS-записей сейчас не включён в активный путь.
|
||
- Планировщик ограничивает только фазу открытия FakeTLS-соединения. Он не
|
||
пытается полностью замаскировать статистику уже установленного MTProto-трафика.
|
||
- Это не гарантия вечного обхода блокировок: DPI-правила меняются.
|
||
- Это неофициальный форк, использовать его нужно с пониманием рисков.
|
||
|
||
## Потом реализуем
|
||
|
||
- Вернуть DRS аккуратно: случайные размеры TLS-записей, фазовая модель, защита
|
||
от повторяющихся шаблонов и сброс состояния после простоя, но только после
|
||
проверки досылки неполных записей и пути передачи данных на реальном MTProxy.
|
||
- IPT: неблокирующая имитация межпакетных интервалов с разделением на
|
||
рукопожатие, поддержание соединения, интерактивный трафик и передачу файлов.
|
||
- Больше стабильных браузерных профилей из `telemt/tdlib-obf`: Android Brave,
|
||
дополнительные Chrome/Firefox/Yandex-варианты, Windows/iOS профили, но только
|
||
через защитные тесты совместимости с MTProxy.
|
||
- Ручное переопределение профиля для отладки без публичного интерфейса, чтобы
|
||
быстро сравнивать профили при тестах.
|
||
- Политика ECH с учётом маршрута и автоматическим отключением неудачного
|
||
варианта, чтобы не долбить сеть ECH-профилем, если конкретный маршрут его
|
||
режет.
|
||
- Управляемая политика отправки ClientHello: мягкая фрагментация только фазы
|
||
рукопожатия, маленький jitter между кусками, подробные логи размера кусков и
|
||
быстрый флаг отката. Сейчас это не включено в стабильный путь.
|
||
- Ограничение MSS как отдельный эксперимент после проверки фрагментации
|
||
ClientHello: оно меняет TCP-поведение ниже уровня FakeTLS и поэтому требует
|
||
отдельного сравнения с VPN/без VPN.
|
||
- Маскировка прикладного уровня: обрамление HTTP/2 или HTTP/1.1 поверх
|
||
TLS-подобного транспорта. Это самый глубокий и самый рискованный слой, его
|
||
нельзя делать косметически.
|
||
- Заполняющий трафик во время простоя и маскировка жизненного цикла соединений,
|
||
если они не будут ломать MTProto и батарею на Android.
|
||
|
||
## Основа и благодарности
|
||
|
||
- [DrKLO/Telegram](https://github.com/DrKLO/Telegram) — официальный Telegram
|
||
Android.
|
||
- [tsrman/tg](https://github.com/tsrman/tg) — рабочая база изменений FakeTLS,
|
||
JA4 и временных интервалов.
|
||
- [telemt/tdlib-obf](https://github.com/telemt/tdlib-obf) — идеи и референсы по
|
||
профилям маскировки, DRS/IPT и реестру профилей.
|