Как стать автором
Обновить

Комментарии 155

Этот пост так же хороший и приводит новые технические подробности.


PS: если я правильно понял, автор поста — автор прокси на Python который один из первых и добавил поддержку.

Да, всё так. Написал эту статью параллельно с добавлением поддержки, но, видя как авторы телеграм маскировали её в коммитах бета-версий, решил подождать релиза, чтобы не дать создателям средств DPI времени подготовиться.

Не планируете реализовать «слив» трафика с другим SNI на другой порт?


Т.е когда к вам идёт запрос не на домен который указан в прокси а на другой — показывать обычный сайт.

Как раз экспериментирую с этим.

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь

Ну так со временем будет eSNI и это будет иметь смысл.

Это и сейчас можно сделать разными способами, используя сторонний мультиплексор типа SSLH или даже средствами nginx.

Но было бы гораздо практичнее, особенно для чайников, если бы это было реализовано на уровне прокси. Да и обнаружение было бы гораздо сложнее, сейчас оно всё-таки возможно, если система контроля активная и сама пощупает порт.

Нет нельзя, сейчас домен А может использоваться только: либо телеграм либо веб-сервер.


Доработке прокси позволит использовать один и тот же домен А и для прокси и для веб-сервер. Отправляя запросы на последний в случае ошибки в секрете.

Отправляя запросы на последний в случае ошибки в секрете.


Да, это наиболее верный подход, как по мне.
Можно вообще использовать какой-то отдельный урл для проксирования. Вряд ли сканилка в https найдет что-то вроде domain.com/0aihs9iqwhrhiasdihfhoiaoihsf/
А на самом домене вешать уже нормальный сайт.

URL в TLS mtproto proxy протоколе не используется

Ну так самое время начать. Это же очевидное решение — сто лет назад люди вешали php-анонимайзеры на отдельный урл своего сайта. А теперь https и вовсе не позволит найти этот урл, если его хоть немного спрятать.

Ну для этого придётся полноценный TLS реализовывать. То что в mtproto proxy — хоть и маскируется под TLS 1.3, но внутри совсем по другому устроено.

Ну так это как раз и есть то место, где нужен полноценный https. Как DPI будут бороться с мессенджером, для поднятия прокси которому достаточно на любой свой сайт закинуть по какому-нибудь хитрому урлу php-файл? А если в клиент можно вбивать такие урлы сотнями?
Зачем нужен mtproto proxy, который прямо таки кричит в любой DPI помощнее «вот он я, берите меня, unknown proto!» — тоже неясно.
НЛО прилетело и опубликовало эту надпись здесь
mtproto proxy, который прямо таки кричит в любой DPI помощнее «вот он я, берите меня, unknown proto!»


Почему? DPI никакого URL внутри TLS не увидит, как и не увидит того, что они не используются. Максимум — SNI (так никто не мешает указывать реальный dyndns-хост). DPI видит, что «на хост ходят по TLS» и всё. Расшифровать содержимое оно не может, не зная секрета. Попробует зайти на хост само — получит страничку с котиками (если в проксе настроен проброс невалидных коннектов на реальный веб-сервер).

Где я ошибаюсь, полагая, что у DPI нет критериев для вынесения предположения о содержимом TLS1.3 сессии?
> DPI никакого URL внутри TLS не увидит,
Зато увидит, что работающего TLS на втором конце нет.
Зато увидит, что работающего TLS на втором конце нет.


Я и прошу объяснить — КАК? По каким признакам?
Ткнётся активным сканером и посмотрит, что там.
И получит полноценный TLS с подставленного проксей дочернего сервера. Прокся это умеет, надо лишь настроить.
Во-первых, на момент написания первого моего коммента — не умела.
Во-вторых, активный сканер может прикинуться телего-клиентом и найти проксю, если не использовать какие-то дополнительные признаки — eSNI заголовок или URL.

Активному сканеру, как и клиенту Telegram, нужно знать secret для того, чтобы подключиться к прокси-серверу

Чтобы подключиться — да. Чтобы его обнаружить — нет, не обязательно (по крайней мере в текущем виде). Достаточно послать любой ключ, прокся ответит, что он не верный.

Ключ не посылается в пакетах. Прокси не отвечает что ключ не верный. Если всё-таки отвечает, то это бага в проксе.

активный сканер может прикинуться телего-клиентом

Может прикинуться. Только он не знает секрета. «Ничего не ответил прокси, лишь хомпейдж Википедии выдал…»
НЛО прилетело и опубликовало эту надпись здесь
Речь о том, что без FakeTLS пошарить порт на белом айпи таки можно :)

