Wi-Fi og mange andre forkortelser. Sådan får du data om Wi-Fi-noder i en Android-applikation uden at blive hævet

En dag havde jeg brug for at scanne Wi-Fi-netværk fra Android-applikationer og få detaljerede data om adgangspunkter.

Her stod vi over for flere vanskeligheder: off.Android-dokumentation mange af de beskrevne klasser blev forældet (API-niveau > 26), hvilket ikke blev afspejlet i det; beskrivelsen af ​​nogle ting i dokumentationen er minimal (f.eks. klassens funktionsfelt ScanResultat i skrivende stund er næsten intet beskrevet, selvom det indeholder mange vigtige data). Den tredje vanskelighed kan ligge i, at når man først kommer tæt på Wi-Fi, udover at læse teorien og opsætte routeren via localhost, så skal man forholde sig til en række forkortelser, der virker forståelige hver for sig. Men det er måske ikke indlysende, hvordan man relaterer og strukturerer dem (dømmekraft er subjektiv og afhænger af tidligere erfaringer).

Denne artikel diskuterer, hvordan man opnår omfattende data om Wi-Fi-miljøet fra Android-kode uden NDK, hacks, men kun ved hjælp af Android API og forstår, hvordan man fortolker det.

Lad os ikke forsinke og begynde at skrive kode.

1. Opret et projekt

Denne note er beregnet til dem, der har oprettet et Android-projekt mere end én gang, så vi udelader detaljerne i dette element. Koden nedenfor vil blive præsenteret i Kotlin, minSdkVersion=23.

2. Adgangstilladelser

For at arbejde med Wi-Fi fra applikationen skal du indhente flere tilladelser fra brugeren. I overensstemmelse med dokumentation, for at scanne netværket på enheder med OS-versioner efter 8.0 skal du udover adgang til at se netværksmiljøets tilstand enten have adgang til at ændre tilstanden for enhedens Wi-Fi-modul eller adgang til koordinater (ca. eller nøjagtig). Fra og med version 9.0 skal du bede brugeren om begge dele og også udtrykkeligt anmode brugeren om at slå lokationstjenester til. Glem ikke at galant forklare brugeren, at dette er Googles indfald og ikke vores ønske om at spionere på ham :)

Så i AndroidManifest.xml tilføjer vi:

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

Og i koden, der indeholder et link til den aktuelle aktivitet:

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. Opret en BroadcastReceiver og abonner på dataopdateringshændelser om scanning af Wi-Fi-netværksmiljøet

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
}

WiFiManager.startScan-metoden i dokumentationen er markeret som afskrevet siden API-version 28, men deaktiveret. vejlede foreslår at bruge det.

I alt modtog vi en liste over genstande ScanResultat.

4. Se på ScanResult og forstå vilkårene

Lad os se på nogle felter i denne klasse og beskrive, hvad de betyder:

SSID — Service Set Identifier er navnet på netværket

BSSID – Basic Service Set Identifier – MAC-adresse på netværksadapteren (Wi-Fi-punkt)

niveau — Indikator for modtaget signalstyrke [dBm (russisk dBm) — Decibel, referenceeffekt 1 mW.] — En indikator for den modtagne signalstyrke. Tager en værdi fra 0 til -100, jo længere fra 0, jo mere signalstyrke blev tabt undervejs fra Wi-Fi-punktet til din enhed. Flere detaljer kan f.eks. findes på Википедии. Her vil jeg fortælle dig, at du bruger Android-klassen WifiManager du kan kalibrere signalniveauet på en skala fra fremragende til frygtelig i det trin, du vælger:

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

frekvens — driftsfrekvens for Wi-Fi-punktet [Hz]. Ud over selve frekvensen kan du være interesseret i den såkaldte kanal. Hvert punkt har sin egen driftsrenhed. I skrivende stund er det mest populære udvalg af Wi-Fi-punkter 2.4 GHz. Men for at være mere præcis sender punktet information til din telefon med en nummereret frekvens tæt på den nævnte. Antal kanaler og tilsvarende frekvenser standardiseret. Dette gøres for at nærliggende punkter opererer ved forskellige frekvenser og derved ikke forstyrrer hinanden og ikke gensidigt reducerer transmissionshastigheden og kvaliteten af ​​transmissionen. I dette tilfælde fungerer punkterne ikke ved én frekvens, men over en række frekvenser (parameter Kanalbredde), kaldet kanalbredden. Det vil sige, at punkter, der opererer på tilstødende (og ikke kun tilstødende, men endda 3 fra dem selv) kanaler forstyrrer hinanden. Du kan finde denne enkle kode nyttig, som giver dig mulighed for at beregne kanalnummeret ud fra frekvensværdien for punkter med en frekvens på 2.4 og 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
            }
        }

kapaciteter - det mest interessante felt for analyse, arbejde med hvilket krævede meget tid. Her er punktets "evner" skrevet i linjen. I dette tilfælde behøver du ikke lede efter detaljer om strengfortolkning i dokumentationen. Her er nogle eksempler på, hvad der kan være i denne linje:

