前言

如果想要对针对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. 运筹帷幄决胜千里,Python3.10原生协程asyncio工业级真实协程异步消费任务调度实践

    我们一直都相信这样一种说法:协程是比多线程更高效的一种并发工作方式,它完全由程序本身所控制,也就是在用户态执行,协程避免了像线程切换那样产生的上下文切换,在性能方面得到了很大的提升.毫无疑问,这是颠扑 ...

  2. vue中vuex实现持久化的几种方法

    前提:大家都知道vuex真的数据共享是不持久的,例如登录后一刷新,state中存的token就会消失,导致你需要再次进行登录操作 在这给大家列出几种解决方案: 第一种(也是一些项目中常使用的):使用缓 ...

  3. ASP.NET Core依赖注入系统学习教程:关于服务注册使用到的方法

    在.NET Core的依赖注入框架中,服务注册的信息将会被封装成ServiceDescriptor对象,而这些对象都会存储在IServiceCollection接口类型表示的集合中,另外,IServi ...

  4. Luogu3919 【模板】可持久化数组(主席树)

    主席树模板题,注意空间\((n+m) \log(n)\) #include <iostream> #include <cstdio> #include <cstring& ...

  5. HTML创建访问加密代码

    在</head>前面加入即可 普通方式 此方法屏蔽F12查看源码但是屏蔽不了Ctrl+U查看源码 解决方式加密html即可注意!解密比较繁琐切记要记住自己设置的密码 <SCRIPT ...

  6. Filter(过滤器)、ThreadLocal(本地线程)、Listener(监听器)

    Filter(过滤器) Filter过滤器它的作用是:拦截请求,过滤响应. 过滤器链 1)执行的顺序依次是: A B C Demo03 C2 B2 A2 2)如果采取的是注解的方式进行配置,那么过滤器 ...

  7. 区块相隔虽一线,俱在支付同冶熔,Vue3.0+Tornado6前后端分离集成Web3.0之Metamask区块链虚拟三方支付功能

    最近几年区块链技术的使用外延持续扩展,去中心化的节点认证机制可以大幅度改进传统的支付结算模式的经营效率,降低交易者的成本并提高收益.但不能否认的是,区块链技术也存在着极大的风险,所谓身怀利器,杀心自起 ...

  8. 输入a、b、c三个整数,按先大后小的顺序输出a、b和c。注意请使用指针变量的方式进行比较和输出。

    `void swap(int *a,int *b,int c){ if(a < *b){ int temp = *a; //防止temp没有初始化 随机存放地址指向系统工作区间 可以对temp初 ...

  9. 在 C# CLR 中学习 C++ 之了解 extern

    一:背景 在 CLR 源码中有很多的 extern 和 extern "C" 这样的关键词,比如下面这些代码: extern size_t gc_global_mechanisms ...

  10. imread opencv

    ''' Mat cv::imread ( const String & filename, int flags = IMREAD_COLOR ) Python: retval = cv.imr ...