Wi-Fi y muchas otras abreviaturas. Cómo obtener datos sobre nodos Wi-Fi en una aplicación de Android sin hincharse

Una vez necesitaba escanear desde una aplicación de Android para redes Wi-Fi y obtener una visualización detallada de los datos sobre los puntos de acceso.

Aquí tuvimos que afrontar varias dificultades: apagado.Documentación de Android muchas de las clases descritas quedaron obsoletas (nivel API > 26), lo que no se reflejó en ellas; la descripción de algunas cosas en la documentación es mínima (por ejemplo, el campo de capacidades de la clase Resultado de la exploracion en el momento de escribir este artículo no se describe casi nada, aunque contiene muchos datos importantes). La tercera dificultad puede radicar en el hecho de que cuando te acercas por primera vez a Wi-Fi, además de leer la teoría y configurar el enrutador a través de localhost, tienes que lidiar con una serie de abreviaturas que parecen comprensibles individualmente. Pero puede que no sea 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 el entorno Wi-Fi del código de Android sin NDK, hacks, pero solo usando la API de Android y cómo interpretarlos.

No nos demoremos y comencemos a escribir código.

1. Crea un proyecto

La nota está dirigida a aquellos que han creado un proyecto de Android más de una vez, por lo que omitimos los detalles de este párrafo. El siguiente código estará en Kotlin, minSdkVersion=23.

2. Permisos de acceso

Para trabajar con Wi-Fi desde la aplicación, necesitará obtener varios permisos del usuario. De acuerdo con documentación, para escanear la red en dispositivos con versiones de sistema operativo posteriores a 8.0, además de acceder 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 (aproximadamente o exacto). A partir de la versión 9.0, debe solicitar al usuario ambas cosas y también solicitarle explícitamente que active los servicios de ubicación. No olvides explicarle galantemente al usuario que este es un capricho de Google y no nuestro deseo de espiarlo :)

En total, agregue a AndroidManifest.xml:

    <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) {
        // TODO: перед вызовом этой функции надо рассказать пользователю, зачем Вам доступ к местоположению
        val lm: LocationManager =
            context.applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager

        val gpsEnabled: Boolean = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
        val networkEnabled: Boolean = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

        if (!gpsEnabled && !networkEnabled) {
            context.startActivity(Intent(ACTION_LOCATION_SOURCE_SETTINGS));
        }
    }

3. Cree un BroadcastReceiver y suscríbase a los eventos de actualización de datos sobre el escaneo del entorno de la 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 obsoleto desde la versión 28 de API, pero desactivado. guía sugiere usarlo.

En total, recibimos una lista de objetos. Resultado de la exploracion.

4. Mire ScanResult y comprenda los términos

Veamos algunos campos de esta clase y describamos lo que significan:

SSID - Service Set Identifier es el nombre de la red

BSSID – Identificador de conjunto de servicios básicos – Dirección MAC del adaptador de red (puntos Wi-Fi)

nivel — Indicador de intensidad de la señal recibida [dBm (dBm ruso) — Decibelio, potencia de referencia 1 mW.] — Un indicador de la intensidad 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 perdió en el camino desde el punto Wi-Fi hasta su dispositivo. Se pueden encontrar más detalles, por ejemplo, en Р'РёРєРёРμРμРμРёРёРёРёРё. Aquí te cuento que usar la clase de Android Wi-FiAdministrador Puedes calibrar el nivel de la señal en una escala de excelente a terrible en el paso que elijas:

        val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
        val numberOfLevels = 5
        val level = WifiManager.calculateSignalLevel(level, numberOfLevels)

