Что скрывает MAX
3 поста
3 поста
1 пост
Сегодня затронем все те вопросы которые всплывают в комментариях: есть ли вообще в MAX шифрование? Как он смотрится в сравнении с Telegram, WhatsApp и Signal? Что там с безопасностью личных файлов?
Как сказал один "великий" человек: добавить шифрование можно, а зачем ?
В MAX нет сквозного e2e шифрования (от пользователя к пользователю) ни для одного типа общения - личные чаты, группы, звонки, голосовые, файлы
Все сообщения идут открытым текстом через внутри обычного TLS - сервер VK видит каждое слово
Для сравнения: в Telegram есть Secret Chats и E2E-звонки, в WhatsApp заявлен и проверен исследователями E2E для всего, в Signal - E2E + open source
На телефоне всё хранится в открытой SQLite базе данных
Фотографии из чатов доступны по прямой ссылке без авторизации и не истекают годами
MAX - единственный из четвёрки, где владелец видит 100% сообщений и файлов
Когда вы отправляете сообщение в мессенджере, оно проходит через сервер. Вопрос: может ли сервер его прочитать?
Два варианта:
TLS (транспортное шифрование) - сообщение зашифровано по дороге от вас до сервера и от сервера до собеседника. Но на сервере оно лежит в открытом виде. Сервер видит всё. Это как если бы вы отправили письмо в конверте через почту, а на сортировочном пункте его открыли, прочитали, положили в новый конверт и только потом отправили получателю.
E2E (сквозное шифрование от пользователя к пользователю) - сообщение зашифровано ключом, который есть только у вас и собеседника. Сервер передаёт зашифрованный blob и не может его прочитать. Это как отправить запертый сейф - почта передаёт, но ключ только у получателя.
В MAX - только TLS. E2E нет.
Декомпилировал APK, посмотрел wire-протокол, проанализировал трафик. Вот мои результаты:
Личные сообщения - plaintext MsgPack по TCP. Из одного перехвата за 30 минут я извлёк 22 телефонных номера и 6 уведомлений о прочтении - всё открытым текстом.
Групповые чаты - тот же протокол, тот же формат. Никакой разницы с личными.
Каналы - аналогично.
Голосовые звонки - шифрование DTLS-SRTP есть, но оно от вас до сервера VK, не от вас до собеседника. Все звонки идут через TURN-сервер VK. Ключи шифрования у VK.
Видеозвонки - то же что голосовые. Тот же TURN-сервер, те же ключи у VK.
Конференции - через отдельный vchat API на OK.ru, та же схема с relay.
Голосовые сообщения - загружаются на CDN без шифрования. Сервер VK их ещё и переводит в текст (отдельная фича).
Видеосообщения - то же что и голосовые.
Файлы / фотографии / Аудио и Видеокружки - без шифрования на CDN. Про это отдельная история ниже.
Секретные чаты - их нет. Вообще. В коде нашёл UI-ресурсы с названиями вроде secret_mode_started, но крипто-кода за ними ноль. Ни DH-обмена ключами, ни отдельного протокола.
Я проверил все официальные документы:
legal.max.ru/pp (политика конфиденциальности) - слово «шифрование» не встречается ни разу.
legal.max.ru/ps (пользовательское соглашение) - ноль информации
help.max.ru/help/security (раздел безопасности) - пароли, сессии, онлайн-статус, родительский контроль. Шифрование не упоминается.
«Безопасный режим» - скрытие из поиска, звонки только от контактов, фильтр контента 16+/18+.
Для сравнения: у Telegram есть отдельная страница про MTProto и шифрование . У WhatsApp - Security Whitepaper. У Signal - полная документация протокола и исходный код. У MAX - ничего, потому что нечего описывать.
Самый частый запрос в комментариях: «а Telegram тоже так делает?» и «разбери WhatsApp».
Собрал таблицу от наименее защищённого к наиболее. Данные по MAX - мой анализ кода. По Telegram - документация + open source клиент. По WhatsApp - их Whitepaper + внешние аудиты (код закрыт). По Signal - open source + аудиты.
MAX единственный из четырёх, где: код закрыт + нет E2E ни для чего + сервер видит 100% + медиа без авторизации + ни одного аудита.
Источники:
Telegram E2E: core.telegram.org/api/end-to-end
Telegram код: github.com/DrKLO/Telegram
MTProto верификация: Miculan et al., Computers & Security, 2022
WhatsApp Whitepaper: whatsapp.com/security/WhatsApp-Security-Whitepaper.pdf
Cloudflare аудит: blog.cloudflare.com/key-transparency (2024)
Signal документация: signal.org/docs
Signal код: github.com/signalapp
Signal аудит: Oxford/QUT/McMaster
Эта информация уже упоминалась в других исследованиях, но не мог не затронуть это. Я отправил фото в чат, потом удалил сообщение, а потом открыл ссылку на это фото в другом браузере без логина и фото загрузилось. Потом сделал то же самое с голосовым сообщением. Аудио тоже доступно после удаления.
Каждая фотография хранится на CDN по адресу вроде i.oneme.ru/i?r=%3C%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%3E. Токен - подписанная строка, которую генерирует сервер.
Авторизация не нужна. Открываете ссылку в любом браузере, без логина - фото загружается
Ссылка не истекает.
CORS открыт: Access-Control-Allow-Origin: * - любой сайт может подгрузить картинку
Удалил сообщение, а фото осталось. Проверено: отправил фото, удалил, открыл по ссылке и увидел свое "удаленное" изображение
Токен уникальный и изменённый токен возвращает 400. Но сами токены раздаются всем участникам чата открытым текстом через wire-протокол. Переслали кому-то фото и этот кто-то навсегда имеет рабочую ссылку. Отозвать ссылку нельзя у пользователя механизма отзыва нет, только у сервера.
Вот пример отправленного фото в чат и позже удаленное из него ссылка :
Вы это можете так же легко проверить загрузите большое изображение, удалите его и попробуйте загрузить его еще раз и оно загрузится мгновенно так как файл уже есть...
Голосовые сообщения хранятся на maxvd*.okcdn.ru. Тут чуть лучше - URL подписан и живёт 24 часа. Но:
Авторизация не нужна. Так же как с фотографиями
Удалил сообщение, а аудио осталось. Проверено: отправил фото, удалил, открыл по ссылке и услышал свое "удаленное" аудио. Вы так же можете это легко проверить
Срок жизни - 24 часа (поле expires в URL). В пределах суток аудио доступно кому угодно
Все параметры URL подписаны подменить srcIp, id, sig нельзя (400). Но имея полную ссылку - скачать может любой
URL аудио содержит IP (srcIp=146.70.231.14) и ID пользователя (userId=125915255) открытым текстом. Ссылка
TLS шифрует канал между вами и сервером. Никто посередине (провайдер, сосед с Wi-Fi) не прочитает. Но сервер - прочитает. А сервер MAX - это сервер VK. VK зарегистрирован как «организатор распространения информации» и по закону обязан хранить переписку и отдавать по запросу.
E2E шифрование решает эту проблему - даже владелец сервера не может прочитать сообщения. В MAX его нет.
Не про то, есть ли что скрывать. Про то, кто контролирует ваши данные.
Без E2E:
Взлом сервера = все сообщения утекли.
Запрос по закону = все сообщения отдали.
Инсайдер в компании = все сообщения / контакты / файлы доступны (а учитывая сколько сейчас стоит пробив по номеру телефона и другие "сервисы". то скоро появится новая услуга).
С E2E: даже если сервер взломан - данные зашифрованы ключами, которые есть только у вас.
Добавить E2E в существующий мессенджер - это не флаг на сервере переключить. Нужно:
Реализовать протокол обмена ключами (DH / X3DH)
Добавить double ratchet или аналог для каждого чата
Переписать хранение сообщений (сервер больше не может их читать)
Переделать поиск, превью, пуши (всё это сейчас работает потому что сервер видит текст)
В коде MAX нет ни одного класса связанного с E2E - ни DH, ни pre-keys, ни session keys. Архитектура строилась без расчёта на это.
Не совсем. У Telegram есть альтернатива:
Есть Secret Chats - настоящий E2E (правда только 1-на-1 и только мобильные)
Звонки - E2E с emoji-верификацией
CDN-файлы требуют авторизацию и зашифрованы AES-256-CTR
Есть certificate pinning
У MAX ничего из этого нет. Ни одной альтернативы для пользователя, который хочет приватность.
Telegram не идеален - обычные чаты действительно сервер может читать и просматривать. Но у пользователя есть выбор открытые чаты или защищенные. В MAX выбора нет.
Если вам нужен E2E для всех чатов (не только секретных) - WhatsApp или Signal. В WhatsApp E2E включён по умолчанию для всего. В Signal - E2E + open source + независимые аудиты. Так же можно еще посмотреть на мессенджер Threema
Через Frida-хуки на SSL_read/SSL_write я перехватил расшифрованный трафик MAX. Формат - MsgPack (бинарный JSON). Декодер писал сам
За 30 минут работы приложения перехватил 85 фреймов. Из них:
Опкод 0x80 (INCOMING_MESSAGE_PUSH) - входящие сообщения, plaintext, видно текст и sender
Опкод 0x40 (SEND_MESSAGE_NEW) - исходящие сообщения, plaintext
Опкод 0x23 (SUBSCRIBE_PRESENCE) - кто онлайн
MAX:
- Формат: точный timestamp + status (1=online, 2=offline)
- Максимальная точность: секунда. Unix timestamp seen: 1776043570 = 2026-04-13 01:26:10 UTC
Telegram:- Точность плавает: был недавна, был на этой неделе....
Опкод 0x84 (REPORT_READ_RECEIPT) - кто прочитал
22 телефонных номера в открытом виде
Всё это идёт внутри TLS. Сервер VK видит тот же plaintext.
Файл: ru/ok/tamtam/nano/Protos.java
Класс Audio (голосовые сообщения):
url, token, audioId, duration, transcription
Полей IV, key, cipher, encrypted - нет
Класс Video (видеосообщения):
url, token, videoId, duration, thumbnail, transcription
Полей IV, key, cipher, encrypted - нет
Для сравнения: в Signal Protocol каждое сообщение содержит ratchet key, counter, cipher header. В MAX - просто текст.
Файл: res/xml/network_security_config.xml
Ноль элементов <pin-set>. Зато 5 доменов с cleartextTrafficPermitted="true" - это HTTP без шифрования вообще (для MobileID российских операторов).
Certificate pinning - это защита от MiTM-атак (подмена сертификата на уровне провайдера или wifi). Telegram, WhatsApp, Signal - все его используют. MAX - нет.
Вся переписка в SQLite без шифрования:
Нет SQLCipher - база открывается любым SQLite-просмотрщиком
Нет EncryptedSharedPreferences - токены авторизации в plaintext
AndroidKeyStore используется только для биометрической авторизации, не для сообщений
MAX - мессенджер который не шифрует ничего. Ни сообщения, ни звонки, ни файлы, ни фотографии. Сервер VK видит всё. На устройстве всё так же хранится в открытом виде. Фотографии доступны по ссылке навсегда без входа и проверки прав дотсупа.
VK нигде не обещает шифрование - ни на сайте, ни в документах. Потому что его нет.
В следующей части - практическая защита: что реально можно сделать если MAX приходится использовать. Split VPN, Knox, Frida, Virtual Device...
В первой статье я разобрал как MAX собирает ваш IP, определяет VPN и какие сайты из вашей сети. Как MAX помогает РКН строить железный занавес: VPN-детект, сбор IP и проверки на Госуслуги
Дальше - про звонки. В MAX встроена система распознавания ключевых слов (KWS - Keyword Spotting). Нейросеть, которая прогоняет аудио с микрофона прямо во время разговора.
Я не утверждаю, что MAX прямо сейчас слушает ваши разговоры в поисках слов вроде «Путин», «митинг» или «VPN». Текущая модель распознавания обучена только на фразу «не слышу» - по задумке это для определения плохой связи. Прямо сейчас функция выключена на сервере.
Но....я разобрал архитектуру, проследил весь код от микрофона до отправки на сервер - и вот что важно:
VK может подменить нейронку на любую другую когда захочет (будет проверка на новые слова)
Замена происходит без обновления приложения - достаточно изменить один URL в серверном конфиге или обновить уже существующую (Приложение само подтянет новый набор слов)
При срабатывании - результат улетает на сервер VK
Пользователь ни о чём не знает, согласие никто не спрашивает
Во время звонков в MAX работает система распознавания ключевых слов (KWS) на базе нейросети BC-ResNet
Она подтягивается с серверов VK, работает на устройстве внутри WebRTC (технология для звонков)
Сейчас модель обучена на фразу «не слышу» и выключена сервером ("use": false)
VK может: включить KWS одной настройкой и поменять список слов на проверку, сделать это для конкретного пользователя - без обновления приложения и без уведомления
KWS работает только во время звонков, не в фоне, не на голосовых сообщениях
При срабатывании - автоматический отчёт на сервер VK с уровнем уверенности
KWS (Keyword Spotting) - распознавание конкретных слов в аудиопотоке. Вы с этим знакомы: «Окей, Google», «Привет, Алиса», «Hey Siri» - всё это KWS.
Принцип простой: нейросеть берёт звук с микрофона, режет на кусочки по 10 миллисекунд и на каждом решает - это ключевое слово или нет?
VK встроил такую штуку прямо в свой модифицированный WebRTC. Нейросеть крутится локально на устройстве и слушает аудио во время звонка.
Я скачал модель с серверов VK (https://st.okcdn.ru/static/calls_android/1-0-1/kws_270525.zip), разобрал её архитектуру и запустил.
Текущий функционал:
Архитектура: BC-ResNet (Broadcasted Residual Network) - компактная нейросеть для мобильных устройств
Режим: streaming - обрабатывает аудио в реальном времени, кусочек за кусочком
Задача: ответ да/нет - «это ключевое слово» или «это всё остальное» (тишина, шум, обычная речь)
Ключевое слово: «не слышу» (то есть собеседник жалуется, что плохо слышно)
Размер: 1.17 МБ, ~300 тысяч параметров
Для звонков это имеет смысл: собеседник говорит «не слышу» - значит со связью проблемы, приложение может это подхватить.
Распознавать «не слышу» - безобидно. А вот как это устроено внутри - уже нет.
Нейронка приходит с сервера. KWS не зашит в код приложения. При запуске приложение получает от сервера конфиг с URL модели, забирает файл и грузит в нейросетевой движок. Сервер VK решает:
Какую нейронку загружать (URL файла)
Включена ли KWS вообще (флаг use)
Сколько времени слушать (таймер turn_off_in_ms, сейчас 60 секунд)
VK может подменить модель хоть завтра - на любые другие слова. Обновлять приложение не нужно. Спрашивать пользователя - тоже.
Приложение не проверяет, что именно нейронка распознаёт. Если завтра VK положит на CDN модель, обученную на слово «протест» - приложение скачает её и запустит точно так же.
А что в политике конфиденциальности? Я проверил оба документа - Политику конфиденциальности (legal.max.ru/pp) и Пользовательское соглашение (legal.max.ru/ps). Упоминания KWS или анализа аудио во время звонков - ноль.
При этом в Пользовательском соглашении есть описание того, как голосовые и видеосообщения переводятся в текст.
Когда нейронка считает, что услышала ключевое слово, происходит следующее:
Выдаёт уровень уверенности (число от 0 до 1)
Приложение берёт максимальное значение за сессию
Приложение формирует отчёт и отправляет на сервер VK (api.ok.ru/api/log/externalLog) через канал vchat.clientStats
VK видит: в таком-то звонке у такого-то пользователя сработал детектор, уверенность такая-то. Всё привязано к userId как и call_id (vchat.clientStats отправляется в привязке к конкретной VoIP-сессии)
Во время анализа я перехватил реальный ответ сервера VK с конфигурацией KWS. Вот что сервер присылает приложению:
URL модели: https://st.okcdn.ru/static/calls_android/1-0-1/kws_270525.zip
MD5: 00320292950aa4896ccc057550442789
Включено: НЕТ ("use": false)
Таймаут: 60 секунд на звонок
Я забрал модель по этому URL - без авторизации, без cookies. Любой может это сделать прямо сейчас. MD5 совпадает с тем, что прислал сервер.
Ещё я побрутил CDN в поисках других моделей - перебрал 200+ вариантов путей. Нашёл только одну модель в трёх версиях SDK (1-0-1, 1-0-2, 1-0-3) - все три идентичны (одинаковый MD5). На данный момент VK использует только одну модель для фразы «не слышу».
Работает только во время звонков - закрыли приложение, и всё. Кода для фоновой работы KWS я не нашёл.
С голосовыми сообщениями не связан - те переводятся с аудио в текст на серверах VK, это другой процесс, другой код.
Сейчас выключен (use: false). Нейронка уже на устройствах, но не запущена. И ищет она только «не слышу», а не что-то «опасное».
KWS - не единственная интересная вещь в работе звонков MAX:
Все звонки идут через сервер VK. P2P-соединений я не увидел - все медиаданные проходят через TURN-сервер VK. Шифрование DTLS-SRTP есть, но от вас до сервера, не от вас до собеседника. На relay-сервере шифрование заканчивается - ключи у VK.
Флаг записи аудио. В коде есть PMS-ключ calls-sdk-log-audio - если VK его включит, аудио звонка пишется в файл. Управляется с сервера.
Модифицированный WebRTC. VK не использует стандартный WebRTC - они его модифицировали. В модификации добавлены: нативная запись аудио в Opus (nativeAudioStartRecord, nativeAudioWriteFrame), KWS-интеграция, и кастомные параметры.
Ниже - код, конфиги и результаты реверс-инжиниринга. Версия APK 26.12.1 (6679).
KWS встроен в модифицированный WebRTC внутри нативной библиотеки libjingle_peerconnection_so.so. Точка входа - JNI-метод:
Java_org_webrtc_PeerConnectionFactory_nativeSetKeywordSpotterParams
Это вызов из Java в нативный код. Принимает два параметра: isEnabled (включить/выключить) и filePath (путь к модели на устройстве).
Полная цепочка от сервера до нейросети:
Сервер присылает RemoteSettings с конфигом KWS
MLFeaturesManagerImpl забирает модель по URL, проверяет MD5
KwsFeatureDelegate вызывает setKeywordSpotterParams(isEnabled, filePath)
PeerConnectionFactory передаёт параметры в нативный WebRTC
kws_impl.cc загружает TFLite-модель
BCResNetKWS::computeProbs() (в libEnhancementLibShared.so) обрабатывает аудио
KwsBufferizator буферизует фреймы и передаёт через NativeDoubleArrayConsumer
KeywordSpotterManagerImpl получает уровень уверенности, берёт максимум
ConversationKwsStat.onKeyword(maxConfidence) формирует отчёт
Отчёт уходит через vchat.clientStats на серверы VK
Пакет: ru.ok.android.externcalls.sdk.audio - это SDK звонков.
/ru/ok/android/externcalls/sdk/audio/KeywordSpotterManagerImpl.java
Файл: calls_kws.tflite из kws_270525.zip
Вход: [1, 1, 40] - 1 фрейм × 40 мел-частотны
Выход: [1, 1, 2] - бинарный softmax: [P(фон), P(ключевое_слово)]
Архитектура: BC-ResNet (Broadcasted Residual Network), streaming mode
384 тензора, ~1.17 МБ
Конфиг: algorithm_name = "bcresnet_kws", sample_rate = 16000
Результаты запуска модели на тестовых данных:
Тишина: P(keyword) стремится к 0.0000 после ~40 фреймов тишины
Случайный шум: P(keyword) = 0.0000 - ложных срабатываний нет
Модель уверенно отличает целевую фразу от всего остального
Вот полная карта: какие серверы участвуют, какие запросы идут и в каком формате.
Шаг 1 - Получение конфига (при подключении к серверу):
Откуда: api.oneme.ru:443 (постоянное TCP-соединение, MsgPack wire-протокол)
Что: RemoteSettings - серверный конфиг со всеми фичами, включая KWS
Формат: MsgPack binary → расшифровывается в JSON
Ключи KWS в ответе:
"android.wordspotter.config" → {"turn_off_in_ms": 60000}
"android.mlfeatures.ws_0" → {"url": "https://st.okcdn.ru/static/calls_android/1-0-1/kws_270525.zi...", "cs": "00320292950aa4896ccc057550442789", "use": false}
Шаг 2 - Скачивание модели (при первом запуске или обновлении URL):
Откуда: st.okcdn.ru (CDN VK, HTTP GET)
URL: https://st.okcdn.ru/static/calls_android/1-0-1/kws_270525.zi...
Авторизация: никакой - публичный доступ
Формат: ZIP-архив (828 КБ) → внутри calls_kws.tflite (TFLite-модель, 1.17 МБ) + config.cfg
Проверка: MD5 скачанного файла сравнивается с cs из конфига
Сохраняется: {filesDir}/ml_features/ws/calls_kws.tflite + config.cfg
Шаг 3 - Работа KWS (во время звонка, если use: true):
Аудио с микрофона → WebRTC соединение → BCResNetKWS::computeProbs() в libEnhancementLibShared.so
Вся обработка на устройстве, в сеть пока ничего не уходит
Таймаут: turn_off_in_ms (60 секунд) - после этого KWS останавливается
Шаг 4 - Отправка результата (после/во время звонка):
Куда: api.ok.ru/api/log/externalLog (POST, gzip, session_key)
Канал: vchat.clientStats
{"metric": "bad_call_detected_by_audio_spotter", "string_value": "не слышу", "double_value": 0.95} + userId, sessionId, call_id
Медиаданные самого звонка:
Куда: 155.212.206.115:43210 (TURN-сервер VK, UDP)
Шифрование: DTLS-SRTP (end-to-relay, не end-to-end)
Сертификат: QRtpServer 1.1.10
Файл: defpackage/ConversationKwsStat.java
При срабатывании формируется событие:
metric: "bad_call_detected_by_audio_spotter"
string_value: "не слышу" (захардкожено)
double_value: максимальная уверенность за звонок
Канал: vchat.clientStats
Имя "не слышу" зашито в код - но модель определяется URL с сервера. Если VK ее заменит, код по-прежнему будет отправлять "не слышу" как строку, даже если реальная модель будет детектить совершенно другую фразу. Или VK обновит и код в следующей версии.
/ru/ok/android/externcalls/sdk/stat/kws/ConversationKwsStat.java
Проверяйте сами:
HTTP 200, 828 КБ
Без авторизации, без cookies, без заголовков
Внутри ZIP: calls_kws.tflite (модель, 1.17 МБ) + config.cfg
MD5 файла: 00320292950aa4896ccc057550442789 - совпадает с серверным конфигом
Содержимое config.cfg:
algorithm_name = "bcresnet_kws"
sample_rate = 16000
Я проверил три версии SDK на CDN:
https://st.okcdn.ru/static/calls_android/1-0-1/kws_270525.zi... - HTTP 200, MD5: 00320292...
https://st.okcdn.ru/static/calls_android/1-0-2/kws_270525.zi... - HTTP 200, тот же MD5
https://st.okcdn.ru/static/calls_android/1-0-3/kws_270525.zi... - HTTP 200, тот же MD5
Одна и та же модель во всех версиях. Имя файла kws_270525 предполагает дату создания 27.05.2025. Просканировал 200+ вариантов путей - других моделей на CDN не нашёл.
Цепочка замены модели:
VK меняет поле url в android.mlfeatures.ws_0 на новый адрес или просто обновляет модель
Сервер пушит обновлённый RemoteSettings через постоянное TCP-соединение
MLFeaturesManagerImpl видит новый URL, тянет новый ZIP
Проверяет MD5 (новый, для нового файла)
Распаковывает .tflite + .cfg в ml_features/ws/
При следующем звонке KwsFeatureDelegate загружает новую модель
BCResNetKWS начинает детектить новые ключевые слова
На стороне приложения нет проверки того, что именно модель распознаёт. Нет whitelist допустимых слов. Нет уведомления пользователя. MD5 проверяет только целостность файла - что скачалось без ошибок.
Запись аудио через серверный флаг: PMS-ключ calls-sdk-log-audio (key 129) может включить запись аудио звонка в файл. Плюс JNI-методы nativeStartAecDump / nativeStopAecDump позволяют дампить raw-аудио в файловый дескриптор. Всё управляется сервером.
Все звонки через relay VK: Все медиаданные идут через TURN-сервер VK (155.212.206.115:43210). Шифрование DTLS-SRTP - от вас до сервера, не от вас до собеседника. Сертификат сервера: QRtpServer 1.1.10.
Типы ML-фич в конфиге:
WS (WordSpotter) - распознавание ключевых слов ← то, что мы разобрали
NS (Noise Suppression) - подавление шума
MLFeaturesManagerImpl поддерживает несколько типов моделей. Сейчас WS и NS, но подцепить новый тип - дело пары строк.
/ru/ok/tamtam/android/prefs/PmsKey.java - Коротко и наглядно: вот он, переключатель записи аудио, управляемый с сервера.
/one/video/calls/audio/opus/FileWriter.java - Это нативная запись аудио в файл.
Если вы дочитали до сюда - вы уже поняли суть.
Скажу одно: разница между «детектором плохой связи» и «детектором произвольных слов» - это один URL в JSON-конфиге. Модель, код, процесс отправки на сервер - всё одно и то же. Меняется только файл на CDN.
Я не знаю, планирует ли VK это использовать иначе. Но я знаю, что в политике конфиденциальности об этом ни слова, согласие не спрашивается, а модель можно скачать и проверить прямо сейчас. Ссылка выше.
Код - вот он. Модель - в открытом доступе. Проверяйте.
Вышла вторая часть исследования: Что скрывает MAX #2: В мессенджер встроена система распознавания ключевых слов во время звонков
В третьей части планирую ответить на самые частые вопросы из комментариев и с форумов. Если есть вопрос - пишите, разберу с кодом в руках.
1. MAX слушает микрофон в фоне?
2. Есть ли в MAX сквозное шифрование (E2E) как в telegram?
3. Фото из чатов доступны без авторизации?
4. Чем MAX отличается от Telegram по безопасности?
Ответы на эти и другие ваши вопросы будут в третьей части.
Вышла третья часть исследования:
MAX (ex-TamTam) от VK при каждом открытии тайно определяет ваш внешний IP и VPN через 6 сервисов (Yandex, Amazon, Mail.ru и др.)
URL этих сервисов спрятаны в коде как массивы чисел — чтобы их нельзя было найти поиском и быстрым анализом сетевого трафика
Приложение проверяет доступность российских сервисов, включая gosuslugi.ru (Госуслуги интегрированы в MAX, но проверяется именно доступность, а не функции)
Определяет VPN и отправляет информацию на сервер. В коде звонков — виджет «Лучше без VPN» (найден в коде, динамически не проверял)
В коде присутствуют домены main.telegram.org и mmg.whatsapp.net — в текущей версии не используются, но сам факт их наличия рядом с активными проверками примечателен
Все данные привязаны к вашему аккаунту MAX (OK.ru ID): IP + оператор + VPN-статус + время
Если интересно — ниже подробный разбор. Если хочется увидеть сам код — листайте до «Часть 2».
Я провожу полный аудит безопасности мессенджера MAX. Эта статья — про систему сбора IP и VPN-детект. Если тема зайдёт, дальше будет:
Тихий звонок: как сервер может инициировать вызов без звонка и UI
Ключевые слова через микрофон: в MAX встроены ASR (распознавание речи), идентификация голоса и детект ключевых слов — с моделями, которые доставляет сервер
End-to-end шифрование? Нет, не слышали: сообщения, контакты и токены хранятся в открытом виде
Я занимаюсь аудитом безопасности мобильных приложений. Недавно взялся за MAX — мессенджер от VK, который активно продвигают как «замену Telegram». Скачал APK из Google Play и RuStore, декомпилировал через JADX и начал копаться в коде.
При анализе сетевого трафика заметил странность: при каждом запуске приложение лезет на checkip.amazonaws.com. Это сервис Amazon, который возвращает твой внешний IP-адрес. Зачем мессенджеру знать мой IP через Amazon?
Потянул за ниточку — и нашёл не баг, а целую подсистему. Спроектированную, отлаженную, с защитой от обнаружения и гарантированной доставкой данных на сервер. Это не аналитика. Это инфраструктура сетевой слежки.
Здесь — суть без кода. Что делает приложение, зачем и чем это грозит.
Каждый раз, когда вы открываете MAX, приложение тайно определяет ваш внешний IP-адрес. Для этого у него есть 6 сервисов:
ipv4-internet.yandex.net — Яндекс
ipv6-internet.yandex.net — Яндекс
ifconfig.me — независимый
api.ipify.org — независимый
checkip.amazonaws.com — Amazon
ip.mail.ru — Mail.ru (VK Group)
Шесть штук — для одной задачи. Зачем столько? Для надёжности: если один сервис не ответит, сработает другой. Порядок каждый раз перемешивается случайным образом — так сложнее обнаружить запросы по сетевому паттерну.
Но самое интересное: адреса этих сервисов спрятаны в коде. Не как обычные строки вроде "https://ip.mail.ru/", а как массивы чисел:
{104, 116, 116, 112, 115, 58, 47, 47, 105, 112, 46, 109, 97, 105, 108, 46, 114, 117, 47}
Каждое число — это ASCII-код буквы. 104 = «h», 116 = «t», и так далее. Получается https://ip.mail.ru/. Обычный поиск по коду (grep, strings) эти URL никогда не найдёт. Это не ошибка и не оптимизация — это намеренное сокрытие.
И ещё: приложение использует для запросов не стандартную библиотеку OkHttp (которую легко перехватить), а низкоуровневые raw-сокеты (java.net.Socket). Это обходит сетевые перехватчики, прокси и мониторинг трафика.
Для понимания: обычное приложение, которому нужен ваш IP для легитимных целей (например, геолокация для контента), делает один запрос через стандартную библиотеку. MAX делает 6 резервных запросов через скрытые URL по raw-сокетам. Это инженерия уровня малвари.
Параллельно со сбором IP приложение проверяет доступность пяти хостов — может ли ваш телефон до них «дотянуться»:
api.oneme.ru (VK Group) — свой API
gstatic.com (Google) — проверка доступности интернета
mtalk.google.com (Google) — push-уведомления
calls.okcdn.ru (VK Group) — CDN для звонков
gosuslugi.ru (Правительство РФ) — российский государственный сервис
Госуслуги в этом списке не случайность — в MAX интегрирована авторизация через ЕСИА (об этом в следующих статьях). Но проверка касается не авторизации, а базовой сетевой доступности: можно ли с вашего устройства достучаться до gosuslugi.ru? Результат — просто «да» или «нет» — уходит на сервер вместе с VPN-флагом и IP.
Зачем? Комбинация ответов даёт VK полную картину:
Госуслуги доступны + нет VPN + российский оператор = обычный пользователь в России
Госуслуги не доступны + есть VPN + российский оператор = пользователь обходит блокировки
Госуслуги не доступны, но другие иностранные сервисы доступны + нет VPN + неизвестный оператор = пользователь обходит блокировку + пользователь обходит проверку на VPN
Та же логика с другими хостами: gstatic.com и mtalk.google.com показывают, доступен ли Google (актуально для стран с фильтрацией как Китай ). Все пять проверок — это не «работает ли сервис», а снятие отпечатка вашей сетевой среды.
MAX определяет VPN через стандартный API Android. Ловит любой — NordVPN, WireGuard, OpenVPN корпоративный, встроенный.
Что происходит дальше: при каждом открытии приложения VPN-статус отправляется на сервер VK вместе с вашим IP и остальными данными. Это подтверждено и анализом кода, и наблюдением трафика. VK точно знает: пользуетесь ли вы VPN, в какой момент и с какого IP.
Кроме того, в коде найден виджет «Лучше без VPN» — панель, которая должна появляться во время звонков при включённом VPN. В ресурсах приложения есть строки на русском и английском. Динамически (на реальном устройстве) я это пока не воспроизводил, но код и ресурсы — на месте.
Каждый раз, когда вы открываете MAX (или приложение просыпается в фоне), оно собирает и отправляет на сервер VK следующие данные:
Ваш ID аккаунта MAX (OK.ru ID) — из аккаунта
ID сессии — из приложения
Ключ авторизации — из HTTP-запроса
Внешний IP-адрес — через один из 6 IP-сервисов
Мобильный оператор — из SIM-карты (напр. «MTS»)
Тип соединения — WiFi / 4G / 5G
VPN включён? — через системный API
Доступность 5 сервисов — через сетевые проверки
Время — системные часы
Вот как выглядит пакет, который уходит на сервер (реконструкция из кода):
Кто: пользователь #123456789 (аккаунт MAX)
Когда: 2026-04-12, 14:30:00
IP: 185.xxx.xxx.xxx
VPN: ДА
Оператор: MTS
Связь: WiFi
Сервисы: gosuslugi.ru — да, Google — да, API MAX — да, CDN звонков — да, push — да
Это не анонимная аналитика. Каждая запись однозначно привязана к вашему аккаунту. А поскольку VK Group владеет OK.ru, VK.com и Mail.ru — аккаунт MAX связывается с профилем ВКонтакте, почтой и другими сервисами экосистемы.
У MAX 107 миллионов зарегистрированных пользователей и 77 миллионов ежедневной аудитории (данные VK, март 2026). С сентября 2025 приложение обязательно предустановлено на все Android-устройства, продаваемые в России и Беларуси.
Представьте: 77 миллионов человек открывают MAX каждый день. При каждом открытии — пакет с IP, VPN-статусом, оператором. Это десятки миллионов точек данных ежедневно: кто использует VPN, кто нет, с какого IP, через какого оператора, доступны ли заблокированные сервисы. В реальном времени.
Это не просто «аналитика одного приложения». Это инфраструктура мониторинга сетевой среды в масштабе целой страны. С каждой новой блокировкой РКН данные HOST_REACHABILITY становятся ценнее — они показывают, работает ли блокировка и кто её обходит.
В том же файле, где хранятся адреса активных сервисов, закодированы домены конкурентов: main.telegram.org и mmg.whatsapp.net.
В текущей версии они не используются.
Я не утверждаю, что проверка доступности Telegram «готова к мгновенной активации». Но сам факт — домены конкурентов лежат в одном файле с рабочей системой сбора данных — примечателен.
Отдельно: MAX проверяет, установлен ли Telegram на вашем устройстве — ищет три варианта приложения: основной, бета и веб-версию. Официально это для кнопки «Поделиться» (Telegram ставится первым в списке).
Подробности — в технической части ниже.
Вся система слежки управляется одним серверным флагом через систему PMS (Push Management System) от VK. Это как remote config, только мощнее: PMS-значения доставляются через постоянное TCP-соединение и применяются мгновенно.
Что это значит на практике:
VK может включить сбор IP для всех пользователей одним переключателем
Может включить для конкретного пользователя (таргетированная слежка)
Может включить для когорты (A/B-тест, определённый оператор, определённый регион)
Может выключить перед проверкой (например, перед аудитом Google Play)
Всё это — без обновления приложения
Для этой функции даже выделен отдельный пул потоков с именем "host-reachability". Это не аналитика, прикрученная сбоку — это самостоятельная подсистема, спроектированная как отдельный компонент.
И ещё: данные никогда не теряются. Если отправка не удалась — 3 повтора. Если и после 3 раз не получилось — данные сохраняются в локальное хранилище и отправляются позже. При сворачивании приложения — принудительный flush всех накопленных событий. VK спроектировал это так, чтобы получить 100% собранных данных.
Я не буду спекулировать о том, передаёт ли VK эти данные спецслужбам. Вместо этого — факты.
VK/MAX — зарегистрированный «организатор распространения информации» по 149-ФЗ. По закону Яровой (374-ФЗ) он обязан хранить метаданные коммуникаций 1 год и предоставлять их по запросу. IP-адрес + userId + timestamp + VPN-статус — это именно те метаданные, которые подлежат хранению. А 276-ФЗ (поправки 2024) ужесточил ответственность за использование VPN для обхода блокировок — и HOST_REACHABILITY даёт точные доказательства: кто, когда и откуда использовал VPN.
При этом IP-адрес — это персональные данные по 152-ФЗ. Согласие на такой сбор у пользователя не запрашивается.
Важно: я не утверждаю, что VK злоупотребляет этими данными. Я утверждаю, что код собирает их, закон обязывает хранить и отдавать, а у пользователя нет возможности отказаться. Выводы — за вами.
Ниже — конкретный код из декомпилированного APK. Все ссылки на файлы и строки — из JADX-дизассемблера, версия APK 26.12.1.
Файл: defpackage/f28.java:43
Вот как выглядит адрес https://ip.mail.ru/ в коде MAX:
// f28.java:53 x = new int[]{104, 116, 116, 112, 115, 58, 47, 47, 105, 112, 46, 109, 97, 105, 108, 46, 114, 117, 47};
Декодируем вручную:
104 → h 116 → t 116 → t 112 → p 115 → s 58 → : 47 → / 47 → / 105 → i 112 → p 46 → . 109 → m 97 → a 105 → i 108 → l 46 → . 114 → r 117 → u 47 → /
Результат: https://ip.mail.ru/
Все 16 URL в файле f28.java закодированы таким образом. Декодирование происходит лениво через rd4.java (cases 17-22 для IP-сервисов, 12-16 для проверок).
Это не минификация и не оптимизация. ProGuard обфусцирует имена классов и методов, но не превращает строковые литералы в массивы int. Это ручная работа разработчиков, цель которой — сделать URL невидимыми для инструментов статического анализа (grep, strings, jadx string search).
Файл: defpackage/w18.java:139-140
// Собираем все 6 IP-сервисов в список
List o1 = rz3.o1(sz3.U(
f28.o.getValue(), // Yandex IPv4
f28.q.getValue(), // Yandex IPv6
f28.s.getValue(), // ifconfig.me
f28.u.getValue(), // api.ipify.org
f28.w.getValue(), // checkip.amazonaws.com
f28.y.getValue())); // ip.mail.ru
// Перемешиваем — каждый раз случайный порядок
Collections.shuffle(o1);
Дальше — последовательный перебор: первый сервис, который вернул IP ≠ 127.0.0.1, побеждает. Остальные не опрашиваются.
Ключевой момент: запросы идут через java.net.Socket + ByteArrayOutputStream — это raw TCP, не OkHttp. OkHttp — стандартная HTTP-библиотека Android, которую легко перехватить через прокси (Burp Suite, Charles, mitmproxy). Raw-сокеты обходят всё это. Исследователь, настроивший прокси на устройстве, не увидит эти запросы в стандартных инструментах.
Файл: defpackage/c28.java:100-116
ro9 ro9Var = new ro9();
// Результаты проверки доступности хостов: {hostname: true/false}
ro9Var.put("hosts", vibVar);
// Мобильный оператор: "25001:MTS"
ro9Var.put("operator", str);
// Тип соединения: WiFi (0) или мобильная сеть (1)
ro9Var.put("connection_type", new Integer( nc4Var.g() ? nc4Var.b().a : 1));
// Внешний IP-адрес
if (str2 != null) {
ro9Var.put("ip", str2); }
// Флаг VPN
if (((nc4) b19Var.getValue()).e()) {
ro9Var.put("vpn", new Integer(1)); }
// Отправка: тип HOST_REACHABILITY, событие GET_HOST_REACHABILITY
lg9.h(lg9Var, "HOST_REACHABILITY", "GET_HOST_REACHABILITY", ro9Var.b(), 8);
Реконструированный JSON, который уходит на сервер:
{
"type": "HOST_REACHABILITY",
"event": "GET_HOST_REACHABILITY",
"userId": 123456789,
"sessionId": 987654321,
"time": 1712847600000,
"params": {
"hosts": {
"api.oneme.ru": true,
"gstatic.com": true,
"calls.okcdn.ru": true,
"gosuslugi.ru": true,
"mtalk.google.com": true
},
"operator": "25001:MTS",
"connection_type": 0,
"ip": "185.xxx.xxx.xxx",
"vpn": 1
}
}
Адрес назначения: api.ok.ru/api/log/externalLog (POST, gzip, аутентифицирован через session_key).
Файл: defpackage/lg9.java:49-103
Отправка → Ошибка? → Повтор 1 → Ошибка? → Повтор 2 → Ошибка? → Повтор 3 → Ошибка? → Сохранить в локальное хранилище (vgh) → Отправить позже при подключении
До 3 повторов (строка 66: intValue() > 3). После 3 неудач — данные сохраняются в локальное хранилище vgh (строки 68-77) для отправки позже. При сворачивании приложения — принудительный flush (r87.java:68.
Результат: ваши данные отправятся серверу VK в любом случае. Нет сети? Подождёт. Сервер упал? Повторит. Вы закрыли приложение? Дошлёт при следующем запуске. Система спроектирована так, что ни одно событие не теряется.
Файл: one/me/calls/ui/ui/call/panels/VpnPanelWidget.java, defpackage/dy1.java:46
// dy1.java:46 — VPN обнаружен во время звонка
VpnPanelWidget widget = new VpnPanelWidget();
// Добавляется в UI с тегом "call_vpn_panel_widget_tag"
Файл: defpackage/l4j.java
l4j.a // ENABLED — VPN включён
l4j.b // DISABLED — VPN выключен
l4j.c // USER_IGNORED — пользователь закрыл предупреждение
l4j.d // UNKNOWN — не удалось определить
Аналитическое событие при показе баннера: BAD_CONNECTION_ALERT с типом VPN (m82.java:43.
Файл: defpackage/rd4.java:87
case 15: return hb0.a(f28.l); // main.telegram.org
case 16: return hb0.a(f28.m); // mmg.whatsapp.net
Файл: defpackage/c28.java:150 — список активных проверок:
List U = sz3.U(
"api.oneme.ru",
(String) f28.b.getValue(), // gstatic.com
(String) f28.f.getValue(), // calls.okcdn.ru
(String) f28.h.getValue(), // gosuslugi.ru
(String) f28.d.getValue()); // mtalk.google.com
f28.l (Telegram) и f28.m (WhatsApp) — есть в декодере rd4.java, но отсутствуют в активном списке c28.java. Более того, на уровне smali-байткода для этих полей нет move-result-object / sput-object — результат декодирования отбрасывается. Это мёртвый код или заготовка, но не работающая функциональность.
Также в f28 есть неактивные домены Huawei Push:
token-drcn.push.dbankcloud.com (DRCN = Data Region China North)
Основной триггер: каждый раз, когда приложение переходит в foreground (вы открыли MAX или вернулись к нему):
r87.java:82 → app foreground callback
→ проверяет PMS-флаг (e28.java:142-146)
→ если включён и нет активной проверки
→ запускает c28 (полный пайплайн)
Также: при логине (AccountInitializer), при фоновом пробуждении (one.me.background.wake.HostReachabilityChecker).
Даже до авторизации: код в y18.java:66-100 показывает, что сетевые проверки работают в разных режимах в зависимости от состояния сессии (не подключён / подключён / залогинен). Фингерпринтинг сети начинается до того, как пользователь вошёл в аккаунт.
MAX — приложение, предустановленное на 100+ миллионов устройств — содержит систему, которая при каждом открытии:
Узнаёт ваш IP через 6 скрытых сервисов
Определяет, включён ли VPN
Проверяет, доступны ли российские и международные сервисы из вашей сети
Привязывает всё это к вашему реальному аккаунту
Гарантированно доставляет данные на сервер VK — без потерь, с повторами
Эта система управляется сервером и может быть включена для всех, для группы или для конкретного пользователя — без обновления приложения. Адреса сервисов намеренно спрятаны, запросы идут в обход стандартных сетевых библиотек.
Я различаю возможность в коде и подтверждённое использование. Серверный флаг может быть не включён для всех. Но код — вот он. Проверяйте.
Сбор IP и VPN-детект — это верхушка айсберга. В коде MAX я нашёл ещё много интересного и планирую рассказать в следующих статьях.
Пишите в комментариях, что разобрать первым.
Версия APK: 26.12.1 (6679) Разработчик: ООО «КОММУНИКАЦИОННАЯ ПЛАТФОРМА» (VK Group, Москва) Метод: статический анализ декомпилированного APK (JADX) SHA256: 47420c41b742e67daae85ee75e0aa76935736c7491bab9da942c012e2115ffa1
Примечание о названиях файлов: большинство классов в APK обфусцированы ProGuard — вместо осмысленных имён вроде HostReachabilityManager.java вы увидите f28.java, c28.java, w18.java. В JADX они попадают в defpackage/ (дефолтный пакет для классов без имени пакета). Это нормально для обфусцированного кода. Классы с нормальными именами (например, VpnPanelWidget.java) сохранили свои пути — я указываю полный путь там, где он есть.
Если нашли ошибку — пишите, исправлю. Я за точность, а не за хайп.
Привет, Пикабу.
У меня, возможно, нет литературного таланта, зато есть наблюдательность, аналитика и немного наглости, чтобы копаться в алгоритмах Author.Today.
Я не пишу книги. Я — человек, который любит их читать и помогает им быть замеченными. И сегодня хочу поделиться с вами одним своим экспериментом. Без морализаторства. Просто факты, размышления и пара вопросов, над которыми стоит подумать каждому автору на платформе.
Сегодня я хотел бы показать вам два простых теста.
Всего по 60 просмотров. Ни лайков, ни комментариев, ни чтения, ни добавлений в библиотеку. Только просмотры.
А вот результат уже на ваше усмотрение, но мне он понравился
Автор пишет — платформа решает, кто увидит.
И дело часто не в качестве текста, а в том, кто заметит его первым.
Но как платформа это решает? Что значит “рейтинг”? Как работает этот невидимый механизм, который выталкивает одни книги наверх, а другие — прячет на 13-й странице раздела “городское фэнтези”?
Я использую собственную платформу, которая собирает данные с AT и бустит книги по различным метрикам:
• Рейтинги в популярных, по жанрам
• Динамика просмотров, чтения и активности
• Сравнение с похожими книгами
• Реакция системы на разные сценарии (сплески, затухания, органику)
• Бустинг любого параметра за любое время
Это не “накрутка 5000 просмотров за 500 рублей и 1 минуту”, а тестирование гипотез и работа с поведением платформы. Я стараюсь делать это не в лоб, а с умом — чтобы помочь книгам стать видимыми, не вызывая подозрений или откатов.
P.S. Для сохранения книг в секрете их изображение и часть данных изменена.
Стартовые условия:
Книга в районе 9600 места в популярных.
1-3 просмотр в час. Книга вообще без активности, но не мертвая.
Жанры: боевое фэнтези и доп. — попаданцы.
Что я сделал:
На протяжении 6 часов → добавлял по 10 просмотров в час.
(Случайные аккаунты, разные IP, реальный тайминг).
Вместе с искусственно добавленными 10 просмотрами в час начали приходить органические: до +10 просмотров в час дополнительно, без каких-либо действий со стороны автора.
Что произошло:
Вместе с искусственно добавленными 10 просмотрами в час начали приходить органические: до +10 просмотров в час дополнительно, без каких-либо действий со стороны автора.
Система начала воспринимать книгу как “активную” — началось вовлечение через алгоритмы видимости(отображение в лентах, подборках, категориях).
Книга стабильно поднималась примерно на 1000 позиций в час, пока не вышла на уровень 3400+ в “Популярных”, 180 в основном жанре и ~1000 в дополнительном.
Даже после остановки буста книга удержала позиции за счёт органики, не откатившись.
Вывод:
Алгоритм AT реагирует на стабильную положительную динамику. Даже малый рост (в рамках “нормального поведения читателей”) воспринимается как реальный интерес. Это активирует встроенные механизмы продвижения — показ другим пользователям, появление в поиске, блоках “вам может понравиться”.
Старт:
9500 место,
1-5 просмотров в час (т.е. почти тишина).
Действие:
60 просмотров за 1 час, интервал — каждые 10, 30 секунд.
Результат:
После добавления 60 просмотров в течение 1 часа, рейтинг сначала скакнул — от 9500 → до 7200, а затем до 2800 за 3 обновления системы.
Однако никакой органики не пришло. Более того, пропали даже те 5 просмотров/час, которые были до вброса.
Уже через два апдейта началось плавное падение по ~400 позиций за шаг — система буквально “выкинула” книгу обратно вниз.
Вывод:
Алгоритм Author.Today распознаёт резкий, но не подкреплённый интерес как аномалию. Такой всплеск не сопровождается естественным вовлечением — ни переходами к другим главам, ни дочитываниями, ни лайками. Поэтому он быстро теряет вес, и система убирает его из выдачи. В итоге — не просто откат, а минус даже к базовой органике.
Одна книга, за которой я просто наблюдал.
Была где-то на 160 месте в популярных.
После выхода новой главы — просмотры скакнули до 300–400 в час.
Через 2 часа — топ-1 в “популярных” и в жанре.
А органика выросла до 150–200 просмотров/час.
Всё произошло потому что:
Автоматическое уведомление подписчиков
Буст посетителей с 70 - 100 в час до 400 + в час
📖 Чтение ~35 минут (1 человек) → 80+ часов суммарного чтения за 1 час
⚙️ Алгоритм увидел: посещения + чтение + обратную связь
Алгоритм ценит устойчивость: если книга даёт сигнал “ей интересуются” — пусть даже немного — она получает шанс быть замеченной.
Резкий, искусственный пик без “поддержки” воспринимается как шум и отбрасывается.
Даже 10 просмотров в час, если они выглядят как реальное поведение читателей, могут переместить книгу вверх на тысячи позиций и активировать органику.