有一天,我需要從 Android 應用程式掃描 Wi-Fi 網路並獲取有關接入點的詳細數據。
在這裡我們不得不面對幾個困難:
本文討論如何從 Android 程式碼中獲取有關 Wi-Fi 環境的全面數據,無需 NDK、hacks,而僅使用 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"/>
在包含目前活動連結的程式碼中:
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 起標示為已棄用,但已關閉。
總共,我們收到了一個物件列表
4.查看ScanResult並理解術語
讓我們來看看這個類別的一些欄位並描述它們的含義:
SSID — 服務集識別碼是網路的名稱
BSSID – 基本服務集識別碼 – 網路介面卡(Wi-Fi 熱點)的 MAC 位址
水平 — 接收訊號強度指示器 [dBm(俄羅斯 dBm)— 分貝,參考功率 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也正在逐步推出。 理論上,企業解決方案CCKM(思科集中金鑰管理)可能有一個選項,但我從未遇到過。
存取點可能已配置為透過 MAC 位址進行身份驗證。 或者,如果存取點使用過時的WEP演算法提供數據,那麼實際上沒有身份驗證(這裡的金鑰是加密金鑰)。 我們將此類選項歸類為「其他」。
還有一種在公共 Wi-Fi 中流行的隱藏強制門戶檢測的方法 - 透過瀏覽器發出身份驗證請求。 這類接入點在掃描器看來是開放的(從實體連接的角度來看)。 因此,我們將它們歸類為 OPEN。
第二個值可以表示為 密鑰管理演算法。 這是上述認證方法的一個參數。 討論加密金鑰的具體交換方式。 讓我們考慮一下可能的選擇。 EAP - 在提到的 WPA-Enterprice 中使用,使用資料庫來驗證輸入的驗證資料。 SAE-採用先進的WPA3,更耐暴力破解。 PSK - 最常見的選項,涉及輸入密碼並以加密形式傳輸。 IEEE8021X - 根據國際標準(與 WPA 系列支援的標準不同)。 OWE(機會無線加密)是 IEEE 802.11 標準的擴展,針對我們歸類為開放的點。 OWE 透過加密來確保透過不安全網路傳輸的資料的安全性。 當沒有存取鍵時,也可以選擇一個選項,我們將此選項稱為「NONE」。
第三個參數就是所謂的。 加密方案 — 密碼究竟如何用來保護傳輸的資料。 讓我們列出選項。 WEP - 使用 RC4 流密碼,秘密金鑰是加密金鑰,這在現代密碼學領域被認為是不可接受的。 TKIP - 用於 WPA,CKIP - 用於 WPA2。 TKIP+CKIP - 可以在支援 WPA 和 WPA2 的點中指定,以實現向後相容性。
您可以找到一個單獨的 WEP 標記,而不是三個元素:
[WEP]
正如我們上面所討論的,這足以不指定使用金鑰的演算法(該演算法不存在)和加密方法(預設情況下相同)。
現在考慮這個括號:
[ESS]
它 Wi-Fi工作模式 或 Wi-Fi 網路拓撲。 您可能會遇到 BSS(基本服務集)模式 - 當連接的裝置透過一個存取點進行通訊時。 可以在本地網路上找到。 通常,需要存取點來連接來自不同本地網路的設備,因此它們是擴展服務集 - ESS 的一部分。 IBSS(獨立基本服務集)類型指示設備是對等網路的一部分。
您也可能會看到 WPS 標誌:
[WPS]
WPS(Wi-Fi 保護設定)是一種用於半自動初始化 Wi-Fi 網路的協定。 若要初始化,使用者可以輸入 8 個字元的密碼或按下路由器上的按鈕。 如果您的存取點是第一種類型,而此核取方塊出現在您的存取點名稱旁邊,強烈建議您前往管理面板並停用 WPS 存取。 事實是,通常可以透過 MAC 位址找到 8 位元 PIN,或者可以在可預見的時間內整理出來,從而被不誠實的人利用。
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