Wi-Fi og mange andre forkortelser. Hvordan få data om Wi-Fi-noder i en Android-applikasjon uten å bli hoven

En dag trengte jeg å skanne Wi-Fi-nettverk fra Android-applikasjoner og få detaljerte data om tilgangspunkter.

Her måtte vi møte flere vanskeligheter: off.Android-dokumentasjon mange av de beskrevne klassene ble avviklet (API-nivå > 26), noe som ikke ble reflektert i det; beskrivelsen av noen ting i dokumentasjonen er minimal (for eksempel evnefeltet til klassen Skanneresultat i skrivende stund er nesten ingenting beskrevet, selv om det inneholder mye viktig data). Den tredje vanskeligheten kan ligge i det faktum at når du først kommer i nærheten av Wi-Fi, annet enn å lese teorien og sette opp ruteren via localhost, må du forholde deg til en rekke forkortelser som virker forståelige hver for seg. Men det er kanskje ikke åpenbart hvordan man skal forholde seg og strukturere dem (dømmekraft er subjektiv og avhenger av tidligere erfaring).

Denne artikkelen diskuterer hvordan du kan få omfattende data om Wi-Fi-miljøet fra Android-kode uten NDK, hacks, men bare ved å bruke Android API og forstå hvordan du tolker det.

La oss ikke utsette og begynne å skrive kode.

1. Lag et prosjekt

Dette notatet er ment for de som har opprettet et Android-prosjekt mer enn én gang, så vi utelater detaljene for dette elementet. Koden nedenfor vil bli presentert i Kotlin, minSdkVersion=23.

2. Tilgangstillatelser

For å jobbe med Wi-Fi fra applikasjonen, må du få flere tillatelser fra brukeren. I samsvar med dokumentasjon, for å skanne nettverket på enheter med OS-versjoner etter 8.0, i tillegg til tilgang til å se statusen til nettverksmiljøet, trenger du enten tilgang til å endre tilstanden til enhetens Wi-Fi-modul, eller tilgang til koordinater (ca. eller eksakt). Fra og med versjon 9.0 må du be brukeren om begge deler, og også eksplisitt be brukeren om å slå på lokasjonstjenester. Ikke glem å galant forklare brukeren at dette er Googles innfall, og ikke vårt ønske om å spionere på ham :)

Så i AndroidManifest.xml legger vi til:

    <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 som inneholder en lenke til gjeldende 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. Opprett en kringkastingsmottaker og abonner på dataoppdateringshendelser om skanning av Wi-Fi-nettverksmiljø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 dokumentasjonen er merket som avskrevet siden API-versjon 28, men av. veilede foreslår å bruke det.

Totalt fikk vi en liste over objekter Skanneresultat.

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

La oss se på noen felt i denne klassen og beskrive hva de betyr:

SSID — Service Set Identifier er navnet på nettverket

BSSID – Basic Service Set Identifier – MAC-adressen til nettverksadapteren (Wi-Fi-punkt)

nivå — Mottatt signalstyrkeindikator [dBm (russisk dBm) — Desibel, referanseeffekt 1 mW.] — En indikator for den mottatte signalstyrken. Tar en verdi fra 0 til -100, jo lenger fra 0, jo mer signalstyrke gikk tapt underveis fra Wi-Fi-punktet til enheten. Flere detaljer finner du for eksempel på Wikipedia. Her skal jeg fortelle deg at du bruker Android-klassen WifiManager du kan kalibrere signalnivået på en skala fra utmerket til forferdelig i trinnet du velger:

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

frekvens — driftsfrekvensen til Wi-Fi-punktet [Hz]. I tillegg til selve frekvensen kan du være interessert i den såkalte kanalen. Hvert punkt har sin egen driftsrenhet. I skrivende stund er det mest populære utvalget av Wi-Fi-punkter 2.4 GHz. Men for å være mer presis, sender punktet informasjon til telefonen din med en nummerert frekvens nær den som er nevnt. Antall kanaler og tilsvarende frekvenser standardisert. Dette gjøres slik at nærliggende punkter opererer med forskjellige frekvenser, og derved ikke forstyrrer hverandre og ikke gjensidig reduserer hastigheten og kvaliteten på overføringen. I dette tilfellet opererer punktene ikke på én frekvens, men over et frekvensområde (parameter kanalbredde), kalt kanalbredden. Det vil si at punkter som opererer på tilstøtende (og ikke bare tilstøtende, men til og med 3 fra seg selv) kanaler forstyrrer hverandre. Du kan finne denne enkle koden nyttig, som lar deg beregne kanalnummeret fra frekvensverdien 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
            }
        }

evner - det mest interessante feltet for analyse, arbeid med som krevde mye tid. Her er "egenskapene" til punktet skrevet i linjen. I dette tilfellet trenger du ikke se etter detaljer om strengtolkning i dokumentasjonen. Her er noen eksempler på hva som kan stå i denne linjen:

