Wi-Fi und viele andere Abkürzungen. So erhalten Sie Daten über Wi-Fi-Knoten in einer Android-Anwendung, ohne aufzublähen

Eines Tages musste ich Wi-Fi-Netzwerke von Android-Anwendungen aus scannen und detaillierte Daten über Zugangspunkte erhalten.

Hier mussten wir uns mehreren Schwierigkeiten stellen: off.Android-Dokumentation viele der beschriebenen Klassen wurden veraltet (API-Level > 26), was sich darin nicht widerspiegelte; Die Beschreibung einiger Dinge in der Dokumentation ist minimal (z. B. das Feld „Fähigkeiten“ der Klasse). Scan-Ergebnis Zum Zeitpunkt des Schreibens ist fast nichts beschrieben, obwohl es viele wichtige Daten enthält. Die dritte Schwierigkeit dürfte darin liegen, dass man sich bei der ersten Annäherung an WLAN neben der Lektüre der Theorie und der Einrichtung des Routers über localhost mit einer Reihe von Abkürzungen herumschlagen muss, die einzeln verständlich erscheinen. Es ist jedoch möglicherweise nicht offensichtlich, wie man sie in Beziehung setzt und strukturiert (die Beurteilung ist subjektiv und hängt von früheren Erfahrungen ab).

In diesem Artikel wird erläutert, wie Sie umfassende Daten über die Wi-Fi-Umgebung aus Android-Code ohne NDK und Hacks, aber nur mithilfe der Android-API erhalten und verstehen, wie diese zu interpretieren sind.

Lassen Sie uns nicht zögern und mit dem Schreiben von Code beginnen.

1. Erstellen Sie ein Projekt

Dieser Hinweis richtet sich an diejenigen, die mehr als einmal ein Android-Projekt erstellt haben, daher lassen wir die Details zu diesem Punkt weg. Der folgende Code wird in Kotlin dargestellt, minSdkVersion=23.

2. Zugriffsberechtigungen

Um über die Anwendung mit WLAN arbeiten zu können, müssen Sie vom Benutzer mehrere Berechtigungen einholen. Gemäß DokumentationUm das Netzwerk auf Geräten mit Betriebssystemversionen nach 8.0 zu scannen, benötigen Sie zusätzlich zum Zugriff auf die Anzeige des Status der Netzwerkumgebung entweder Zugriff zum Ändern des Status des Wi-Fi-Moduls des Geräts oder Zugriff auf Koordinaten (ungefähr). oder genau). Ab Version 9.0 müssen Sie den Benutzer zu beidem auffordern und den Benutzer außerdem explizit auffordern, die Ortungsdienste zu aktivieren. Vergessen Sie nicht, dem Nutzer galant zu erklären, dass dies eine Laune von Google ist und nicht unser Wunsch, ihn auszuspionieren :)

Also werden wir in AndroidManifest.xml Folgendes hinzufügen:

    <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"/>

Und im Code, der einen Link zur aktuellen Aktivität enthält:

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. Erstellen Sie einen BroadcastReceiver und abonnieren Sie Datenaktualisierungsereignisse zum Scannen der Wi-Fi-Netzwerkumgebung

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
}

Die WiFiManager.startScan-Methode in der Dokumentation ist seit API-Version 28 als veraltet markiert, aber deaktiviert. Guide schlägt vor, es zu verwenden.

Insgesamt haben wir eine Objektliste erhalten Scan-Ergebnis.

4. Sehen Sie sich ScanResult an und verstehen Sie die Bedingungen

Schauen wir uns einige Felder dieser Klasse an und beschreiben, was sie bedeuten:

SSID — Service Set Identifier ist der Name des Netzwerks

BSSID – Basic Service Set Identifier – MAC-Adresse des Netzwerkadapters (WLAN-Punkt)

Grad des — Anzeige der empfangenen Signalstärke [dBm (russisches dBm) – Dezibel, Referenzleistung 1 mW.] — Ein Indikator für die empfangene Signalstärke. Nimmt einen Wert von 0 bis -100 an. Je weiter von 0 entfernt, desto mehr Signalleistung ging auf dem Weg vom WLAN-Punkt zu Ihrem Gerät verloren. Nähere Einzelheiten finden Sie beispielsweise unter Wikipedia. Hier erzähle ich Ihnen das anhand der Android-Klasse WLAN-Manager Sie können den Signalpegel in dem von Ihnen gewählten Schritt auf einer Skala von ausgezeichnet bis schrecklich kalibrieren:

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