Впрочем, я думаю, что и с ним можно, если речь именно о белом айпи, а не домене — средствами nginx. Но смысла лично я мало вижу, подожду решения с проверкой ключа.
SSLH не может различить «просто TLS» и «OpenVPN в „tls-crypt“ режиме». По крайней мере, у меня это не заработало ни на 1.18-1 из штатных реп, ни на самособранном 1.20.
SNI же виден при установке соединения TLS.
И поэтому использование какого-то особого SNI — сразу палево, разве нет?

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

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

Закончил экспериментировать и закоммитил эту функциональность. Сейчас можно указывать хост и порт на который будет незаметно проксироваться трафик "плохих" клиентов.


Попробую продемонстрировать как это работает:


  • есть сайт который выглядит для браузера "обычно": https://bitblockinfo.com/
  • в то же время он является замаскированным прокси сервером tg://proxy?server=bitblockinfo.com&port=443&secret=7gARIjNEVWZ3iJmqu8zd7v9iaXRibG9ja2luZm8uY29t.

Это сделано для того, чтобы нельзя было задетектить прокси-сервер по особенностям работы с TLS.

Вот это офигенно!
Супер! И это у меня заработало в цепочке: openvpn (shared port) → mtprotoproxy → apache2. Апач, конечно же, показывает кошечек и порно. Всё на стандартном 443. «Вы знаете? Я счастлив.» © Друпи.

PS: мобильный Telegram (андроид) сходит с ума от сгенерированной и втянутой ссылки на прокси с более-менее длинным TLS_DOMAIN. И просто зависает.

Советую посмотреть это подробнее, вдруг там буфер переполняется и можно хакнуть.

Возможно, но делать ревизию кода мне не по силам.

Я обратил внимание, что сгенерированный секрет с доменом google.com не содержит в конце символа суффикса «=». И с ним моб.клиент работает. А вот с моим доменом секрет имеет в конце суффикс «=» — и клиент ломается. Криво декодируется base64 в клиенте?

Можно проверить, декодировав в обратную сторону. В клиенте для ios есть бага с декодированием некоторых символов base64.

В Telegram Desktop v.1.8.1 же вообще не удается вставить секрет с символами "=" или "%" — они просто не пропечатываются.
С моим доменом секрет получается в таком виде:
7u***************wjjNQRnYXRlLmtueXNob3YuaW5mbw%3D%3D

Кстати, а можно попросить объяснить, на что влияет значение TLS_DOMAIN в текущей реализации? Особенно учитывая, что можно произвольно изменить несколько последних символов секрета уже прописывая его в клиенте и тебе ничего за это не будет?

Сейчас ни на что, кроме генерации ссылки в начале работы. Планирую его использовать на серверах с большим количеством секретов, чтобы по имени можно сразу было бы пробовать расшифровать с нужным секретом.


В ближайший час планирую поменять вывод адреса прокси по умолчанию, чтобы не использовалось base64 пока авторы Telegram не починят клиента для ios.

Поменял код, чтобы в ссылках на прокси не использовался base64. Верну обратно когда пофиксят.

Данная правка помогла также андроид-клиенту (ошибка, видимо, у них общая). Огромное спасибо!
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Подтверждаю, мобильный клиент заработал с hex-секретом.
Почему Python был выбран для разработки? Он же медленнее той же Ноды в пять раз (Нода даже С++ уже уделывает в некоторых областях). Понятно что это потому-что его и учили, ибо он довольно новый, но зачем учить один из самых медленных по всем тестам языков?

На практике проблемы производительности не возникает. Даже самой дешёвой виртуальной машины хватает чтобы обслужить несколько тысяч пользователей. Но столько пользователей набрать тяжело т.к. основные каналы распространения информации о прокси серверах мониторятся. При большом числе пользователей прокси сервер может быть заблокирован по паттернам трафика

Понятно, но проблемы производительности и на PHP не возникает, хотя говорят что тормозный, а он очень даже, до JS конечно не дотягивает, но все же. Вот я и спрашиваю, почему именно Python? По мне это как с парашютом прыгать — можно ноги сломать, причем неиллюзорно так, но я же не прыгаю там, где можно ноги сломать, я на травку прыгаю.

У меня больше всего опыта работы с этим языком. Но есть и другие реализации разной степени продвинутости: на go, erlang, си, c#, nodejs, java, pony и т.д.


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

