RKNHardering

More: Author   ReportBugs   
Tags:

Android-приложение для обнаружения VPN и прокси на устройстве. Реализует методику РКН по выявлению средств обхода блокировок.

Минимальная версия Android: 8.0 (API 26).

Архитектура

Четыре независимых модуля проверки запускаются параллельно, результаты передаются в VerdictEngine для вынесения итогового вердикта.

VpnCheckRunner
├── GeoIpChecker       — внешний IP через ip-api.com
├── DirectSignsChecker — системные флаги и переменные
├── IndirectSignsChecker — сетевые интерфейсы, маршруты, DNS, dumpsys
└── BypassChecker      — сканирование портов + Xray API
        └── VerdictEngine — логика итогового вердикта

Модули проверки

1. GeoIP (GeoIpChecker)

Источник: http://ip-api.com/json/ с полями status,country,countryCode,isp,org,as,proxy,hosting,query.

Поле API Что проверяется Флаг детекта
countryCode Страна IP не RU да
hosting IP принадлежит хостинг-провайдеру да
proxy IP в базе известных прокси/VPN да
country, isp, org, as, query Информационно (в вывод) нет

Детект срабатывает если хотя бы одно из трёх полей положительно.

Таймаут соединения и чтения: 10 секунд. При ошибке сети детект = false.


2. Прямые признаки (DirectSignsChecker)

Системные признаки без сетевых запросов.

2.1 NetworkCapabilities (checkVpnTransport)

API: ConnectivityManager.getNetworkCapabilities(activeNetwork)

Проверка Метод/поле Флаг детекта
NetworkCapabilities.TRANSPORT_VPN caps.hasTransport(TRANSPORT_VPN) да
IS_VPN caps.toString().contains("IS_VPN") да
VpnTransportInfo caps.toString().contains("VpnTransportInfo") да

IS_VPN и VpnTransportInfo — внутренние флаги Android, не раскрытые в публичном API, проверяются через строковое представление объекта.

2.2 Системные прокси-переменные (checkSystemProxy)

Системная переменная Назначение Флаг детекта
System.getProperty("http.proxyHost") Хост HTTP-прокси да (если не пусто)
System.getProperty("http.proxyPort") Порт HTTP-прокси нет (инфо)
System.getProperty("socksProxyHost") Хост SOCKS-прокси да (если не пусто)
System.getProperty("socksProxyPort") Порт SOCKS-прокси нет (инфо)

Дополнительно: если порт входит в список известных, фиксируется отдельная находка.

Известные порты: 1080, 9000, 5555 (SOCKS), 8080, 3128 (HTTP proxy), 9050, 9150 (Tor).

2.3 Известные VPN-приложения (checkKnownVpnApps)

Проверяет наличие установленных пакетов через PackageManager.getPackageInfo. Факт установки фиксируется независимо от того, активно ли приложение в данный момент.

Все перечисленные ниже приложения на Android создают TUN-интерфейс tun0 через VpnService API (имя назначается ядром, не приложением) — поэтому обнаружение по имени интерфейса не даёт дополнительной информации. Проверка по пакету надёжнее.

Пакет Приложение
com.v2ray.ang v2rayNG
io.nekohasekai.sfa sing-box
app.hiddify.com Hiddify
com.github.metacubex.clash.meta ClashMeta for Android
com.github.shadowsocks Shadowsocks
com.github.shadowsocks.tv Shadowsocks TV
com.happproxy HAPP VPN
io.github.saeeddev94.xray XrayNG
moe.nb4a NekoBox
io.github.dovecoteescapee.byedpi ByeDPI
com.romanvht.byebyedpi ByeByeDPI
org.outline.android.client Outline
com.psiphon3 Psiphon
org.getlantern.lantern Lantern
com.wireguard.android WireGuard
com.strongswan.android strongSwan
org.torproject.android Tor Browser
info.guardianproject.orfox Orbot
org.torproject.torbrowser Tor Browser (official)

3. Косвенные признаки (IndirectSignsChecker)

3.1 Capability NOT_VPN (checkNotVpnCapability)

ConnectivityManager.getNetworkCapabilities(activeNetwork).toString() проверяется на наличие строки NOT_VPN. Отсутствие этого флага в активной сети подозрительно.

Результат Флаг детекта
NOT_VPN присутствует нет
NOT_VPN отсутствует да

3.2 Сетевые интерфейсы (checkNetworkInterfaces)

API: NetworkInterface.getNetworkInterfaces(). Проверяются активные (isUp) интерфейсы.

