Wi-Fi i mnoge druge kratice. Kako dobiti podatke o Wi-Fi čvorovima u Android aplikaciji, a da pritom ne nateknete

Jednog sam dana trebao skenirati Wi-Fi mreže iz Android aplikacija i dobiti detaljne podatke o pristupnim točkama.

Ovdje smo se morali suočiti s nekoliko poteškoća: isključeno.Android dokumentacija mnoge od opisanih klasa postale su zastarjele (API razina > 26), što se nije odrazilo na to; opis nekih stvari u dokumentaciji je minimalan (na primjer, polje mogućnosti klase ScanResult u trenutku pisanja gotovo ništa nije opisano, iako sadrži mnogo važnih podataka). Treća poteškoća može ležati u činjenici da kada se prvi put približite Wi-Fi-ju, osim čitanja teorije i postavljanja usmjerivača putem lokalnog hosta, morate se nositi s brojnim kraticama koje se pojedinačno čine razumljivim. Ali možda nije očito kako ih povezati i strukturirati (prosudba je subjektivna i ovisi o prethodnom iskustvu).

Ovaj članak govori o tome kako dobiti sveobuhvatne podatke o Wi-Fi okruženju iz Android koda bez NDK-a, hakiranja, ali samo pomoću Android API-ja i razumjeti kako to protumačiti.

Nemojmo odgađati i počnimo pisati kod.

1. Izradite projekt

Ova je bilješka namijenjena onima koji su izradili Android projekt više puta, stoga izostavljamo detalje ove stavke. Kod u nastavku bit će predstavljen u Kotlinu, minSdkVersion=23.

2. Dopuštenja za pristup

Za rad s Wi-Fi iz aplikacije, morat ćete dobiti nekoliko dozvola od korisnika. U skladu s dokumentacija, kako biste skenirali mrežu na uređajima s verzijama OS-a nakon 8.0, osim pristupa pregledu stanja mrežnog okruženja, potreban vam je ili pristup za promjenu stanja Wi-Fi modula uređaja ili pristup koordinatama (približno ili točan). Počevši od verzije 9.0, morate od korisnika zatražiti oboje, a također izričito zatražiti od korisnika da uključi usluge lokacije. Ne zaboravite galantno objasniti korisniku da je to Googleov hir, a ne naša želja da ga špijuniramo :)

Dakle, u AndroidManifest.xml ćemo dodati:

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

A u kodu koji sadrži poveznicu na trenutnu aktivnost:

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. Napravite BroadcastReceiver i pretplatite se na događaje ažuriranja podataka o skeniranju okruženja Wi-Fi mreže

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
}

Metoda WiFiManager.startScan u dokumentaciji je označena kao depricated od API verzije 28, ali isključena. voditi predlaže korištenje.

Ukupno smo dobili popis objekata ScanResult.

4. Pogledajte ScanResult i razumite uvjete

Pogledajmo neka polja ove klase i opišimo što ona znače:

SSID — Identifikator skupa usluga naziv je mreže

BSSID – Osnovni identifikator skupa usluga – MAC adresa mrežnog adaptera (Wi-Fi točka)

nivo — Indikator jačine primljenog signala [dBm (ruski dBm) — Decibel, referentna snaga 1 mW.] — Indikator jačine primljenog signala. Uzima vrijednost od 0 do -100, što je dalje od 0, veća je snaga signala izgubljena na putu od Wi-Fi točke do vašeg uređaja. Više detalja možete pronaći, na primjer, na Wikipedija. Ovdje ću vam to reći koristeći Android klasu WifiManager možete kalibrirati razinu signala na skali od izvrsnog do užasnog u koraku koji odaberete:

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

frekvencija — radna frekvencija Wi-Fi točke [Hz]. Osim same frekvencije, možda će vas zanimati i kanal tzv. Svaka točka ima svoju radnu čistoću. U vrijeme pisanja, najpopularniji raspon Wi-Fi točaka je 2.4 GHz. Ali, da budemo precizniji, točka prenosi informacije na vaš telefon na numeriranoj frekvenciji bliskoj navedenoj. Broj kanala i odgovarajućih frekvencija standardizirani. To je učinjeno kako bi obližnje točke radile na različitim frekvencijama, pri čemu ne ometaju jedna drugu i ne smanjuju međusobno brzinu i kvalitetu prijenosa. U ovom slučaju, točke ne rade na jednoj frekvenciji, već u rasponu frekvencija (parametar širina kanala), koja se naziva širina kanala. Odnosno, točke koje djeluju na susjednim (i ne samo susjednim, već čak 3 od sebe) kanalima interferiraju jedna s drugom. Možda će vam biti koristan ovaj jednostavan kod koji vam omogućuje izračunavanje broja kanala iz vrijednosti frekvencije za točke s frekvencijom od 2.4 i 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
            }
        }

sposobnosti - najzanimljivije područje za analizu, rad s kojim je zahtijevao puno vremena. Ovdje su u retku ispisane "sposobnosti" točke. U ovom slučaju ne morate tražiti pojedinosti interpretacije niza u dokumentaciji. Evo nekoliko primjera onoga što bi moglo biti u ovom retku:

[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. Razumijevanje kratica i mogućnosti raščlanjivanja

Vrijedno je spomenuti da se klase paketa android.net.wifi.* koriste ispod haube od strane uslužnog programa za Linux wpa_supplicant a izlazni rezultat u polju mogućnosti je kopija polja zastavica prilikom skeniranja.

Djelovat ćemo dosljedno. Razmotrimo prvo izlaz formata u kojem su elementi unutar zagrada odvojeni znakom “-“:

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

Prvo značenje opisuje tzv. način provjere autentičnosti. Odnosno, koji redoslijed radnji moraju izvršiti uređaj i pristupna točka da bi se pristupna točka mogla koristiti i kako šifrirati sadržaj. U trenutku pisanja ovog posta najčešće opcije su WPA i WPA2, kod kojih ili svaki povezani uređaj izravno ili putem tzv. RADIUS poslužitelj (WPA-Enterprice) daje lozinku preko šifriranog kanala. Najvjerojatnije pristupna točka u vašem domu pruža vezu prema ovoj shemi. Razlika između druge i prve verzije je u tome što ima jaču šifru: AES nasuprot nesigurnom TKIP-u. WPA3, koji je složeniji i napredniji, također se postupno uvodi. Teoretski, možda postoji opcija s enterprice rješenjem CCKM (Cisco Centralized Key Management), ali na to nisam nikad naišao.

Pristupna točka je možda konfigurirana za provjeru autentičnosti pomoću MAC adrese. Ili, ako pristupna točka daje podatke koristeći zastarjeli WEP algoritam, tada zapravo nema provjere autentičnosti (tajni ključ ovdje je ključ za šifriranje). Takve opcije svrstavamo u OSTALO.
Postoji i metoda koja je popularna u javnom wi-fiju sa skrivenim Captive Portal Detection - zahtjev za autentifikaciju putem preglednika. Takve pristupne točke skeneru izgledaju kao otvorene (što i jesu sa stajališta fizičke veze). Stoga ih svrstavamo u OTVORENE.

Druga vrijednost može se označiti kao algoritam za upravljanje ključem. Ovo je parametar gore opisane metode provjere autentičnosti. Govori o tome kako se točno razmjenjuju ključevi šifriranja. Razmotrimo moguće opcije. EAP - koristi se u spomenutom WPA-Enterpriceu, koristi bazu podataka za provjeru unesenih autentifikacijskih podataka. SAE - koristi se u naprednom WPA3, otpornijem na grubu silu. PSK - najčešća opcija, uključuje unos lozinke i njezin prijenos u šifriranom obliku. IEEE8021X - prema međunarodnom standardu (različit od onog koji podržava obitelj WPA). OWE (Opportunistic Wireless Encryption) je proširenje standarda IEEE 802.11 za točke koje smo klasificirali kao OPEN. OWE osigurava sigurnost podataka koji se prenose preko nezaštićene mreže šifrirajući ih. Moguća je i opcija kada nema pristupnih ključeva, nazovimo ovu opciju NEMA.

Treći parametar je tzv. sheme šifriranja — kako se točno šifra koristi za zaštitu prenesenih podataka. Nabrojimo opcije. WEP - koristi RC4 stream šifru, tajni ključ je ključ za šifriranje, što se smatra neprihvatljivim u svijetu moderne kriptografije. TKIP - koristi se u WPA, CKIP - u WPA2. TKIP+CKIP - može se navesti u točkama sposobnim za WPA i WPA2 radi kompatibilnosti unazad.

Umjesto tri elementa, možete pronaći usamljenu WEP oznaku:

[WEP]

Kao što smo gore spomenuli, to je dovoljno da ne odredite algoritam za korištenje ključeva, koji ne postoji, i metodu šifriranja, koja je prema zadanim postavkama ista.

Sada razmotrite ovu zagradu:

[ESS]

ovo Wi-Fi način rada ili Topologija Wi-Fi mreže. Možete naići na način BSS (Basic Service Set) - kada postoji jedna pristupna točka preko koje povezani uređaji komuniciraju. Može se pronaći na lokalnim mrežama. Pristupne točke su u pravilu potrebne za povezivanje uređaja iz različitih lokalnih mreža, pa su dio Extended Service Sets - ESS. Tip IBSSs (Independent Basic Service Sets) označava da je uređaj dio Peer-to-Peer mreže.

Također možete vidjeti WPS oznaku:

[WPS]

WPS (Wi-Fi Protected Setup) je protokol za poluautomatsku inicijalizaciju Wi-Fi mreže. Za inicijalizaciju, korisnik ili unese lozinku od 8 znakova ili pritisne gumb na usmjerivaču. Ako je vaša pristupna točka prve vrste i ovaj potvrdni okvir se pojavljuje pored naziva vaše pristupne točke, toplo vam preporučujemo da odete na administratorsku ploču i onemogućite WPS pristup. Činjenica je da se često 8-znamenkasti PIN može saznati po MAC adresi ili se može srediti u dogledno vrijeme, što netko nepošteno može iskoristiti.

6. Napravite model i funkciju parsiranja

Na temelju onoga što smo gore saznali, opisat ćemo što se dogodilo pomoću klasa podataka:

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

Sada napišimo funkciju koja će analizirati polje mogućnosti:


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. Pogledajte rezultat

Skenirat ću mrežu i pokazati vam što sam našao. Prikazani su rezultati jednostavnog izlaza putem Log.d:

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

Ostalo je neistraženo pitanje povezivanja na mrežu iz aplikacijskog koda. Reći ću samo da su vam za čitanje spremljenih lozinki iz OS-a mobilnog uređaja potrebna root prava i spremnost da preturate po datotečnom sustavu kako biste pročitali wpa_supplicant.conf. Ako logika aplikacije zahtijeva unos lozinke izvana, veza se može uspostaviti kroz klasu android.net.wifi.WifiManager.

Hvala Egor Ponomarev za vrijedne dodatke.

Ako mislite da nešto treba dodati ili ispraviti, napišite u komentarima :)

Izvor: www.habr.com

Dodajte komentar