Ого! Не знал что у него столько реализаций!) Ясно, спасибо, покопаюсь…
он довольно новый

лол


https://en.wikipedia.org/wiki/Python_(programming_language)


First appeared 1990; 29 years ago

https://en.wikipedia.org/wiki/Node.js


Initial release May 27, 2009; 10 years ago

https://en.wikipedia.org/wiki/JavaScript


First appeared December 4, 1995; 23 years ago

Edit: добавил JS

А, полагаю это и есть ответ на мой вопрос, давно его знают просто, вот и пишут) Просто как-то психологически не могу перестроиться на то, что это старый язык. Старыми считаются С, Java, да хоть PHP тот же, что уж там…
1) Нода не в пять раз быстрее Питона.
2) «В некоторых областях» бывает всякое: и Free Pascal быстрее Си и C++, и Нода в пять раз медленее Питона, и PHP отстаёт от Go и C++ на смешные 7-15%.
3) Нода память жрёт как не в себя.
4) Js один из худших языков среди массовых.
Правильно, да :)
Дубль.Может кому-то пригодится: здесь в статье на Хабре можно почитать статью про официальный халявный MTProxy, но главное в комментах кучу живых вариантов реализации MTProxy от python… разных docker образов и пд., которые уже успешно и давно используются в Telegram.

На самом деле не хватает последнего шага:


  1. Возможность серверу определять домен по SNI и если домен не попадает в заданный список, то пропускать коннект дальше (на обычный web сервер с заглушкой).
  2. Возможность работы через WebSocket'ы, работая в качестве backend'а для обычного web server'а типа nginx, при этом добавив минимальную http basic аутентификацию.

Тогда прокси можно будет вешать на реальных живых сайтах, с реальным трафиком. Просто будет поддомен stat.mysite.ru или даже просто конкретный URL на сайте, закрытый http basic auth'ом.
Даже если "постучался" — тебя послали нафиг, а владелец сайта (ну если вдруг ему начнут задавать вопросы) честно скажет "это закрытый раздел сайта".


Ну а телега сможет сделать upgrade до вебсокетов и дальше кидать свой трафик, к примеру завернув туда тот самый mtproxy.
Тот же nginx в качестве фронтенда великолепно подойдёт.
И нет необходимости прикрываться именами типа google.com, наоборот веселее будет кому-то получить прокси на реальных серверах с реальным доменным именем типа bstat.gosuslugi.ru, закрывшись самым настоящим сертификатом *.gosuslugi.ru ;)))

Лучше даже просто кидать 504 Gateway Timeout по прошествии нескольких минут. Без соединения по факту.
Первый плюс — у себя сэкономим трафик и ресурсы.
Второе, — займем ресурсы проверяющего бота.
  1. Тут даже можно пойти дальше — сделать один домен и если HMAC из поля random handshake-пакета не валидный, то редиректить. Сегодня вечером будет версия, которая так делает.
  2. Насколько я понял, тут нужна поддержка со стороны фронта или клиента Telegram. Для себя можно поднимать веб-версии Telegram, но маловероятно, что сторонний человек согласиться ввести свои credentials на сайт, не связанный с тг.

Обязательно нужна поддержка со стороны клиентов, причём тех, которые лежат в google/apple market'ах.
Как минимум — возможность работы через WebSocket.


Но если это реализовать, то блокировать телегу можно будет только тем самым "законом о суверенном интернете" с полной изоляцией российского сегмента сети. И то не факт ;)

Это всё замечательно, но не спасёт от цензуры техногигантов: Telegram на iOS фильтрует некоторые каналы по указке калифорнийский леваков, хотя до этого блокировал лишь террористов, ЦП и за многократные нарушения DMCA.

Вам никто не мешает использовать не родной клиент или официальную веб-версию.

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

Так а что ещё нужно для просмотра заблокированного контента на платформе? Для звонков и всего — основной клиент, для заблокированного — веб версия.

На android не фильтрует же? Пересаживайтесь на android, какие проблемы?
С фильтрацией все интересно)
Место действия Казахстан.
У меня, фильтруются всякие 18+ стикеры, каналы и они мне не доступны. Но моим друзьям, у которых я тестил акки, им все было доступно.
Узбекистан, любая версия Телеграм (андроид, iOS и даже веб) блокирует 18+ каналы (якобы забанены за порнографию)
Так а поставить MtProtoProxy за nginx'ом с валидными сайтами можно?
Я пробовал когда-то с помощью SSLH, но тогда сайты видели все коннекты клиентов как от 127.0.0.1.
У SSLH есть «прозрачный режим», но его настройка несколько геморройна.