Frequenz — Betriebsfrequenz des WLAN-Punkts [Hz]. Neben der Frequenz selbst könnte Sie auch der sogenannte Kanal interessieren. Jeder Punkt hat seine eigene Betriebsreinheit. Zum Zeitpunkt des Verfassens dieses Artikels ist 2.4 GHz der beliebteste WLAN-Punktbereich. Genauer gesagt überträgt der Punkt Informationen auf einer nummerierten Frequenz, die der genannten nahe kommt, an Ihr Telefon. Anzahl der Kanäle und entsprechende Frequenzen standardisiert. Dies geschieht, damit nahegelegene Punkte mit unterschiedlichen Frequenzen arbeiten und sich dadurch nicht gegenseitig stören und die Geschwindigkeit und Qualität der Übertragung nicht gegenseitig beeinträchtigen. In diesem Fall arbeiten die Punkte nicht mit einer Frequenz, sondern über einen Frequenzbereich (Parameter). Kanalbreite), genannt Kanalbreite. Das heißt, Punkte, die auf benachbarten (und nicht nur benachbarten, sondern sogar 3 von sich selbst entfernten) Kanälen arbeiten, stören sich gegenseitig. Möglicherweise ist dieser einfache Code hilfreich, mit dem Sie die Kanalnummer aus dem Frequenzwert für Punkte mit einer Frequenz von 2.4 und 5 GHz berechnen können:


    /* по частоте определяем номер канала */
    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
            }
        }

Fähigkeiten - das interessanteste Analysefeld, dessen Arbeit viel Zeit in Anspruch nahm. Hier werden die „Fähigkeiten“ des Punktes in die Zeile geschrieben. In diesem Fall müssen Sie nicht in der Dokumentation nach Details zur String-Interpretation suchen. Hier sind einige Beispiele dafür, was in dieser Zeile stehen könnte:

