Wi-Fi ja monet muut lyhenteet. Kuinka saada tietoja Wi-Fi-solmuista Android-sovelluksessa turvomatta

Eräänä päivänä minun piti skannata Wi-Fi-verkkoja Android-sovelluksista ja saada yksityiskohtaisia ​​tietoja tukiasemista.

Tässä meidän oli kohdattava useita vaikeuksia: off.Android-dokumentaatio monet kuvatuista luokista poistuivat käytöstä (API-taso > 26), mikä ei näkynyt siinä; joidenkin asioiden kuvaus dokumentaatiossa on minimaalinen (esim. luokan ominaisuuskenttä ScanResult kirjoitushetkellä melkein mitään ei ole kuvattu, vaikka se sisältää paljon tärkeää tietoa). Kolmas vaikeus voi olla se tosiasia, että kun pääset ensimmäisen kerran lähelle Wi-Fi-yhteyttä, joudut käsittelemään useita lyhenteitä, jotka vaikuttavat yksittäin ymmärrettäviltä. Mutta ei ehkä ole selvää, miten ne suhteutetaan ja jäsennetään (arviointi on subjektiivinen ja riippuu aikaisemmasta kokemuksesta).

Tässä artikkelissa käsitellään kattavan tiedon hankkimista Wi-Fi-ympäristöstä Android-koodista ilman NDK:ta, hakkeroita, mutta vain Android-sovellusliittymän avulla ja miten sitä tulkitaan.

Älä viivyttele ja aloita koodin kirjoittaminen.

1. Luo projekti

Tämä huomautus on tarkoitettu niille, jotka ovat luoneet Android-projektin useammin kuin kerran, joten jätämme pois tämän kohdan yksityiskohdat. Alla oleva koodi esitetään Kotlinissa, minSdkVersion=23.

2. Käyttöoikeudet

Jotta voit työskennellä sovelluksen Wi-Fi-yhteyden kanssa, sinun on hankittava käyttäjältä useita käyttöoikeuksia. Mukaisesti dokumentointi, jotta voit skannata verkkoa laitteissa, joiden käyttöjärjestelmäversio on 8.0 jälkeen, tarvitset verkkoympäristön tilan tarkastelun lisäksi joko pääsyn laitteen Wi-Fi-moduulin tilan muuttamiseen tai pääsyn koordinaatteihin (likimääräinen tai tarkka). Versiosta 9.0 alkaen sinun on pyydettävä käyttäjää molempiin ja myös nimenomaisesti pyydettävä käyttäjää ottamaan sijaintipalvelut päälle. Älä unohda selittää käyttäjälle rohkeasti, että tämä on Googlen mielijohte, ei meidän halumme vakoilla häntä :)

Joten, AndroidManifest.xml lisäämme:

    <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 koodissa, joka sisältää linkin nykyiseen toimintoon:

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. Luo BroadcastReceiver ja tilaa Wi-Fi-verkkoympäristön skannausta koskevat datapäivitystapahtumat

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
}

Dokumentaation WiFiManager.startScan-menetelmä on merkitty poistetuksi API-versiosta 28 lähtien, mutta pois käytöstä. ohjaavat ehdottaa sen käyttöä.

Yhteensä saimme luettelon esineistä ScanResult.

4. Katso ScanResult ja ymmärrä ehdot

Tarkastellaan joitain tämän luokan kenttiä ja kuvataan, mitä ne tarkoittavat:

SSID — Service Set Identifier on verkon nimi

BSSID – Basic Service Set Identifier – verkkosovittimen MAC-osoite (Wi-Fi-piste)

taso — Vastaanotetun signaalin voimakkuuden ilmaisin [dBm (venäläinen dBm) — Desibeli, vertailuteho 1 mW.] — Vastaanotetun signaalin voimakkuuden ilmaisin. Ottaa arvon väliltä 0–100, mitä kauempana arvosta 0, sitä enemmän signaalitehoa katosi matkalla Wi-Fi-pisteestä laitteeseesi. Tarkempia tietoja löytyy esimerkiksi osoitteesta wikipedia. Tässä kerron sinulle Android-luokan avulla WifiManager voit kalibroida signaalin tason asteikolla erinomaisesta kauheaan valitsemassasi vaiheessa:

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

taajuus — Wi-Fi-pisteen toimintataajuus [Hz]. Itse taajuuden lisäksi saatat olla kiinnostunut ns. kanavasta. Jokaisella pisteellä on oma toimintapuhtautensa. Kirjoitushetkellä suosituin Wi-Fi-pistealue on 2.4 GHz. Mutta tarkemmin sanottuna piste lähettää tietoja puhelimeesi numeroidulla taajuudella, joka on lähellä nimettyä. Kanavien lukumäärä ja vastaavat taajuudet standardoitu. Tämä tehdään niin, että lähellä olevat pisteet toimivat eri taajuuksilla, jolloin ne eivät häiritse toisiaan eivätkä heikennä toisiaan lähetyksen nopeutta ja laatua. Tässä tapauksessa pisteet eivät toimi yhdellä taajuudella, vaan useilla taajuuksilla (parametri kanavan leveys), jota kutsutaan kanavan leveydeksi. Toisin sanoen vierekkäisillä (eikä vain vierekkäisillä, vaan jopa 3 itsestään) kanavilla toimivat pisteet häiritsevät toisiaan. Saatat olla hyödyllinen tämä yksinkertainen koodi, jonka avulla voit laskea kanavanumeron taajuusarvosta pisteille, joiden taajuus 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
            }
        }

kyvyt - mielenkiintoisin analyysiala, jonka kanssa työskentely vaati paljon aikaa. Täällä pisteen "ominaisuudet" on kirjoitettu riville. Tässä tapauksessa sinun ei tarvitse etsiä tietoja merkkijonojen tulkinnasta dokumentaatiosta. Tässä on esimerkkejä siitä, mitä tällä rivillä voi olla:

[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. Lyhenteiden ymmärtäminen ja jäsennysominaisuudet

On syytä mainita, että android.net.wifi.*-paketin luokkia käyttää konepellin alla Linux-apuohjelma wpa_supplicantin ja tulosten tulos ominaisuuskentässä on kopio lippukentästä skannattaessa.

Toimimme johdonmukaisesti. Tarkastellaan ensin sellaisen muodon tulosta, jossa suluissa olevat elementit erotetaan "-"-merkillä:

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

Ensimmäinen merkitys kuvaa ns. todennus tapa. Eli mikä toimintosarja laitteen ja tukiaseman on suoritettava, jotta tukiasema sallii itsensä käytön ja miten hyötykuorma salataan. Tätä postausta kirjoitettaessa yleisimmät vaihtoehdot ovat WPA ja WPA2, joissa joko kukin kytketty laite suoraan tai ns. RADIUS-palvelin (WPA-Enterprice) tarjoaa salasanan salatun kanavan kautta. Todennäköisesti kotisi tukiasema tarjoaa yhteyden tämän järjestelmän mukaisesti. Ero toisen version ja ensimmäisen välillä on, että siinä on vahvempi salaus: AES vs. turvaton TKIP. Myös WPA3, joka on monimutkaisempi ja edistyneempi, otetaan vähitellen käyttöön. Teoriassa voi olla vaihtoehto yritysratkaisulla CCKM (Cisco Centralised Key Management), mutta en ole koskaan törmännyt siihen.

Tukiasema on ehkä määritetty todentamaan MAC-osoitteen perusteella. Tai jos tukiasema tarjoaa tietoja vanhentuneen WEP-algoritmin avulla, todennusta ei todellisuudessa ole (salainen avain tässä on salausavain). Luokittelemme tällaiset vaihtoehdot MUIHIN.
On olemassa myös menetelmä, joka on suosittu julkisessa wi-fi:ssä piilotetulla Captive Portal Detection -tunnistuksella - todennuspyyntö selaimen kautta. Tällaiset tukiasemat näyttävät skannerille avoimina (mitä ne ovat fyysisen yhteyden kannalta). Siksi luokittelemme ne AVOIN.

Toinen arvo voidaan merkitä nimellä avainten hallintaalgoritmi. Tämä on edellä kuvatun todennusmenetelmän parametri. Puhuu tarkalleen kuinka salausavaimia vaihdetaan. Mietitään mahdollisia vaihtoehtoja. EAP - käytetty mainitussa WPA-Enterpricessä, käyttää tietokantaa syötettyjen todennustietojen tarkistamiseen. SAE - käytetty edistyneessä WPA3:ssa, kestää paremmin raakaa voimaa. PSK - yleisin vaihtoehto, sisältää salasanan syöttämisen ja sen lähettämisen salatussa muodossa. IEEE8021X - kansainvälisen standardin mukainen (erilainen kuin WPA-perheen tukema). OWE (Opportunistic Wireless Encryption) on IEEE 802.11 -standardin laajennus pisteisiin, jotka olemme luokitellut AVOIN. OWE varmistaa suojaamattoman verkon kautta siirrettävien tietojen turvallisuuden salaamalla ne. Vaihtoehto on myös mahdollinen, kun avaimia ei ole, kutsutaan tätä vaihtoehtoa EI MITÄÄN.

Kolmas parametri on ns. salausjärjestelmiä — kuinka tarkasti salausta käytetään siirrettyjen tietojen suojaamiseen. Listataan vaihtoehdot. WEP - käyttää RC4-virtasalausta, salainen avain on salausavain, jota ei pidetä hyväksyttävänä nykyaikaisen kryptografian maailmassa. TKIP - käytössä WPA:ssa, CKIP - WPA2:ssa. TKIP+CKIP - voidaan määrittää WPA- ja WPA2-yhteensopiviin kohtiin taaksepäin yhteensopivuuden takaamiseksi.

Kolmen elementin sijasta löydät yksinäisen WEP-merkin:

[WEP]

Kuten edellä käsittelimme, tämä riittää, jotta ei määritetä avaimien käytön algoritmia, jota ei ole olemassa, ja salausmenetelmää, joka on oletuksena sama.

Harkitse nyt tätä hakasuljetta:

[ESS]

Se Wi-Fi-käyttötila tai Wi-Fi-verkon topologia. Saatat kohdata BSS (Basic Service Set) -tilan - kun on yksi tukiasema, jonka kautta yhdistetyt laitteet kommunikoivat. Löytyy paikallisista verkoista. Pääsääntöisesti liityntäpisteitä tarvitaan eri paikallisten verkkojen laitteiden yhdistämiseen, joten ne ovat osa Extended Service Sets - ESS -palvelua. IBSS-tyyppi (Independent Basic Service Sets) osoittaa, että laite on osa vertaisverkkoa.

Saatat myös nähdä WPS-lipun:

[WPS]

WPS (Wi-Fi Protected Setup) on protokolla Wi-Fi-verkon puoliautomaattiseen alustukseen. Alustaakseen käyttäjä joko syöttää 8-merkkisen salasanan tai paina reitittimen painiketta. Jos tukiasemasi on ensimmäistä tyyppiä ja tämä valintaruutu näkyy tukiasemasi nimen vieressä, sinun on erittäin suositeltavaa siirtyä hallintapaneeliin ja poistaa WPS-käyttö käytöstä. Tosiasia on, että usein 8-numeroinen PIN-koodi voidaan selvittää MAC-osoitteen perusteella tai se voidaan selvittää ennakoitavissa olevassa ajassa, jota joku voi epärehellisesti hyödyntää.

6. Luo malli ja jäsennysfunktio

Sen perusteella, mitä yllä saimme selville, kuvaamme tapahtumia tietoluokkien avulla:

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

Kirjoita nyt funktio, joka jäsentää ominaisuuskentän:


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. Katso tulos

Selailen verkon ja näytän mitä löysin. Näytetään yksinkertaisen tulostuksen tulokset Log.d:n kautta:

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

Ongelma verkkoon liittymisestä sovelluskoodista jäi tutkimatta. Sanon vain, että jotta voit lukea tallennettuja salasanoja mobiililaitteen käyttöjärjestelmästä, tarvitset pääkäyttäjän oikeudet ja halukkuuden selata tiedostojärjestelmää lukeaksesi wpa_supplicant.conf. Jos sovelluslogiikka vaatii salasanan syöttämistä ulkopuolelta, yhteys voidaan muodostaa luokan kautta android.net.wifi.WifiManager.

Kiitos Egor Ponomarev arvokkaita lisäyksiä varten.

Jos uskot, että jotain kaipaa lisäystä tai korjausta, kirjoita kommentteihin :)

Lähde: will.com

Lisää kommentti