Wi-Fi және басқа да көптеген қысқартулар. Ісінбей Android қосымшасында Wi-Fi түйіндері туралы деректерді қалай алуға болады

Бір күні маған Android қолданбаларынан Wi-Fi желілерін сканерлеу және кіру нүктелері туралы толық деректерді алу қажет болды.

Мұнда біз бірнеше қиындықтарға тап болдық: өшірулі.Android құжаттамасы сипатталған көптеген сыныптар ескірді (API деңгейі > 26), бұл онда көрсетілмеді; құжаттамадағы кейбір заттардың сипаттамасы минималды (мысалы, сыныптың мүмкіндіктер өрісі ScanResult жазу кезінде көптеген маңызды деректер бар болса да, ештеңе сипатталған жоқ). Үшінші қиындық мынада болуы мүмкін: Wi-Fi желісіне алғаш жақындаған кезде, теорияны оқып, маршрутизаторды localhost арқылы орнатудан басқа, сіз жеке-жеке түсінікті болып көрінетін бірқатар қысқартулармен айналысуыңыз керек. Бірақ оларды қалай байланыстыру және құрылымдау анық болмауы мүмкін (пайымдау субъективті және бұрынғы тәжірибеге байланысты).

Бұл мақалада Android кодынан Wi-Fi ортасы туралы толық деректерді NDKсыз, бұзусыз, бірақ тек Android API көмегімен қалай алуға болатыны және оны қалай түсіндіруге болатыны талқыланады.

Кешіктірмей, код жазуды бастайық.

1. Жоба жасаңыз

Бұл жазба Android жобасын бірнеше рет жасағандарға арналған, сондықтан біз бұл тармақтың мәліметтерін өткізбейміз. Төмендегі код Kotlin ішінде көрсетіледі, minSdkVersion=23.

2. Қол жеткізу рұқсаттары

Қолданбадағы Wi-Fi желісімен жұмыс істеу үшін пайдаланушыдан бірнеше рұқсат алу қажет. Сәйкес құжаттама, 8.0-ден кейінгі ОЖ нұсқалары бар құрылғыларда желіні сканерлеу үшін желі ортасының күйін көруге рұқсат беруден басқа, құрылғының Wi-Fi модулінің күйін өзгертуге рұқсат немесе координаттарға (шамамен) рұқсат қажет. немесе дәл). 9.0 нұсқасынан бастап пайдаланушыға екеуін де сұрау керек, сонымен қатар пайдаланушыдан орынды анықтау қызметтерін қосуды нақты сұрау керек. Пайдаланушыға бұл Google-дың қалауы екенін және біздің оған тыңшылық жасау ниетіміз емес екенін батыл түрде түсіндіруді ұмытпаңыз :)

Сонымен, 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"/>

Ағымдағы әрекетке сілтеме бар кодта:

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. BroadcastReceiver жасаңыз және Wi-Fi желісі ортасын сканерлеу туралы деректерді жаңарту оқиғаларына жазылыңыз

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 әдісі API 28 нұсқасынан бері жойылған деп белгіленген, бірақ өшірулі. гид пайдалануды ұсынады.

Барлығы біз нысандардың тізімін алдық ScanResult.

4. ScanResult қараңыз және шарттарды түсініңіз

Осы сыныптың кейбір өрістерін қарастырайық және олардың нені білдіретінін сипаттайық:

SSID — Қызметтер жиынтығы идентификаторы — желінің атауы

BSSID – Негізгі қызмет жиынтығы идентификаторы – желі адаптерінің MAC мекенжайы (Wi-Fi нүктесі)

деңгей — Қабылдаған сигнал күшінің индикаторы [дБм (орысша дБм) — Децибел, анықтамалық қуат 1 мВт.] — қабылданған сигнал күшінің көрсеткіші. 0-ден -100-ге дейінгі мәнді қабылдайды, 0-ден неғұрлым алыс болса, Wi-Fi нүктесінен құрылғыға дейінгі жолда соғұрлым сигнал қуаты жоғалады. Толық ақпаратты, мысалы, мына жерден табуға болады Уикипедия. Мұнда мен сізге Android класын пайдалану туралы айтайын WifiManager Сіз таңдаған қадамда сигнал деңгейін тамашадан қорқыныштыға дейінгі шкала бойынша калибрлеуге болады:

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

жиілік — Wi-Fi нүктесінің жұмыс жиілігі [Гц]. Жиіліктің өзінен басқа, сізді арна деп аталатын нәрсе қызықтыруы мүмкін. Әрбір нүктенің өзіндік жұмыс тазалығы бар. Жазу кезінде Wi-Fi нүктелерінің ең танымал диапазоны - 2.4 ГГц. Бірақ, дәлірек айтсақ, нүкте ақпаратты телефоныңызға аталғанға жақын нөмірленген жиілікте жібереді. Арналар саны және сәйкес жиіліктер стандартталған. Бұл жақын орналасқан нүктелер әртүрлі жиілікте жұмыс істеуі үшін жасалады, осылайша бір-біріне кедергі жасамайды және беру жылдамдығы мен сапасын өзара төмендетпейді. Бұл жағдайда нүктелер бір жиілікте емес, жиілік диапазонында жұмыс істейді (параметр арна ені), арна ені деп аталады. Яғни, іргелес (тек іргелес емес, тіпті өздерінен 3) арналарда жұмыс істейтін нүктелер бір-біріне кедергі жасайды. Сіз 2.4 және 5 ГГц жиіліктегі нүктелер үшін жиілік мәнінен арна нөмірін есептеуге мүмкіндік беретін осы қарапайым кодты пайдалы деп таба аласыз:


    /* по частоте определяем номер канала */
    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
            }
        }

мүмкіндіктері - талдау үшін ең қызықты сала, онымен жұмыс көп уақытты қажет етеді. Мұнда нүктенің «мүмкіндіктері» жолда жазылған. Бұл жағдайда құжаттамада жолды түсіндіру мәліметтерін іздеудің қажеті жоқ. Міне, осы жолда болуы мүмкін кейбір мысалдар:

[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. Қысқартуларды түсіну және талдау мүмкіндіктері

Android.net.wifi.* пакетінің сыныптарын Linux утилитасы қалпақ астында пайдаланатынын атап өткен жөн. wpa_supplicant және мүмкіндіктер өрісіндегі шығыс нәтижесі сканерлеу кезіндегі жалаушалар өрісінің көшірмесі болып табылады.

Біз дәйекті әрекет ететін боламыз. Алдымен жақша ішіндегі элементтер «-» белгісімен бөлінген форматтың шығысын қарастырайық:

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

Бірінші мағынасы деп аталатынды сипаттайды. аутентификация әдісі. Яғни, кіру нүктесі өзін пайдалануға және пайдалы жүктемені қалай шифрлауға мүмкіндік беруі үшін құрылғы мен кіру нүктесі қандай әрекеттер тізбегін орындауы керек. Бұл жазбаны жазу кезінде ең көп таралған опциялар - бұл WPA және WPA2, оларда әрбір қосылған құрылғы тікелей немесе деп аталатын арқылы. RADIUS сервері (WPA-Enterprice) шифрланған арна арқылы құпия сөзді береді. Сірә, сіздің үйіңіздегі кіру нүктесі осы схемаға сәйкес қосылымды қамтамасыз етеді. Екінші нұсқа мен бірінші нұсқаның айырмашылығы оның шифры күштірек: AES қауіпті TKIP-ке қарсы. Күрделі әрі жетілдірілген WPA3 жүйесі де біртіндеп енгізілуде. Теориялық тұрғыдан алғанда, CCKM (Cisco орталықтандырылған кілттерді басқару) кәсіпорын шешімімен опция болуы мүмкін, бірақ мен оны ешқашан кездестірмедім.

Кіру нүктесі MAC мекенжайы бойынша аутентификациялау үшін конфигурацияланған болуы мүмкін. Немесе кіру нүктесі ескірген WEP алгоритмін пайдаланып деректерді ұсынса, онда аутентификация жоқ (мұндағы құпия кілт - шифрлау кілті). Біз мұндай опцияларды БАСҚА ретінде жіктейміз.
Сондай-ақ жасырын Captive Portal Detection бар жалпыға қолжетімді Wi-Fi желісінде танымал әдіс бар - браузер арқылы аутентификация сұрауы. Мұндай кіру нүктелері сканерге ашық болып көрінеді (олар физикалық қосылым тұрғысынан). Сондықтан біз оларды АШЫҚ деп жіктейміз.

Екінші мәнді келесідей белгілеуге болады кілттерді басқару алгоритмі. Бұл жоғарыда сипатталған аутентификация әдісінің параметрі. Шифрлау кілттерінің нақты қалай алмасуы туралы әңгімелейді. Мүмкін нұсқаларды қарастырайық. EAP - аталған WPA-Enterprice жүйесінде пайдаланылады, енгізілген аутентификация деректерін тексеру үшін дерекқорды пайдаланады. SAE - жетілдірілген WPA3-те қолданылады, қатал күшке төзімді. PSK - ең кең таралған опция, парольді енгізуді және оны шифрланған түрде беруді қамтиды. IEEE8021X - халықаралық стандартқа сәйкес (WPA отбасы қолдайтыннан басқа). OWE (Opportunistic Wireless Encryption) IEEE 802.11 стандартының біз Ашық деп жіктеген нүктелерге арналған кеңейтімі болып табылады. OWE шифрлау арқылы қорғалмаған желі арқылы берілетін деректердің қауіпсіздігін қамтамасыз етеді. Опция кіру кілттері болмаған кезде де мүмкін болады, бұл опцияны NONE деп атаймыз.

Үшінші параметр деп аталады. шифрлау схемалары — жіберілетін деректерді қорғау үшін шифрдың нақты қалай қолданылатыны. Опцияларды тізімдейміз. WEP – RC4 ағындық шифрын қолданады, құпия кілт – қазіргі криптография әлемінде қолайсыз деп саналатын шифрлау кілті. TKIP - WPA-да, CKIP - WPA2-де қолданылады. TKIP+CKIP - кері үйлесімділік үшін WPA және WPA2 қабілетті нүктелерде көрсетілуі мүмкін.

Үш элементтің орнына сіз жалғыз WEP белгісін таба аласыз:

[WEP]

Жоғарыда талқылағанымыздай, бұл жоқ кілттерді пайдалану алгоритмін және әдепкі бойынша бірдей шифрлау әдісін көрсетпеу үшін жеткілікті.

Енді мына жақшаны қарастырыңыз:

[ESS]

осы Wi-Fi жұмыс режимі немесе Wi-Fi желісінің топологиясы. Сіз BSS (Негізгі қызмет жинағы) режимін кездестіруіңіз мүмкін - қосылған құрылғылар байланысатын бір кіру нүктесі болған кезде. Жергілікті желілерде табуға болады. Әдетте, кіру нүктелері әртүрлі жергілікті желілердегі құрылғыларды қосу үшін қажет, сондықтан олар кеңейтілген қызмет жиындарының бөлігі болып табылады - ESS. IBSSs (Independent Basic Service Sets) түрі құрылғының тең дәрежелі желінің бөлігі екенін көрсетеді.

Сондай-ақ WPS жалауын көре аласыз:

[WPS]

WPS (Wi-Fi Protected Setup) — Wi-Fi желісін жартылай автоматты инициализациялау протоколы. Баптандыру үшін пайдаланушы 8 таңбадан тұратын құпия сөзді енгізеді немесе маршрутизатордағы түймені басады. Егер кіру нүктесі бірінші типті болса және бұл құсбелгі кіру нүктесінің атауының жанында пайда болса, сізге әкімші панеліне өтіп, WPS қатынасын өшіру ұсынылады. Мәселе мынада, көбінесе 8 таңбалы PIN-кодты MAC мекенжайы арқылы білуге ​​болады немесе оны жақын арада шешуге болады, оны біреу адал емес пайдалана алады.

6. Модель және талдау функциясын құру

Жоғарыда білгендерімізге сүйене отырып, біз деректер кластары арқылы не болғанын сипаттаймыз:

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

Енді мүмкіндіктер өрісін талдайтын функцияны жазайық:


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. Нәтижені қараңыз

Мен желіні сканерлеп, не тапқанымды көрсетемін. Log.d арқылы қарапайым шығару нәтижелері көрсетілген:

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

Қолданба кодынан желіге қосылу мәселесі қаралмай қалды. Мен тек мобильді құрылғының операциялық жүйесінен сақталған құпия сөздерді оқу үшін сізге түбірлік құқықтар және wpa_supplicant.conf оқу үшін файлдық жүйені іздеуге дайын болу керек екенін айтайын. Қолданба логикасы құпия сөзді сырттан енгізуді талап етсе, қосылым класс арқылы жүзеге асырылуы мүмкін android.net.wifi.WifiManager.

сізге рахмет Егор Пономарев құнды толықтырулар үшін.

Бірдеңе қосу немесе түзету қажет деп ойласаңыз, түсініктемелерде жазыңыз :)

Ақпарат көзі: www.habr.com

пікір қалдыру