ZaStoGram/README.md
loop-uh 79f713d028 Merge upstream Telegram 12.9.2
# Conflicts:
#	.gitignore
#	README.md
#	TMessagesProj/src/main/java/org/telegram/ui/SecretVoicePlayer.java
#	gradle.properties
2026-08-05 17:41:35 +03:00

903 lines
71 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.

# 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,
отдельного WSS-транспорта и клиентских privacy/UX-настроек форка.
## Простыми словами
- **MTProxy** — прокси для Telegram.
- **FakeTLS** — режим MTProxy, где начало соединения выглядит как TLS/HTTPS.
- **WSS** — WebSocket-транспорт поверх настоящего TLS. В этом форке он живёт
отдельно от MTProxy/FakeTLS и обычного SOCKS5.
- **DPI / ТСПУ** — оборудование провайдера, которое классифицирует и режет
трафик по признакам на проводе.
- **JA4** — отпечаток TLS ClientHello. По нему можно отличить настоящий браузер
от синтетического FakeTLS, если ClientHello сделан плохо.
Проблема MTProxy не только в одном хеше. DPI может смотреть на TLS-рукопожатие,
временные интервалы, размеры записей, количество одновременных соединений и
поведение после рукопожатия. Поэтому здесь важна аккуратная маскировка без
поломки MTProto.
## Что сейчас реализовано
### Где мы сейчас
Текущая архитектура разделяет сбои MTProxy по фазам, чтобы не лечить DNS или
TCP через JA4:
- `host_resolve_failed` — слой DNS/endpoint stability: `sslip.io` разбирается
локально без DNS-запроса, регистр суффикса не важен, а доменные прокси могут
использовать последний успешный IPv4 из кэша. Повторный холодный DNS lookup
для того же `host:port` коротко склеивается, чтобы не стрелять пачкой
одинаковых DNS-запросов;
- `tcp_not_connected` — слой endpoint circuit-breaker: один плохой endpoint не
должен получать пачку быстрых `connect()` от разных аккаунтов, DC и
download/upload-потоков. Активный TCP-start gate держит только фазу
`connect()` и освобождается сразу после `socket_connected` или закрытия
сокета;
- `client_hello_sent_no_server_hello` — слой phase-adaptive FakeTLS: следующий
запуск осторожно меняет только рецепт рукопожатия, а не весь транспорт сразу;
- `mtproxy_packet_sent_no_response` — слой обычного `dd`/legacy MTProxy:
проблема уже после TCP и первого MTProxy-пакета, поэтому JA4/ClientHello тут
не участвуют. Если первый MTProxy-пакет отправлен, но ответа нет, соединение
закрывается отдельным коротким таймаутом этой фазы, а не висит до общего
socket timeout.
GUI и `logcat` используют одни и те же строковые фазы. Если прокси зависает,
первым делом смотрим именно фазу, а не общий текст "подключаемся".
DRS пока не первым: для него уже есть базовый runtime-режим и guard на
завершённые FakeTLS-записи, но агрессивные профили размеров записей будем
включать только после отдельной проверки record-boundary на всём пути
`build -> partial send -> complete TLS frame -> discard`.
### Быстрая проверка перед сборкой
Перед сборкой APK можно прогнать все статические guards по MTProxy-архитектуре
одной командой:
```sh
python3 Tools/check_mtproxy_all.py
```
Этот suite проверяет FakeTLS-путь, профили JA4, фрагментацию ClientHello,
паттерн открытия соединений, startup cover, data-aware IPT, endpoint resilience,
границы завершённых FakeTLS-записей, `dd`/legacy lifecycle, live-статусы GUI,
scheduler проверок прокси и анализатор логов. Он не заменяет установку APK и
реальные логи, но быстро ловит разъезд слоёв в коде.
Для соседних поверхностей есть отдельные быстрые guards, которые не требуют
полной Android-сборки:
```sh
python3 Tools/check_login_proxy_button.py
python3 Tools/check_single_active_presence.py
python3 Tools/check_background_network_policy.py
python3 Tools/check_hide_main_screen_stories.py
python3 Tools/check_zasto_edit_history_contract.py
python3 Tools/check_app_log_cleanup.py
python3 Tools/check_contacts_permission_prompt.py
python3 Tools/check_splash_icon.py
python3 Tools/check_settings_durov_links.py
python3 Tools/check_zapret_proxy_sponsor.py
python3 Tools/check_plugin_client_utils_contract.py
python3 Tools/check_plugin_python_deps.py
python3 Tools/check_plugin_utils_javadoc.py
python3 Tools/check_build_apk_workflow.py
```
### Пользовательские функции форка
Помимо MTProxy-слоя в клиенте есть отдельные пользовательские изменения:
- экран `ZaSto Приватность` с переключателями, которые по умолчанию включают
клиентские override'ы: сохранять удалённые сообщения, сохранять
самоуничтожающиеся / view-once материалы, хранить локальную историю
редактирования сообщений, разрешать сохранение и пересылку защищённого
контента и историй, разрешать скриншоты, не отправлять уведомление о
скриншоте в секретных чатах и отключать рекламные/промо-поверхности Telegram;
- настройка чатов `Скрыть истории на главном экране`: она прячет stories row,
кнопку создания истории и hint только на главном списке диалогов, не выключая
сторис глобально;
- настройка уведомлений `Всегда держать сеть в фоне`: клиент не вызывает свой
`native_pauseNetwork()` при уходе приложения в фон. Это помогает не ронять
живые подключения самим приложением, но не отменяет системные ограничения
Android по батарее и процессам;
- отдельная строка `Zapret VPNs` / `Спонсор прокси` в верхней части списка
чатов с переходом на `zapretvpns_bot`. Она управляется тем же общим
анти-рекламным переключателем: если `Отключить рекламу` включено, локальная
sponsor-строка тоже скрывается;
- дополнительный блок в настройках со ссылками `Наш канал`, `Наш VPN` и
`Донаты & поддержать`;
- раздел `Плагины` в настройках: установка Python `.plugin`-файлов,
совместимых с базовой моделью exteraGram, включение/выключение, удаление и
отдельные экраны настроек плагинов;
- профиль показывает рядом с ID строку `Дата-центр` — домашний DC аккаунта,
канала или группы (например `DC4, Amsterdam (vesta)`), с копированием по
нажатию. DC определяется по `dc_id` аватарки, поэтому для аккаунтов без
аватарки строка скрыта (кроме собственного аккаунта, где DC известен из
активного подключения);
- экран входа показывает кнопку прокси сразу в обычном login-flow и при отмене
удаления аккаунта, даже если прокси ещё не настроен;
- открытие контактов больше не вызывает автоматический lifecycle-показ
объяснения про доступ к контактам;
- только выбранный аккаунт отправляет online presence. Фоновый keep-awake не
должен превращать остальные аккаунты в постоянно online;
- Android 12+ splash использует логотип форка со стаканом, а не стандартный
самолётик Telegram;
- файловые логи приложения чистятся на старте процесса до первой новой записи,
чтобы свежий запуск не смешивался со старыми локальными логами.
### Плагины exteraGram
В ZaStoGram встроен Python-движок плагинов для `.plugin`-файлов, близких к
формату exteraGram. Он живёт внутри основного Android-клиента, а не как
отдельный бот или внешний скрипт:
- CPython запускается через Chaquopy в модуле `TMessagesProj`; текущий runtime
— Python 3.11;
- пользовательские `.plugin`-файлы ставятся из интерфейса приложения и хранятся
в data-директории приложения, а не зашиваются в APK;
- каждый плагин — один Python-файл с метаданными вроде `__id__`, `__name__`,
`__version__`, `__min_version__`, `__icon__` и классом-наследником
`BasePlugin`;
- жизненный цикл плагина: `on_plugin_load()`, `on_plugin_unload()` и
`create_settings()` для собственного экрана настроек;
- настройки плагинов рендерятся нативными Telegram-ячейками через `ui.settings`
(`Header`, `Switch`, `Input`, `Selector`, `Text`, `Divider`) и сохраняются
отдельно для каждого id плагина;
- доступны helper-модули `client_utils`, `android_utils`, `hook_utils`,
`file_utils`, `ui.alert` и `ui.bulletin`;
- `client_utils` экспортирует `send_request()` и `RequestCallback` для
exteraGram-плагинов, которые импортируют request callback вручную;
- в APK заранее кладутся Python-зависимости `requests`, `Pillow`/`PIL` и
`pyfiglet`, потому что runtime-установки через pip на устройстве нет;
- хуки Java-методов работают через Pine с Xposed-совместимым API:
`MethodHook`, `XposedHook`, `hook_method()` и `hook_all_constructors()`;
- для сетевых запросов сейчас подключён post-response hook:
`post_request_hook()` может пропустить, заменить или отменить ответ через
`HookStrategy`.
Путь в приложении:
**Настройки -> Плагины -> + -> выбрать `.plugin` файл**. После установки
плагин можно открыть, включить/выключить, удалить или настроить, если сам
плагин отдаёт модель настроек.
Это совместимость с реализованной здесь базовой поверхностью exteraGram, а не
обещание полной поддержки каждого публичного плагина. Если внешний плагин
использует API, которого нет в `TMessagesProj/src/main/python` или Java-мостах
`org.telegram.plugins`, его нужно адаптировать.
### Отдельный WSS-транспорт
WSS — это не MTProxy FakeTLS и не Python/локальный relay. В форке он вынесен в
нативный путь `WssTransport.cpp` и управляется отдельным состоянием. На первом
запуске режим по умолчанию становится официальным WSS Telegram:
- режим WSS выбирается в списке прокси отдельной секцией: `Выкл`, официальный
WSS Telegram или свой WSS gateway;
- официальный WSS использует `kws2.web.telegram.org` и
`kws4.web.telegram.org` для поддержанных DC2/DC4 и не требует собственного
сервера;
- WSS-настройки сохраняются отдельно от обычного `currentProxy`, включая
`wssHost`, `wssPath`, `wssUseForMiniApps` и выбранный SOCKS5 upstream;
- если WSS включён, legacy proxy path отключается, а MTProxy-настройки не
показываются как активные для этого режима;
- официальный WSS использует реальный TLS/WebSocket upgrade и каталог
Telegram WSS route'ов, а для DC без стабильного WSS route может уйти в
обычный fallback;
- выбранный SOCKS5 для WSS подключается до TLS/WebSocket как upstream. Это не
то же самое, что "SOCKS внутри WSS", и не должно перетирать обычный список
прокси Telegram;
- для mini apps есть отдельный `wssUseForMiniApps` hook, который использует тот
же выбранный WSS SOCKS upstream, а не случайный legacy-прокси.
#### Route via WSS (web, DC2/DC4 only)
Эта настройка — то, что в интерфейсе включает маршрутизацию Telegram-трафика
через официальные веб-релеи WSS вместо прямых IP датацентров. В коде это режим
`Официальный` WSS-транспорта (`wssTransportMode = TRANSPORT_WSS_OFFICIAL`), и на
первом запуске форка он включается по умолчанию.
Что происходит, когда галка стоит:
- клиент подключается к `kws2.web.telegram.org` / `kws4.web.telegram.org` —
тем же WebSocket-релеям, которые использует Telegram Web. Это настоящий
HTTPS/TLS и настоящий WebSocket upgrade, а не имитация;
- MTProto-трафик едет внутри WSS к IP, на котором обычно нет блока, вместо
прямого подключения к заблокированным IP датацентров;
- прокси при этом не нужен вообще: нет промежуточного узла — нет и лишнего
пинга прокси. Именно поэтому в заблокированной сети этот режим часто
ощущается быстрее, чем оригинальный Telegram, который сначала долго долбит
прямые IP;
- транспорт реализован в нативном C++ (`WssTransport.cpp`), без Python и без
локального relay через `127.0.0.1`, то есть без накладных расходов
локальной петли.
Ограничение «web, DC2/DC4 only» означает, что стабильные официальные
WSS-route'ы есть только у DC2 и DC4. Для соединений с другими DC клиент уходит
в обычный fallback-путь; если и он зарезан, для полного покрытия нужен свой
WSS gateway (режим `Свой`) или MTProxy. Это не недоработка форка, а текущее
состояние каталога официальных WSS-route'ов Telegram.
### Путь MTProxy FakeTLS от tsrman-коммита
В активном пути транспорта сохранено поведение, близкое к рабочему
`tsrman/tg`-варианту:
- ClientHello держится в отдельном буфере до полной отправки: если `send()`
отправил только часть данных, остаток досылается на следующем `EPOLLOUT`;
- по умолчанию фаза данных использует стабильное фиксированное ограничение
`2878` байт;
- дополнительные слои фазы данных включаются только через явные настройки GUI и
не меняют MTProto-формат.
Это важно для надёжности: сначала соединение должно стабильно работать, и только
потом можно включать дополнительные слои и сравнивать результат на конкретном
провайдере или прокси.
### Стабильный выбор TLS-профиля
Для MTProxy FakeTLS выбранный TLS-профиль теперь не меняется хаотично на каждое
соединение. Профиль выбирается стабильно по адресу прокси, секрету и локальной
соли.
В автоматическом пуле сейчас два профиля, которые показали себя наиболее
безопасными для текущей диагностики:
- Firefox Android;
- Yandex.
Так разные установки и разные прокси могут получать разные сетевые отпечатки, но
один конкретный адрес прокси не прыгает между профилями каждую секунду.
Android Chrome, Android OkHttp и Desktop Firefox оставлены в ручном режиме для
сравнительных тестов. Android OkHttp пока не входит в Auto, потому что этот
профиль нужно дополнительно проверить на стабильность с разными MTProxy.
В настройках списка прокси добавлен выбор `JA4 / TLS-профиль`.
- `Auto` — режим по умолчанию. Клиент сам выбирает стабильный профиль для
конкретного `адрес:порт:секрет`;
- `Auto rotate` — стабильный профиль для адреса прокси, но после подозрительного
FakeTLS-сбоя клиент переключает этот адрес на другой профиль из безопасного
пула. Ошибки до открытия TCP не вращают JA4, потому что ClientHello в таком
случае ещё не участвовал. `dropped_early_after_appdata` ускоряет fallback и
endpoint-backoff, но тоже не вращает JA4: первые MTProto-данные уже пошли,
значит проблему надо искать в post-handshake lifecycle, размерах и таймингах,
а не в ClientHello. Поздний `dropped_after_appdata` остаётся диагностикой
позднего разрыва без смены рецепта;
- `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, не откатывая код. По умолчанию
режим выключен, чтобы рабочий транспорт оставался максимально близким к
стабильному пути.
### Управляемый размер TLS-записей
В настройках списка прокси добавлен режим `Размер TLS-записей`. Он действует
только после успешного FakeTLS-рукопожатия и меняет размер следующих
`ApplicationData`-записей, в которые упаковывается MTProto-трафик.
Доступные варианты:
- `Выкл` — старое стабильное поведение с верхней границей `2878` байт;
- `Мягко` — выбирает размер из небольшого набора безопасных значений;
- `Разно` — использует более широкий диапазон размеров.
Этот слой не перепаковывает MTProto и не держит отдельное состояние
"продолжения" TLS-записи. Сначала собирается полная TLS-запись в pending-буфере,
потом она досылается до конца, и только после полной отправки полезная нагрузка
выбрасывается из очереди MTProto. Это снижает риск поломать partial-send и
позволяет тестировать размер записей прямо из GUI. Завершённая FakeTLS-запись
логируется как `mtproxy_data tls_frame_complete`; этот marker пишется только
после полной отправки frame и списания payload из очереди.
### Управляемые тайминги данных
В настройках списка прокси добавлен режим `Тайминги трафика`. Он добавляет
короткую неблокирующую паузу только между полностью отправленными FakeTLS
`ApplicationData`-записями.
Доступные варианты:
- `Выкл` — без пауз;
- `Мягко` — короткие интервалы для осторожного A/B-теста;
- `Средне` — более заметное растягивание последовательности записей.
Неполная TLS-запись не задерживается и всегда досылается целиком. Это важно,
чтобы не ломать MTProto и не превращать сетевой поток в состояние, где часть
payload уже выкинута из очереди, но не дошла до сокета.
### Стартовая маскировка MTProxy
В настройках списка прокси добавлен режим `Стартовая маскировка MTProxy`. Он
включается только для `ee` FakeTLS и только после успешного
`server_hello_hmac_ok`, то есть рукопожатие ClientHello/ServerHello не
трогается.
Доступные варианты:
- `Выкл` — не меняет первые `ApplicationData`-записи;
- `Мягко` — временно включает осторожные размеры TLS-записей и короткие
неблокирующие паузы для первых записей;
- `Строго` — сильнее меняет первые записи, но только в коротком стартовом
окне.
Этот слой не создаёт фоновые проверки прокси, не открывает сторонние сайты и не
генерирует отдельный "веб-трафик". Он меняет только упаковку первых реальных
MTProto-данных внутри уже установленного FakeTLS-соединения, чтобы старт меньше
выглядел как типичный MTProxy-залп.
### Мягкое сокращение параллельных каналов MTProxy
Когда включён MTProxy, загрузки и отправки файлов больше не создают отдельный
набор параллельных каналов скачивания и выгрузки для каждого запроса. Они
мягко сводятся к основному каналу скачивания/выгрузки, а прямые соединения без
MTProxy сохраняют прежнее распределение.
В настройках списка прокси слой называется
`Меньше параллельных каналов MTProxy`. По умолчанию он включён, но его можно
выключить без пересборки, если нужно сравнить поведение конкретного прокси.
Это не настоящее мультиплексирование протокола и не перепаковка MTProto. Это
первый безопасный слой против сетевого шаблона, где клиент почти одновременно
создаёт несколько похожих потоков к одному прокси. Цель — уменьшить залп и
проверить эффект на блокировках, не меняя формат MTProto-пакетов и не ломая
сервер MTProxy.
### Паттерн MTProxy-подключений
Для `ee` FakeTLS добавлен управляемый планировщик открытия соединений к одному
адресу прокси. Он ограничивает не скорость уже установленного MTProto-трафика,
а только залп одинаковых попыток
`DNS/resolve -> connect -> ClientHello -> server_hello_hmac_ok`.
Ключ планировщика: `адрес прокси:порт:SNI`.
В настройках списка прокси слой называется `Паттерн подключений MTProxy`. Это
не флажок, а режим:
- `Обычный` — без очереди, максимально близко к базовому транспорту;
- `Мягкий` — лёгкий контроллер допуска: до двух активных FakeTLS-рукопожатий
на адрес прокси без штрафной паузы;
- `Браузерный` — сначала одно настоящее FakeTLS-рукопожатие, после первого
успешного `server_hello_hmac_ok` плавно допускается второй слот; тяжёлые
соединения скачивания и выгрузки стартуют позже обычных и медийных;
- `Тихий` — одно активное рукопожатие и более медленная последовательная выдача
слотов; после недавних успехов допускается два;
- `Строгий` — одно активное рукопожатие, ограничение быстрых последовательных
стартов `connect()`, более длинные паузы для очереди и короткая ограниченная
штрафная пауза после таймаута или зависания рукопожатия.
Старый флаг `mtProxyHandshakeAdmission` мигрируется в `Мягкий` режим, если он
был включён. Новый режим хранится как `mtProxyConnectionPatternMode`,
передаётся в родной C++ слой через `setProxySettings` и перезапускает соединения
при изменении.
Когда режим не `Обычный`, задача планировщика:
- начинать delegate DNS-разрешение доменных `ee` FakeTLS-прокси только после
допуска в очередь, чтобы DNS/TCP-отказы не обходили браузерный backoff;
- держать максимум два активных FakeTLS-рукопожатия в `Мягком` режиме и максимум
одно в `Браузерном`/`Тихом`/`Строгом`, кроме недавних успешных
`server_hello_hmac_ok`, после которых `Браузерный` и `Тихий` режимы могут
открыть второй слот;
- ставить новые рукопожатия в очередь по приоритету:
`Generic/Temp -> GenericMedia -> Push -> Download -> Upload`;
- не пропускать служебную проверку прокси через жёсткую очередь, чтобы не
получать ложный результат "прокси не работает" из-за ожидания слота;
- освобождать слот сразу после `server_hello_hmac_ok`, чтобы загрузка и отправка
файлов после подключения шли параллельно как раньше.
Зависания в фазе `client_hello_sent` в `Обычном` и `Мягком` режимах в основном
логируются. В `Браузерном`, `Тихом` и `Строгом` режимах они дополнительно дают
короткую ограниченную штрафную паузу для низкоприоритетных попыток, чтобы
клиент не создавал быстрый повторяемый шум на одном адресе прокси. Успешный
`server_hello_hmac_ok` сбрасывает штраф, чтобы рабочий прокси не оставался
искусственно медленным.
Отдельно обрабатываются повторы `tcp_not_connected`, когда попытка умерла до
отправки `ClientHello`. Общий endpoint-слой ставит короткую паузу для всех
режимов, включая `Обычный`, а `Браузерный`, `Тихий` и `Строгий` режимы делают
эту паузу заметнее. Это снижает шторм повторных DNS/`connect()` по одному
адресу прокси, но не смешивает сетевое душение до ClientHello с
JA4/ServerHello-ошибками.
Это сделано против узнаваемого залпового шаблона, когда клиент почти
одновременно открывает много одинаковых FakeTLS-соединений к одному прокси.
Приложение не управляет TCP SYN-пакетами напрямую: оно управляет стартом
`connect()`, а retransmit на уровне TCP делает ОС. Поэтому строгий режим
приближает поведение к редким открытиям соединений, но не обещает точное
правило "один SYN в секунду".
### Устойчивость endpoint'а
Поверх планировщика FakeTLS добавлен общий слой устойчивости endpoint'а. Он
работает для всех MTProxy-секретов, включая обычные `dd`/legacy, и не меняет
формат MTProto-пакетов.
У слоя два разных ключа, потому что разные фазы видят разный объём данных:
- сетевой ключ `host:port` используется для DNS, `tcp_not_connected` и
ограничения одновременных TCP connect-попыток. На этой фазе ещё нет
ClientHello, SNI и JA4, поэтому нельзя дробить state по секрету или TLS-профилю;
- FakeTLS-рецепт использует ключ `host:port:тип секрета:SNI`. Этот state нужен
только после post-ClientHello сбоев, где уже действительно участвовали SNI,
ClientHello и выбранный TLS-профиль.
Что делает слой:
- запоминает последний успешно разрешённый IPv4 для доменного прокси по ключу
`host:port` и может использовать его как fallback, если следующий DNS lookup
не дал адрес. Этот DNS-кэш намеренно не зависит от `ee`/`dd`, секрета и SNI:
если один вариант того же прокси уже дал IP, другой вариант может переиспользовать
его без лишнего DNS-шума;
- коротко склеивает одновременные холодные DNS-разрешения одного `host:port`:
первая попытка идёт в resolver, следующие ждут `dns_coalesce_wait`, затем
сначала пробуют свежий кэш и только потом снова обращаются к DNS;
- после фаз `host_resolve_failed`, `tcp_not_connected`,
`client_hello_sent_no_server_hello` и `mtproxy_packet_sent_no_response`
ставит короткую паузу именно на том уровне, где произошёл сбой: DNS/TCP
ошибки идут на `host:port`, а FakeTLS/post-ClientHello ошибки — на
`host:port:тип секрета:SNI`. В `Обычном`/`Мягком` режимах пауза короче, в
`Браузерном`/`Тихом`/`Строгом` — длиннее;
- не запускает пачку одновременных TCP connect-попыток к одному MTProxy
`host:port`. `tcp_connect_gate` удерживает только pre-TCP фазу и освобождается
на `socket_connected` или `closeSocket`, поэтому уже установленная MTProto
сессия и передача файлов не ограничиваются этим слоем;
- для FakeTLS после повторяющихся post-ClientHello сбоев мягко меняет следующий
рецепт запуска: сначала включает мягкую фрагментацию ClientHello, затем
переключает `Auto`/`Auto rotate` на другой Android TLS-профиль, затем включает
более тихий темп открытия соединений. Ручной профиль из GUI не перетирается;
- сбрасывает штраф после реального ответа прокси:
`server_hello_hmac_ok`, `first_tls_app_recv` или
`first_mtproxy_packet_recv`.
Это не DRS и не мультиплексирование. Слой не трогает уже установленный
MTProto-трафик и не держит слот до конца TCP-сессии. Его задача — уменьшить
шум повторных попыток и дать GUI/логам понятную фазу:
`endpoint_cooldown`, `dns_cache_hit`, `dns_cache_store`,
`phase_adaptive_recipe`.
Java-планировщик проверок хранит свой endpoint-state для фоновых и ручных
proxy-check. Автопереключение прокси учитывает этот state: fallback-ротация не
выбирает endpoint, который находится в свежем failure-backoff после проверки или
в свежем native `endpoint_cooldown`. Короткая пауза после успешной проверки
нужна только чтобы не перепроверять рабочий прокси слишком часто; она не
считается failure-backoff и не мешает выбрать свежий рабочий прокси.
Если scheduler пропускает повторную проверку из-за backoff, строка прокси
получает тот же видимый статус `endpoint_cooldown`, чтобы список не выглядел как
"не проверено" во время намеренной паузы.
Java backoff использует ту же фазовую идею ключей: pre-TLS, `dd` no-response и
ранние post-appdata срывы (`host_resolve_failed`, `tcp_not_connected`,
`network_block_suspected`, `tcp_connected_no_pong`,
`mtproxy_packet_sent_no_response`, `dropped_early_after_appdata`) пишутся в
`host:port` state, чтобы не проверять тот же сетевой endpoint пачкой через
другой секрет. При этом активные proxy-check запросы склеиваются по полному
ключу `host:port:username:password:secret`, чтобы результат проверки одного
секрета не применился к другому секрету.
Если текущее подключение публикует terminal-фазу вроде `host_resolve_failed`,
`tcp_not_connected`, `client_hello_sent_no_server_hello` или
`mtproxy_packet_sent_no_response`, автопереключение не ждёт общий длинный таймаут
статуса "подключаемся". Оно ставит короткий delayed fallback и выбирает другой
кандидат через те же фильтры freshness/failure/cooldown/backoff. Это особенно
важно для `dd`/legacy MTProxy: `mtproxy_packet_sent_no_response` возникает уже
после TCP и первого MTProxy-пакета, поэтому JA4 и ClientHello тут не помогут.
Native-слой дополнительно ограничивает ожидание первого ответа для plain
MTProxy: если после отправки первого MTProxy-пакета входящих байтов нет, фаза
быстро становится terminal-ошибкой и попадает в общий backoff/fallback, вместо
того чтобы несколько секунд выглядеть как обычное "подключаемся".
Такая live terminal-фаза также попадает в endpoint-state `ProxyCheckScheduler`,
чтобы fallback не перескочил на другую строку списка с тем же endpoint'ом.
Повтор той же фазы в пределах короткого окна de-dup не увеличивает backoff
повторно: это защищает от двойного счёта, когда native сначала публикует фазу,
а затем почти сразу закрывает socket с тем же diagnostic.
Общее состояние Telegram `Connected` считается более слабым сигналом, чем
свежая конкретная proxy-фаза: generic `Connected` не затирает ни живые стадии
вроде `client_hello_sent` / `first_tls_app_sent`, ни terminal-ошибки вроде
`host_resolve_failed`, `tcp_not_connected` или `mtproxy_packet_sent_no_response`
в GUI. Ошибку снимает только конкретный успешный proxy-check или новая data-path
фаза этого же прокси, например `first_tls_app_recv` /
`first_mtproxy_packet_recv`; `server_hello_hmac_ok` остаётся стадией
рукопожатия, а не доказательством рабочего трафика. Явный новый старт подключения
из GUI или rotation публикует `connect_start`, чтобы пользователь видел новую
попытку вместо старой terminal-ошибки выбранной строки, но не стирает свежий
usable success: в течение hold-окна после `first_tls_app_recv` /
`first_mtproxy_packet_recv` локальный `connect_start` удерживается как live
telemetry.
Автопереключение также не выбирает кандидата со свежей незавершённой live-фазой:
старый `available=true` не должен перебивать `client_hello_sent`,
`first_tls_app_sent` или ожидание первого plain MTProxy-ответа.
### Диагностика запуска подключения
Добавлены диагностические точки для MTProxy-подключения:
- `connect_start`;
- `endpoint_cooldown`;
- `tcp_connect_gate`;
- `dns_coalesce_wait`;
- `dns_cache_hit`, `dns_cache_store`;
- `phase_adaptive_recipe`;
- `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`, `admission_tcp_failure_cooldown`,
`admission_failure_cooldown`.
В admission-маркерах есть `admission_mode` и `connection_pattern`.
Эти же live-фазы прокидываются в Java для выбранного текущего прокси. Поэтому
окно прокси показывает не один общий текст "подключаемся", а конкретную стадию:
ожидание слота, DNS-разрешение, открытие TCP, отправка `ClientHello`, ожидание
`ServerHello`, запуск MTProto и первые MTProto-данные. Служебные проверки
списка прокси не меняют live-стадию выбранного подключения.
Native live-stage передаётся с ключом `host:port:тип секрета:SNI` для MTProxy
(`host:port` остаётся только fallback для сетевых слоёв), поэтому поздняя фаза
от другого секрета на том же IP:port не перетирает текущий выбранный прокси.
Это нужно, чтобы отличать 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 не завершился;
- `mtproxy_packet_sent_no_response` — для обычного `dd`/legacy MTProxy: TCP
открылся, первый MTProxy-пакет отправлен, но ответ сервера не пришёл;
- `endpoint_cooldown` — клиент выдерживает короткую паузу перед новой попыткой
к этому же endpoint'у после свежего фазового сбоя;
- `tcp_connect_gate` — клиент ждёт, пока закончится уже активная TCP
connect-попытка к тому же MTProxy endpoint'у;
- `dns_coalesce_wait` — клиент ждёт уже запущенное DNS-разрешение того же
доменного прокси, чтобы не создавать пачку одинаковых DNS-запросов;
- `dns_cache_hit` / `dns_cache_store` — клиент использовал или обновил
сохранённый IP доменного прокси;
- `phase_adaptive_recipe` — клиент изменил следующий мягкий рецепт FakeTLS
после сбоя на фазе рукопожатия или первых данных: фрагментация ClientHello,
Android TLS-профиль или темп открытия соединения;
- `network_block_suspected` — повторный TCP-срыв на том же endpoint, похожий на
фильтрацию пути до ответа MTProxy;
- `waiting_tcp` — текущий выбранный прокси ещё ждёт TCP-подключение;
- `client_hello_sent_no_server_hello` — ClientHello отправлен, валидный
ServerHello не пришёл;
- `server_hello_hmac_mismatch` — ServerHello пришёл, но не прошёл HMAC-проверку
MTProxy;
- `post_handshake_no_appdata` — рукопожатие прошло, но первые MTProto-данные не
пришли;
- `dropped_early_after_appdata` — первые MTProto-данные пришли, но соединение
быстро умерло; это post-handshake lifecycle/endpoint-сигнал, а не повод
менять JA4;
- `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 здесь намеренно нет. APK собирается из исходников локально
или через GitHub Actions.
Для локальной сборки нужны Android Studio 2025.1.4, Android NDK 27.2.12479018
и Android SDK 35. После клонирования и при каждом обновлении upstream
инициализируй нативные зависимости:
```sh
git submodule update --init --recursive --depth=1
```
1. Получи `api_id` и `api_hash` на [my.telegram.org](https://my.telegram.org).
2. Укажи свои значения в проекте Telegram Android.
3. Подставь свой ключ подписи релизной сборки.
4. Собери через Android Studio или запусти workflow `Build ZaStoGram APK` в
GitHub Actions.
GitHub Actions настроен на ccache с `CCACHE_COMPILERCHECK=content`, чтобы
повторные сборки нативного кода были заметно быстрее после прогрева кэша.
Workflow собирает отдельные standalone APK под ABI:
- `ZaStoGram-standalone-arm64-v8a.apk`;
- `ZaStoGram-standalone-armeabi-v7a.apk`;
- `ZaStoGram-standalone-x86.apk`;
- `ZaStoGram-standalone-x86_64.apk`.
После успешного запуска создаётся отдельный prerelease для этого run'а, и APK
прикладываются к нему напрямую. GitHub artifacts всё ещё скачиваются как ZIP, а
release asset — это уже сам `.apk`.
На время диагностики MTProxy GitHub Actions включает сетевые логи Telegram прямо
в обычных ABI-артефактах. После установки такого 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.
Рядом создаётся `mtproxy_runtime_contract.txt`. Это строгая проверка live-лога:
в `mtproxy_markers.txt` должны быть `transport_state=...`,
`endpoint_handshake_ok` и `endpoint_data_path_success`, причём
`endpoint_data_path_success` не может идти с `reason=server_hello_hmac_ok`.
`endpoint_data_path_success` должен появляться только после первого `first_tls_app_recv`
или `first_mtproxy_packet_recv` на том же native connection: успешный
`server_hello_hmac_ok` сам по себе подтверждает только рукопожатие, а не
рабочий data-path.
Если надо перепроверить уже сохранённую сессию вручную:
```bash
python3 Tools/verify_mtproxy_runtime_logs.py mtproxy-logs-live/<session>
```
В отчёте есть блок `Layer recommendations`. Он не делает вид, что автоматически
доказал DPI, а раскладывает фазы по слоям ремонта:
`dns_endpoint_stability` для DNS/TCP до ClientHello,
`faketls_handshake_recipe` для pre-ServerHello FakeTLS-сбоев,
`plain_dd_endpoint_backoff` для обычных `dd` no-response и
`faketls_data_path` для обрывов после завершённых FakeTLS ApplicationData.
В этот же endpoint-слой попадают native proxy-check отказы
`fail:tcp_not_connected` и `fail:tcp_connected_no_pong`, чтобы GUI-ошибка
"TCP не открылся" учитывалась в общей картине, даже если полноценная
`ConnectionSocket`-попытка ещё не дошла до FakeTLS.
Для сравнения профилей и endpoint'ов рядом создаются CSV:
- `mtproxy_attempts.csv` — каждая FakeTLS-попытка: endpoint, профиль, фаза
отказа, задержка до HMAC, close/error и набор увиденных маркеров;
- `mtproxy_endpoint_profile_stats.csv` — агрегат по `endpoint + profile`:
количество попыток, процент успеха, топ отказов, медиана HMAC и максимальный
burst рукопожатий за 1 и 5 секунд. В этом же файле есть
`tls_frames_completed`, чтобы отличать обрывы до data-path от обрывов после
реально завершённых FakeTLS `ApplicationData`-записей. Для длинных сессий
счётчик берётся из итогового `mtproxy_disconnect tls_frames_completed=...`,
поэтому анализатор не зависит только от первых подробных per-frame markers;
- `mtproxy_plain_account_stats.csv` — отдельный срез обычных `dd`/legacy
MTProxy по account/DC/type: первый пакет отправлен, пришёл ли первый ответ,
были ли `invalid_packet_length`, `auth_404` и ранние disconnect;
- `mtproxy_proxy_check_stats.csv` — агрегат native proxy-check по endpoint:
`ok`, `tcp_not_connected`, `tcp_connected_no_pong` и close-reasons. Этот файл
нужен, когда GUI показывает "TCP не открылся" ещё до полноценной FakeTLS или
MTProto-попытки;
- `mtproxy_scheduler_stats.csv` — агрегат Java scheduler по endpoint:
`enqueue`, `start`, `finish_ok`, `finish_fail`, `backoff`, `skip_backoff`,
`finish_keep_connected` и `phase_*`. Этот файл показывает, создаёт ли список
прокси лишний шум проверками или, наоборот, правильно сдерживает повторные
попытки после сетевых фаз.
Именно эти файлы нужны, чтобы отделить нашу ошибку транспорта от внешней
блокировки или нестабильного прокси.
Перед публичным релизом это нужно выключить обратно, чтобы не оставлять
подробные сетевые логи в обычной сборке.
## Как пользоваться
1. Подними MTProxy с FakeTLS вне зоны блокировки.
2. Используй `ee`-секрет с доменом.
3. В приложении добавь MTProto-прокси обычным способом:
**Настройки -> Данные и память -> Прокси -> Добавить прокси -> MTProto**.
Маскировка из этого форка применяется именно к FakeTLS/`ee`-секрету. Для обычных
`dd`-секретов и других вариантов без FakeTLS эти изменения не дают того же
эффекта.
Для WSS режима открой:
**Настройки -> Данные и память -> Прокси -> WSS-транспорт**. Официальный WSS
использует `kws2/kws4.web.telegram.org` для DC2/DC4, не требует MTProxy-секрета
и не требует своего сервера. Для своего gateway укажи host/port/path, а SOCKS5
upstream выбери отдельно в WSS-секции списка прокси, если он нужен.
Для плагинов exteraGram открой:
**Настройки -> Плагины -> +** и выбери локальный `.plugin` файл. Ставь только
доверенные плагины: они выполняются внутри процесса приложения и могут
рефлексией или hooks менять поведение клиента.
## Честные ограничения
- SNI не ротируется сам по себе. Он берётся из `ee`-секрета.
- Фаза данных пока не имитирует настоящий HTTP/2 или HTTP/1.1.
- WSS — отдельный транспорт, а не волшебная замена MTProxy. Официальные WSS
route'ы ограничены теми DC, где они реально поддержаны; для остальных путей
нужен fallback или свой gateway.
- Управляемый размер TLS-записей и тайминги данных включаются вручную и нужны
для диагностики, а не как доказанная универсальная защита.
- Стартовая маскировка действует только в начале установленного FakeTLS-сеанса
и не добавляет внешний браузерный трафик.
- Мягкое сокращение каналов уменьшает параллельность download/upload при
активном MTProxy, но не превращает MTProto в браузерный HTTP-трафик.
- Планировщик рукопожатий выключен по умолчанию и включается в GUI. Даже во
включённом виде он ограничивает только фазу открытия FakeTLS-соединения и не
маскирует всю статистику установленного MTProto-трафика.
- `ZaSto Приватность` — это клиентские локальные override'ы. Они не меняют
серверную политику Telegram и не являются полноценным архивным хранилищем для
всех видов историй, сторис и прочих ephemeral-сценариев.
- Совместимость с плагинами exteraGram покрывает реализованные helper-модули,
lifecycle, settings, post-response hooks и Xposed-style method hooks, но не
гарантирует работу любого публичного `.plugin` без адаптации.
- Плагины выполняются внутри процесса приложения. Ошибочный или вредоносный
плагин может ломать UI, сетевую логику или приватность аккаунта.
- Фоновый keep-awake отключает паузу сети на стороне клиента, но Android всё
равно может ограничивать процесс системными настройками батареи.
- Это не гарантия вечного обхода блокировок: DPI-правила меняются.
- Это неофициальный форк, использовать его нужно с пониманием рисков.
## Потом реализуем
- Расширить DRS из простого runtime-режима в фазовую модель: разные профили для
старта, интерактивного трафика, простоя и передачи файлов, защита от
повторяющихся шаблонов и сброс состояния после долгой паузы. Базовый guard на
цепочку `partial send -> complete TLS frame -> discard payload -> next record`
уже есть; следующий шаг — использовать его для более смелых DRS-профилей.
- Расширить IPT из простых неблокирующих пауз между TLS-записями в модель
межпакетных интервалов с разными правилами для рукопожатия, поддержания
соединения, интерактивного трафика и передачи файлов. Этот слой должен быть
data-aware: если есть ожидающие данные, разрешены только короткие burst-паузы
между уже завершёнными TLS-записями; длинный idle-режим не должен задерживать
активный MTProto-трафик.
- Для startup-паттерна использовать ту же дисциплину, что в TeleMT-плане:
сначала фиксировать измеримые фазы и инварианты, затем включать адаптацию.
Любая новая задержка должна иметь источник сигнала: есть ожидающие данные,
нет ожидающих данных, первый ответ получен, endpoint в cooldown. Случайные
паузы без такого сигнала считаются небезопасными для MTProto.
- Больше стабильных браузерных профилей из `telemt/tdlib-obf`: Android Brave,
дополнительные Chrome/Firefox/Yandex-варианты, Windows/iOS профили, но только
через защитные тесты совместимости с MTProxy.
- Политика ECH с учётом маршрута и автоматическим отключением неудачного
варианта, чтобы не долбить сеть ECH-профилем, если конкретный маршрут его
режет.
- Ограничение 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 и реестру профилей.