前言

如果想要对针对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. 从 Airflow 到 Apache DolphinScheduler,有赞大数据开发平台的调度系统演进

    点击上方 蓝字关注我们 作者 | 宋哲琦 ✎ 编 者 按 在不久前的 Apache  DolphinScheduler Meetup 2021 上,有赞大数据开发平台负责人 宋哲琦 带来了平台调度系统 ...

  2. jQuery基础入门(二)

    jQuery 效果 显示和隐藏 在 jQuery 中可以使用 hide() 和 show() 方法来隐藏和显示 HTML 元素,以及使用 toggle() 方法能够切换 hide() 和 show() ...

  3. Luogu4391 [BOI2009]Radio Transmission 无线传输 (KMP)

    \(最小循环节\) \(=\) \(lenghth - next[lenghth]\) #include <iostream> #include <cstdio> #inclu ...

  4. redis-hash命令

    一.HDEL key field [field ...] 从 key 指定的哈希集中移除指定的域.在哈希集中不存在的域将被忽略. 如果 key 指定的哈希集不存在,它将被认为是一个空的哈希集,该命令将 ...

  5. redis-string类型命令

    一.APPEND key value 如果 key 已经存在,并且值为字符串,那么这个命令会把 value 追加到原来值(value)的结尾. 如果 key 不存在,那么它将首先创建一个空字符串的ke ...

  6. 刷题记录:LC1997-访问完所有房间的第一天

    LC1997-访问完所有房间的第一天 题意 这里有 n 个房间,从 0 到 n-1 编号. 你每天访问一个房间,第 0 天访问第 0 号房间. 接下来,你访问房间的[次序]将根据下面的[规则]决定: ...

  7. 「JOI 2015 Final」分蛋糕 2

    「JOI 2015 Final」分蛋糕 2 题解 这道题让我想起了新年趣事之红包这道DP题,这道题和那道题推出来之后的做法是一样的. 我们可以定义dp[i][len][1] 表示从第i块逆时针数len ...

  8. Android同屏、摄像头RTMP推送常用的数据接口设计探讨

    前言 好多开发者在调用Android平台RTMP推送或轻量级RTSP服务接口时,采集到的video数据类型多样化,如420sp.I420.yv12.nv21.rgb的,还有的拿到的图像是倒置的,如果开 ...

  9. PostgreSQL 欺骗优化器之扩展统计信息

    一.什么是扩展统计 扩展统计对象, 追踪指定表.外部表或物化视图的数据. 目前支持的种类: 启用n-distinct统计的 ndistinct. 启用功能依赖性统计的dependencies. 启用最 ...

  10. KingbaseES启动数据库失败后如何分析

    关键字: KingbaseES.sys_ctl.启动日志 一.KingbaseES数据库服务启动 1.1 数据库启动机制 1) 数据库通过sys_ctl工具手工启动数据库服务kingbase. 2) ...