防止安卓手机连接WiFi网络,除非我的应用程序批准?

19
我想开发一个应用程序,只有经过我的批准才能连接到WiFi网络。我希望能够查询访问点的MAC地址,并将其与对应于SSID的已知地址列表进行比较。该应用程序的目标是保护用户免受意外连接恶意访问点(例如使用pineapple设备的类型)的伤害。

从我的研究中,我不清楚如何实现这个目标。例如如何在wifi网络状态更改时得到通知?这样的问题解释了如何检测连接已经发生,但对于我的用例来说,那已经为时过晚了。

既然没有ConnectivityManagerWifiManager提供能够中断正在进行的连接的监听器方法,我就有一些解决方案的想法:

  • 将自己安装为代理并决定是否允许数据通过。但是,根据Android代理设置是否适用于设备上所有应用程序?的提示(答案是“否”),这似乎不是一个选项。

  • 用自己的东西替换现有的WiFi管理器。但是,我在Android开发者指南中找不到关于替换系统组件的任何信息。因此,我不确定这在未root的手机上是否可行。

  • 将网络密码存储在我的应用程序中,并将密码设置为无意义的值,然后捕获警告连接失败的广播消息(可能类似于WifiManager.WPS_AUTH_FAILURE),并有选择地决定重新连接回该网络。这可能是一种可行的(尽管丑陋的)解决方案,但我是否可以在网络仍处于连接状态时将密码恢复为无意义的值,以确保我们不会悄悄地连接到相同名称的另一个SSID? 我不确定。我想到菠萝设备可能会接受任何密码,从而使此方法无效。

  • 找到一种方式来防止Android自动连接到已知网络(即之前使用过或已存储密码的网络)。然后我可以从我的应用程序管理所有连接/断开连接。然而,在我的手机上手动执行此操作我看不到如何实现,因此我怀疑可以通过编程实现。

有人能建议一种适用于非root手机的方法吗?


3
如果你这么做,将会是 Android 操作系统一个巨大的安全设计缺陷。 - Stephan Branczyk
2
允许一个应用程序控制其他应用程序的WiFi将违反沙盒概念。 - Stephan Branczyk
1
@StephanBranczyk 我正在尝试对WiFi进行全局更改,以影响所有应用程序。例如,编写一个启用或禁用WiFi的应用程序是微不足道的,这会影响所有人。我还可以连接到特定的WiFi网络(例如如何在Android中以编程方式连接到特定的wifi网络?)。那么我想要的有什么不同呢? - Duncan Jones
2
你需要一部已经获取了root权限的手机或者一部已经解锁了引导程序且具备被刷新为允许您替换系统Wi-Fi管理器的Android版本的手机。 - iheanyi
1
@Duncan,我已经成功地中断了正在进行的连接,但在连接完成之前无法获取BSSID。我认为它可能不可用或需要更多的研究。如果你只是对中断感兴趣,请告诉我,我会发布一个答案。 - Yazan
显示剩余13条评论
3个回答

8
您无法在未root设备的情况下实现非常强大的系统。以下是我认为最接近的方法:
  1. 使用getConfiguredNetworks()获取用户设备上当前配置的网络列表
  2. 对于列表中的每个WifiConfiguration,将公共字段BSSID设置为所需的“安全”MAC地址
  3. 调用saveConfiguration()以保存更改

或者,在步骤(2.)中,您可以为每个已配置的网络调用disableNetwork(),并根据BSSID有选择地启用它们。请注意,MAC地址仍然很容易被欺骗。


