Prevent Android phone from connecting to WiFi network unless my app approves it?
Asked Answered
D

3

19

I want to develop an app that can prevent connection to a WiFi network unless I approve it. I want to be able to query the MAC address of the access point and compare that to a list of known addresses corresponding to SSIDs. The goal of the app is to protect users from accidentally connecting to malicious access points, such as the types that can be produced with pineapple devices.

I'm not clear from my research how I would achieve this goal. Questions such as How to be notified on wifi network status change? explain how to detect the connection has happened, but for my use case that's already too late.

Neither ConnectivityManager nor WifiManager seem to offer methods for adding listeners that could interrupt a connection in progress.

Some thoughts I've had for a solution:

  • Install myself as a proxy and make the decision as to whether to allow data through. However, this doesn't seem to be an option based on Do Android proxy settings apply to all apps on the device? (hint: the answer is "No").

  • Replace the existing WiFi manager with something of my own creation. However, I've really struggled to find any information in the Android developer guides regarding replacing system components. Consequently, I'm not sure this is possible on non-rooted phones.

  • Store the network passwords within my app and set the passwords in the WiFi manager to nonsense values. Then capture a broadcast message that warns of a failed connection (presumably something like WifiManager.WPS_AUTH_FAILURE) and selectively decide to reconnect back to that network. Might be a possible (if ugly) solution, but can I set the password back to a nonsense value while the network is still connected, to ensure we don't quietly connect to another SSID of the same name? I'm not sure. It occurs to me that pineapple devices would probably accept any password, thus rendering this approach void.

  • Find some way to prevent Android automatically connecting to known networks (i.e. networks that have been used before or have a password stored with them). Then I could manage all connections/disconnections from my app. I can't see how to do this manually on my phone, however, so I'm doubtful this is possible programmatically.

Can anyone suggest an approach that would work on a non-rooted phone?

Dewain answered 1/11, 2014 at 7:44 Comment(18)
If you could do this, it would be a massive security design flaw in the Android OS.Lauzon
@StephanBranczyk Which part exactly and why?Dewain
Allowing one app to control the wifi of other apps would violate the concept of sandboxing.Lauzon
@StephanBranczyk I'm trying to making global changes to WiFi, which affect all apps. For instance, it's trivial to write an app that enables or disables the WiFi which affects everyone. I can also connect to a specific WiFi network (e.g. How to connect to a specific wifi network in Android programmatically?). So why is what I want any different?Dewain
You can try creating service that polls current WiFi and if it is connected, disconnectAquavit
@Aquavit That would not be suitable for me as I don't want any data transmitted to unapproved access points.Dewain
Something like what this app does ? play.google.com/store/apps/details?id=com.hogdex.WifiRulerArchduchess
Mostly just curiosity: Do you plan on distributing your app via Google Play, or via other means?Dissatisfaction
@Slartibartfast That seems similar yes. It would be interesting to install this and see if it replaces the default manager. I'm not sure what is possible, hence this question.Dewain
Your best option is to look at the hidden or internal API and see if you can hook connecting to a wifi network. Other than replacing the system wifi manager, that would be the only way you could interrupt actually connecting to a wifi network.Juliennejuliet
@Juliennejuliet "Other than replacing the system wifi manager" > Is this possible on non-rooted phones? As mentioned in the question, I haven't found any information that explains this is possible.Dewain
You'd either need a rooted phone or a phone with an unlocked bootloader that has been flashed with a build of Android that allows you to replace the system wifi manager.Juliennejuliet
@Duncan i have managed to interrupt a connection in progress, but no success getting BSSID before connection is completed. i think it's not available or need more research, if you are interested in just interrupting tell me, i will post an answerCabrilla
Maybe you should have your devices rooted first, and then grant super user permission. But most users won't be comfortable with app which need rooted phones to run.Dichromatism
@HafizhHerdi If I can't achieve this with a non-rooted phone, I won't proceed.Dewain
@Dissatisfaction Sorry for late response, I would be intending to distribute this via normal channels (i.e. Google Play).Dewain
@Duncan so you are not interested in interrupting the connection? at least respond to my comment with 'No'!Cabrilla
@Cabrilla Sorry, somehow I missed that useful comment. I think if you posted that answer, it would be a very helpful start (and certainly the most helpful answer so far). I don't need a code dump, but it would be interested to hear how you've done it. The ScanResult should provide the BSSID, right? If so, I can retrieve that separately.Dewain
H
9