frecuencia — frecuencia de funcionamiento del punto Wi-Fi [Hz]. Además de la frecuencia en sí, puede que te interese el llamado canal. Cada punto tiene su propia pureza operativa. En el momento de escribir este artículo, la gama más popular de puntos Wi-Fi es la de 2.4 GHz. Pero, para ser más precisos, el punto transmite información a su teléfono en una frecuencia numerada cercana a la nombrada. Número de canales y frecuencias correspondientes estandarizado. Esto se hace para que los puntos cercanos funcionen en diferentes frecuencias, sin interferir entre sí y no reducir mutuamente la velocidad y la calidad de la transmisión. En este caso, los puntos no operan en una frecuencia, sino en un rango de frecuencias (parámetro Ancho de banda), llamado ancho de canal. Es decir, los puntos que operan en los canales vecinos (y no solo en los vecinos, sino incluso 3 de ellos mismos) interfieren entre sí. Puede encontrar útil este código simple, que le permite calcular el número de canal a partir del 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 - el campo más interesante para el análisis, cuyo trabajo requirió mucho tiempo. Aquí las "capacidades" del punto están escritas en la línea. Al mismo tiempo, no puede 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 análisis

Vale la pena mencionar que las clases del paquete android.net.wifi.* son utilizadas internamente por una utilidad de Linux. wpa_supplicant y el resultado de salida en el campo de capacidades es una copia del campo de banderas al escanear.

Actuaremos de forma coherente. Consideremos primero la salida de un formato en el que los elementos entre paréntesis están separados por un signo "-":

[WPA-PSK-TKIP+CCMP]
[WPA2-PSK-CCMP]

El primer significado describe el llamado. método de autentificación. Es decir, qué secuencia de acciones deben realizar el dispositivo y el punto de acceso para que el punto de acceso permita su uso y cómo cifrar la carga útil. Al momento de escribir este artículo, las opciones más comunes son WPA y WPA2, en las que cada dispositivo se conecta directamente oa través de los llamados. El servidor RADIUS (WPA-Enterprice) proporciona la contraseña a través de un canal encriptado. Lo más probable es que en su hogar, el punto de acceso proporcione una conexión de acuerdo con este esquema. La diferencia entre la segunda versión y la primera está en un cifrado más fuerte: AES versus TKIP inseguro. WPA3 también se está introduciendo gradualmente, más complejo y avanzado. En teoría, puede haber una opción con una solución empresarial CCKM (Administración centralizada de claves de Cisco), pero no la he encontrado.

Es posible que el punto de acceso se haya configurado para autenticarse por dirección MAC. O bien, si el punto de acceso proporciona datos mediante el algoritmo WEP obsoleto, en realidad no hay autenticación (la clave secreta aquí es la clave de cifrado). Nos referimos a tales variantes como OTRAS.
También hay un método wi-fi popular con una detección de portal cautivo oculta: una solicitud de autenticación a través de un navegador. Dichos puntos de acceso aparecen para el escáner como abiertos (que lo son en términos de conexión física). Por lo tanto, los referimos al tipo ABIERTO.

El segundo valor se puede denotar como algoritmo de gestión de claves. Es un parámetro del método de autenticación descrito anteriormente. Habla sobre cómo se intercambian exactamente las claves de cifrado. Considera 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 fuerza bruta. PSK: la opción más común, implica ingresar una contraseña y transmitirla en forma encriptada. IEEE8021X: de acuerdo con el estándar internacional (que no sea compatible con la familia WPA). OWE (Opportunistic Wireless Encryption) es una extensión del estándar IEEE 802.11 para puntos que hemos clasificado como ABIERTOS. OWE protege los datos transmitidos a través de una red insegura cifrándolos. También es posible cuando no hay claves de acceso, llamemos a esta opción NINGUNA.

El tercer parámetro es el llamado. esquemas de cifrado - cómo se usa exactamente el cifrado para proteger los datos transmitidos. Hagamos una lista de las opciones. WEP: utiliza el cifrado de flujo RC4, la clave secreta es la clave de cifrado, que se considera inaceptable en el mundo de la criptografía moderna. TKIP se usa en WPA, CKIP se usa 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 sola marca WEP:

[WEP]

Como comentamos anteriormente, esto es suficiente para no especificar el algoritmo de uso de claves, que no existe, y el método de cifrado, que es uno por defecto.