2
只是为了扮演恶魔的代言人,这里的关键词可能是“没有用户的知情和同意”。如果OP的应用程序只在用户在“我知道会发生什么,请执行”类型的免责声明中打勾后才干扰WiFi功能,那么我认为这应该是可以的。 - RenniePet
@JustinPowell 或许从我最初的问题文本中并不清楚,但用户会安装我的应用程序特别是因为它控制他们的WiFi并阻止他们连接到伪装成已知网络的接入点。这不会违反“危险产品”条款,因为我没有干扰其他基础设施。这也不算“系统干扰”,因为用户选择安装我的应用程序就是为了实现这个目的。 - Duncan Jones
@Duncan 啊,我懂了。所以更像是一种“家长控制”安排?似乎这部分已经由WifiManager处理了,因为用户不必自动连接到热点…只是选择。你如何检测热点是否被标记为恶意的? - JstnPwll
@JustinPowell 我还没有把所有细节都梳理清楚。但我正在阅读Troy Hunt的优秀文章,其中展示了Pineapple设备如何轻松连接到存储的SSID,我想我的应用程序可能会引入一些MAC地址检查作为第一道防线。一旦我考虑得更多,可能会有更多的防御措施。 - Duncan Jones
1
我已经添加了一个备选答案。这可能是你能做的最好的选择。 - JstnPwll
显示剩余4条评论

1
你可以监听wifi的连接状态变化,并在该事件上执行操作以启用或禁用wifi。
private ConnectivityManager connectionManager;
boolean previousConnectivityStatus;
private WifiManager wifiManager;

/* Register Connectivity Receiver */
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
context.registerReceiver(networkBroadcastReceiver, intentFilter);

/* Register Wifi State Listener */
IntentFilter wifiStateIntentFilter = new IntentFilter();
wifiStateIntentFilter.addAction("android.net.wifi.WIFI_STATE_CHANGED");
context.registerReceiver(wifiStateReceiver, wifiStateIntentFilter);

connectionManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

private BroadcastReceiver wifiStateReceiver = new BroadcastReceiver()
{
    @Override
    public void onReceive(Context context, Intent intent)
    {
        Utility.traceM("NetworkController.wifiStateReceiver.new BroadcastReceiver() {...}::onReceive");
        int extraWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
        switch (extraWifiState)
            {
            case WifiManager.WIFI_STATE_DISABLED:
                {
                    Utility.trace("Broadcast Wifi State Disabled");
                    if(isWifiStateEventsEnabled)
                    {
                        EventBus.getDefault().post(new NetworkEvent(NetworkEventType.WIFI_DISABLED));
                    }
                    break;
                }
            case WifiManager.WIFI_STATE_ENABLED:
                {
                    Utility.trace("Broadcast Wifi State Enabled");
                    if(isWifiStateEventsEnabled)
                    {
                        EventBus.getDefault().post(new NetworkEvent(NetworkEventType.WIFI_ENABLED));
                    }
                    break;
                }
            }
    }
};

private BroadcastReceiver networkBroadcastReceiver = new BroadcastReceiver()
{
    @Override
    public void onReceive(Context context, Intent intent)
    {
        Utility.traceM("NetworkController.networkBroadcastReceiver.new BroadcastReceiver() {...}::onReceive");
        boolean connectivityStatus = isInternetConnectivityAvailable();
        if (previousConnectivityStatus != connectivityStatus)
        {
            if (connectivityStatus)
            {
                previousConnectivityStatus = true;
                Utility.trace("Broadcast Internet Available");
                EventBus.getDefault().post(new NetworkEvent(NetworkEventType.INTERNET_CONNECTED));
            }
            else
            {
                previousConnectivityStatus = false;
                Utility.trace("Broadcast Internet Disconnected");
                EventBus.getDefault().post(new NetworkEvent(NetworkEventType.INTERNET_DISCONNECTED));
            }
        }
    }
};

这段代码似乎是在建立连接之后才起作用。我想要防止首次建立连接。我的目标是不向未经批准的无线接入点传输任何数据。 - Duncan Jones
这是您唯一可行的选择,除非设备已经被root。我在我的应用程序中为WiFi和移动网络都实现了此功能:https://play.google.com/store/apps/details?id=com.nu.art.software.childzone - TacB0sS

1

正如您所知,当连接到Wifi时,Wifi管理应用程序会在连接的Wifi名称下显示提示消息,例如正在连接、正在验证、正在获取IP等。

因此,我尝试搜索如何检测连接到Wifi网络的这些阶段,找到了一个答案,它是使用接收器来执行SUPPLICANT_STATE_CHANGED_ACTION实现的。