[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. Forståelse af forkortelser og parsefunktioner

Det er værd at nævne, at klasserne i android.net.wifi.*-pakken bruges under emhætten af ​​et Linux-værktøj wpa_supplicant og outputresultatet i kapacitetsfeltet er en kopi af flagfeltet ved scanning.

Vi vil handle konsekvent. Lad os først overveje outputtet af et format, hvor elementer inden for parentes er adskilt af et "-"-tegn:

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

Den første betydning beskriver den såkaldte. autentificeringsmetode. Det vil sige, hvilken rækkefølge af handlinger skal enheden og adgangspunktet udføre, for at adgangspunktet kan tillade sig selv at blive brugt, og hvordan man krypterer nyttelasten. I skrivende stund er de mest almindelige muligheder WPA og WPA2, hvor enten hver tilsluttet enhed direkte eller gennem den såkaldte. RADIUS-serveren (WPA-Enterprice) leverer adgangskoden over en krypteret kanal. Mest sandsynligt giver adgangspunktet i dit hjem en forbindelse i henhold til denne ordning. Forskellen mellem den anden version og den første er, at den har en stærkere chiffer: AES versus den usikre TKIP. WPA3, som er mere kompleks og avanceret, er også gradvist ved at blive introduceret. Teoretisk set kan der være en mulighed med enterprice løsningen CCKM (Cisco Centralized Key Management), men jeg er aldrig stødt på det.

Adgangspunktet kan være konfigureret til at godkende ved hjælp af MAC-adresse. Eller, hvis adgangspunktet leverer data ved hjælp af den forældede WEP-algoritme, er der faktisk ingen godkendelse (den hemmelige nøgle her er krypteringsnøglen). Vi klassificerer sådanne muligheder som ANDRE.
Der er også en metode, der er populær i offentlig wi-fi med skjult Captive Portal Detection - en godkendelsesanmodning gennem en browser. Sådanne adgangspunkter fremstår for scanneren som åbne (hvilket de er set fra den fysiske forbindelses synspunkt). Derfor klassificerer vi dem som ÅBNE.

Den anden værdi kan betegnes som nøglestyringsalgoritme. Dette er en parameter for godkendelsesmetoden beskrevet ovenfor. Taler om præcis, hvordan krypteringsnøgler udveksles. Lad os overveje de mulige muligheder. EAP - brugt i den nævnte WPA-Enterprice, bruger en database til at verificere de indtastede autentificeringsdata. SAE - brugt i avanceret WPA3, mere modstandsdygtig over for brute force. PSK - den mest almindelige mulighed, involverer at indtaste en adgangskode og sende den i krypteret form. IEEE8021X - i henhold til en international standard (forskellig fra den, der understøttes af WPA-familien). OWE (Opportunistic Wireless Encryption) er en udvidelse af IEEE 802.11-standarden for punkter, som vi klassificerede som OPEN. OWE sikrer sikkerheden for data transmitteret over et usikkert netværk ved at kryptere dem. En mulighed er også mulig, når der ikke er nogen adgangsnøgler, lad os kalde denne mulighed INGEN.

Den tredje parameter er den såkaldte. krypteringsordninger — præcis hvordan chifferen bruges til at beskytte de overførte data. Lad os liste mulighederne. WEP - bruger en RC4 stream cipher, den hemmelige nøgle er krypteringsnøglen, hvilket anses for uacceptabelt i en verden af ​​moderne kryptografi. TKIP - bruges i WPA, CKIP - i WPA2. TKIP+CKIP - kan specificeres i punkter, der er i stand til WPA og WPA2 for bagudkompatibilitet.

I stedet for tre elementer kan du finde et ensomt WEP-mærke:

[WEP]

Som vi diskuterede ovenfor, er dette nok til ikke at specificere algoritmen til brug af nøgler, som ikke eksisterer, og krypteringsmetoden, som er den samme som standard.

Overvej nu denne parentes:

[ESS]

Det Wi-Fi driftstilstand eller Wi-Fi-netværkstopologi. Du kan støde på BSS-tilstand (Basic Service Set) - når der er ét adgangspunkt, hvorigennem tilsluttede enheder kommunikerer. Kan findes på lokale netværk. Som regel er adgangspunkter nødvendige for at forbinde enheder fra forskellige lokale netværk, så de er en del af Extended Service Sets - ESS. Typen IBSS'er (Independent Basic Service Sets) angiver, at enheden er en del af et Peer-to-Peer-netværk.

Du kan også se WPS-flaget:

[WPS]

WPS (Wi-Fi Protected Setup) er en protokol til semi-automatisk initialisering af et Wi-Fi-netværk. For at initialisere, indtaster brugeren enten en adgangskode på 8 tegn eller trykker på en knap på routeren. Hvis dit adgangspunkt er af den første type, og dette afkrydsningsfelt vises ud for navnet på dit adgangspunkt, anbefales det kraftigt at gå til administrationspanelet og deaktivere WPS-adgang. Faktum er, at ofte kan den 8-cifrede PIN-kode findes ud af MAC-adressen, eller den kan ordnes inden for en overskuelig tid, hvilket nogen uærligt kan drage fordel af.

6. Opret en model og parsing funktion

Baseret på det, vi fandt ud af ovenfor, vil vi beskrive, hvad der skete ved hjælp af dataklasser:

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

Lad os nu skrive en funktion, der vil analysere kapacitetsfeltet:


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. Se resultatet

Jeg scanner netværket og viser dig, hvad jeg fandt. Vist er resultaterne af simpelt output via Log.d:

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

Spørgsmålet om at oprette forbindelse til netværket fra applikationskoden forblev uundersøgt. Jeg vil kun sige, at for at læse de gemte adgangskoder til operativsystemet på en mobil enhed, skal du have root-rettigheder og en vilje til at rode gennem filsystemet for at læse wpa_supplicant.conf. Hvis applikationslogikken kræver indtastning af en adgangskode udefra, kan forbindelsen oprettes gennem klassen android.net.wifi.WifiManager.

Tak Egor Ponomarev for værdifulde tilføjelser.

Hvis du synes noget skal tilføjes eller rettes, så skriv i kommentarfeltet :)

Kilde: www.habr.com

Tilføj en kommentar