Une fois, j'avais besoin de numériser les applications du réseau Wi-Fi depuis Android et d'obtenir un calcul détaillé des données sur les points d'accès.
Ici, j'ai dû faire face à plusieurs difficultés: dans la non
-documentation d'Android, de nombreuses classes décrites sont devenues obsolètes (niveau API> 26), ce qui ne s'y reflétait pas; la description de certaines choses dans la documentation est minime (par exemple, le champ des capacités de la classe
ScanResult n'était pas du tout décrit au moment de la rédaction, bien qu'il contienne beaucoup de données importantes). La troisième difficulté peut être que lorsque vous vous approchez pour la première fois du Wi-Fi, ce qui est différent de la lecture de la théorie et de la configuration du routeur pour l'hôte local, vous devez traiter un certain nombre d'abréviations qui semblent être claires séparément. Mais il peut ne pas être évident de savoir comment les relier et les structurer (le jugement est subjectif et dépend de l'expérience antérieure).
Cet article explique comment obtenir des données complètes sur les environnements Wi-Fi à partir de code Android sans NDK, sans piratage, mais uniquement en utilisant l'API Android et comment les interpréter.
Nous ne tirerons pas et ne commencerons pas à écrire du code.
1. Créez un projet
La note est conçue pour ceux qui ont créé le projet Android plus d'une fois, donc nous omettons les détails de cet article. Le code ci-dessous sera présenté dans le langage Kotlin, minSdkVersion = 23.
2. Autorisations d'accès
Pour travailler avec le Wi-Fi depuis l'application, vous devrez obtenir plusieurs autorisations de l'utilisateur. Selon la
documentation , afin d'analyser le réseau sur des appareils avec des versions de système d'exploitation après 8.0, en plus d'accéder à l'état de l'environnement réseau, vous devez avoir accès pour modifier l'état du module Wi-Fi de l'appareil ou accéder aux coordonnées (approximatives ou exactes). À partir de la version 9.0, vous devez demander à l'utilisateur à la fois ceci et cela, et en même temps demander explicitement à l'utilisateur d'activer le service de localisation. N'oubliez pas d'expliquer galamment à l'utilisateur qu'il s'agit d'un caprice de Google, et non de notre envie de l'espionner :)
Total, dans AndroidManifest.xml, ajoutez:
<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"/>
Et dans le code qui a un lien vers l'activité en cours:
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. Créez BroadcastReceiver et abonnez-vous pour mettre à jour les événements sur l'analyse de l'environnement du réseau 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 }
La méthode WiFiManager.startScan dans la documentation est marquée comme privée de l'API version 28, mais désactivée.
guide suggère de l'utiliser.
Au total, nous avons reçu une liste d'objets
ScanResult .
4. Nous examinons ScanResult et comprenons les termes
Regardons quelques champs de cette classe et décrivons ce qu'ils signifient:
SSID - Service Set Identifier est le nom du réseau.
BSSID - Basic Service Set Identifier - Adresse MAC de la carte réseau (point Wi-Fi)
niveau - Indicateur de force du signal reçu [dBm (dBm russe) - Décibel, puissance de référence 1 mW.] - Indicateur du niveau du signal reçu. Il prend une valeur de 0 à -100, plus loin de 0, plus la puissance du signal est perdue en cours de route du point Wi-Fi à votre appareil. Plus de détails peuvent être trouvés, par exemple, sur
Wikipedia . Ici, je vais vous dire qu'en utilisant la classe Android
WifiManager, vous pouvez calibrer le niveau du signal sur une échelle excellente à terrible avec l'étape que vous avez sélectionnée:
val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager val numberOfLevels = 5 val level = WifiManager.calculateSignalLevel(level, numberOfLevels)
fréquence - la fréquence du point Wi-Fi [Hz]. En plus de la fréquence elle-même, vous pouvez être intéressé par ce que l'on appelle le canal. Chaque point a sa propre propreté de travail. Au moment de la rédaction du présent document, la plage de points Wi-Fi la plus populaire est de 2,4 GHz. Mais, pour être plus précis, le point transfère des informations sur votre téléphone à une fréquence numérotée proche de celle indiquée. Le nombre de canaux et les valeurs des fréquences correspondantes sont
standardisés . Ceci est fait de sorte que les points voisins fonctionnent à des fréquences différentes, n'interférant ainsi pas entre eux et ne réduisant pas mutuellement la vitesse et la qualité de la transmission. Dans le même temps, les points ne fonctionnent pas sur la même fréquence, mais sur la plage de fréquences (
paramètre channelWidth), appelée largeur de canal. C'est-à-dire que les points opérant sur les canaux voisins (et pas seulement sur les canaux voisins, mais même sur 3 d'eux-mêmes) créent des interférences les uns avec les autres. Ce code simple peut vous être utile, ce qui vous permet de calculer le numéro de canal par la valeur de fréquence pour les points avec une fréquence de 2,4 et 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 } }
capacités est le domaine d'analyse le plus intéressant, ce qui a pris beaucoup de temps à travailler. Ici, les "capacités" du point sont écrites sur la ligne. Dans le même temps, vous ne pouvez pas rechercher des détails d'interprétation de la chaîne dans la documentation. Voici quelques exemples de ce qui pourrait se trouver sur cette ligne:
[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. Comprendre les abréviations et les capacités d'analyse
Il convient de mentionner que les classes du package android.net.wifi. * Utilise l'utilitaire linux
wpa_supplicant sous le capot, et la sortie dans le champ des capacités est une copie du champ des drapeaux pendant l'analyse.
Nous agirons séquentiellement. Tout d'abord, considérez la sortie d'un format dans lequel les éléments à l'intérieur des crochets sont séparés par un "-":
[WPA-PSK-TKIP+CCMP] [WPA2-PSK-CCMP]
La première valeur décrit le soi-disant
méthode d'authentification C'est-à-dire, quelle séquence d'actions doit être effectuée par l'appareil et le point d'accès pour que le point d'accès se permette d'être utilisé et comment crypter la charge utile. Au moment de la rédaction de cet article, les options les plus courantes sont WPA et WPA2, dans lesquelles chaque appareil connecté directement ou via ce qu'on appelle. Le serveur RADIUS (WPA-Enterprice) fournit un mot de passe sur un canal crypté. Très probablement, à votre domicile, le point d'accès fournit une connexion selon ce schéma. La différence entre la deuxième version et la première dans un chiffrement plus robuste: AES contre TKIP non sécurisé. Le WPA3, plus complexe et avancé, est également progressivement introduit. Théoriquement, il peut y avoir une option avec la solution enterprice CCKM (Cisco Centralized Key Managment), mais je ne l'ai jamais rencontrée.
Le point d'accès peut être configuré pour l'authentification par adresse MAC. Ou, si le point d'accès fournit des données à l'aide de l'algorithme WEP obsolète, il n'y a pratiquement pas d'authentification (la clé secrète ici est la clé de chiffrement). Ces options sont classées comme AUTRES.
Il existe également une méthode préférée dans le Wi-Fi public avec détection de portail captif caché - une demande d'authentification via un navigateur. Ces points d'accès semblent ouverts au scanner (ce qu'ils sont du point de vue de la connexion physique). Par conséquent, nous les classons comme OUVERTS.
La deuxième valeur peut être désignée comme
l'algorithme de gestion des clés . Il s'agit d'un paramètre de la méthode d'authentification décrite ci-dessus. Explique comment l'échange de clés de chiffrement se produit exactement. Examinons les options possibles. EAP - utilisé dans le WPA-Enterprice susmentionné, utilise une base de données pour vérifier les données d'authentification entrées. SAE - utilisé en WPA3 avancé, plus résistant à l'éclatement. PSK est l'option la plus courante, elle implique la saisie du mot de passe et sa transmission sous forme cryptée. IEEE8021X - selon la norme internationale (autre que celle prise en charge par la famille WPA). OWE (Opportunistic Wireless Encryption) est une extension de la norme IEEE 802.11, pour les points que nous avons classés comme OUVERTS. OWE assure la sécurité des données transmises sur un réseau non sécurisé en les chiffrant. Une variante est également possible lorsqu'il n'y a pas de clé d'accès; appelons cette option AUCUNE.
Le troisième paramètre est le soi-disant
méthode de chiffrement (schémas de chiffrement) - comment exactement le chiffrement est-il utilisé pour protéger les données transmises. Nous listons les options. WEP - utilise un chiffrement de flux RC4, la clé secrète est une clé de chiffrement, ce qui est considéré comme inacceptable dans le monde de la cryptographie moderne. TKIP - utilisé dans WPA, CKIP - dans WPA2. TKIP + CKIP - peut être spécifié à des points capables de WPA et WPA2 pour une compatibilité descendante.
Au lieu de trois éléments, vous pouvez trouver une seule marque WEP:
[WEP]
Comme nous l'avons vu ci-dessus, cela suffit pour ne pas spécifier l'algorithme d'utilisation des clés, qui n'est pas là, et la méthode de cryptage, qui est une par défaut.
Considérez maintenant ce support:
[ESS]
Il s'agit
du mode de fonctionnement Wi-Fi ou de la
topologie des réseaux Wi-Fi . Vous pouvez rencontrer le mode BSS (Basic Service Set) - lorsqu'il y a un point d'accès par lequel les appareils connectés communiquent. Peut être trouvé sur les réseaux locaux. En règle générale, des points d'accès sont nécessaires pour connecter des périphériques de différents réseaux locaux, ils font donc partie des ensembles de services étendus - ESS. Le type d'IBSS (Independent Basic Service Sets) indique que le périphérique fait partie d'un réseau peer-to-peer.
Le drapeau WPS peut également tomber:
[WPS]
WPS (Wi-Fi Protected Setup) - un protocole pour l'initialisation semi-automatique d'un réseau Wi-Fi. Pour initialiser, l'utilisateur entre un mot de passe à 8 caractères ou serre un bouton sur le routeur. Si votre point d'accès est du premier type et que cet indicateur est mis en surbrillance en face du nom de votre point d'accès, il est fortement recommandé d'aller dans le panneau d'administration et de désactiver l'accès via WPS. Le fait est que souvent un code PIN à 8 chiffres peut être reconnu par l'adresse MAC, ou trié dans les délais prévisibles, dont une personne impure peut profiter.
6. Créez un modèle et une fonction d'analyse
Sur la base de ce que nous avons découvert ci-dessus, nous décrivons avec des classes de données ce qui s'est passé:
enum class AuthMethod { WPA3, WPA2, WPA,
Nous allons maintenant écrire une fonction qui analysera le champ des capacités:
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. Nous regardons le résultat
Je vais scanner le réseau et montrer ce qui s'est passé. Affichage des résultats d'une sortie simple via Log.d:
Capability of Home-Home [WPA2-PSK-CCMP][ESS][WPS] ... capabilities=[Capability(authScheme=WPA2, keyManagementAlgorithm=PSK, cipherMethod=CCMP)], topologyMode=ESS, availableWps=true
La question de la connexion au réseau à partir du code d'application est restée éteinte. Je peux seulement dire que pour lire les mots de passe du système d'exploitation enregistrés d'un appareil mobile, vous avez besoin des droits root et d'une volonté de fouiller dans le système de fichiers pour lire wpa_supplicant.conf. Si la logique d'application implique la saisie d'un mot de passe de l'extérieur, la connexion peut être établie via la classe
android.net.wifi.WifiManager .
Merci à
Egor Ponomarev pour ses précieux ajouts.
Si vous pensez que vous devez ajouter ou corriger quelque chose, écrivez dans les commentaires :)