我尝试添加代码仅用于断开连接,并且成功了,因为Wifi从未连接,图标没有出现在通知栏上,日志不断重复这些步骤,尽管某种程度上它说已连接(在日志中),但实际上设备本身并没有显示任何东西,所以可能只连接了(10毫秒)。

无论如何,以下是我使用的代码:

public class MyNetworkMonitor extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // EXTRA_BSSID
        // SUPPLICANT_STATE_CHANGED_ACTION
        // EXTRA_NEW_STATE

        // Log.i("YAZAN", intent.getAction() + " " +
        // intent.getStringExtra(WifiManager.EXTRA_BSSID));
        // Log.i("YAZAN", intent.getAction() + " "
        // +intent.getStringExtra(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION));
        // Log.i("YAZAN", intent.getAction() + " "
        // +intent.getStringExtra(WifiManager.EXTRA_NEW_STATE));

        //Log.i("YAZAN", intent.getAction() + " " + intent.getStringExtra(WifiManager.EXTRA_BSSID));

        String action  = intent.getAction();
        if(action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)){

             WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
            Log.d("YAZAN", ">>>>SUPPLICANT_STATE_CHANGED_ACTION<<<<<<");
            SupplicantState supl_state=((SupplicantState)intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE));

            switch(supl_state){
            case ASSOCIATED:Log.i("YAZAN", "ASSOCIATED");
                break;
            case ASSOCIATING:
                Log.i("YAZAN", "ASSOCIATING");
                 wifi.disconnect();
                 Log.i("YAZAN", "disconnect()");
                break;
            case AUTHENTICATING:Log.i("YAZAN", "Authenticating...");
            wifi.disconnect();
         Log.i("YAZAN", "disconnect()");
                break;
            case COMPLETED:Log.i("YAZAN", "Connected");
                break;
            case DISCONNECTED:Log.i("YAZAN", "Disconnected");
                break;
            case DORMANT:Log.i("YAZAN", "DORMANT");
            wifi.disconnect();
         Log.i("YAZAN", "disconnect()");
                break;
            case FOUR_WAY_HANDSHAKE:Log.i("YAZAN", "FOUR_WAY_HANDSHAKE");
            wifi.disconnect();
         Log.i("YAZAN", "disconnect()");
                break;
            case GROUP_HANDSHAKE:Log.i("YAZAN", "GROUP_HANDSHAKE");
            wifi.disconnect();
         Log.i("YAZAN", "disconnect()");
                break;
            case INACTIVE:Log.i("YAZAN", "INACTIVE");
                break;
            case INTERFACE_DISABLED:Log.i("YAZAN", "INTERFACE_DISABLED");
                break;
            case INVALID:Log.i("YAZAN", "INVALID");
                break;
            case SCANNING:Log.i("YAZAN", "SCANNING");
                break;
            case UNINITIALIZED:Log.i("YAZAN", "UNINITIALIZED");
                break;
            default:Log.i("YAZAN", "Unknown");
                break;

            }
            int supl_error=intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
            if(supl_error==WifiManager.ERROR_AUTHENTICATING){
                Log.i("YAZAN", "ERROR_AUTHENTICATING!");
            }
        }//if

    }// onReceive()

无论在何处找到wifi.disconnect();,这就是我中断连接的方式。 这里需要做的是获取网络名称或MAC地址,以允许或禁止进程完成。

权限:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>

添加广播接收器:
<receiver android:name=".MyNetworkMonitor" >
            <intent-filter>
                <action android:name="android.net.wifi.supplicant.STATE_CHANGE" />
                <action android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />

                <action android:name="android.net.wifi.STATE_CHANGE" />

            </intent-filter>
        </receiver>

感谢您的提问。

我猜现在已经太晚了,无法获得赏金 :( 如果我的答案是正确的,但它可能对其他人有用 :) 祝好运 - Yazan
1
我点了个赞,谢谢你提供的信息。我把悬赏奖励给了Justin,因为他的答案可能会避免低级巧妙性,就像你的答案展示的那样。但如果我最终采用了你的方法,我承诺会回来,并向你方向投以慷慨的奖励 ;- ) - Duncan Jones

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接