Skip to main content

yourvpndead-vpn-detection

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

跳到安装

来源信息

仓库
reason-machines/trending-skills
最近来源活动
2026年4月13日 07:44
检测到的 SKILL.md 语言
英语
星标
82
分支
15

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
yourvpndead-vpn-detection
description
Android app that detects VPN/proxy servers (VLESS/xray/sing-box) via local SOCKS5 vulnerability, exposing exit IPs and server configs without root
triggers
["detect VPN server IP on Android","scan localhost SOCKS5 proxy ports","find xray v2rayNG exposed ports","check if VPN app is leaking server IP","scan sing-box clash API on localhost","detect VPN using Android network capabilities","find open proxy ports on Android device","check VPN client vulnerability android"]
# YourVPNDead — Android VPN Detection & SOCKS5 Vulnerability Scanner > Skill by [ara.so](https://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.git cd yourvpndead ./gradlew assembleDebug # Output: app/build/outputs/apk/debug/app-debug.apk adb install app/build/outputs/apk/debug/app-debug.apk ``` Or download the pre-built APK from [Releases](https://github.com/loop-uh/yourvpndead/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 capability val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val network = connectivityManager.activeNetwork val 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 properties val 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 pattern fun 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.InetSocketAddress import 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-65535 suspend 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.InputStream import java.io.OutputStream import 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 probe fun 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.InetAddress import 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.HttpURLConnection import java.net.URL import 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 API val 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",
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看