前言

如果想要对针对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版本的不断迭代,上述的方法也许在今后的某个时候就不适用了,如果到了这个时候,就去官方文档里面去寻找答案吧
https://developer.android.com/docs?hl=zh-cn

参考链接

END

建了一个微信的安全交流群,欢迎添加我微信备注进群,一起来聊天吹水哇,以及一个会发布安全相关内容的公众号,欢迎关注


Android掌控WiFi不完全指南的更多相关文章

  1. Android中的WiFi P2P

    Android中的WiFi P2P可以同意一定范围内的设备通过Wifi直接互连而不必通过热点或互联网. 使用WiFi P2P须要Android API Level >= 14才干够,并且不要忘记 ...

  2. OpenCV On Android环境配置最新&最全指南(Eclipse篇)

    简介 本教程是经过本人多次踩坑,并参考网上众多OpenCV On Android的配置教程总结而来,尽希望能帮助学习移动图像处理的朋友们少走弯路.这也是本人第一次在简书上发布文章,如有不足,希望各位d ...

  3. android 基础控件(EditView、SeekBar等)的属性及使用方法

        android提供了大量的UI控件,本文将介绍TextView.ImageView.Button.EditView.ProgressBar.SeekBar.ScrollView.WebView ...

  4. ACM对时间掌控力和日积月累的习惯的意义

    马云说,要想创业成功,不是要知道现在什么东西最火,而是要清楚的知道十年以后什么东西最火.这就意味着,你对时间掌控力,至少要有十年. 但是仔细回想一下自己的学生时代,自己对时间的把握是怎样的?有些人只能 ...

  5. IQ一个人的智力和对科学知识的理解掌握程度。 EQ对环境和个人情绪的掌控和对团队关系的运作能力。 AQ挫折商 一个人面对困境时减除自己的压力、渡过难关的能力。

    IQ: Intelligence Quotient 智商 一个人的智力和对科学知识的理解掌握程度. EQ: Emotional Quotient 情商 一个人对环境和个人情绪的掌控和对团队关系的运作能 ...

  6. Android 中的WiFi剖析

    Android的WiFi 我们通常看到WiFi的守护进程wpa_supplicant在我们的ps的进程列表中,这个就是我们的wifi守护进程.wpa_supplicant在external/wpa_s ...

  7. Android基本控件之Menus

    在我们的手机中有很多样式的菜单,比如:我们的短信界面,每条短信,我们长按都会出现一个菜单,还有很多的种类.那么现在,我们就来详细的讨论一下安卓中的菜单 Android的控件中就有这么一个,叫做Menu ...

  8. Android:控件布局(相对布局)RelativeLayout

    RelativeLayout是相对布局控件:以控件之间相对位置或相对父容器位置进行排列. 相对布局常用属性: 子类控件相对子类控件:值是另外一个控件的id android:layout_above-- ...

  9. Android:控件布局(线性布局)LinearLayout

    LinearLayout是线性布局控件:要么横向排布,要么竖向排布 决定性属性:必须有的! android:orientation:vertical (垂直方向) .horizontal(水平方向) ...

随机推荐

  1. 正则表达式实战:最新豆瓣top250爬虫超详细教程

    检查网页源代码 首先让我们来检查豆瓣top250的源代码,一切网页爬虫都需要从这里开始.F12打开开发者模式,在元素(element)页面通过Ctrl+F直接搜索你想要爬取的内容,然后就可以开始编写正 ...

  2. 成为 Apache 贡献者,So easy!

    点击上方蓝字关注 Apache DolphinScheduler Apache DolphinScheduler(incubating),简称"DS", 中文名 "海豚调 ...

  3. MyBatis 03 缓存

    简介 什么是缓存 存在内存中的临时数据. 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,转从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题 ...

  4. Java SE 13 新增特性

    Java SE 13 新增特性 作者:Grey 原文地址:Java SE 13 新增特性 源码 源仓库: Github:java_new_features 镜像仓库: GitCode:java_new ...

  5. 根节点选择器和 html 选择器

    CSS 中除了用标签选择器选中<html>标签以外还有一个等价的是:root选择器.CSS 变量是有作用域的,全局变量都可以声明在<html>里. <div class= ...

  6. 给博客添加个充电按钮(仿B站)

    今天我准备吧B站的充电按钮移植到本博客,开始- 上代码: HTML <html> <head> <link href="./space.8.f69f7d6f8f ...

  7. 长篇图解etcd核心应用场景及编码实战

    大家好啊,我是字母哥,今天写一篇关于etcd的文章,其实网上也有很多关于etcd的介绍,我就简明扼要,总结提炼,期望大家通过这一篇文章掌握etcd的核心知识以及编码技能! 本文首先用大白话给大家介绍一 ...

  8. 用Python实现广度优先搜索

    图是一种善于处理关系型数据的数据结构,使用它可以很轻松地表示数据之间是如何关联的 图的实现形式有很多,最简单的方法之一就是用散列表 背景 图有两种经典的遍历方式:广度优先搜索和深度优先搜索.两者是相似 ...

  9. Toast 部分记录丢失问题处理

    在日常数据库运维过程中,我们可能会遇到类似以下的错误.该错误信息是由于部分tuple的 toast 字段丢失,导致数据无法访问.需要通过游标方式,将可以访问的数据备份出来. test=# create ...

  10. 第一行代码Android(第3版).pdf下载

    2020年人民邮电出版社出版的图书 <第一行代码Android(第3版)>是2020年4月人民邮电出版社出版的图书,作者是郭霖. 封面: 内容简介: <第一行代码 Android 第 ...