You can't implement a very robust system without rooting the device. Here's the closest you can get, I think:

  1. Use getConfiguredNetworks() to fetch a list of networks currently configured on the user's device
  2. For each WifiConfiguration in the list, set the public field BSSID to the desired "safe" MAC address
  3. Call saveConfiguration() to persist the changes

Alternatively for step (2.), you could call disableNetwork() for each configured network, and selectively enabled them based on the BSSID. Note that MAC addresses can still be spoofed fairly easily.

Holm answered 6/11, 2014 at 14:45 Comment(9)
Just to play devil's advocate, the key phrase here might be "without the user’s knowledge and consent". If the OP's app only messes with the WiFi capabilities after the user has placed a check mark in an "I understand what is going to happen, please do it" type of disclaimer then I'd think it should be OK.Dissatisfaction
Yeah..."Do you want this app to lock you out of your WiFi hotspots?"Holm
I don't know what the OP's app is for. I'll tell a bit about my Android app, just as an example of how apps can be very non-standard. I buy up old cheap Android phones on the local equivalent of eBay and install my app (no Google Play). The phone with my app has become a dedicated wireless alarm device. No SIM card and no other apps. The user can select the location where she (almost always a woman in this context) is holding a meeting. If the client she's having a meeting with starts to become belligerent and threatening she activates the alarm, and security comes to ensure all is OK.Dissatisfaction
@JustinPowell Perhaps it wasn't clear from my original question text, but users would install my app specifically because it controls their WiFi and stops them connecting to access points masquerading as known networks. It wouldn't violate the "Dangerous Products" clause since I'm not interfering with other infrastructure. Nor would this count as "System Interference" since the user has chosen to install my app for exactly this purpose.Dewain
@Duncan Ah, gotcha. So more of a "parental controls" sort of arrangement? It seems like this is partly already handled by the WifiManager, since users don't have to connect to hotspots automatically...just by choice. How are you detecting whether the hotspot is flagged as malicious?Holm
@JustinPowell I haven't ironed out all the details yet. But I was reading Troy Hunt's excellent article about Pineapple devices and it demonstrates how readily a computer (and I presume also a phone) would connect to SSIDs it has stored. My app would probably introduce some MAC address checking as a first defence. Possibly more defences once I've given it more thought.Dewain
@JustinPowell I notice you've left this answer in place - do you still believe it to be true, based on what I've said so far?Dewain
I've added an alternate answer. Probably the best you can do.Holm
@JustinPowell Bingo! I didn't realise BSSID could be set in this manner. Fantastic, thanks, this pretty much solves the problem. I'm almost certainly going to award the bounty here unless someone blows my mind with another answer. +1 for now :-)Dewain
V
1

you can listen to connectivity change of wifi and act on that events to enable disable 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));
            }
        }
    }
};
Varicotomy answered 11/11, 2014 at 4:16 Comment(2)
This code appears to act after a connection has been established. I want to prevent the connection occurring in the first place. My goal is for no data to be transmitted to wireless access points that aren't approved.Dewain
This is the only viable option you have unless the device is rooted, I've done this in my app for both wifi and mobile: play.google.com/store/apps/…Diba
C
1

as you know when connecting to the Wifi the sifi manager app displays a hint message under the Wifi name that is connecting,

like connecting, authenticating, obtaining IP ... etc

so i tried to search how can detect those stages of connecting to a Wifi network i came to an answer showing how is this done, it was done using the a receiver to SUPPLICANT_STATE_CHANGED_ACTION

and i tried to implement it adding the code to just disconnect ... and that was success as the Wifi never got connected, the icon did not appear on the notification bar and the logs keep repeating the steps, though some how it say's connected (at logs) but nothing actually appears on the device itself, so maybe it got connected for like (10 MS)

anyhow , below is the code i used:

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()

where ever you find a wifi.disconnect(); thats how i interrupted the connection. what remains here, is to get the network name or mac address to allow or disallow the process to complete

Permissions:

<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"/>

adding the broadcast receiver:

<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>

thanks

Cabrilla answered 12/11, 2014 at 7:51 Comment(2)
i guess it's too late to get the bounty :( if my answer is correct, but here it's it might be useful for someone else :) good luckCabrilla
A +1 from me, thanks for the info. I awarded the bounty to Justin as his answer may help avoid lower-level cunningness as displayed in your answer. But if I end up using this approach, I promise to return and throw a nice bounty in your direction ;-)Dewain

© 2022 - 2024 — McMap. All rights reserved.