Ahora considere este paréntesis:

[ESS]

Lo modo de funcionamiento wifi o topología de redes wifi. Es posible que se encuentre con 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 regla general, los puntos de acceso son necesarios para conectar dispositivos de diferentes redes locales, por lo que forman parte de los conjuntos de servicios extendidos - ESS. El tipo IBSS (Conjuntos de Servicios Básicos Independientes) indica que el dispositivo es parte de una red Peer-to-Peer.

La bandera WPS también puede ser capturada:

[WPS]

WPS (Wi-Fi Protected Setup) es 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 presiona un botón en el enrutador. Si su punto de acceso es del primer tipo y esta casilla de verificación se muestra junto al nombre de su punto de acceso, se recomienda enfáticamente que vaya al panel de administración y deshabilite el acceso WPS. El hecho es que a menudo se puede encontrar un PIN de 8 dígitos por la dirección MAC, o se puede resolver en un tiempo previsible, que alguien deshonesto puede usar.

6. Crea un modelo y una función de análisis

Según lo que descubrimos anteriormente, describiremos lo que sucedió con las clases de datos:

/* схема аутентификации */
enum class AuthMethod {
    WPA3,
    WPA2,
    WPA, // Wi-Fi Protected Access
    OTHER, // включает в себя Shared Key Authentication и др. использующие mac-address-based и WEP
    CCKM, // Cisco
    OPEN // Open Authentication. Может быть со скрытым Captive Portal Detection - запрос аутентификации через браузер
}

/* алгоритм ввода ключей */
enum class KeyManagementAlgorithm {
    IEEE8021X, // по стандарту
    EAP, // Extensible Authentication Protocol, расширяемый протокол аутентификации
    PSK, // Pre-Shared Key — каждый узел вводит пароль для доступа к сети
    WEP, // в WEP пароль является ключом шифрования (No auth key)
    SAE, // Simultaneous Authentication of Equals - может быть в WPA3
    OWE, // Opportunistic Wireless Encryption - в роутерах новых поколений, публичных сетях типа OPEN
    NONE // может быть без шифрования в OPEN, OTHER
}

/* метод шифрования */
enum class CipherMethod {
    WEP, // Wired Equivalent Privacy, Аналог шифрования трафика в проводных сетях
    TKIP, // Temporal Key Integrity Protocol
    CCMP, // Counter Mode with Cipher Block Chaining Message Authentication Code Protocol,
    // протокол блочного шифрования с кодом аутентичности сообщения и режимом сцепления блоков и счетчика
    // на основе AES
    NONE // может быть без шифрования в OPEN, OTHER
}

/* набор методов шифрования и протоколов, по которым может работать точка */
data class Capability(
    var authScheme: AuthMethod? = null,
    var keyManagementAlgorithm: KeyManagementAlgorithm? = null,
    var cipherMethod: CipherMethod? = null
)

/* Режим работы WiFi (или топология сетей WiFi) */
enum class TopologyMode {
    IBSS, // Эпизодическая сеть (Ad-Hoc или IBSS – Independent Basic Service Set).
    BSS, // Основная зона обслуживания Basic Service Set (BSS) или Infrastructure Mode.
    ESS // Расширенная зона обслуживания ESS – Extended Service Set.
}

Ahora escribamos 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. Ver el resultado

Escanearé la red y te mostraré lo que pasó. Se muestran los resultados de una 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 la conexión a la red desde el código de la aplicación quedó sin examinar. Solo diré que para leer las contraseñas guardadas del sistema operativo de un dispositivo móvil, necesita derechos de root y la voluntad de hurgar en el sistema de archivos para leer wpa_supplicant.conf. Si la lógica de la aplicación requiere ingresar una contraseña desde afuera, la conexión se puede realizar a través de la clase android.net.wifi.WifiManager.

Gracias Egor Ponomarev para valiosas adiciones.

Si crees que es necesario agregar o corregir algo, escribe en los comentarios :)

Fuente: habr.com

Añadir un comentario