[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å forkortelser og analysefunksjoner

Det er verdt å nevne at klassene til android.net.wifi.*-pakken brukes under panseret av et Linux-verktøy wpa_supplicant og utdataresultatet i funksjonsfeltet er en kopi av flaggfeltet ved skanning.

Vi vil handle konsekvent. La oss først vurdere utdataene til et format der elementer i parenteser er atskilt med et "-"-tegn:

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

Den første betydningen beskriver den såkalte. autentiseringsmetode. Det vil si hvilken rekkefølge av handlinger må enheten og tilgangspunktet utføre for at tilgangspunktet skal tillate seg å brukes og hvordan man krypterer nyttelasten. I skrivende stund dette innlegget er de vanligste alternativene WPA og WPA2, der enten hver tilkoblet enhet direkte eller gjennom den såkalte. RADIUS-serveren (WPA-Enterprice) gir passordet over en kryptert kanal. Mest sannsynlig gir tilgangspunktet i hjemmet en forbindelse i henhold til denne ordningen. Forskjellen mellom den andre versjonen og den første er at den har en sterkere chiffer: AES versus den usikre TKIP. WPA3, som er mer kompleks og avansert, blir også gradvis introdusert. Teoretisk sett kan det være et alternativ med enterprice-løsningen CCKM (Cisco Centralized Key Management), men jeg har aldri vært borti den.

Tilgangspunktet kan ha blitt konfigurert til å autentisere med MAC-adresse. Eller, hvis tilgangspunktet gir data ved hjelp av den utdaterte WEP-algoritmen, er det faktisk ingen autentisering (den hemmelige nøkkelen her er krypteringsnøkkelen). Vi klassifiserer slike alternativer som ANDRE.
Det er også en metode som er populær i offentlig wi-fi med skjult Captive Portal Detection - en autentiseringsforespørsel gjennom en nettleser. Slike tilgangspunkter fremstår for skanneren som åpne (som de er fra den fysiske tilkoblingens synspunkt). Derfor klassifiserer vi dem som ÅPNE.

Den andre verdien kan betegnes som nøkkelstyringsalgoritme. Dette er en parameter for autentiseringsmetoden beskrevet ovenfor. Snakker om nøyaktig hvordan krypteringsnøkler utveksles. La oss vurdere de mulige alternativene. EAP - brukt i nevnte WPA-Enterprice, bruker en database for å verifisere de angitte autentiseringsdataene. SAE - brukt i avansert WPA3, mer motstandsdyktig mot brute force. PSK - det vanligste alternativet, innebærer å skrive inn et passord og overføre det i kryptert form. IEEE8021X - i henhold til en internasjonal standard (forskjellig fra den som støttes av WPA-familien). OWE (Opportunistic Wireless Encryption) er en utvidelse av IEEE 802.11-standarden for punkter som vi har klassifisert som ÅPNE. OWE sikrer sikkerheten til data som overføres over et usikret nettverk ved å kryptere dem. Et alternativ er også mulig når det ikke er noen tilgangsnøkler, la oss kalle dette alternativet INGEN.

Den tredje parameteren er den såkalte. krypteringsordninger — nøyaktig hvordan chifferen brukes for å beskytte de overførte dataene. La oss liste opp alternativene. WEP - bruker et RC4-strømchiffer, den hemmelige nøkkelen er krypteringsnøkkelen, som anses som uakseptabel i moderne kryptografi. TKIP - brukt i WPA, CKIP - i WPA2. TKIP+CKIP - kan spesifiseres i punkter som kan WPA og WPA2 for bakoverkompatibilitet.

I stedet for tre elementer kan du finne et ensomt WEP-merke:

[WEP]

Som vi diskuterte ovenfor, er dette nok til å ikke spesifisere algoritmen for bruk av nøkler, som ikke eksisterer, og krypteringsmetoden, som er den samme som standard.

Vurder nå denne parentesen:

[ESS]

Den Wi-Fi driftsmodus eller Wi-Fi-nettverkstopologi. Du kan støte på BSS-modus (Basic Service Set) - når det er ett tilgangspunkt som tilkoblede enheter kommuniserer gjennom. Finnes på lokale nettverk. Som regel er det nødvendig med tilgangspunkter for å koble til enheter fra forskjellige lokale nettverk, så de er en del av Extended Service Sets - ESS. IBSS-typene (Independent Basic Service Sets) indikerer at enheten er en del av et node-til-node-nettverk.

Du kan også se WPS-flagget:

[WPS]

WPS (Wi-Fi Protected Setup) er en protokoll for halvautomatisk initialisering av et Wi-Fi-nettverk. For å initialisere skriver brukeren enten inn et 8-tegns passord eller trykker på en knapp på ruteren. Hvis tilgangspunktet ditt er av den første typen og denne avmerkingsboksen vises ved siden av navnet på tilgangspunktet, anbefales det på det sterkeste å gå til administrasjonspanelet og deaktivere WPS-tilgang. Faktum er at ofte kan den 8-sifrede PIN-koden bli funnet ut av MAC-adressen, eller den kan sorteres ut i løpet av overskuelig tid, noe noen uærlig kan dra nytte av.

6. Lag en modell og parsefunksjon

Basert på det vi fant ut ovenfor, vil vi beskrive hva som skjedde ved hjelp av 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.
}

La oss nå skrive en funksjon som vil analysere funksjonsfeltet:


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 skanner nettverket og viser deg hva jeg fant. Vist er resultatene av enkel utdata 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ørsmålet om å koble til nettverket fra applikasjonskoden forble uundersøkt. Jeg vil bare si at for å lese lagrede passord fra operativsystemet til en mobilenhet, trenger du rotrettigheter og vilje til å rote gjennom filsystemet for å lese wpa_supplicant.conf. Hvis applikasjonslogikken krever inntasting av passord utenfra, kan tilkoblingen gjøres gjennom klassen android.net.wifi.WifiManager.

Takk Egor Ponomarev for verdifulle tillegg.

Hvis du mener noe må legges til eller rettes, skriv i kommentarfeltet :)

Kilde: www.habr.com

Legg til en kommentar