Μια μέρα χρειάστηκε να σαρώσω δίκτυα Wi-Fi από εφαρμογές Android και να λάβω λεπτομερή δεδομένα σχετικά με τα σημεία πρόσβασης.
Εδώ αντιμετωπίσαμε αρκετές δυσκολίες:
Αυτό το άρθρο περιγράφει πώς μπορείτε να αποκτήσετε ολοκληρωμένα δεδομένα σχετικά με το περιβάλλον Wi-Fi από κώδικα Android χωρίς NDK, hacks, αλλά μόνο χρησιμοποιώντας το API Android και κατανοήστε πώς να το ερμηνεύσετε.
Ας μην καθυστερήσουμε και ας αρχίσουμε να γράφουμε κώδικα.
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"/>
Και στον κώδικα που περιέχει έναν σύνδεσμο προς την τρέχουσα Δραστηριότητα:
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 στην τεκμηρίωση έχει επισημανθεί ως καταργημένη από την έκδοση 28 του API, αλλά είναι απενεργοποιημένη.
Συνολικά, λάβαμε μια λίστα με αντικείμενα
4. Κοιτάξτε το ScanResult και κατανοήστε τους όρους
Ας δούμε μερικά πεδία αυτής της κατηγορίας και ας περιγράψουμε τι σημαίνουν:
SSID — Αναγνωριστικό συνόλου υπηρεσιών είναι το όνομα του δικτύου
BSSID – Αναγνωριστικό βασικού συνόλου υπηρεσιών – Διεύθυνση MAC του προσαρμογέα δικτύου (σημείο Wi-Fi)
επίπεδο — Ένδειξη ισχύος ληφθέντος σήματος [dBm (Ρωσικά dBm) — Decibel, ισχύς αναφοράς 1 mW.] — Ένδειξη της ισχύος του λαμβανόμενου σήματος. Λαμβάνει μια τιμή από 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 [Hz]. Εκτός από την ίδια τη συχνότητα, μπορεί να σας ενδιαφέρει το λεγόμενο κανάλι. Κάθε σημείο έχει τη δική του καθαρότητα λειτουργίας. Τη στιγμή της γραφής, το πιο δημοφιλές εύρος σημείων 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. Κατανόηση των συντομογραφιών και των δυνατοτήτων ανάλυσης
Αξίζει να αναφέρουμε ότι οι κλάσεις του πακέτου android.net.wifi.* χρησιμοποιούνται κάτω από την κουκούλα από ένα βοηθητικό πρόγραμμα Linux
Θα ενεργήσουμε με συνέπεια. Ας εξετάσουμε πρώτα την έξοδο μιας μορφής στην οποία τα στοιχεία μέσα στις παρενθέσεις διαχωρίζονται με ένα σύμβολο "-":
[WPA-PSK-TKIP+CCMP]
[WPA2-PSK-CCMP]
Η πρώτη έννοια περιγράφει το λεγόμενο. μέθοδος αυθεντικότητας. Δηλαδή, ποια ακολουθία ενεργειών πρέπει να εκτελέσει η συσκευή και το σημείο πρόσβασης για να μπορέσει το σημείο πρόσβασης να χρησιμοποιηθεί και πώς να κρυπτογραφήσει το ωφέλιμο φορτίο. Τη στιγμή της σύνταξης αυτής της ανάρτησης, οι πιο συνηθισμένες επιλογές είναι το WPA και το WPA2, στα οποία είτε κάθε συνδεδεμένη συσκευή απευθείας είτε μέσω των λεγόμενων. Ο διακομιστής RADIUS (WPA-Enterprice) παρέχει τον κωδικό πρόσβασης σε ένα κρυπτογραφημένο κανάλι. Πιθανότατα, το σημείο πρόσβασης στο σπίτι σας παρέχει σύνδεση σύμφωνα με αυτό το σχήμα. Η διαφορά μεταξύ της δεύτερης έκδοσης και της πρώτης είναι ότι έχει έναν ισχυρότερο κρυπτογράφηση: AES έναντι του ανασφαλούς TKIP. Το WPA3, το οποίο είναι πιο περίπλοκο και προηγμένο, εισάγεται επίσης σταδιακά. Θεωρητικά, μπορεί να υπάρχει μια επιλογή με τη λύση Enterprice CCKM (Cisco Centralized Key Management), αλλά δεν την έχω συναντήσει ποτέ.
Το σημείο πρόσβασης μπορεί να έχει διαμορφωθεί για έλεγχο ταυτότητας με διεύθυνση MAC. Ή, εάν το σημείο πρόσβασης παρέχει δεδομένα χρησιμοποιώντας τον απαρχαιωμένο αλγόριθμο WEP, τότε στην πραγματικότητα δεν υπάρχει έλεγχος ταυτότητας (το μυστικό κλειδί εδώ είναι το κλειδί κρυπτογράφησης). Κατατάσσουμε τέτοιες επιλογές ως ΑΛΛΕΣ.
Υπάρχει επίσης μια μέθοδος που είναι δημοφιλής στο δημόσιο wi-fi με κρυφή ανίχνευση αιχμαλώτων πύλης - ένα αίτημα ελέγχου ταυτότητας μέσω ενός προγράμματος περιήγησης. Τέτοια σημεία πρόσβασης εμφανίζονται στον σαρωτή ως ανοιχτά (που είναι από την άποψη της φυσικής σύνδεσης). Επομένως, τα ταξινομούμε ως ΑΝΟΙΚΤΑ.
Η δεύτερη τιμή μπορεί να συμβολιστεί ως αλγόριθμος διαχείρισης κλειδιών. Αυτή είναι μια παράμετρος της μεθόδου ελέγχου ταυτότητας που περιγράφεται παραπάνω. Μιλάει για το πώς ακριβώς ανταλλάσσονται τα κλειδιά κρυπτογράφησης. Ας εξετάσουμε τις πιθανές επιλογές. EAP - χρησιμοποιείται στο αναφερόμενο WPA-Enterprice, χρησιμοποιεί μια βάση δεδομένων για την επαλήθευση των καταχωρημένων δεδομένων ελέγχου ταυτότητας. SAE - χρησιμοποιείται σε προηγμένο WPA3, πιο ανθεκτικό στην ωμή βία. PSK - η πιο κοινή επιλογή, περιλαμβάνει την εισαγωγή ενός κωδικού πρόσβασης και τη μετάδοσή του σε κρυπτογραφημένη μορφή. IEEE8021X - σύμφωνα με ένα διεθνές πρότυπο (διαφορετικό από αυτό που υποστηρίζεται από την οικογένεια WPA). Το OWE (Opportunistic Wireless Encryption) είναι μια επέκταση του προτύπου IEEE 802.11 για σημεία που ταξινομήσαμε ως OPEN. Το OWE διασφαλίζει την ασφάλεια των δεδομένων που μεταδίδονται μέσω ενός μη ασφαλούς δικτύου κρυπτογραφώντας το. Μια επιλογή είναι επίσης δυνατή όταν δεν υπάρχουν κλειδιά πρόσβασης, ας ονομάσουμε αυτήν την επιλογή ΚΑΜΙΑ.
Η τρίτη παράμετρος είναι η λεγόμενη. σχήματα κρυπτογράφησης — πώς ακριβώς χρησιμοποιείται η κρυπτογράφηση για την προστασία των μεταδιδόμενων δεδομένων. Ας παραθέσουμε τις επιλογές. WEP - χρησιμοποιεί έναν κρυπτογράφηση ροής RC4, το μυστικό κλειδί είναι το κλειδί κρυπτογράφησης, το οποίο θεωρείται απαράδεκτο στον κόσμο της σύγχρονης κρυπτογραφίας. TKIP - χρησιμοποιείται σε WPA, CKIP - σε WPA2. TKIP+CKIP - μπορεί να καθοριστεί σε σημεία με δυνατότητα WPA και WPA2 για συμβατότητα προς τα πίσω.
Αντί για τρία στοιχεία, μπορείτε να βρείτε ένα μοναχικό σήμα WEP:
[WEP]
Όπως συζητήσαμε παραπάνω, αυτό αρκεί για να μην καθοριστεί ο αλγόριθμος για τη χρήση κλειδιών, ο οποίος δεν υπάρχει, και η μέθοδος κρυπτογράφησης, η οποία είναι η ίδια από προεπιλογή.
Τώρα λάβετε υπόψη αυτήν την παρένθεση:
[ESS]
Το Λειτουργία Wi-Fi ή Τοπολογία δικτύου Wi-Fi. Ενδέχεται να αντιμετωπίσετε τη λειτουργία BSS (Basic Service Set) - όταν υπάρχει ένα σημείο πρόσβασης μέσω του οποίου επικοινωνούν οι συνδεδεμένες συσκευές. Μπορεί να βρεθεί σε τοπικά δίκτυα. Κατά κανόνα, τα σημεία πρόσβασης χρειάζονται για τη σύνδεση συσκευών από διαφορετικά τοπικά δίκτυα, επομένως αποτελούν μέρος των Εκτεταμένων Συνόλων Υπηρεσιών - ESS. Ο τύπος IBSSs (Independent Basic Service Sets) υποδεικνύει ότι η συσκευή είναι μέρος ενός δικτύου Peer-to-Peer.
Μπορείτε επίσης να δείτε τη σημαία 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
Το ζήτημα της σύνδεσης στο δίκτυο από τον κωδικό εφαρμογής παρέμεινε ανεξέταστο. Θα πω μόνο ότι για να διαβάσετε τους αποθηκευμένους κωδικούς πρόσβασης του λειτουργικού συστήματος μιας κινητής συσκευής, χρειάζεστε δικαιώματα root και προθυμία να ψάξετε στο σύστημα αρχείων για να διαβάσετε το wpa_supplicant.conf. Εάν η λογική της εφαρμογής απαιτεί την εισαγωγή ενός κωδικού πρόσβασης από έξω, η σύνδεση μπορεί να γίνει μέσω της κλάσης
σας ευχαριστώ
Εάν πιστεύετε ότι κάτι πρέπει να προστεθεί ή να διορθωθεί, γράψτε στα σχόλια :)
Πηγή: www.habr.com