Если пока отказаться от этого FakeTLS, то средствами nginx можно проверять версию TLS (например) и неизвестные версии отправлять на МТ-прокси. В этом случае снаружи будет виден порт 443, на него можно будет соединиться стандартным клиентом и увидеть веб-сайт, но соединения от телеграм-клиентов туда же будут видны как неведомая хрень. В теории это не должно быть основанием для блока, так как такой подход используется для OpenVPN и других задач, на практике же непонятно.
Хм, а это интересно. Но что-то не получается. Вы ведь имели ввиду ssl_preread_protocol?
Да.
Вероятно, nginx собран без модуля ngx_stream_ssl_preread_module?
В пакетах по-умолчанию его почему-то нет, надо самому собирать.
Да нет, вроде всё есть в пакетах:
nginx version: nginx/1.16.0
built by gcc 6.3.0 20170516 (Debian 6.3.0-18+deb9u1)
built with OpenSSL 1.1.0j 20 Nov 2018 (running with OpenSSL 1.1.1c 28 May 2019)
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -fdebug-prefix-map=/data/builder/debuild/nginx-1.16.0/debian/debuild-base/nginx-1.16.0=. -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'
Может что-то не так делаю…
Может :)
У меня работает примерно так:

map $ssl_preread_alpn_protocols $upstream {
default ssh;
~\bhttp/0.9\b web;
~\bhttp/1.0\b web;
~\bhttp/1.1\b web;
...


Но там у меня по alpn и несколько другие задачи. На web висит веб-сервер с сайтами, на ssh всякая байдистика и мтпрото там тоже есть — полёт нормальный :)
А вебсервер на порту, отличном от 443?
Конечно, как иначе :)
Схема примерно такая:
stream {
upstream ssh {
server localhost:550;
}

upstream web {
server localhost:410;
}


map $ssl_preread_alpn_protocols $upstream {

default ssh;
~\bhttp/0.9\b web;
~\bhttp/1.0\b web;
~\bhttp/1.1\b web;
}

server {
listen 443;

proxy_pass $upstream;
ssl_preread on;
}
}


Соответственно, ниже в конфиге веб-сервер слушает порт 410, а 550 — там уже висит прокси и ещё кое-что.

Можно попробовать намудрить примерно то же самое, но с проверкой по версии TLS или ещё чему-нибудь.

Теперь можно.

В хендшейке нет отправки сертификата сервером. И дальнейших шагов. DPI легко может блочить такую сессию как невалидную ИМХО.

Это TLSv1.3.

Ну так Телеграм то мимикрирует под 1.2, а не 1.3.
И если они будут мимикрировать под 1.3 то все равно сертификат нужен будет, просто он будет в том же пакете что и ServerHello.

А если сертификата нет, то для любой вменяемой DPI это знак что надо этот траффик роутить в blackhole.

Нет, телеграм маскируется под TLS 1.3

Откуда такая уверенность?
Для TLSv1.3 он в ClientHello должен посылать 0x0304 в расширении supported_versions, а сервер отвечать тем же. И Wireshark это детектит. В коде прокси я нашел только 0x0303 т.е. TLSv1.2

https://tools.ietf.org/html/rfc8446#section-4.1.2


legacy_version: In previous versions of TLS, this field was used for
version negotiation and represented the highest version number
supported by the client. Experience has shown that many servers
do not properly implement version negotiation, leading to "version
intolerance" in which the server rejects an otherwise acceptable
ClientHello with a version number higher than it supports. In
TLS 1.3, the client indicates its version preferences in the
"supported_versions" extension (Section 4.2.1) and the
legacy_version field MUST be set to 0x0303, which is the version
number for TLS 1.2. TLS 1.3 ClientHellos are identified as having
a legacy_version of 0x0303 and a supported_versions extension
present with 0x0304 as the highest version indicated therein.
(See Appendix D for details about backward compatibility.)

Я читал RFC. И поэтому еще раз могу сказать — в коде Proxy, которое описывается в статье:
1) нет 0x0304 в supported_versions
2) нет сертификата

Поэтому, как мне кажется, полезность данной обфускации невелика. Вот Websockets с реальным сертификатом было бы полезнее гораздо.

Константа 0x0304 есть в коде клиента т.к. именно клиент отсылает пакет с supported_versions.

В клиенте есть, но прокси тоже должен ответить 0x0304 чтобы сессия выглядела валидной. Плюс, опять же, сертификат должен быть чем-то хотя-бы декодируемым в X.509. Без этого сессия для DPI будет выглядеть не такой, как 99.99% других.

Как по мне — было бы идеально добавить способ использовать обычное TLS туннелирование (наверное с обмазкой всякими ALPN и прочим h2 чтобы выглядеть как HTTPS) и гонять MTProto уже внутри без модификаций.

Таким образом можно было бы поднять прокси на валидном TLS сертификате и полностью удовлетворять TLS-спеке если глядеть снаружи…
В клиенте есть, но прокси тоже должен ответить 0x0304 чтобы сессия выглядела валидной. Плюс, опять же, сертификат должен быть чем-то хотя-бы декодируемым в X.509.

Не знаю, запустил wireshark, дернул страничку своего сайта через TLSv1.3 + HTTP/2 — пакеты такие же, как у телеграма. Нет в ServerHello никаких 0x0304, нет X.509 сертификатов.


Наверное если активно пытаться по https подключиться, то разница будет заметна. Но в пассивном режиме не вижу разницы.

Нет в ServerHello никаких 0x0304
Вот кусок ответа cloudflare.com:



нет X.509 сертификатов.
Т.е. вы считаете что сертификаты не участвуют в TLS 1.3 хендшейке? :) На самом деле Wireshark, судя по всему, еще не особо умеет 1.3 разбирать. Если глянете на ServerHello там большая часть пакета вообще никак не помечена — там сертификат и едет.
Супер! Вот я теперь про сертификат думаю… У телеграмма ServerHello + CCS на вашем скрине 290 байт, а в реальном хендшейке там сзади еще Certificate Chain и еще что-то едет и пакет почти всегда в MTU упирается т.е. больше 1.5кб

Возможно имеет смысл реализовать что-то вроде:
1. Берем тот домен, который указан в SNI. Сейчас, как я понял, в клиенте захардкожен google.com
2. Резолвим, лезем туда по TLS на 443 порт
3. Тащим оттуда цепочку сертификатов
4. Кешируем
5. Отдаем ее всем клиентам в хендшейке
6. Повторяем пункты 1-4 в бэкграунде раз в Х минут

как я понимаю CertificateСhain едет в ApplicationData фрейме? У меня, как и в python прокси, первый ApplicationData фрейм генерится как случайные данные случайной длины от 0 до 256:
https://github.com/seriyps/mtproto_proxy/blob/5e601bce3e19cdc3d3f2b77313e3ee57b8dce482/src/mtp_fake_tls.erl#L109
https://github.com/alexbers/mtprotoproxy/blob/e43ae9991102c8471c7f2436df63f7d64906940c/mtprotoproxy.py#L839
Возможно стоит увеличить размер?


Как я понимаю, засовывать туда реальный сертификат смысла большого нет, т.к. он зашифрован и пассивным анализом трафика его не расшифровать?

Да, согласен, я как-то пропустил что в 1.3 начали сообщение Certificate шифровать. Поэтому и Wireshark его не выделяет теперь.

Если там нет какой-либо структуры т.е. это просто бинарный блоб который парсится только после дешифровки (а так оно скорее всего и есть, но не читал пока), то думаю достаточно генерировать побольше мусора чтобы добить размер пакета до 1.5кб и в таком случае будет неотличимо от «средней» реальной сессии с цепочкой сертификатов из 2-3 штук.

Спасибо за отличные мысли. Воплотил их в коде. Теперь прокси-сервер маскируется как tls 1.3 и отдаёт 1024-4096 случайных байт под видом зашифрованного сертификата.

И DPI мгновенно определит это опросом, в том случае, когда его пробросит на «реальный https» (если это настроено). Просто разницей в длине пакетов. :(

Сходу правило — «блокировать сайт, где TLS-соединения с разных клиентов дают существенно разную длину ApplicationData первого TLS-фрейма». Оно будет даже не слишком тяжёлым для DPI и не потребует активного сканирования https/tls DPI-агентом.
Большое спасибо, отличный прокси!

Следующим шагом, возможно, полезно было бы периодически дергать google.com и получать от него цепочку сертификатов — достаточно просто ее длины в зашифрованном виде. Пока в клиентах захардкожен Гугл, то можно только его юзать, а дальше по ситуации.

И отдавать клиентам не рандом в диапазоне 1..4kb а именно эту длину. Так будет еще правдоподобнее :)

Но, возможно, это уже оверкилл. Хотя реализовать не сложно.
в клиентах захардкожен Гугл

Тут одно «но»: отдать невалидному клиенту MASK_HOST:MASK_PORT с захардкоженным google.com не получится — браузер заругается, что хост, на который пришли, и домен в полученном сертификате не совпадают.

Смотря как заходить браузером. Если заходить по ip-адресу или по другому имени, то заругается, а если по правильному имени, то не заругается. Самый простой способ зайти браузером "правильно" — прописать google.com в /etc/hosts или аналогичный файл.


Аналогично ведёт себя и настоящий google.com.

если по правильному имени, то не заругается

Это надо в hosts ковыряться, а активному агенту-сканеру РКН вы в hosts не залезете. :) Лучше уж ip своей прокси в любом dynamic dns сервисе зарегистировать и поставить его в TLS_DOMAIN. Всё красиво, все довольны.
Не, тут речь именно про анализ трафика «снаружи» с помощью DPI и как можно сделать сессию аналогичную настоящей.

Защита от каких-либо пробников, которые не зная секрета сканят адрес в поисках Telegram прокси — это ортогональная задача.

Получив первый пакет от клиента (ClientHello) мы уже понимаем знает он секрет или нет. И если он знает, то отвечаем телеграмным ServerHello с прилепленным рандомом размером с реальный Certificate Chain гугля.

А если нет, то проксируем на MASK_HOST:MASK_PORT прозрачно (это и так делается)
Я как раз о том, что: а) реальный CertChain проксируемого сайта ещё надо узнать (хотя вот автор даже не против это внедрить) — раз; б) DPI (не знающий секрета) постучался к нам на проксю по ip адресу, его прокинуло на гугль, он получил сертификат гугля, посмотрел на имена хостов в сертификате, разрезолвил все — вашего ip там нет. Добро пожаловать в бан. Собственно, он может сделать это и раньше — смотреть на SNI в запросах и сравнивая dest ip с результатами резолвинга домена из SNI. ДПвБ.

Единственный способ избежать этого: a) не подделывать tls_domain, а иметь настоящий, правильно ресолвящийся в ip нашего хоста; б) проксировать сайт, отдающий либо правильный сертификат с правильным хостнеймом, либо самогенерированный, но всё равно с правильным хостнеймом.
а) Это не проблема
б) DPI не стучится никуда, он зеркало траффика анализирует :) Но суть понятна, стучится некая проба.

Так вот, вы сертификат который google.com отдает видели? Там в subjectAltName наверное сотня вайлдкардов вида *.google.com, *.google.co.uk и так далее. Как проба их резолвить будет? :)

Ну даже если бы это было возможно — Гугл работает через anycast и прочий GeoDNS и в зависимости откуда отправлен запрос — IP будут совсем разные. Причем они еще могут ротироваться из сотни вариантов отдавая в DNS случайные N штук.
Но суть понятна, стучится некая проба

Да. И они уже давно работают. Отложенное сканирование целевых хостов по логам подозрительной активности делается.

сотня вайлдкардов

Я понял суть вашего посыла. :) Но редиректить на гугл — это всё равно как-то не по фэн-шую. Хотя, может, я и слишком заморачиваюсь.
он зеркало траффика анализирует

Как я уже писал, достаточно проверить, что хост отвечает при установлении TLS1.3-соединения сильно рандомными по длине ApplicationData (хранящим CertChain).
б) проксировать сайт, отдающий либо правильный сертификат с правильным хостнеймом, либо самогенерированный, но всё равно с правильным хостнеймом.
Проблема в том что в данный момент телеграм шлет google.com. Если они это изменят и будут в SNI отправлять hostname прокси — тогда да.
Нет, не шлёт. Получив ссылку https://t.me/proxy?server=ip&port=443&secret=eesecret[hex:hostname], где к секрету дописано ещё и имя хоста (TLS_DOMAIN в терминах автора прокси) и запомнив её, телега отправляет в запросе совершенно нормальный SNI с именем хоста из ссылки.

Я только что проверил это в wireshark.
Ну, если дописать — то хорошо. Только по дефолту прокси генерирует ссылки в обычном формате без хостнейма и 99% людей ИМХО пользуются ими.

А так, конечно, хорошо — осталось проверять размер certchain у хоста в MASK_HOST и отдавать такую же длину.
Только по дефолту прокси генерирует ссылки в обычном формате без хостнейма

