前言
如果想要对针对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:
封装后的函数如下
// 使用 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版本的不断迭代,上述的方法也许在今后的某个时候就不适用了,如果到了这个时候,就去官方文档里面去寻找答案吧 😄
https://developer.android.com/docs?hl=zh-cn
参考链接
END
公众号,欢迎关注 😃