Паттерн интерфейса Протокол Флаг детекта
tun\d+ TUN (OpenVPN, WireGuard в режиме TUN) да
tap\d+ TAP (OpenVPN в режиме TAP) да
wg\d+ WireGuard да
ppp\d+ PPP (L2TP/PPTP) да
ipsec.* IPSec да

3.3 Аномалия MTU (checkMtu)

Проверяется MTU интерфейсов. VPN-туннели обычно имеют MTU < 1500 из-за инкапсуляции.

Условие Флаг детекта
VPN-подобный интерфейс (tun/tap/wg/ppp/ipsec) с MTU 1–1499 да
Нестандартный интерфейс (не wlan/rmnet/eth/lo) с MTU 1–1499 да

Стандартные интерфейсы (wlan.*, rmnet.*, eth.*, lo) не проверяются на MTU.

3.4 Таблица маршрутизации (checkRoutingTable)

Источник: /proc/net/route. Строки с destination=00000000 — маршрут по умолчанию (0.0.0.0/0).

Условие Флаг детекта
Маршрут по умолчанию через wlan.*, rmnet.*, eth.*, lo нет
Маршрут по умолчанию через любой другой интерфейс да

3.5 DNS-серверы (checkDns)

API: ConnectivityManager.getLinkProperties(activeNetwork).dnsServers.

Адрес DNS Интерпретация Флаг детекта
127.x.x.x Localhost — локальный DNS-резолвер VPN да
10.x.x.x / 172.16–31.x.x / 192.168.x.x Частная подсеть — типично для VPN-туннеля да
169.254.x.x Link-local нет
Публичный адрес Норма нет

3.6 dumpsys vpn_management (checkDumpsysVpn)

Только Android 12+ (API 31+). Запускает dumpsys vpn_management через Runtime.exec.

Результат Флаг детекта
Строка Active package name: в выводе да
Строка Active vpn type: в выводе да
Запись вида \d+:... в секции VPNs: да
Пустой вывод / Permission Denial / Can't find service нет

3.7 dumpsys activity services VpnService (checkDumpsysVpnService)

Запускает dumpsys activity services android.net.VpnService через Runtime.exec.

Результат Флаг детекта
ServiceRecord с VpnService в выводе да
(nothing) или нет ServiceRecord нет
Пустой вывод / Permission Denial нет

Из найденных записей извлекается имя пакета VPN-приложения.


4. Bypass-проверка (BypassChecker)

Сканирование localhost на наличие открытых прокси и Xray/V2Ray API. Запускается параллельно в двух потоках.

4.1 Сканер прокси (ProxyScanner + ProxyProber)

Сканирует 127.0.0.1 и ::1. Режимы:

Режим Описание
AUTO Сначала популярные порты, при неудаче — полное сканирование
MANUAL Конкретный порт

Популярные порты (AUTO, фаза 1): 1080, 2080, 1081, 10808, 10809, 12334, 7890.

Полное сканирование (AUTO, фаза 2): диапазон 1024–65535, параллельность 200 воркеров, таймаут соединения 80 мс, таймаут чтения 120 мс.

Определение типа прокси (ProxyProber.probeNoAuthProxyType):

Тип Протокол обнаружения
SOCKS5 \x05\x01\x00 → ожидание \x05\x00 (version=5, method=NO_AUTH)
HTTP CONNECT ifconfig.me:443 HTTP/1.1 → ожидание HTTP/x.x 200

Обнаруживаются только прокси без аутентификации.

При нахождении прокси дополнительно: запрашивается прямой IP (ifconfig.me) и IP через прокси. Если они различаются — фиксируется per-app split bypass.

4.2 Сканер Xray gRPC API (XrayApiScanner)

Сканирует 127.0.0.1 и ::1, диапазон 1024–65535, параллельность 100 воркеров, таймаут 200 мс.

Метод обнаружения (isGrpcEndpoint): отправка HTTP/2 connection preface + пустой SETTINGS-фрейм, ожидание SETTINGS-фрейма в ответе (тип 0x04). Не требует protobuf/gRPC зависимостей.

Результат Флаг детекта
Открытый SOCKS5 или HTTP прокси на localhost да
Xray gRPC API на localhost да

Вердикт (VerdictEngine)

Bypass GeoIP Прямые Косвенные Вердикт
да любой любые любые DETECTED
нет да да или косв. да или прям. DETECTED
нет да нет нет NEEDS_REVIEW
нет нет да да NEEDS_REVIEW
нет нет нет или да нет или да NOT_DETECTED

Bypass — наиболее сильный сигнал, перекрывает остальные.


Сборка

Требования: JDK 17+, Android SDK с Build Tools для API 36.

./gradlew assembleDebug

Благодарности

runetfreedom — за per-app-split-bypass-poc, на основе которого реализована детекция per-app split bypass.

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools