Аднойчы мне спатрэбілася сканаваць з Android прыкладання сеткі Wi-Fi і атрымліваць падрабязную выкладку дадзеных аб кропках доступу.
Тут прыйшлося сутыкнуцца з некалькімі цяжкасцямі: у
У дадзеным артыкуле разгледжана як з Android кода атрымаць вычарпальныя дадзеныя аб Wi-Fi асяроддзі без NDK, хакаў, а толькі з дапамогай Android API і зразумець, як іх інтэрпрэтаваць.
Не будзем цягнуць і пачнем пісаць код.
1. Ствараем праект
Нататка разлічана на тых, хто больш за адзін раз ствараў Android праект, таму падрабязнасці дадзенага пункта апускаем. Код ніжэй будзе прадстаўлены на мове Kotlin, minSdkVersion=23.
2. Дазволы на доступы
Для працы з Wi-Fi з дадатку спатрэбіцца атрымаць ад карыстальніка некалькі дазволаў. У адпаведнасці з
Разам, у 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"/>
А ў кодзе, у якім ёсць спасылка на бягучую Activity:
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 у дакументацыі пазначаны як depricated з версіі API 28, але оф.
Разам атрымалі спіс аб'ектаў
4. Глядзім на ScanResult і разбіраемся ў тэрмінах
Паглядзім на некаторыя палі гэтага класа і апішам, што яны азначаюць:
ідэнтыфікатар SSID — Service Set Identifier - гэта назва сеткі
BSSID – Basic Service Set Identifier – MAC адрас сеткавага адаптара (Wi-Fi кропкі)
ўзровень - Received Signal Strength Indicator [dBm (рускае дБм) - Дэцыбел, апорная магутнасць 1 мВт.] - Паказчык ўзроўню прыманага сігналу. Прымае значэнне ад 0 да -100, чым далей ад 0, тым больш магутнасці сігналу згубілася па шляху ад Wi-Fi кропкі да вашай прылады. Падрабязней можна паглядзець напрыклад на
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 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
}
}
магчымасці - Найболей цікавае поле для аналізу, праца з якім запатрабавала шмат часу. Тут у радок запісваюцца "магчымасці" кропкі. Пры гэтым падрабязнасці інтэрпрэтацыі радка ў дакументацыі можна не шукаць. Вось некалькі прыкладаў таго, што можа ляжаць у гэтым радку:
[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. Разбіраемся ў абрэвіятурах і парсім capabilities
Варта згадаць, што класы пакета android.net.wifi.* выкарыстоўвае пад капотам linux-утыліту
Будзем дзейнічаць паслядоўна. Разгледзім спачатку выснова такога фармату, пры якім усярэдзіне дужак элементы аддзеленыя знакам «-«:
[WPA-PSK-TKIP+CCMP]
[WPA2-PSK-CCMP]
Першае значэнне апісвае т.зв. метад аўтэнтыфікацыі (authentication). Гэта значыць, якую паслядоўнасць дзеянняў павінны вырабіць прыладу і кропка доступу, каб кропка доступу дазволіла сабой карыстацца і якім чынам шыфраваць карысную нагрузку. На момант напісання паста самыя частыя варыянты гэта WPA і WPA2, пры якім альбо кожная якая падключаецца прылада напроста, альбо праз т.зв. RADIUS-сервер (WPA-Enterprice) падае пароль па зашыфраваным канале. Хутчэй за ўсё ў вас дома кропка доступу дае падключэнне па гэтай схеме. Адрозненне другой версіі ад першай у больш устойлівым шыфры: AES супраць небяспечнага TKIP. Таксама паступова ўкараняецца WPA3, больш складаны і прасунуты. Тэарытычна можа сустрэцца варыянт з enterprice-рашэннем CCKM (Cisco Centralized Key Managment), але мне так і не сустрэўся.
Кропка доступу магла быць настроена на аўтэнтыфікацыю па MAC-адрасу. Ці, калі кропка доступу падае дадзеныя па састарэлым алгарытме WEP, то аўтэнтыфікацыі фактычна няма (сакрэтны ключ тут і з'яўляецца ключом шыфравання). Такія варыянты аднясем да тыпу OTHER.
Яшчэ ёсць упадабаны ў грамадскіх wi-fi метад са ўтоеным Captive Portal Detection - запыт аўтэнтыфікацыі праз браўзэр. Такія кропкі доступу выглядаюць для сканара як адчыненыя (якімі з кропкі зроку фізічнага падлучэння і з'яўляюцца). Таму аднясем іх да тыпу OPEN.
Другое значэнне можна пазначыць як алгарытм выкарыстання ключоў (key management). З'яўляецца параметрам метаду аўтэнтыфікацыі, аб якім напісана вышэй. Гаворыць аб тым, як менавіта адбываецца абмен ключамі шыфравання. Разгледзім магчымыя варыянты. EAP - выкарыстоўваецца ў згаданым WPA-Enterprice, выкарыстоўвае базу дадзеных для зверкі уведзеных аўтэнтыфікацыйных дадзеных. SAE - выкарыстоўваецца ў прасунутым WPA3, больш устойлівая да перабору. PSK - самы часты варыянт, мае на ўвазе ўвод пароля і яго перадачу ў зашыфраваным выглядзе. IEEE8021X – па міжнародным стандарце (выдатнаму ад падтрыманым сямействам WPA). OWE (Opportunistic Wireless Encryption) з'яўляецца пашырэннем стандарту IEEE 802.11, для кропак, якія мы аднеслі да тыпу OPEN. OWE забяспечвае бяспеку дадзеных, якія перадаюцца па неабароненай сетцы, за кошт іх шыфравання. Таксама магчымы варыянт калі ключоў доступу няма, назавем такі варыянт NONE.
Трэцім параметрам з'яўляецца т.зв. метад шыфравання (encryption schemes) - як менавіта выкарыстоўваецца шыфр для зашытых перадаваных дадзеных. Пералічоны варыянты. WEP – выкарыстоўвае струменевы шыфр RC4, сакрэтны ключ з'яўляецца ключом шыфравання, што ў свеце сучаснай крыптаграфіі лічыцца непрымальным. TKIP - выкарыстоўваецца ў WPA, CKIP - у WPA2. TKIP+CKIP - можа быць паказаны ў кропках якія ўмеюць WPA і WPA2 для зваротнай сумяшчальнасці.
Замест трох элементаў можна сустрэць адзінокую пазнаку WEP:
[WEP]
Як мы абмеркавалі вышэй, гэтага дастаткова каб не канкрэтызаваць алгарытм выкарыстання ключоў, якога няма, і метаду шыфравання, якое адно па-змаўчанні.
Цяпер разгледзім такі клямарчык:
[ESS]
Гэта рэжым працы Wi-Fi або тапалогія сетак Wi-Fi. Вам можа сустрэцца Рэжым BSS (Basic Service Set) - калі ёсць адна кропка доступу, праз якую маюць зносіны падлучаныя прылады. Можна сустрэць у лакальных сетках. Як правіла кропкі доступу патрэбныя для таго, каб злучаць прылады з розных лакальных сетак, таму яны з'яўляюцца часткай Extended Service Sets – ESS. Тып IBSSs (Independent Basic Service Sets) кажа аб тым, што прылада з'яўляецца часткай Peer-to-Peer сеткі.
Яшчэ можа трапіцца сцяг WPS:
[WPS]
WPS (Wi-Fi Protected Setup) – пратакол паўаўтаматычнай ініцыялізацыі сеткі Wi-Fi. Для ініцыялізацыі карыстач альбо ўводзіць 8-знакавы пароль, альбо заціскае кнопку на роўтары. Калі ваш пункт доступу адносіцца да першага тыпу і гэты сцяжок высветліўся насупраць імя вашага пункту доступу, вам настойліва рэкамендуецца зайсці ў адмінку і адключыць доступ па WPS. Справа ў тым, што часта 8-знакавы PIN можна пазнаць па MAC-адрасу, альбо перабраць за аглядны час, чым хтосьці нячысты на руку зможа скарыстацца.
6. Ствараем мадэль і функцыю парсінгу
На аснове таго, што высветлілі вышэй апішам data-класамі тое, што атрымалася:
/* схема аутентификации */
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.
}
Цяпер напішам функцыю, якая будзе парсіць поле capabilities:
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
Неасветленым засталося пытанне падлучэння да сеткі з кода прыкладання. Скажу толькі, што для таго, каб лічыць захаваныя паролі АС мабільнай прылады, патрэбныя root-правы і гатовасць парыцца ў файлавай сістэме каб прачытаць wpa_supplicant.conf. Калі логіка прыкладання мяркуе ўвод пароля звонку, падлучэнне можна ажыццявіць праз клас
Дзякуй
Калі лічыце, што трэба нешта дадаць ці выправіць, пішыце ў каментары 🙂
Крыніца: habr.com