Wi-Fi ja palju muid lühendeid. Kuidas hankida Androidi rakenduses andmeid Wi-Fi sõlmede kohta ilma paistetuseta

Ühel päeval pidin Androidi rakendustest WiFi-võrke skannima ja pääsupunktide kohta üksikasjalikke andmeid hankima.

Siin pidime silmitsi seisma mitmete raskustega: väljas. Androidi dokumentatsioon paljud kirjeldatud klassid muutusid aegunud (API tase > 26), mis selles ei kajastunud; osade asjade kirjeldus dokumentatsioonis on minimaalne (näiteks klassi võimaluste väli ScanResult kirjutamise ajal pole peaaegu midagi kirjeldatud, kuigi see sisaldab palju olulisi andmeid). Kolmas raskus võib peituda selles, et kui te esimest korda Wi-Fi-le lähedale jõuate, peale teooria lugemise ja ruuteri localhosti kaudu seadistamise, peate tegelema mitmete lühenditega, mis tunduvad individuaalselt mõistetavad. Kuid ei pruugi olla ilmne, kuidas neid seostada ja struktureerida (hinnang on subjektiivne ja sõltub varasemast kogemusest).

Selles artiklis käsitletakse, kuidas hankida kõikehõlmavaid andmeid Wi-Fi keskkonna kohta Androidi koodist ilma NDK-ta, häkkideta, kuid ainult Android API-d kasutades, ja mõista, kuidas seda tõlgendada.

Ärgem viivitagem ja hakakem koodi kirjutama.

1. Loo projekt

See märkus on mõeldud neile, kes on Androidi projekti loonud rohkem kui korra, seega jätame selle üksuse üksikasjad välja. Allolev kood esitatakse Kotlinis, minSdkVersion=23.

2. Juurdepääsuload

Rakenduse Wi-Fi-ga töötamiseks peate hankima kasutajalt mitu luba. Kooskõlas dokumentatsioon, võrgu skannimiseks seadmetes, mille operatsioonisüsteemi versioon on pärast 8.0, vajate lisaks juurdepääsule võrgukeskkonna oleku vaatamisele kas juurdepääsu seadme Wi-Fi mooduli oleku muutmiseks või juurdepääsu koordinaatidele (ligikaudne või täpne). Alates versioonist 9.0 peate kasutajalt küsima mõlemat ja selgesõnaliselt paluma kasutajal asukohateenused sisse lülitada. Ärge unustage kasutajale galantselt selgitada, et see on Google'i kapriis, mitte meie soov tema järele luurata :)

Seega lisame faili AndroidManifest.xml:

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

Ja koodis, mis sisaldab linki praegusele tegevusele:

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. Looge BroadcastReceiver ja tellige andmete värskendamise sündmused Wi-Fi võrgukeskkonna skannimise kohta

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
}

Meetod WiFiManager.startScan dokumentatsioonis on alates API versioonist 28 märgitud kehtetuks, kuid välja lülitatud. suunata предлагает использовать его.

Kokku saime objektide nimekirja ScanResult.

4. Vaadake ScanResultit ja mõistke tingimusi

Vaatame mõnda selle klassi välja ja kirjeldame, mida need tähendavad:

SSID — Service Set Identifier on võrgu nimi

BSSID – Basic Service Set Identifier – võrguadapteri MAC-aadress (Wi-Fi-punkt)

tase — Vastuvõetud signaali tugevuse indikaator [dBm (Vene dBm) — Detsibell, võrdlusvõimsus 1 mW.] — Vastuvõetud signaali tugevuse indikaator. Võtab väärtuse vahemikus 0 kuni –100, mida kaugemal 0-st, seda rohkem signaali võimsust kadus teel Wi-Fi-punktist teie seadmesse. Täpsemalt leiab näiteks aadressilt Wikipedia. Siin ma ütlen teile, et kasutades Android-klassi WifiManager saate signaali taseme kalibreerida skaalal suurepärasest kohutavani valitud sammuga:

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

sagedus — Wi-Fi-punkti töösagedus [Hz]. Lisaks sagedusele endale võib huvi pakkuda nn kanal. Igal punktil on oma tööpuhtus. Selle artikli kirjutamise ajal on kõige populaarsem WiFi-punktide vahemik 2.4 GHz. Kuid täpsemini edastab punkt teavet teie telefoni nummerdatud sagedusega, mis on lähedal nimetatud sagedusele. Kanalite arv ja vastavad sagedused standardiseeritud. Seda tehakse nii, et lähedalasuvad punktid töötaksid erinevatel sagedustel, ei segaks seejuures üksteist ega vähendaks vastastikku edastuse kiirust ja kvaliteeti. Sel juhul ei tööta punktid ühel sagedusel, vaid mitmel sagedusvahemikul (parameeter Kanali laius), mida nimetatakse kanali laiuseks. See tähendab, et punktid, mis töötavad külgnevatel (ja mitte ainult külgnevatel, vaid isegi 3-l üksteisest) kanalitel, segavad üksteist. See lihtne kood võib teile kasulikuks osutuda, mis võimaldab arvutada kanali numbri sageduse väärtusest punktide jaoks, mille sagedus on 2.4 ja 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
            }
        }

võimeid - kõige huvitavam analüüsivaldkond, millega töötamine nõudis palju aega. Siin on reale kirjas punkti “võimalused”. Sel juhul ei pea te dokumentatsioonist stringi tõlgendamise üksikasju otsima. Siin on mõned näited selle kohta, mis sellel real olla võib:

[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. Lühendite mõistmine ja parsimise võimalused

Tasub mainida, et paketi android.net.wifi.* klasse kasutab kapoti all Linuxi utiliit wpa_ ja väljundtulemus võimaluste väljal on skaneerimisel lippude välja koopia.

Tegutseme järjekindlalt. Vaatleme esmalt sellise vormingu väljundit, milles sulgudes olevad elemendid on eraldatud märgiga "-":

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

Esimene tähendus kirjeldab nn. autentimismeetod. See tähendab, millist toimingute jada peavad seade ja pääsupunkt tegema, et pääsupunkt lubaks end kasutada ja kuidas krüpteerida kasulikku koormust. Selle postituse kirjutamise hetkel on levinumad valikud WPA ja WPA2, milles kas iga ühendatud seade otse või läbi nn. RADIUS-server (WPA-Enterprice) annab parooli krüptitud kanali kaudu. Tõenäoliselt pakub teie kodu pöörduspunkt selle skeemi järgi ühendust. Erinevus teise versiooni ja esimese vahel seisneb selles, et sellel on tugevam šifr: AES versus ebaturvaline TKIP. Tasapisi võetakse kasutusele ka WPA3, mis on keerulisem ja arenenum. Teoreetiliselt võib ettevõtte lahendusega CCKM (Cisco Centralized Key Management) olla võimalus, kuid ma pole sellega kunagi kokku puutunud.

Pöörduspunkt võib olla konfigureeritud MAC-aadressi järgi autentima. Või kui pääsupunkt edastab andmeid vananenud WEP-algoritmi abil, siis autentimist tegelikult ei toimu (salajane võti on siin krüpteerimisvõti). Me liigitame sellised valikud kategooriasse MUU.
Samuti on avalikus wi-fis populaarne meetod peidetud Captive Portal Detectioniga – autentimistaotlus läbi brauseri. Sellised pääsupunktid tunduvad skannerile avatud (mida nad füüsilise ühenduse seisukohast on). Seetõttu klassifitseerime need AVATUD.

Teist väärtust saab tähistada kui võtmehaldusalgoritm. See on ülalkirjeldatud autentimismeetodi parameeter. Räägib täpselt sellest, kuidas krüpteerimisvõtmeid vahetatakse. Vaatleme võimalikke valikuid. EAP - kasutatakse mainitud WPA-Enterprice'is, kasutab sisestatud autentimisandmete kontrollimiseks andmebaasi. SAE – kasutatakse täiustatud WPA3-s, vastupidavam toorele jõule. PSK - kõige levinum valik, hõlmab parooli sisestamist ja selle krüpteeritud kujul edastamist. IEEE8021X - vastavalt rahvusvahelisele standardile (erineb WPA perekonna toetatud standardist). OWE (Opportunistic Wireless Encryption) on IEEE 802.11 standardi laiendus punktidele, mille klassifitseerisime AVATUD. OWE tagab turvamata võrgu kaudu edastatavate andmete turvalisuse neid krüpteerides. Valik on võimalik ka siis, kui juurdepääsuklahve pole, nimetagem seda valikut EI OLE.

Kolmas parameeter on nn. krüpteerimisskeemid — kuidas täpselt šifrit edastatud andmete kaitsmiseks kasutatakse. Loetleme valikud. WEP - kasutab RC4 voošifrit, salajaseks võtmeks on krüpteerimisvõti, mida peetakse kaasaegse krüptograafia maailmas vastuvõetamatuks. TKIP - kasutatakse WPA-s, CKIP - WPA2-s. TKIP+CKIP – tagasiühilduvuse tagamiseks saab määrata WPA- ja WPA2-võimelistes punktides.

Kolme elemendi asemel võite leida üksiku WEP-märgi:

[WEP]

Nagu eespool arutasime, piisab sellest, et mitte määrata võtmete kasutamise algoritmi, mida pole olemas, ja krüpteerimismeetodit, mis on vaikimisi sama.

Nüüd kaaluge seda sulgu:

[ESS]

see Wi-Fi töörežiim või Wi-Fi võrgu topoloogia. Võite kohata režiimi BSS (Basic Service Set) – kui on üks pääsupunkt, mille kaudu ühendatud seadmed suhtlevad. Võib leida kohalikest võrkudest. Reeglina on erinevatest kohtvõrkudest pärit seadmete ühendamiseks vaja pääsupunkte, seega kuuluvad need laiendatud teenusekomplekti – ESS-i. IBSS-ide (Independent Basic Service Sets) tüüp näitab, et seade on võrdõigusvõrgu osa.

Võite näha ka WPS-i lippu:

[WPS]

WPS (Wi-Fi Protected Setup) on Wi-Fi võrgu poolautomaatse lähtestamise protokoll. Initsialiseerimiseks sisestab kasutaja kas 8-kohalise parooli või vajutab ruuteri nuppu. Kui teie pääsupunkt on esimest tüüpi ja see märkeruut kuvatakse teie pääsupunkti nime kõrval, on teil tungivalt soovitatav minna administraatoripaneelile ja keelata WPS-i juurdepääs. Fakt on see, et sageli saab 8-kohalise PIN-koodi teada MAC-aadressi järgi või saab selle ettenähtava aja jooksul korda teha, mida keegi ebaausalt ära võib kasutada.

6. Loo mudel ja sõelumisfunktsioon

Ülaltoodud andmete põhjal kirjeldame juhtunut andmeklasside abil:

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

Nüüd kirjutame funktsiooni, mis analüüsib võimaluste välja:


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. Vaata tulemust

Skaneerin võrku ja näitan teile, mida ma leidsin. Kuvatakse lihtsa väljundi tulemused läbi Log.d:

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

Uurimata jäi rakenduskoodist võrku ühendamise küsimus. Ütlen vaid, et mobiilseadme OS-ist salvestatud paroolide lugemiseks on vaja juurõigusi ja valmisolekut failisüsteemis tuhnida, et lugeda wpa_supplicant.conf. Kui rakendusloogika nõuab parooli sisestamist väljastpoolt, saab ühenduse luua läbi klassi android.net.wifi.WifiManager.

Tänan Jegor Ponomarev väärtuslike täienduste eest.

Kui arvad, et midagi vajab lisamist või parandamist, siis kirjuta kommentaaridesse :)

Allikas: www.habr.com

Lisa kommentaar