Android掌控WiFi不完全指南
前言
如果想要對針對WiFi的攻擊進行監測,就需要定期獲取WiFi的運行狀態,例如WiFi的SSID,WiFi強度,是否開放,加密方式等資訊,在Android中通過WiFiManager來實現
WiFiManager簡介
WiFiManager這個類是Android暴露給開發者使用的一個系統服務管理類,其中包含對WiFi響應的操作函數;其隱藏掉的系統服務類為IWifiService,這個類是google私有的,屬於系統安全級別的API類
我們需要通過WifiManager進行函數操作完成UI,監聽對應的廣播消息,從而實現獲取WiFi資訊的功能
內置方法
方法 | 含義 |
---|---|
addNetwork(WifiConfiguration config) | 通過獲取到的網路的鏈接狀態資訊,來加入網路 |
calculateSignalLevel(int rssi , int numLevels) | 計算訊號的等級 |
compareSignalLevel(int rssiA, int rssiB) | 對照連接A 和連接B |
createWifiLock(int lockType, String tag) | 創建一個wifi 鎖,鎖定當前的wifi 連接 |
disableNetwork(int netId) | 讓一個網路連接失效 |
disconnect() | 斷開連接 |
enableNetwork(int netId, Boolean disableOthers) | 連接一個連接 |
getConfiguredNetworks() | 獲取網路連接的狀態 |
getConnectionInfo() | 獲取當前連接的資訊 |
getDhcpInfo() | 獲取DHCP 的資訊 |
getScanResulats() | 獲取掃描測試的結果 |
getWifiState() | 獲取一個wifi 接入點是否有效 |
isWifiEnabled() | 推斷一個wifi 連接是否有效 |
pingSupplicant() | ping 一個連接。推斷能否連通 |
ressociate() | 即便連接沒有準備好,也要連通 |
reconnect() | 假設連接準備好了,連通 |
removeNetwork() | 移除某一個網路 |
saveConfiguration() | 保留一個配置資訊 |
setWifiEnabled() | 讓一個連接有效 |
startScan() | 開始掃描 |
updateNetwork(WifiConfiguration config) | 更新一個網路連接的資訊 |
其他常用基類
ScanResult
通過wifi 硬體的掃描來獲取一些周邊的wifi 熱點的資訊
欄位 | 含義 |
---|---|
BSSID | 接入點的地址,這裡主要是指小範圍幾個無線設備相連接所獲取的地址,比如說兩台筆記型電腦通過無線網卡進行連接,雙方的無線網卡分配的地址 |
SSID | 網路的名字,當我們搜索一個網路時,就是靠這個來區分每個不同的網路接入點 |
Capabilities | 網路接入的性能,這裡主要是來判斷網路的加密方式等 |
Frequency | 頻率,每一個頻道交互的MHz 數 |
Level | 等級,主要來判斷網路連接的優先數。 |
WifiInfo
WiFi連接成功後,可通過WifiInfo類獲取WiFi的一些具體資訊
方法 | 含義 |
---|---|
getBSSID() | 獲取BSSID |
getDetailedStateOf() | 獲取client的連通性 |
getHiddenSSID() | 獲得SSID 是否被隱藏 |
getIpAddress() | 獲取IP 地址 |
getLinkSpeed() | 獲得連接的速度 |
getMacAddress() | 獲得Mac 地址 |
getRssi() | 獲得802.11n 網路的訊號 |
getSSID() | 獲得SSID |
getSupplicanState() 返回詳細client狀態的資訊 |
wifiConfiguration
WiFi的配置資訊
類名 | 含義 |
---|---|
WifiConfiguration.AuthAlgorthm | 用來判斷加密方法 |
WifiConfiguration.GroupCipher | 獲取使用GroupCipher 的方法來進行加密 |
WifiConfiguration.KeyMgmt | 獲取使用KeyMgmt 進行 |
WifiConfiguration.PairwiseCipher | 獲取使用WPA 方式的加密 |
WifiConfiguration.Protocol | 獲取使用哪一種協議進行加密 |
wifiConfiguration.Status | 獲取當前網路的狀態 |
許可權
app AndroidManifest.xml
申請許可權
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
Android 6.0版本中如果未開啟GPS是無法獲取到掃描列表的,需要動態申請ACCESS_COARSE_LOCATION
// 檢測項目是否被賦予定位許可權
public void checkPermissions(Context context){
if(ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED){//未開啟定位許可權
//開啟定位許可權,200是標識碼
ActivityCompat.requestPermissions((Activity) context,new String[]{Manifest.permission.ACCESS_FINE_LOCATION},200);
}
}
在運行之前調用該函數進行申請即可
牛刀小試
WiFi狀態分類
- 網卡正在關閉 WIFI_STATE_DISABLING WIFI ( 狀態碼:0 )
- 網卡不可用 WIFI_STATE_DISABLED WIFI ( 狀態碼:1 )
- 網卡正在打開 WIFI_STATE_ENABLING WIFI ( 狀態碼:2 )
- 網卡可用 WIFI_STATE_ENABLED WIFI ( 狀態碼:3 )
- 網卡狀態不可知 WIFI_STATE_UNKNOWN WIFI ( 狀態碼:4 )
程式碼中獲取WIFI的狀態
// 獲取 WIFI 的狀態.
public static int getWifiState(WifiManager manager) {
return manager == null ? WifiManager.WIFI_STATE_UNKNOWN : manager.getWifiState();
}
獲取WiFiManager實例
// 獲取 WifiManager 實例.
public static WifiManager getWifiManager(Context context) {
return context == null ? null : (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
}
開啟、關閉WIFI
// 開啟/關閉 WIFI.
public static boolean setWifiEnabled(WifiManager manager, boolean enabled) {
return manager != null && manager.setWifiEnabled(enabled);
}
掃描周圍的WiFi
// 開始掃描 WIFI.
public static void startScanWifi(WifiManager manager) {
if (manager != null) {
manager.startScan();
}
}
獲取掃描結果
// 獲取掃描 WIFI 的熱點:
public static List<ScanResult> getScanResult(WifiManager manager) {
return manager == null ? null : manager.getScanResult();
}
獲取歷史WiFi配置資訊
// 獲取已經保存過的/配置好的 WIFI 熱點.
public static List<WifiConfiguration> getConfiguredNetworks(WifiManager manager) {
return manager == null ? null : manager.WifiConfiguration();
}
獲取對應scanResult的配置資訊
List<WifiConfiguration> configs = wifiManager.getMatchingWifiConfig(scanResult);
// 可以列印一下看具體的情況:
if (configs == null || configs.isEmpty()) return;
for (WifiConfiguration config : configs) {
Log.v(TAG, "config = " + config);
}
獲取WIFI MAC地址
public String getWifiBSSID() {
return mWifiInfo.getBSSID();
}
獲取本機MAC地址
Android M版本之後,通過wifiInfo.getMacAddress()
獲取的MAC地址是一個固定的假地址,值為02:00:00:00:00:00
,在這裡通過getMacAddress
函數獲取真實MAC
// 獲取本機MAC地址
// Android M版本之後,通過wifiInfo.getMacAddress()獲取的MAC地址是一個固定的假地址,值為02:00:00:00:00:00
public String getSelfMac(){
String mac=mWifiInfo==null?"null":mWifiInfo.getMacAddress();
if(TextUtils.equals(mac, "02:00:00:00:00:00")) {
String temp = getMacAddress();
if (!TextUtils.isEmpty(temp)) {
mac = temp;
}
}
return mac;
}
private static String getMacAddress(){
String macAddress = "";
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface iF = interfaces.nextElement();
byte[] addr = iF.getHardwareAddress();
if (addr == null || addr.length == 0) {
continue;
}
StringBuilder buf = new StringBuilder();
for (byte b : addr) {
buf.append(String.format("%02X:", b));
}
if (buf.length() > 0) {
buf.deleteCharAt(buf.length() - 1);
}
String mac = buf.toString();
// WifiMonitorLogger.i("mac", "interfaceName="+iF.getName()+", mac="+mac);
if(TextUtils.equals(iF.getName(), "wlan0")){
return mac;
}
}
} catch (SocketException e) {
e.printStackTrace();
return macAddress;
}
return macAddress;
}
獲取WIFI的網路速度和速度單位
// 獲取當前連接wifi的速度
public int getConnWifiSpeed(){
return mWifiInfo.getLinkSpeed();
}
// 獲取當前連接wifi的速度單位
public String getConnWifiSpeedUnit(){
return WifiInfo.LINK_SPEED_UNITS;
}
獲取當前連接WIFI的訊號強度
// 獲取當前連接wifi的訊號強度
public int getConnWifiLevel(){
return mWifiManager.calculateSignalLevel(mWifiInfo.getRssi(),5);
}
獲取當前連接的WIFI的加密方式
本來我以為wifiinfo裡面應該會有解決方案,但是搜索了一下之後發現 如何在不掃描所有wifi網路的情況下獲取當前wifi連接的加密類型?
看來還是需要遍歷scanresults,但是很顯然SSID容易重複,所以用WIFI BSSID來唯一確定
// 獲取當前WIFI連接的加密方式
// capabilities的格式是 [認證標準+秘鑰管理+加密方案]
public String getConnCap(){
String currentBSSID=mWifiInfo.getBSSID();
for(ScanResult result:scanResultList){
// WifiMonitorLogger.i(currentBSSID+":"+result.BSSID);
if(currentBSSID.equals(result.BSSID)){
return result.capabilities;
}
}
return "null";
}
另外返回的capabilities格式一般為[認證標準+秘鑰管理+加密方案]
,所以看到的時候不用太慌張
可以通過以下方式來判定加密
static final int SECURITY_NONE = 0;
static final int SECURITY_WEP = 1;
static final int SECURITY_PSK = 2;
static final int SECURITY_EAP = 3;
private int getType(ScanResult result) {
if (result == null) {
return SECURITY_NONE;
}
String capbility = result.capabilities;
if (capbility == null || capbility.isEmpty()) {
return SECURITY_NONE;
}
// 如果包含WAP-PSK的話,則為WAP加密方式
if (capbility.contains("WPA-PSK") || capbility.contains("WPA2-PSK")) {
return SECURITY_PSK;
} else if (capbility.contains("WPA2-EAP")) {
return SECURITY_EAP;
} else if (capbility.contains("WEP")) {
return SECURITY_WEP;
} else if (capbility.contains("ESS")) {
// 如果是ESS則沒有密碼
return SECURITY_NONE;
}
return SECURITY_NONE;
}
JAVA程式碼連接WiFi
Android提供了兩種方式連接WiFi:
- 通過配置連接
- 通過networkId連接
封裝後的函數如下
// 使用 WifiConfiguration 連接.
public static void connectByConfig(WifiManager manager, WifiConfiguration config) {
if (manager == null) {
return;
}
try {
Method connect = manager.getClass().getDeclaredMethod("connect", WifiConfiguration.class, Class.forName("android.net.wifi.WifiManager$ActionListener"));
if (connect != null) {
connect.setAccessible(true);
connect.invoke(manager, config, null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 使用 networkId 連接.
public static void connectByNetworkId(WifiManager manager, int networkId) {
if (manager == null) {
return;
}
try {
Method connect = manager.getClass().getDeclaredMethod("connect", int.class, Class.forName("android.net.wifi.WifiManager$ActionListener"));
if (connect != null) {
connect.setAccessible(true);
connect.invoke(manager, networkId, null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
保存網路
// 保存網路.
public static void saveNetworkByConfig(WifiManager manager, WifiConfiguration config) {
if (manager == null) {
return;
}
try {
Method save = manager.getClass().getDeclaredMethod("save", WifiConfiguration.class, Class.forName("android.net.wifi.WifiManager$ActionListener"));
if (save != null) {
save.setAccessible(true);
save.invoke(manager, config, null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
添加網路
// 添加網路.
public static int addNetwork(WifiManager manager, WifiConfiguration config) {
if (manager != null) {
manager.addNetwork(config);
}
}
忘記網路
// 忘記網路.
public static void forgetNetwork(WifiManager manager, int networkId) {
if (manager == null) {
return;
}
try {
Method forget = manager.getClass().getDeclaredMethod("forget", int.class, Class.forName("android.net.wifi.WifiManager$ActionListener"));
if (forget != null) {
forget.setAccessible(true);
forget.invoke(manager, networkId, null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
禁用網路
// 禁用網路.
public static void disableNetwork(WifiManager manager, int netId) {
if (manager == null) {
return;
}
try {
Method disable = manager.getClass().getDeclaredMethod("disable", int.class, Class.forName("android.net.wifi.WifiManager$ActionListener"));
if (disable != null) {
disable.setAccessible(true);
disable.invoke(manager, networkId, null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
斷開連接
// 斷開連接.
public static boolean disconnectNetwork(WifiManager manager) {
return manager != null && manager.disconnect();
}
短暫禁用網路
// 禁用短暫網路.
public static void disableEphemeralNetwork(WifiManager manager, String SSID) {
if (manager == null || TextUtils.isEmpty(SSID))
return;
try {
Method disableEphemeralNetwork = manager.getClass().getDeclaredMethod("disableEphemeralNetwork", String.class);
if (disableEphemeralNetwork != null) {
disableEphemeralNetwork.setAccessible(true);
disableEphemeralNetwork.invoke(manager, SSID);
}
} catch (Exception e) {
e.printStackTrace();
}
}
監控WIFI變化
我們很有可能會有這樣的需求:在WIFI斷開或者連接的時候,將當前的WIFI數據保存下來
事實上Android中WIFI發生變化的時候,會發送廣播,我們只需要監聽系統中發送的WIFI變化的廣播就可以實現相關的功能了
開啟許可權
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
註冊監聽廣播
我們先使用動態註冊網路狀態的監聽廣播
PS:註冊監聽有兩種方式,無論使用哪種註冊方式均需要在AndroidMainest清單文件裡面進行註冊
- 靜態註冊
也就是說在AndroidManifest文件中對BroadcastReceiver進行註冊,通常還會加上action用來過濾;此註冊方式即使退出應用後,仍然能夠收到相應的廣播
- 動態註冊
調用Context中的registerReceiver對廣播進行動態註冊,使用unRegisterReceiver方法對廣播進行取消註冊的操作;故此註冊方式一般都是隨著所在的Activity或者應用銷毀以後,不會再收到該廣播
動態註冊的程式碼如下
@Override
protected void onStart() {
super.onStart();
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
registerReceiver(NetworkReceiver.getInstance(),filter);
}
然後寫具體的NetworkReceiver
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.widget.Toast;
import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
/**
* @author panyi
* @date 2022/8/23
* 廣播接收器 用來監聽WIFI的變化
*/
public class NetworkReceiver extends BroadcastReceiver {
private volatile static NetworkReceiver sInstance;
public NetworkReceiver(){}
public static NetworkReceiver getInstance(){
if (sInstance == null) {
synchronized (NetworkReceiver.class) {
if (sInstance == null) {
sInstance = new NetworkReceiver();
}
}
}
return sInstance;
}
// WIFI連接狀態改變的監聽
@Override
public void onReceive(Context context, Intent intent) {
String action=intent.getAction();
if(action==WifiManager.WIFI_STATE_CHANGED_ACTION){
switch(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WIFI_STATE_UNKNOWN)){
case WIFI_STATE_ENABLED :// WIFI連接
Toast.makeText(context, "WiFi enabled", Toast.LENGTH_SHORT).show();
break;
case WIFI_STATE_DISABLED:// WIFI斷開
Toast.makeText(context, "WiFi disabled", Toast.LENGTH_SHORT).show();
break;
}
}
}
}
繼承BroadcastReceiver
廣播監聽類之後重寫onReceive
方法,根據監聽到的不同內容進行具體需求的修改即可
最後,隨著Android版本的不斷迭代,上述的方法也許在今後的某個時候就不適用了,如果到了這個時候,就去官方文檔裡面去尋找答案吧 😄
//developer.android.com/docs?hl=zh-cn
參考鏈接
- Android wifi 設置相關
- Android WIFI 模組解析(1) – 掘金
- Android進階之路 – WiFi的全面使用 – 掘金
- Android開發–WIFI實現_一葉飄舟的部落格-CSDN部落格
- WifiManager – Android中文版 – API參考文檔
- Android WiFi開發 (一)掃描、連接、資訊_VNanyesheshou的部落格-CSDN部落格_android wifi掃描連接
- Android中wifi管理器WifiManager使用方法_淼森007的部落格-CSDN部落格_android wifimanager
- Android-WiFi開發之 WifiManager
- Android獲取本機WiFi MAC Address之坑
- Android WIFI認證方式_柚子君下的部落格-CSDN部落格_android wifi 認證
- Android WIFI功能——WifiManager
- Android進階之路 – 實時監聽網路狀態 – 掘金
- Android —— WIFI狀態相關的系統廣播_沃克哈德丶的部落格-CSDN部落格_android wifi相關的廣播
- WiFi開發(一)–WiFi開關與狀態監聽
- //www.jianshu.com/p/16d4ff4c4cbe
END
建了一個微信的安全交流群,歡迎添加我微信備註進群
,一起來聊天吹水哇,以及一個會發布安全相關內容的公眾號,歡迎關注 😃