如果想要對針對WiFi的攻擊進行監測,就需要定期獲取WiFi的執行狀態,例如WiFi的SSID,WiFi強度,是否開放,加密方式等資訊,在Android中通過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) | 更新一個網路連線的資訊 |
通過wifi 硬體的掃描來獲取一些周邊的wifi 熱點的資訊
欄位 | 含義 |
---|---|
BSSID | 接入點的地址,這裡主要是指小範圍幾個無線裝置相連線所獲取的地址,比如說兩臺筆電通過無線網路卡進行連線,雙方的無線網路卡分配的地址 |
SSID | 網路的名字,當我們搜尋一個網路時,就是靠這個來區分每個不同的網路接入點 |
Capabilities | 網路接入的效能,這裡主要是來判斷網路的加密方式等 |
Frequency | 頻率,每一個頻道互動的MHz 數 |
Level | 等級,主要來判斷網路連線的優先數。 |
WiFi連線成功後,可通過WifiInfo類獲取WiFi的一些具體資訊
方法 | 含義 |
---|---|
getBSSID() | 獲取BSSID |
getDetailedStateOf() | 獲取client的連通性 |
getHiddenSSID() | 獲得SSID 是否被隱藏 |
getIpAddress() | 獲取IP 地址 |
getLinkSpeed() | 獲得連線的速度 |
getMacAddress() | 獲得Mac 地址 |
getRssi() | 獲得802.11n 網路的訊號 |
getSSID() | 獲得SSID |
getSupplicanState() 返回詳細client狀態的資訊 |
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 的狀態.
public static int getWifiState(WifiManager manager) {
return manager == null ? WifiManager.WIFI_STATE_UNKNOWN : manager.getWifiState();
}
// 獲取 WifiManager 範例.
public static WifiManager getWifiManager(Context context) {
return context == null ? null : (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
}
// 開啟/關閉 WIFI.
public static boolean setWifiEnabled(WifiManager manager, boolean enabled) {
return manager != null && manager.setWifiEnabled(enabled);
}
// 開始掃描 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 熱點.
public static List<WifiConfiguration> getConfiguredNetworks(WifiManager manager) {
return manager == null ? null : manager.WifiConfiguration();
}
List<WifiConfiguration> configs = wifiManager.getMatchingWifiConfig(scanResult);
// 可以列印一下看具體的情況:
if (configs == null || configs.isEmpty()) return;
for (WifiConfiguration config : configs) {
Log.v(TAG, "config = " + config);
}
public String getWifiBSSID() {
return mWifiInfo.getBSSID();
}
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的速度
public int getConnWifiSpeed(){
return mWifiInfo.getLinkSpeed();
}
// 獲取當前連線wifi的速度單位
public String getConnWifiSpeedUnit(){
return WifiInfo.LINK_SPEED_UNITS;
}
// 獲取當前連線wifi的訊號強度
public int getConnWifiLevel(){
return mWifiManager.calculateSignalLevel(mWifiInfo.getRssi(),5);
}
本來我以為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;
}
Android提供了兩種方式連線WiFi:
封裝後的函數如下
// 使用 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資料儲存下來
事實上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版本的不斷迭代,上述的方法也許在今後的某個時候就不適用了,如果到了這個時候,就去官方檔案裡面去尋找答案吧