Una vez que necesitaba escanear aplicaciones de red Wi-Fi de Android y obtener un cálculo detallado de datos sobre puntos de acceso.
Aquí tuve que enfrentar varias dificultades: en la
falta de documentación de Android, muchas de las clases descritas quedaron en desuso (nivel API> 26), lo que no se reflejó en él; la descripción de algunas cosas en la documentación es mínima (por ejemplo, el campo de capacidades de la clase
ScanResult en el momento de la escritura no se describió de ninguna manera, aunque contiene muchos datos importantes). La tercera dificultad puede ser que cuando se acerque por primera vez a Wi-Fi, que es diferente de leer la teoría y configurar el enrutador para localhost, debe lidiar con una serie de abreviaturas que parecen estar claras por separado. Pero puede no ser obvio cómo relacionarlos y estructurarlos (el juicio es subjetivo y depende de la experiencia previa).
Este artículo analiza cómo obtener datos completos sobre entornos de Wi-Fi a partir del código de Android sin NDK, hacks, pero solo usando la API de Android y comprende cómo interpretarlos.
No tiraremos y comenzaremos a escribir código.
1. Crear un proyecto
La nota está diseñada para aquellos que han creado el proyecto de Android más de una vez, por lo tanto, omitimos los detalles de este elemento. El siguiente código se presentará en el idioma Kotlin, minSdkVersion = 23.
2. Permisos de acceso
Para trabajar con Wi-Fi desde la aplicación, deberá obtener varios permisos del usuario. De acuerdo con la
documentación , para escanear la red en dispositivos con versiones de SO posteriores a 8.0, además del acceso para ver el estado del entorno de red, necesita acceso para cambiar el estado del módulo Wi-Fi del dispositivo o acceso a las coordenadas (aproximadas o exactas). A partir de la versión 9.0, debe solicitar al usuario ambos y otros, y solicitar explícitamente al usuario que habilite el servicio de ubicación. No olvides explicar valientemente al usuario que esto es un capricho de Google, y no nuestro deseo de espiarlo :)
Total, en AndroidManifest.xml agregar:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
Y en el código que tiene un enlace a la Actividad actual:
import android.app.Activity import android.content.Context import android.location.LocationManager import androidx.core.app.ActivityCompat .... if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { ActivityCompat.requestPermissions( activity, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.CHANGE_WIFI_STATE), 1 ) makeEnableLocationServices(activity.applicationContext) } else { ActivityCompat.requestPermissions( activity, arrayOf(Manifest.permission.CHANGE_WIFI_STATE), 1 ) } fun makeEnableLocationServices(context: Context) {
3. Cree BroadcastReceiver y suscríbase para actualizar eventos al escanear el entorno de red Wi-Fi
val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager val wifiScanReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false) if (success) { scanSuccess() } } } val intentFilter = IntentFilter() intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) context.registerReceiver(wifiScanReceiver, intentFilter) val success = wifiManager.startScan() if (!success) { } .... private fun scanSuccess() { val results: List<ScanResult> = wifiManager.scanResults }
El método WiFiManager.startScan en la documentación está marcado como depricated de API versión 28, pero desactivado.
guía sugiere usarlo.
En total, recibimos una lista de objetos
ScanResult .
4. Observamos ScanResult y entendemos los términos
Veamos algunos campos de esta clase y describamos lo que significan:
SSID : el Identificador de conjunto de servicios es el nombre de la red.
BSSID - Identificador de conjunto de servicios básicos - Dirección MAC del adaptador de red (punto Wi-Fi)
level - Indicador de intensidad de la señal recibida [dBm (dBm ruso) - Decibel, potencia de referencia 1 mW.] - Indicador del nivel de la señal recibida. Toma un valor de 0 a -100, cuanto más lejos de 0, más potencia de señal se pierde en el camino desde el punto de Wi-Fi a su dispositivo. Se pueden encontrar más detalles en
Wikipedia, por ejemplo. Aquí te diré que usando el
WifiManager clase Android
, puedes calibrar el nivel de señal en una escala de excelente a terrible con el paso que seleccionaste:
val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager val numberOfLevels = 5 val level = WifiManager.calculateSignalLevel(level, numberOfLevels)
frecuencia : la frecuencia del punto de Wi-Fi [Hz]. Además de la frecuencia en sí, puede interesarle el llamado canal. Cada punto tiene su propia limpieza de trabajo. Al momento de escribir, el rango más popular de puntos de Wi-Fi es 2.4 GHz. Pero, para ser más precisos, el punto transfiere información a su teléfono con una frecuencia numerada cercana a la mencionada. El número de canales y los valores de las frecuencias correspondientes están
estandarizados . Esto se hace para que los puntos cercanos funcionen a diferentes frecuencias, por lo que no interfieren entre sí y no reducen mutuamente la velocidad y la calidad de la transmisión. Al mismo tiempo, los puntos no funcionan en la misma frecuencia, sino en el rango de frecuencia (
parámetro channelWidth), llamado ancho del canal. Es decir, los puntos que operan en canales vecinos (y no solo en canales vecinos, sino incluso en 3 de ellos mismos) crean interferencia entre sí. Este código simple puede ser útil para usted, que le permite calcular el número de canal por el valor de frecuencia para puntos con una frecuencia de 2.4 y 5 Ghz:
val channel: Int get() { return if (frequency in 2412..2484) { (frequency - 2412) / 5 + 1 } else if (frequency in 5170..5825) { (frequency - 5170) / 5 + 34 } else { -1 } }
Capacidades es el campo más interesante para el análisis, con el que tomó mucho tiempo trabajar. Aquí, las "capacidades" del punto se escriben en la línea. En este caso, no se pueden buscar detalles de la interpretación de la cadena en la documentación. Aquí hay algunos ejemplos de lo que podría estar en esta línea:
[WPA-PSK-TKIP+CCMP][WPA2-PSK-TKIP+CCMP][WPS][ESS] [WPA2-PSK-CCMP][ESS] [WPA2-PSK-CCMP+TKIP][ESS] [WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][ESS] [ESS][WPS]
5. Comprender las abreviaturas y las capacidades de parsim
Vale la pena mencionar que las clases del paquete android.net.wifi. * Utiliza la utilidad de linux
wpa_supplicant debajo del capó, y la salida en el campo de capacidades es una copia del campo de banderas durante el escaneo.
Actuaremos secuencialmente. Primero, considere la salida de un formato en el que los elementos dentro de los corchetes están separados por un signo "-":
[WPA-PSK-TKIP+CCMP] [WPA2-PSK-CCMP]
El primer valor describe el llamado
método de autenticación Es decir, qué secuencia de acciones debe realizar el dispositivo y el punto de acceso para que el punto de acceso se permita utilizar y cómo cifrar la carga útil. En el momento de escribir esta publicación, las opciones más comunes son WPA y WPA2, en las que cada dispositivo conectado directamente o mediante el llamado. El servidor RADIUS (WPA-Enterprice) proporciona una contraseña a través de un canal encriptado. Lo más probable es que el punto de acceso en su hogar proporcione una conexión de acuerdo con este esquema. La diferencia entre la segunda versión y la primera en un cifrado más robusto: AES contra TKIP inseguro. WPA3, que es más complejo y avanzado, también se está introduciendo gradualmente. Teóricamente, puede haber una opción con la solución enterprice CCKM (Cisco Centralized Key Managment), pero nunca me he encontrado.
El punto de acceso podría configurarse para la autenticación por dirección MAC. O, si el punto de acceso proporciona datos utilizando el algoritmo WEP desactualizado, entonces prácticamente no hay autenticación (la clave secreta aquí es la clave de cifrado). Estas opciones se clasifican como OTRAS.
También hay un método favorito en wi-fi público con detección de portal cautivo oculto: una solicitud de autenticación a través de un navegador. Dichos puntos de acceso parecen estar abiertos al escáner (que son desde el punto de vista de la conexión física). Por lo tanto, los clasificamos como ABIERTOS.
El segundo valor puede designarse como
el algoritmo de gestión de claves . Es un parámetro del método de autenticación descrito anteriormente. Habla sobre cómo ocurre exactamente el intercambio de claves de cifrado. Consideremos las posibles opciones. EAP: utilizado en el mencionado WPA-Enterprice, utiliza una base de datos para verificar los datos de autenticación ingresados. SAE: utilizado en WPA3 avanzado, más resistente a la rotura. PSK es la opción más común, implica ingresar la contraseña y transmitirla en forma cifrada. IEEE8021X: de acuerdo con el estándar internacional (que no sea compatible con la familia WPA). OWE (Cifrado inalámbrico oportunista) es una extensión del estándar IEEE 802.11, para los puntos que clasificamos como ABIERTO. OWE garantiza la seguridad de los datos transmitidos a través de una red insegura cifrándolos. También es posible una variante cuando no hay claves de acceso; llamemos a esta opción NINGUNO.
El tercer parámetro es el llamado
método de cifrado (esquemas de cifrado) : cómo se usa exactamente el cifrado para proteger los datos transmitidos. Enumeramos las opciones. WEP: utiliza un cifrado de flujo RC4, la clave secreta es una clave de cifrado, que se considera inaceptable en el mundo de la criptografía moderna. TKIP: utilizado en WPA, CKIP: en WPA2. TKIP + CKIP: se puede especificar en puntos compatibles con WPA y WPA2 para compatibilidad con versiones anteriores.
En lugar de tres elementos, puede encontrar una marca WEP solitaria:
[WEP]
Como discutimos anteriormente, esto es suficiente para no especificar el algoritmo para usar claves, que no está allí, y el método de cifrado, que es uno por defecto.
Ahora considere este soporte:
[ESS]
Este es
el modo de operación de Wi-Fi o la
topología de las redes Wi-Fi . Puede encontrar el modo BSS (Conjunto de servicios básicos), cuando hay un punto de acceso a través del cual se comunican los dispositivos conectados. Se puede encontrar en las redes locales. Por lo general, se necesitan puntos de acceso para conectar dispositivos desde diferentes redes locales, por lo que forman parte de los conjuntos de servicios extendidos (ESS). El tipo de IBSS (conjuntos de servicios básicos independientes) indica que el dispositivo forma parte de una red punto a punto.
La bandera WPS también puede caer:
[WPS]
WPS (Configuración protegida de Wi-Fi): un protocolo para la inicialización semiautomática de una red Wi-Fi. Para inicializar, el usuario ingresa una contraseña de 8 caracteres o sujeta un botón en el enrutador. Si su punto de acceso es del primer tipo y esta marca está resaltada frente al nombre de su punto de acceso, le recomendamos encarecidamente que vaya al panel de administración y desactive el acceso a través de WPS. El hecho es que, a menudo, la dirección MAC puede reconocer un PIN de 8 dígitos, o resolverlo en el tiempo previsible, del que alguien inmundo puede aprovechar.
6. Crear un modelo y función de análisis
En base a lo que descubrimos anteriormente, describimos con clases de datos lo que sucedió:
enum class AuthMethod { WPA3, WPA2, WPA,
Ahora escribiremos una función que analizará el campo de capacidades:
private fun parseCapabilities(capabilitiesString: String): List < Capability > { val capabilities: List < Capability > = capabilitiesString .splitByBrackets() .filter { !it.isTopology() && !it.isWps() } .flatMap { parseCapability(it) } return if (!capabilities.isEmpty()) { capabilities } else { listOf(Capability(AuthMethod.OPEN, KeyManagementAlgorithm.NONE, CipherMethod.NONE)) } } private fun parseCapability(part: String): List < Capability > { if (part.contains("WEP")) { return listOf(Capability( AuthMethod.OTHER, KeyManagementAlgorithm.WEP, CipherMethod.WEP )) } val authScheme = when { part.contains("WPA3") - > AuthMethod.WPA3 part.contains("WPA2") - > AuthMethod.WPA2 part.contains("WPA") - > AuthMethod.WPA else - > null } val keyManagementAlgorithm = when { part.contains("OWE") - > KeyManagementAlgorithm.OWE part.contains("SAE") - > KeyManagementAlgorithm.SAE part.contains("IEEE802.1X") - > KeyManagementAlgorithm.IEEE8021X part.contains("EAP") - > KeyManagementAlgorithm.EAP part.contains("PSK") - > KeyManagementAlgorithm.PSK else - > null } val capabilities = ArrayList < Capability > () if (part.contains("TKIP") || part.contains("CCMP")) { if (part.contains("TKIP")) { capabilities.add(Capability( authScheme ? : AuthMethod.OPEN, keyManagementAlgorithm ? : KeyManagementAlgorithm.NONE, CipherMethod.TKIP )) } if (part.contains("CCMP")) { capabilities.add(Capability( authScheme ? : AuthMethod.OPEN, keyManagementAlgorithm ? : KeyManagementAlgorithm.NONE, CipherMethod.CCMP )) } } else if (authScheme != null || keyManagementAlgorithm != null) { capabilities.add(Capability( authScheme ? : AuthMethod.OPEN, keyManagementAlgorithm ? : KeyManagementAlgorithm.NONE, CipherMethod.NONE )) } return capabilities } private fun parseTopologyMode(capabilitiesString: String): TopologyMode ? { return capabilitiesString .splitByBrackets() .mapNotNull { when { it.contains("ESS") - > TopologyMode.ESS it.contains("BSS") - > TopologyMode.BSS it.contains("IBSS") - > TopologyMode.IBSS else - > null } } .firstOrNull() } private fun parseWPSAvailable(capabilitiesString: String): Boolean { return capabilitiesString .splitByBrackets() .any { it.isWps() } } private fun String.splitByBrackets(): List < String > { val m = Pattern.compile("\\[(.*?)\\]").matcher(this) val parts = ArrayList < String > () while (m.find()) { parts.add(m.group().replace("[", "").replace("]", "")) } return parts } private fun String.isTopology(): Boolean { return TopologyMode.values().any { this == it.name } } private fun String.isWps(): Boolean { return this == "WPS" }
8. Nos fijamos en el resultado
Escanearé la red y mostraré lo que sucedió. Mostrando los resultados de salida simple a través de Log.d:
Capability of Home-Home [WPA2-PSK-CCMP][ESS][WPS] ... capabilities=[Capability(authScheme=WPA2, keyManagementAlgorithm=PSK, cipherMethod=CCMP)], topologyMode=ESS, availableWps=true
La cuestión de conectarse a la red desde el código de la aplicación permaneció sin iluminar. Solo puedo decir que para leer las contraseñas guardadas del sistema operativo de un dispositivo móvil, necesita derechos de root y estar dispuesto a hurgar en el sistema de archivos para leer wpa_supplicant.conf. Si la lógica de la aplicación implica ingresar una contraseña desde afuera, la conexión se puede hacer a través de la clase
android.net.wifi.WifiManager .
Gracias a
Egor Ponomarev por valiosas adiciones.
Si cree que necesita agregar o arreglar algo, escriba los comentarios :)