Android掌控WiFi不完全指南

2022-10-23 06:00:35

前言

如果想要對針對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版本的不斷迭代,上述的方法也許在今後的某個時候就不適用了,如果到了這個時候,就去官方檔案裡面去尋找答案吧