Критическая уязвимость VLESS клиентов? Подержите мое пиво…

от автора

Приветствую тебя, %username%! Ох и давно я не писал ничего на Хабр (10+ лет) — чернила высохли, перо затупилось. И все же, читая последние сводки, мой академический интерес проснулся.

Если вдруг пропустили и не понимаете о чем я, то информационный фон сейчас бурлит: тут и новости про то, как большинство популярных приложений детектируют VPN, и выход утилиты RKNHardering с методичками по борьбе с обходами, и тревожные отчеты о свободе интернета в 2026 году. Но последней статьей которая на меня повлияла стала статья про критическую уязвимость VLESS-клиентов, из-за которой «скоро все ваши VPN будут заблокированы».

Смахнув скупую мужскую слезу, вызванную этим богатым на эмоции потоком, я задумался: а насколько вообще сложно детектируется VPN на Android? Оказалось, что даже с использованием сплит-туннелирования у приложений остается вагон возможностей для детекта (хоть и не 100%, но все же) :-/ .

Что там по признакам VPN?

Для начала я собрал мысли и идеи о том, какие признаки наличия VPN может собирать приложение, и набросал небольшую тестовую тулзу. Смысла ее публиковать особо нет, все то, что она делает, уже есть и в том самом RKNHardering (поэтому и скриншот из нее).

Теория подтвердилась практикой: приложение, находящееся в исключениях VPN, все равно находит его признаки. Оно видит наличие tun интерфейса, видит роуты в системе, список установленных пакетов, а главное — видит поднятый локальный socks на устройстве и может узнать внешний IP VPN сервера. К сожалению, с большинством из этих признаков (вроде списка пакетов или роутов) ничего не поделать, так как Android — достаточно свободная в этом плане ОС. Но вот с открытым socks-портом, который и стал причиной паники в последней статье, побороться можно и нужно.

Идея: прячем концы в воду

Раз разгорелся энтузиазм, надо направить его в нужное русло. В той самой статье про уязвимость VLESS автор справедливо заметил одну пугающую вещь: на данный момент ни один популярный VPN-клиент под Android не маскирует свой локальный socks-порт и дает возможность узнать внешний IP. Все они (v2RayTun, V2BOX, v2rayNG, Hiddify и прочие) светят им наружу, как маяком.

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

Чтобы закрыть этот socks от любопытных глаз других приложений, нужно сделать так, чтобы прокси поднимался на случайном порту и был запаролен случайными кредами, которые генерируются на лету и известны только самому приложению-клиенту. В итоге никакая другая софтина на телефоне (даже если просканирует локалхост) в этот прокси не пробьется и не задетектит внешний IP.

Под капотом: Xray, tun2socks и обход ограничений ОС

С VLESS все оказалось просто: есть проект xray-core, билды которого существуют под множество платформ. Бери бинарники, запускай из приложения, пользуй socks. Но хочется ведь иметь на устройстве полноценный сетевой интерфейс (tun).

Сам Xray умеет в туннель, однако на практике оказался крайне капризным в этом плане. Сколько я ни пытался поднять нормальный туннель его родными средствами, всегда всплывали какие-то подводные камни. Идти в гору оказалось непросто, поэтому было принято тактическое решение поискать альтернативы.

И альтернатива нашлась — tun2socks. Официального билда для Android у него нет, но статически собранный бинарник linux-arm64 заводится на зеленом роботе без каких-либо проблем. Получается, все инструменты есть, нужна только обертка, которая всё это свяжет.

Архитектурно клиент написан на солянке из Flutter, Kotlin и чуть-чуть C++. Что-то делегировал ИИ — и регулярно бил по рукам, когда тот пускался во все тяжкие. В итоге многое все равно писал сам: иногда реально проще сделать ручками, чем добиться от куска кремния выполнения своей задумки.

Отдельно стоит упомянуть запуск самих бинарников. Начиная с Android 10, система строго бьет по рукам за запуск исполняемых файлов из data-директории приложения (W^X violation). Чтобы обойти это ограничение, я упаковал исполняемые файлы в lib/ как .so библиотеки — старый добрый трюк, который отлично работает.

Вместо простыней кода просто покажу кусок лога, где отлично видно, как происходит динамическая генерация и связка:

[2026-04-12T09:44:46.641632] [INFO] Starting VPN[2026-04-12T09:44:46.641818] [INFO] Underlying network set: 176[2026-04-12T09:44:46.641946] [INFO] nativeSetMaxFds result (parent): 0[2026-04-12T09:44:46.643867] [INFO] TUN established, dup fd=142[2026-04-12T09:44:46.644004] [INFO] xray started[2026-04-12T09:44:46.644130] [INFO] Starting tun2socks (native): /data/app/~~_xfukaXUtA4X7kd1BmB4UA==/com.teapodstream.teapodstream-zTL0GGuDWxPk8XDn0VkAiw==/lib/arm64/libtun2socks.so -device fd://142 -proxy socks5://upu6CHvvV:ZJC3GMDXKOLFdPKAfxBxrHiU@127.0.0.1:45478 -mtu 1500 -loglevel error -tcp-sndbuf 524288 -tcp-rcvbuf 524288 -tcp-auto-tuning[2026-04-12T09:44:46.644247] [INFO] tun2socks started (pid=28880)[2026-04-12T09:44:46.644399] [INFO] VPN connected

Обратите внимание на строку запуска tun2socks: порт 45478 и забористые логин/пароль генерируются на лету при каждом старте соединения. Файловый дескриптор tun интерфейса (fd://142) пробрасывается напрямую в бинарник.

Итоги

И вот я таки родил работающее приложение — TeapodStream (отсылка к HTTP-коду 418 + намек на туннель/прокси/поток).

Главный вопрос: работает ли защита? Да. Проверка на тех самых приложениях-детекторах из статьи про уязвимость VLESS, а также тесты через RKNHardering показали, что они перестали детектировать открытый socks. Прокси и внешний IP спрятан.

Функционал пока тривиальный, но покрывающий базу:

  • Поддержка протоколов: VLESS, VMess, Trojan, Shadowsocks.

  • Раздельное туннелирование (сплит-туннелинг) — выбор приложений для исключения из VPN.

  • Подписки — автоматическое обновление конфигураций по URL.

  • И прочие необходимые мелочи.

Приложение с исходниками лежит на GitHub: https://github.com/Wendor/teapod-stream

По итогу я доволен: мозг размял, проблему решил, а раз вы читаете эту статью — значит, в этот раз я поработал не только на личный опыт, но и осмелился поделился результатом с сообществом)

Напоследок хочу спросить сообщество: как думаете, какие еще неочевидные маркеры наличия VPN могут начать массово использовать для детекта в ближайшем будущем?

Всем бобра!

ссылка на оригинал статьи https://habr.com/ru/articles/1022422/