Suatu hari saya perlu memindai jaringan Wi-Fi dari aplikasi Android dan mendapatkan data detail tentang titik akses.
Di sini kami harus menghadapi beberapa kesulitan: banyak dari kelas yang dijelaskan menjadi tidak digunakan lagi (API level > 26), yang tidak tercermin di dalamnya; deskripsi beberapa hal dalam dokumentasi sangat minim (misalnya, bidang kemampuan kelas pada saat penulisan, hampir tidak ada yang dijelaskan, meskipun mengandung banyak data penting). Kesulitan ketiga mungkin terletak pada kenyataan bahwa ketika Anda pertama kali mengenal Wi-Fi, selain membaca teori dan mengatur router melalui localhost, Anda harus berurusan dengan sejumlah singkatan yang tampaknya dapat dimengerti satu per satu. Namun mungkin tidak jelas bagaimana menghubungkan dan menyusunnya (penilaian bersifat subyektif dan bergantung pada pengalaman sebelumnya).
Artikel ini membahas cara mendapatkan data komprehensif tentang lingkungan Wi-Fi dari kode Android tanpa NDK, peretasan, tetapi hanya menggunakan Android API dan memahami cara menafsirkannya.
Jangan menunda lagi dan mulai menulis kode.
1. Buat proyek
Catatan ini ditujukan bagi mereka yang telah membuat proyek Android lebih dari satu kali, jadi kami menghilangkan detail item ini. Kode di bawah ini akan disajikan di Kotlin, minSdkVersion=23.
2. Izin akses
Untuk bekerja dengan Wi-Fi dari aplikasi, Anda perlu mendapatkan beberapa izin dari pengguna. Menurut , untuk memindai jaringan pada perangkat dengan versi OS setelah 8.0, selain akses untuk melihat status lingkungan jaringan, Anda memerlukan akses untuk mengubah status modul Wi-Fi perangkat, atau akses ke koordinat (perkiraan atau tepat). Mulai versi 9.0, Anda harus meminta pengguna untuk keduanya, dan juga secara eksplisit meminta pengguna untuk mengaktifkan layanan lokasi. Jangan lupa dengan gagah menjelaskan kepada pengguna bahwa ini adalah keinginan Google, dan bukan keinginan kami untuk memata-matainya :)
Jadi, di AndroidManifest.xml kita akan menambahkan:
<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"/>
Dan dalam kode yang berisi link ke Aktivitas saat ini:
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. Buat BroadcastReceiver dan berlangganan acara pembaruan data tentang pemindaian lingkungan jaringan 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
}
Metode WiFiManager.startScan dalam dokumentasi ditandai sebagai tidak digunakan lagi sejak API versi 28, tetapi tidak aktif. menyarankan untuk menggunakannya.
Secara total, kami menerima daftar objek .
4. Lihat ScanResult dan pahami istilahnya
Mari kita lihat beberapa bidang di kelas ini dan jelaskan maksudnya:
SSID — Service Set Identifier adalah nama jaringan
BSSID – Pengidentifikasi Set Layanan Dasar – Alamat MAC adaptor jaringan (titik Wi-Fi)
tingkat — Indikator Kekuatan Sinyal yang Diterima [dBm (dBm Rusia) — Desibel, daya referensi 1 mW.] — Indikator kekuatan sinyal yang diterima. Mengambil nilai dari 0 hingga -100, semakin jauh dari 0, semakin banyak daya sinyal yang hilang sepanjang jalur dari titik Wi-Fi ke perangkat Anda. Rincian lebih lanjut dapat ditemukan, misalnya, di . Di sini saya akan memberitahu Anda bahwa menggunakan kelas Android Anda dapat mengkalibrasi level sinyal pada skala dari sangat baik hingga buruk pada langkah yang Anda pilih:
val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
val numberOfLevels = 5
val level = WifiManager.calculateSignalLevel(level, numberOfLevels)
frekuensi — frekuensi pengoperasian titik Wi-Fi [Hz]. Selain frekuensinya sendiri, Anda mungkin tertarik dengan apa yang disebut saluran. Setiap titik memiliki kemurnian pengoperasiannya sendiri. Pada saat artikel ini ditulis, rentang titik Wi-Fi paling populer adalah 2.4 GHz. Namun, lebih tepatnya, titik tersebut mengirimkan informasi ke telepon Anda pada frekuensi bernomor yang mendekati frekuensi yang disebutkan. Jumlah saluran dan frekuensi yang sesuai . Hal ini dilakukan agar titik-titik terdekat beroperasi pada frekuensi yang berbeda, sehingga tidak saling mengganggu dan tidak saling mengurangi kecepatan dan kualitas transmisi. Dalam hal ini, titik-titik beroperasi tidak pada satu frekuensi, tetapi pada rentang frekuensi (parameter lebar saluran), disebut lebar saluran. Artinya, titik-titik yang beroperasi pada saluran yang berdekatan (dan tidak hanya berdekatan, tetapi bahkan 3 saluran yang terpisah) saling mengganggu. Anda mungkin menemukan kode sederhana ini berguna, yang memungkinkan Anda menghitung nomor saluran dari nilai frekuensi untuk titik dengan frekuensi 2.4 dan 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
}
}
kemampuan - bidang yang paling menarik untuk dianalisis, pengerjaannya membutuhkan banyak waktu. Di sini “kemampuan” poin tersebut dituliskan pada baris. Dalam hal ini, Anda tidak perlu mencari detail interpretasi string di dokumentasi. Berikut adalah beberapa contoh dari apa yang mungkin ada di baris ini:
[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. Memahami singkatan dan kemampuan parsing
Perlu disebutkan bahwa kelas paket android.net.wifi.* digunakan secara tersembunyi oleh utilitas Linux dan hasil keluaran pada kolom ability adalah salinan dari kolom flag saat memindai.
Kami akan bertindak secara konsisten. Pertama mari kita pertimbangkan output dari format di mana elemen di dalam tanda kurung dipisahkan oleh tanda “-“:
[WPA-PSK-TKIP+CCMP]
[WPA2-PSK-CCMP]
Arti pertama menggambarkan apa yang disebut. metode otentikasi. Yaitu, urutan tindakan apa yang harus dilakukan perangkat dan titik akses agar titik akses dapat digunakan dan bagaimana mengenkripsi payload. Pada saat penulisan posting ini, opsi yang paling umum adalah WPA dan WPA2, di mana masing-masing perangkat terhubung secara langsung atau melalui apa yang disebut. Server RADIUS (WPA-Enterprice) menyediakan kata sandi melalui saluran terenkripsi. Kemungkinan besar, titik akses di rumah Anda menyediakan koneksi sesuai dengan skema ini. Perbedaan antara versi kedua dan versi pertama adalah ia memiliki sandi yang lebih kuat: AES versus TKIP yang tidak aman. WPA3, yang lebih kompleks dan canggih, juga diperkenalkan secara bertahap. Secara teoritis, mungkin ada opsi dengan solusi perusahaan CCKM (Cisco Centralized Key Management), tapi saya belum pernah menemukannya.
Jalur akses mungkin telah dikonfigurasi untuk mengautentikasi berdasarkan alamat MAC. Atau, jika titik akses menyediakan data menggunakan algoritma WEP yang sudah ketinggalan zaman, maka sebenarnya tidak ada otentikasi (kunci rahasia di sini adalah kunci enkripsi). Kami mengklasifikasikan opsi tersebut sebagai LAINNYA.
Ada juga metode yang populer di wi-fi publik dengan Captive Portal Detection yang tersembunyi - permintaan otentikasi melalui browser. Titik akses tersebut tampak terbuka bagi pemindai (dilihat dari sudut pandang koneksi fisik). Oleh karena itu, kami mengklasifikasikannya sebagai TERBUKA.
Nilai kedua dapat dilambangkan sebagai algoritma manajemen kunci. Ini adalah parameter metode autentikasi yang dijelaskan di atas. Berbicara tentang bagaimana tepatnya kunci enkripsi dipertukarkan. Mari pertimbangkan opsi yang memungkinkan. EAP - digunakan dalam WPA-Enterprice yang disebutkan, menggunakan database untuk memverifikasi data otentikasi yang dimasukkan. SAE - digunakan pada WPA3 tingkat lanjut, lebih tahan terhadap kekerasan. PSK - opsi paling umum, melibatkan memasukkan kata sandi dan mengirimkannya dalam bentuk terenkripsi. IEEE8021X - menurut standar internasional (berbeda dengan yang didukung oleh keluarga WPA). OWE (Enkripsi Nirkabel Oportunistik) merupakan perpanjangan dari standar IEEE 802.11 untuk titik yang kami klasifikasikan sebagai TERBUKA. OWE memastikan keamanan data yang dikirimkan melalui jaringan tidak aman dengan mengenkripsinya. Sebuah opsi juga dimungkinkan ketika tidak ada kunci akses, sebut saja opsi ini TIDAK ADA.
Parameter ketiga adalah yang disebut. skema enkripsi — bagaimana tepatnya sandi digunakan untuk melindungi data yang dikirimkan. Mari kita daftar pilihannya. WEP - menggunakan stream cipher RC4, kunci rahasianya adalah kunci enkripsi, yang dianggap tidak dapat diterima di dunia kriptografi modern. TKIP - digunakan di WPA, CKIP - di WPA2. TKIP+CKIP - dapat ditentukan dalam titik berkemampuan WPA dan WPA2 untuk kompatibilitas mundur.
Alih-alih tiga elemen, Anda dapat menemukan tanda WEP yang sepi:
[WEP]
Seperti yang telah kita bahas di atas, ini cukup untuk tidak menentukan algoritma untuk menggunakan kunci, yang tidak ada, dan metode enkripsi, yang sama secara default.
Sekarang perhatikan braket ini:
[ESS]
Itu Mode pengoperasian Wi-Fi или Topologi jaringan Wi-Fi. Anda mungkin menemukan mode BSS (Basic Service Set) - ketika ada satu titik akses yang melaluinya perangkat yang terhubung berkomunikasi. Dapat ditemukan di jaringan lokal. Biasanya, titik akses diperlukan untuk menghubungkan perangkat dari jaringan lokal yang berbeda, sehingga merupakan bagian dari Extended Service Sets - ESS. Jenis IBSS (Independent Basic Service Sets) menunjukkan bahwa perangkat merupakan bagian dari jaringan Peer-to-Peer.
Anda juga mungkin melihat bendera WPS:
[WPS]
WPS (Wi-Fi Protected Setup) adalah protokol untuk inisialisasi semi-otomatis jaringan Wi-Fi. Untuk menginisialisasi, pengguna memasukkan kata sandi 8 karakter atau menekan tombol di router. Jika jalur akses Anda adalah tipe pertama dan kotak centang ini muncul di sebelah nama jalur akses Anda, Anda sangat disarankan untuk membuka panel admin dan menonaktifkan akses WPS. Faktanya, seringkali PIN 8 digit dapat diketahui berdasarkan alamat MAC, atau dapat diurutkan dalam waktu yang dapat diperkirakan, yang dapat dimanfaatkan oleh seseorang yang tidak jujur.
6. Membuat model dan fungsi parsing
Berdasarkan apa yang kami temukan di atas, kami akan menjelaskan apa yang terjadi menggunakan kelas 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.
}
Sekarang mari kita tulis sebuah fungsi yang akan mengurai bidang kemampuan:
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. Lihat hasilnya
Saya akan memindai jaringan dan menunjukkan apa yang saya temukan. Tampil hasil output sederhana melalui Log.d:
Capability of Home-Home [WPA2-PSK-CCMP][ESS][WPS]
...
capabilities=[Capability(authScheme=WPA2, keyManagementAlgorithm=PSK, cipherMethod=CCMP)], topologyMode=ESS, availableWps=true
Masalah menghubungkan ke jaringan dari kode aplikasi masih belum terselesaikan. Saya hanya akan mengatakan bahwa untuk membaca kata sandi yang disimpan dari OS perangkat seluler, Anda memerlukan hak root dan kemauan untuk mengobrak-abrik sistem file untuk membaca wpa_supplicant.conf. Jika logika aplikasi mengharuskan memasukkan kata sandi dari luar, koneksi dapat dilakukan melalui kelas .
Terima kasih untuk tambahan yang berharga.
Jika menurut Anda ada yang perlu ditambahkan atau diperbaiki, tulis di kolom komentar :)
Sumber: www.habr.com