[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. Abkürzungen und Analysefunktionen verstehen

Es ist erwähnenswert, dass die Klassen des Pakets android.net.wifi.* unter der Haube von einem Linux-Dienstprogramm verwendet werden wpa_supplicant und das Ausgabeergebnis im Capabilities-Feld ist eine Kopie des Flags-Feldes beim Scannen.

Wir werden konsequent handeln. Betrachten wir zunächst die Ausgabe eines Formats, in dem Elemente in Klammern durch ein „-“-Zeichen getrennt sind:

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

Die erste Bedeutung beschreibt das sogenannte. Authentifizierungsmethode. Das heißt, welche Abfolge von Aktionen müssen das Gerät und der Access Point ausführen, damit der Access Point verwendet werden kann, und wie die Nutzdaten verschlüsselt werden. Zum Zeitpunkt des Verfassens dieses Beitrags sind WPA und WPA2 die gebräuchlichsten Optionen, bei denen entweder jedes verbundene Gerät direkt oder über das sogenannte. Der RADIUS-Server (WPA-Enterprice) stellt das Passwort über einen verschlüsselten Kanal bereit. Höchstwahrscheinlich stellt der Access Point in Ihrem Zuhause eine Verbindung nach diesem Schema bereit. Der Unterschied zwischen der zweiten und der ersten Version besteht darin, dass sie über eine stärkere Verschlüsselung verfügt: AES gegenüber dem unsicheren TKIP. Nach und nach wird auch WPA3 eingeführt, das komplexer und fortschrittlicher ist. Theoretisch könnte es eine Option mit der Enterprise-Lösung CCKM (Cisco Centralized Key Management) geben, aber ich bin noch nie darauf gestoßen.

Der Access Point wurde möglicherweise für die Authentifizierung anhand der MAC-Adresse konfiguriert. Oder wenn der Access Point Daten mithilfe des veralteten WEP-Algorithmus bereitstellt, erfolgt tatsächlich keine Authentifizierung (der geheime Schlüssel ist hier der Verschlüsselungsschlüssel). Wir klassifizieren solche Optionen als ANDERE.
Es gibt auch eine in öffentlichen WLANs beliebte Methode mit versteckter Captive-Portal-Erkennung – eine Authentifizierungsanfrage über einen Browser. Für den Scanner erscheinen solche Access Points als offen (was sie aus Sicht der physikalischen Verbindung auch sind). Daher stufen wir sie als OFFEN ein.

Der zweite Wert kann als bezeichnet werden Schlüsselverwaltungsalgorithmus. Dies ist ein Parameter der oben beschriebenen Authentifizierungsmethode. Beschreibt genau, wie Verschlüsselungsschlüssel ausgetauscht werden. Betrachten wir die möglichen Optionen. EAP – wird im erwähnten WPA-Enterprice verwendet und verwendet eine Datenbank, um die eingegebenen Authentifizierungsdaten zu überprüfen. SAE – wird im erweiterten WPA3 verwendet und ist widerstandsfähiger gegen rohe Gewalt. PSK – die gebräuchlichste Variante, beinhaltet die Eingabe eines Passwortes und dessen Übertragung in verschlüsselter Form. IEEE8021X – gemäß einem internationalen Standard (anders als der von der WPA-Familie unterstützte). OWE (Opportunistic Wireless Encryption) ist eine Erweiterung des IEEE 802.11-Standards für Punkte, die wir als OFFEN klassifiziert haben. OWE gewährleistet die Sicherheit der über ein ungesichertes Netzwerk übertragenen Daten durch Verschlüsselung. Eine Option ist auch möglich, wenn keine Zugriffsschlüssel vorhanden sind. Nennen wir diese Option NONE.

Der dritte Parameter ist der sogenannte. Verschlüsselungsschemata — wie genau die Chiffre zum Schutz der übertragenen Daten verwendet wird. Lassen Sie uns die Optionen auflisten. WEP – verwendet eine RC4-Stream-Verschlüsselung, der geheime Schlüssel ist der Verschlüsselungsschlüssel, der in der Welt der modernen Kryptographie als inakzeptabel gilt. TKIP – wird in WPA verwendet, CKIP – in WPA2. TKIP+CKIP – kann aus Gründen der Abwärtskompatibilität in WPA- und WPA2-fähigen Punkten angegeben werden.

Anstelle von drei Elementen finden Sie eine einzelne WEP-Markierung:

[WEP]

Wie wir oben besprochen haben, reicht es aus, den Algorithmus zur Verwendung von Schlüsseln, der nicht existiert, und die Verschlüsselungsmethode, die standardmäßig dieselbe ist, nicht anzugeben.

Betrachten Sie nun diese Klammer:

[ESS]

Es Wi-Fi-Betriebsmodus oder Topologie des Wi-Fi-Netzwerks. Möglicherweise stoßen Sie auf den BSS-Modus (Basic Service Set), wenn es einen Zugangspunkt gibt, über den verbundene Geräte kommunizieren. Kann in lokalen Netzwerken gefunden werden. Access Points werden in der Regel zur Verbindung von Geräten aus verschiedenen lokalen Netzwerken benötigt und sind daher Teil von Extended Service Sets – ESS. Der IBSS-Typ (Independent Basic Service Sets) gibt an, dass das Gerät Teil eines Peer-to-Peer-Netzwerks ist.

Möglicherweise sehen Sie auch die WPS-Flagge:

[WPS]

WPS (Wi-Fi Protected Setup) ist ein Protokoll zur halbautomatischen Initialisierung eines Wi-Fi-Netzwerks. Zur Initialisierung gibt der Benutzer entweder ein 8-stelliges Passwort ein oder drückt eine Taste am Router. Wenn es sich bei Ihrem Zugangspunkt um den ersten Typ handelt und dieses Kontrollkästchen neben dem Namen Ihres Zugangspunkts angezeigt wird, wird dringend empfohlen, zum Admin-Bereich zu gehen und den WPS-Zugriff zu deaktivieren. Tatsache ist, dass die 8-stellige PIN oft anhand der MAC-Adresse herausgefunden oder in absehbarer Zeit geklärt werden kann, was jemand auf unehrliche Weise ausnutzen kann.

6. Erstellen Sie ein Modell und eine Parsing-Funktion

Basierend auf dem, was wir oben herausgefunden haben, werden wir anhand von Datenklassen beschreiben, was passiert ist:

/* схема аутентификации */
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.
}

Schreiben wir nun eine Funktion, die das Feld „capabilities“ analysiert:


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. Sehen Sie sich das Ergebnis an

Ich scanne das Netzwerk und zeige Ihnen, was ich gefunden habe. Dargestellt sind die Ergebnisse der einfachen Ausgabe über Log.d:

Capability of Home-Home [WPA2-PSK-CCMP][ESS][WPS]
...
capabilities=[Capability(authScheme=WPA2, keyManagementAlgorithm=PSK, cipherMethod=CCMP)], topologyMode=ESS, availableWps=true

Die Frage der Verbindung zum Netzwerk über den Anwendungscode blieb ungeklärt. Ich möchte nur sagen, dass Sie zum Lesen gespeicherter Passwörter vom Betriebssystem eines mobilen Geräts Root-Rechte und die Bereitschaft benötigen, das Dateisystem zu durchsuchen, um wpa_supplicant.conf zu lesen. Wenn die Anwendungslogik die Eingabe eines Passworts von außen erfordert, kann die Verbindung über die Klasse hergestellt werden android.net.wifi.WifiManager.

Danke Egor Ponomarev für wertvolle Ergänzungen.

Wenn Sie der Meinung sind, dass etwas hinzugefügt oder korrigiert werden muss, schreiben Sie es in die Kommentare :)

Source: habr.com

Kommentar hinzufügen