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:
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
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.
En total, recibimos una lista de objetos.
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
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
/* по частоте определяем номер канала */
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.
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
Gracias
Si crees que es necesario agregar o corregir algo, escribe en los comentarios :)
Fuente: habr.com