Если не указан TLS_DOMAIN — да. Но в нём-то как раз самая мякотка. :)
Я бы с удовольствием отдавал точно ту cert chain, которую отдаёт мой Апач, который показывает РКН не знающим секрета юзерам котиков. Там честный-благородный LE, отчего ж не отдать. И выглядеть будет при возможном скане абсолютно нормально.

Думаю, стоит периодически опрашивать https-сервер за ним и сохранять ответы на handshake (extensions и сертификат). Причём делать несколько запросов, чтобы понять какие части extensions случайные, а какие — нет. Возможно добавлю и это тоже

Просто отлично! Ждём с нетерпением.

Готово. Набор TLS extensions оказался одним и тем же у большинства tls 1.3. Хотел ещё незашифрованные сертификаты из tls 1.2 поддержать, но клиенты Telegram, как выяснилось, поддерживают только зашифрованные.

Рукоплещу. Пошёл тестировать…
Работает. Пришлось, правда, поставить экспериментальный Апач с поддержкой TLS1.3.

PS: ssl handshake к проксе и к реальному сайту очень сильно различаются. Например, секций application data у апача две, а у телеги — одна. Так что её все равно определят и забанят, если захотят. Хотя это будет накладно, поскольку придётся парсить tls.

В application data заворачиваются все данные, которое посылает приложение, их должно быть не один или два, а много.


Реальный гугл:
1


Прокси-сервер:
2

Вместо PyCrypto лучше использовать пакет cryptography. PyCrypto заброшен и много лет не поддерживается.


Хорошо было бы сделать нормальный python-пакет, добавить setup.py файл и указать зависимости, для возможности установки вашего пакета стандартными средствами, например, в виртуальное окружение.

Прокси поддерживает сразу несколько библиотек:


  1. Cryptography
  2. Если Cryptography не найден, то PyCryptodome или PyCrypto
  3. Если ничего не найдено, то испольуется медленная реализация криптографии на Python

Python-пакет есть: https://pypi.org/project/mtprotoproxy/, правда там стабильная версия, в которой ещё нет экспериментальной поддержки TLS.


Ещё есть образ на докерхабе: https://hub.docker.com/r/alexbers/mtprotoproxy

А как вы сделали пакет на pypi? В репозитории не вижу ничего для packaging (setup.py, pyproject.toml, etc).


Я пока просто взял последнюю версию из исходников и запускаю через сервис systemd. Не люблю я докеры-шмокеры для таких простых вещей. :)

Он в параллельной ветке: https://github.com/alexbers/mtprotoproxy/tree/pypi.
С докером хорошо в том плане, что можно зафиксировать environment: версию Python и библиотек. Другие способы хороши, но поддерживать пользователей чуть сложнее, приходится спрашивать какая у них ОС, как они запускают и т.д. Это позволяет поддерживать пользователей бесплатно, не тратя на это много свободного времени.

У меня и основная ветка отлично работает под pypy3. Аж с песнями. Стоит пакет cryptography и тоже без проблем работает.

Кстати можно несколько прокси-серверов на одном и том же порту запускать если нужна суперскорость. В этом случае, если машина многоядерная, будут использоваться все ядра. А если много оперативной памяти то можно размеры буферов увеличить, TO_CLT_BUFSIZE и TO_TG_BUFSIZE в конфиге.

С PyCrypto, кажется, не работает нововведение с TLS. Сервер валится если использовать expiremental-ключ:


...
mtprotoproxy/mtprotoproxy.py", line 430, in read
    return self.decryptor.decrypt(data)
/lib/python3.5/site-packages/Crypto
    return self._cipher.decrypt(ciphertext)
TypeError: argument must be read-only bytes-like object, not bytearray

С cryptography получилось подключиться с Android-клиента.


Зачем вообще тянуть поддержку чего-либо кроме cryptography? PyCrypto заброшен, ваша реализация медленная. Зачем это нужно, если есть нормальная библиотека, которая активно поддерживается?

А каким клиентом на Андроиде подключались? Официальный и его бета у меня чего-то не шмогли.

Официальная последняя версия v5.10.0 (1684).
Посмотрите в логи, возможно, у вас на сервере тоже exception происходит.

Неа, не происходит. И у меня cryptography установлен, так что должен работать по идее. Дамп траффика не смотрел пока, но в логах тихо, а на телефоне — Connecting…

Аналогично.
cryptography установлен. клиент v5.10.0 (1684)

А ключ какой юзался? Тот который прокси выводит в stdout при старте с ремаркой experimental?

