วันหนึ่งฉันต้องสแกนเครือข่าย Wi-Fi จากแอปพลิเคชัน Android และรับข้อมูลโดยละเอียดเกี่ยวกับจุดเข้าใช้งาน
ที่นี่เราต้องเผชิญกับความยากลำบากหลายประการ:
บทความนี้อธิบายวิธีรับข้อมูลที่ครอบคลุมเกี่ยวกับสภาพแวดล้อม Wi-Fi จากโค้ด Android ที่ไม่มี 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"/>
และในโค้ดที่มีลิงก์ไปยังกิจกรรมปัจจุบัน:
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 — Service Set Identifier คือชื่อของเครือข่าย
BSSID – ตัวระบุชุดบริการพื้นฐาน – ที่อยู่ MAC ของอะแดปเตอร์เครือข่าย (จุด Wi-Fi)
ระดับ — ตัวบ่งชี้ความแรงของสัญญาณที่ได้รับ [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 (การจัดการคีย์แบบรวมศูนย์ของ Cisco) แต่ฉันไม่เคยเจอมันเลย
จุดเชื่อมต่ออาจถูกกำหนดค่าให้ตรวจสอบสิทธิ์ด้วยที่อยู่ MAC หรือหากจุดเข้าใช้งานให้ข้อมูลโดยใช้อัลกอริธึม WEP ที่ล้าสมัย ก็แสดงว่าไม่มีการตรวจสอบสิทธิ์จริง ๆ (คีย์ลับในที่นี้คือคีย์เข้ารหัส) เราจัดประเภทตัวเลือกเช่น OTHER
นอกจากนี้ยังมีวิธีการที่ได้รับความนิยมใน Wi-Fi สาธารณะพร้อม Captive Portal Detection ที่ซ่อนอยู่ - คำขอตรวจสอบสิทธิ์ผ่านเบราว์เซอร์ จุดเชื่อมต่อดังกล่าวปรากฏต่อเครื่องสแกนว่าเปิดอยู่ (ซึ่งมาจากมุมมองของการเชื่อมต่อทางกายภาพ) ดังนั้นเราจึงจัดประเภทเป็น 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 (ชุดบริการพื้นฐานอิสระ) ระบุว่าอุปกรณ์เป็นส่วนหนึ่งของเครือข่าย Peer-to-Peer
คุณอาจเห็นธง WPS:
[WPS]
WPS (การตั้งค่าการป้องกัน Wi-Fi) เป็นโปรโตคอลสำหรับการเริ่มต้นเครือข่าย Wi-Fi แบบกึ่งอัตโนมัติ ในการเริ่มต้น ผู้ใช้จะต้องป้อนรหัสผ่าน 8 ตัวอักษรหรือกดปุ่มบนเราเตอร์ หากจุดเข้าใช้งานของคุณเป็นประเภทแรกและช่องทำเครื่องหมายนี้ปรากฏถัดจากชื่อจุดเข้าใช้งานของคุณ ขอแนะนำอย่างยิ่งให้ไปที่แผงผู้ดูแลระบบและปิดใช้งานการเข้าถึง WPS ความจริงก็คือบ่อยครั้งที่ PIN 8 หลักสามารถค้นหาได้จากที่อยู่ 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 หากตรรกะของแอปพลิเคชันจำเป็นต้องป้อนรหัสผ่านจากภายนอก การเชื่อมต่อสามารถทำได้ผ่านคลาส
ขอบคุณ
หากคุณคิดว่าจำเป็นต้องเพิ่มหรือแก้ไขบางสิ่ง โปรดเขียนความคิดเห็นไว้ :)
ที่มา: will.com