Да, ключ experimental, в конфиге TLS_ONLY = False. На двух андроид-устройствах попробовал, подключается и работает.

Странно. Всё то же самое, конфиг:
PORT = 1443

USERS = {
    "tg":  "xxx",
}

SECURE_ONLY = True


Пробовал и без SECURE_ONLY, ничего не меняется. Проксю последнюю из GIT вытянул.
cryptography обновлен?
Из репы Ubuntu 18, возможно старый конечно. Попробую из pip дернуть.

Не пользуюсь вообще пакетами из репы дистра. Всегда venv и те версии пакетов, какие нужны, либо самые последние. Так всегда надёжнее, удобнее и проще.

После установки cryptography из pip внутри venv все заработало, спасибо!

Насчет надежнее — да, скорее всего. Удобнее и проще — врядли. Тот же cryptography входит в базовую ОС и городить только ради него venv было не охота.

Советую использовать PyCryptodome или cryptography,
PyCrypto заброшен, как вы верно сказали. Думаю дропнуть его поддержку.

Устранил вероятную причину такого поведения. Проверьте, пожалуйста, что стало лучше.

России пока не научились, но закон уже приняли.

Вообще нет, на yota невозможно пользоваться mtproto-проксями как раз из-за этого.

Да, вы правы, отдельные провайдеры экспериментируют с этим, но массово, как в Иране или Китае пока не блокируют по протоколу.

на yota невозможно пользоваться mtproto-проксями

не испытывал затруднений на Yota с MTProxy в dd-режиме. Хотя шейпить и детектить трафик они пытаются, да

Картинки не загружается, работает очень медленно. Что с dd, что без него.

НЛО прилетело и опубликовало эту надпись здесь
При запуске прокси на Python вижу приписку в конце:
The default secret 0123456789abcdef0123456789abcdef is used, this is not recommended
На это обращать внимание?

Конечно, поменяйте секрет на свой, сейчас там 1234...

Сгенерируйте свой собственный секрет и измените в конфиге. Генерировать так, например:


openssl rand -hex 16

Это значит, что обнаружить прокси автоматическим сканированием с перебором секретов будет легче. Лучше использовать уникальный секрет.

Может лучше форсить пользователя задать какой-то секрет, либо генерировать при запуске рандомный? А то многие пойдут по пути наименьшего сопротивления…

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

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

Telegram, это лучшее, что случилось с 1996 года с интернетом (по личным наблюдениям)
А вы не из роскомнадзора?
о, еще работает! насколько долго планируете его держать включенным?

Боюсь обещать, зависит от нескольких непредсказуемых факторов.


Кстати, сегодня, после двух недель тестирования и ~50 коммитов, вышел стабильный релиз прокси-сервера. Хотел бы поблагодарить людей, которые писали отличные идеи в комментарии, в личку, слали pull request'ы, рассказывали о своём опыте работы и багах с которыми они встречались.


С багами отдельная история. Так, например, один баг оказался в реализации TCP в ядре Linux. Другой — в реализации PyPy. Третий — в реализации некоторых клиентов Telegram. В предыдущих версиях баги находились в реализации asyncio и в модуле криптографии. Осталось ещё в сервере Telegram найти багу и получится полный комплект :)

Мой вопрос связан с моими пробелами в работе с командной строкой. Подскажите, пожалуйста, где я туплю.
Сервер расположен на AWS. Прокси работает идеально, пока в терминале так:
ubuntu@ip-136-32-17-212:~$ cd mtprotoproxy
ubuntu@ip-136-32-17-212:~/mtprotoproxy$ python3 mtprotoproxy.py
tg: tg://proxy?server=17.221.145.24&port=4433&secret=ee20200004000700004000503450600001676f6f676c6rtyu36fsd (new)

То есть я зашёл по ssh, запустил прокси от пользователя ubuntu, и он от него работает. Но стоит мне закрыть терминал, как соединение с прокси в клиенте обрывается. Насколько я понимаю, «python3 mtprotoproxy.py» перестаёт выполняться. Как оставить его работать?

Самый быстрый способ — использовать утилиту screen. Тогда будет работать до следующей перезагрузки. Как сделать чтобы работало и после перезагрузки зависит от ОС. Обычно это написание специального unit-файла для systemd. Планирую описать это подробнее в гитхабовской wiki.


Если запускать рекомендуемым способом, через docker-compose, то запуск после перезагрузки происходит автоматически.

Большое спасибо, screen помог.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации