101

Looking to find the best way to prevent / detect GPS spoofing on Android. Any suggestions on how this is accomplished, and what can be done to stop it? I am guessing the user has to turn on mock locations to spoof GPS, if this is done, then they can spoof GPS?

I guess I would need to just detect if Mock Locations are enabled? Any other suggestions?

Chrispix
  • 17,941
  • 20
  • 62
  • 70
  • 2
    I think he's asking about the Location Spoofing function available in the DDMS view in Eclipse. – Shawn Walton Jul 30 '11 at 00:54
  • 2
    I have a location based game which I don't want people cheating on, so I wand to block spoofing, my understanding is it can happen one of two ways.. Having mock locations enabled, and building a custom image that does low level spoofing and disregards the spoofing setting in the settings app. Trying to find the Settings.System Provider for MockLocations, or looking to see if it gets enabled (with a listener in the middle of the app). – Chrispix Jul 30 '11 at 01:46

10 Answers10

145

I have done some investigation and sharing my results here,this may be useful for others.

First, we can check whether MockSetting option is turned ON

public static boolean isMockSettingsON(Context context) {
    // returns true if mock location enabled, false if not enabled.
    if (Settings.Secure.getString(context.getContentResolver(),
                                Settings.Secure.ALLOW_MOCK_LOCATION).equals("0"))
        return false;
    else
        return true;
}

Second, we can check whether are there other apps in the device, which are using android.permission.ACCESS_MOCK_LOCATION (Location Spoofing Apps)

public static boolean areThereMockPermissionApps(Context context) {
    int count = 0;

    PackageManager pm = context.getPackageManager();
    List<ApplicationInfo> packages =
        pm.getInstalledApplications(PackageManager.GET_META_DATA);

    for (ApplicationInfo applicationInfo : packages) {
        try {
            PackageInfo packageInfo = pm.getPackageInfo(applicationInfo.packageName,
                                                        PackageManager.GET_PERMISSIONS);

            // Get Permissions
            String[] requestedPermissions = packageInfo.requestedPermissions;

            if (requestedPermissions != null) {
                for (int i = 0; i < requestedPermissions.length; i++) {
                    if (requestedPermissions[i]
                        .equals("android.permission.ACCESS_MOCK_LOCATION")
                        && !applicationInfo.packageName.equals(context.getPackageName())) {
                        count++;
                    }
                }
            }
        } catch (NameNotFoundException e) {
            Log.e("Got exception " , e.getMessage());
        }
    }

    if (count > 0)
        return true;
    return false;
}

If both above methods, first and second are true, then there are good chances that location may be spoofed or fake.

Now, spoofing can be avoided by using Location Manager's API.

We can remove the test provider before requesting the location updates from both the providers (Network and GPS)

LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE);

try {
    Log.d(TAG ,"Removing Test providers")
    lm.removeTestProvider(LocationManager.GPS_PROVIDER);
} catch (IllegalArgumentException error) {
    Log.d(TAG,"Got exception in removing test  provider");
}

lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, locationListener);

I have seen that removeTestProvider(~) works very well over Jelly Bean and onwards version. This API appeared to be unreliable till Ice Cream Sandwich.

Flutter Update: Use Geolocator and check Position object's isMocked property.

Faizan Mubasher
  • 4,427
  • 11
  • 45
  • 81
Jambaaz
  • 2,788
  • 1
  • 19
  • 30
  • Very interesting observations. Especially last one +1 for sharing this. – ar-g Mar 12 '15 at 08:37
  • 2
    Note about the removeTestProvider method. If you allow the Location Manager to work in background the user can go to mock app and restart mocking location. Your location manager will then start receiving mock locations until you call removeTestProvider again. – Timur_C Mar 17 '15 at 17:35
  • 4
    Also your app must have `android.permission.ACCESS_MOCK_LOCATION` permission for `removeTestProvider` to work, which I think is the biggest disadvantage. – Timur_C Mar 17 '15 at 17:41
  • 22
    thanks for the answer! just a point: in Android 6.0 ALLOW_MOCK_LOCATION is deprecated. And actually there's no checkbox for mock location as well. One can check if location is fake or not right from location object: **location.isFromMockProvider()** – Silwester Jan 20 '16 at 13:51
  • In some samsung devices Settings.Secure.getString(context.getContentResolver(),Settings.Secure.ALLOW_MOCK_LOCATION) returns "1" even if mock locations are not set – ajan May 08 '16 at 10:32
  • @Timur_C ACCESS_MOCK_LOCATION is deprecated, how you using removeTestProvider method now ? – blackkara May 12 '16 at 12:34
  • 5
    @Blackkara I didn't use it after all. I used a customized combination of `isMockSettingsON()`, `Location.isFromMockProvider()` and `areThereMockPermissionApps()` with a black list of apps. There are a lot of preinstalled system apps with `ACCESS_MOCK_LOCATION` permission, for example on HTC and Samsung devices. A whitelist of all legitimate apps would be better but a black list of most popular location spoofing apps worked well in my case. And I also checked if the device was rooted. – Timur_C May 12 '16 at 23:11
  • @Timur_C I asked a [question](http://stackoverflow.com/questions/37175039/how-somewechat-apps-ignore-fake-location-then-detect-real) which refers indirectly this answer and also your above comment. To understand my question, i needed to use removeTestProvider method but i'm out of luck. Because it needs permission and the permission is deprecated. – blackkara May 12 '16 at 23:44
  • @Mistaken Android 6.0 onward, this will always return '1 ' :Settings.Secure.getString(context.getContentResolver(),Settings.Secure.ALLOW_MOC‌​K_LOCATION) For Android 6.0 you can use something like this: AppOpsManager opsManager = (AppOpsManager) LockTimer.getSystemService(Context.APP_OPS_SERVICE); isMockLocation = (opsManager.checkOp(AppOpsManager.OPSTR_MOCK_LOCATION, android.os.Process.myUid(), BuildConfig.APPLICATION_ID)== AppOpsManager.MODE_ALLOWED); – LoveForDroid May 31 '16 at 15:32
  • Some of you tried to handle this fake locations from apps like Fake GPS Go? i tried with a NMEA listener too, but isn't works! also the method isFromMockProvider() doesnt works wit this app, any idea on how to handle this, i am reading a lot of post for a possible solution – Bryan Acuña Núñez Jan 19 '17 at 21:45
  • @Timur_C How do you find the whitelist of the legitimate apps. On OnePlus 5, a weather app is showing the access to MOCK_LOCATION. How do you do it? – Aman Verma Dec 23 '18 at 21:21
  • As I mentioned in my previous comment, I gave up on a whitelist and instead used a blacklist of popular location spoofing apps found on Play Store. – Timur_C Dec 24 '18 at 12:37
  • @Timur_C It would be great if you could wrap your solution into an open source library and give options for extending the blacklist and whitelist – Mohamed Haseel Oct 20 '20 at 09:25
53

Since API 18, the object Location has the method .isFromMockProvider() so you can filter out fake locations.

If you want to support versions before 18, it is possible to use something like this:

boolean isMock = false;
if (android.os.Build.VERSION.SDK_INT >= 18) {
    isMock = location.isFromMockProvider();
} else {
    isMock = !Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION).equals("0");
}
Fernando
  • 1,263
  • 10
  • 11
  • I'm fairly sure your second expression is backwards (return true when it should return false). I think it should be: `isMock = !Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION).equals("0");` – charles-allen Jul 18 '16 at 18:48
  • 3
    You're welcome. Thanks for posting a more modern answer! Really this is the correct answer as of today. – charles-allen Jul 20 '16 at 22:56
  • 1
    How we can do it without "location" object for SDK above 18? – Ajit Sharma Jan 19 '17 at 08:11
34

It seems that the only way to do this is to prevent Location Spoofing preventing MockLocations. The down side is there are some users who use Bluetooth GPS devices to get a better signal, they won't be able to use the app as they are required to use the mock locations.

To do this, I did the following :

// returns true if mock location enabled, false if not enabled.
if (Settings.Secure.getString(getContentResolver(),
       Settings.Secure.ALLOW_MOCK_LOCATION).equals("0")) 
       return false; 
       else return true;
Chrispix
  • 17,941
  • 20
  • 62
  • 70
  • 4
    This is not fool proof though. Users on a non-rooted device can still set a mock location, w/ a time in the future, then disable mock locations, and the mock location is still active. Even worse, they can call the mock location the same provider name as Network/Gps and it apparently pulls from that.. – Chrispix Jan 15 '12 at 04:10
  • 3
    Furthermore, [Fake GPS](https://play.google.com/store/apps/details?id=com.lexa.fakegps) doesn't require the mock location setting on rooted devices. – Paul Lammertsma Mar 07 '12 at 13:21
  • Could always check to verify that the fake.gps app is not installed :) – Chrispix Mar 12 '12 at 19:38
  • 12
    You can use `return !x` instead of `if(x) return false; else return true`. – CodesInChaos Aug 12 '14 at 13:10
  • In fact, Fake location will change mock location setting even on rooted devices. – PageNotFound Aug 19 '14 at 13:02
  • How we can do above Mock location enable check for Marshmallow OS? – Ajit Sharma Jan 19 '17 at 08:09
  • I'm using o this code and I'm pretty sure mock location is enable and an app use of mock location, but this code always return true. why? – Nima.S-H Dec 13 '18 at 08:40
27

Stumbled upon this thread a couple years later. In 2016, most Android devices will have API level >= 18 and should thus rely on Location.isFromMockProvider() as pointed out by Fernando.

I extensively experimented with fake/mock locations on different Android devices and distros. Unfortunately .isFromMockProvider() is not 100% reliable. Every once in a while, a fake location will not be labeled as mock. This seems to be due to some erroneous internal fusion logic in the Google Location API.

I wrote a detailed blog post about this, if you want to learn more. To summarize, if you subscribe to location updates from the Location API, then switch on a fake GPS app and print the result of each Location.toString() to the console, you will see something like this:

enter image description here

Notice how, in the stream of location updates, one location has the same coordinates as the others, but is not flagged as a mock and has a much poorer location accuracy.

To remedy this problem, I wrote a utility class that will reliably suppress Mock locations across all modern Android versions (API level 15 and up):

LocationAssistant - Hassle-free location updates on Android

Basically, it "distrusts" non-mock locations that are within 1km of the last known mock location and also labels them as a mock. It does this until a significant number of non-mock locations have arrived. The LocationAssistant can not only reject mock locations, but also unburdens you from most of the hassle of setting up and subscribing to location updates.

To receive only real location updates (i.e. suppress mocks), use it as follows:

public class MyActivity extends Activity implements LocationAssistant.Listener {

    private LocationAssistant assistant;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // You can specify a different accuracy and interval here.
        // The last parameter (allowMockLocations) must be 'false' to suppress mock locations.  
        assistant = new LocationAssistant(this, this, LocationAssistant.Accuracy.HIGH, 5000, false);
    }

    @Override
    protected void onResume() {
        super.onResume();
        assistant.start();
    }

    @Override
    protected void onPause() {
        assistant.stop();
        super.onPause();
    }

    @Override
    public void onNewLocationAvailable(Location location) {
        // No mock locations arriving here
    }

    ...
}

onNewLocationAvailable() will now only be invoked with real location info. There are some more listener methods you need to implement, but in the context of your question (how to prevent GPS spoofing) this is basically it.

Of course, with a rooted OS you can still find ways of spoofing location info that are impossible for normal apps to detect.

André
  • 4,417
  • 4
  • 29
  • 56
KlaasNotFound
  • 1,035
  • 10
  • 15
  • You should briefly summarise the linked blog post (where does isFromMockProvider fail). – charles-allen Jul 18 '16 at 18:52
  • @CodeConfident - thanks for the remark! Not sure what I should add though. The second paragraph of my answer _is_ the summary of the blog post. **.isFromMockProvider** fails sporadically and unpredictably. In the article I just describe in more detail the steps I took to discover and remedy this. – KlaasNotFound Jul 20 '16 at 22:52
  • Well I was forced to jump to your article to understand which to me feels like it runs against the intention of SO. My best suggestion would be: (1) insert your pic that shows the dodgy location (not flagged as a mock) and (2) quickly note your logic for eliminating them (ignore within 1km of a mock) – charles-allen Jul 20 '16 at 23:06
  • Ok, gotcha. I think in the context of the OP, the specifics of _why_ **.isFromMockProvider()** is unreliable are not too relevant. But I will attempt to add the details you mentioned for the bigger picture. Thanks for the feedback! – KlaasNotFound Jul 20 '16 at 23:17
  • Thank you! For me this is now much clearer and definitely adds to the discussion. +1 – charles-allen Jul 21 '16 at 00:47
  • 1
    What if user do not have Google Play Service installed? – Yuriy Chernyshov Aug 09 '16 at 17:59
  • Good Job! :) Thanks! – Daksh Gargas Aug 05 '17 at 20:28
7

If you happened to know the general location of cell towers, you could check to see if the current cell tower matches the location given (within an error margin of something large, like 10 or more miles).

For example, if your app unlocks features only if the user is in a specific location (your store, for example), you could check gps as well as cell towers. Currently, no gps spoofing app also spoofs the cell towers, so you could see if someone across the country is simply trying to spoof their way into your special features (I'm thinking of the Disney Mobile Magic app, for one example).

This is how the Llama app manages location by default, since checking cell tower ids are much less battery intensive than gps. It isn't useful for very specific locations, but if home and work are several miles away, it can distinguish between the two general locations very easily.

Of course, this would require the user to have a cell signal at all. And you would have to know all the cell towers ids in the area --on all network providers-- or you would run the risk of a false negative.

Stephen S
  • 356
  • 1
  • 5
  • 16
4

try this code its very simple and usefull

  public boolean isMockLocationEnabled() {
        boolean isMockLocation = false;
        try {
            //if marshmallow
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                AppOpsManager opsManager = (AppOpsManager) getApplicationContext().getSystemService(Context.APP_OPS_SERVICE);
                isMockLocation = (opsManager.checkOp(AppOpsManager.OPSTR_MOCK_LOCATION, android.os.Process.myUid(), BuildConfig.APPLICATION_ID)== AppOpsManager.MODE_ALLOWED);
            } else {
                // in marshmallow this will always return true
                isMockLocation = !android.provider.Settings.Secure.getString(getApplicationContext().getContentResolver(), "mock_location").equals("0");
            }
        } catch (Exception e) {
            return isMockLocation;
        }
        return isMockLocation;
    }
  • This is a much better version of the isMockLocationEnabled method above. – setzamora Mar 15 '18 at 18:17
  • AppOpsManager.checkOp() throws SecurityException If the app has been configured to crash on this op. Like: java.lang.SecurityException: packagename from uid 11151 not allowed to perform MOCK_LOCATION. This method is about to detect "if your app can mock locations". But not "if received locations are mocked". – Sergio Aug 01 '19 at 12:26
  • @Sergio by "if your app can mock locations" you mean if the current app has permission to mock locations, right? – Victor Laerte Jan 23 '20 at 20:10
  • @VictorLaerte I've last a context of a topic :/ Right. But the question was: "how to detect if a received location is mocked or is from a mock provider". Or how to ignore fake locations. – Sergio Jan 31 '20 at 10:20
2

This scrip is working for all version of android and i find it after many search

LocationManager locMan;
    String[] mockProviders = {LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER};

    try {
        locMan = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

        for (String p : mockProviders) {
            if (p.contentEquals(LocationManager.GPS_PROVIDER))
                locMan.addTestProvider(p, false, false, false, false, true, true, true, 1,
                        android.hardware.SensorManager.SENSOR_STATUS_ACCURACY_HIGH);
            else
                locMan.addTestProvider(p, false, false, false, false, true, true, true, 1,
                        android.hardware.SensorManager.SENSOR_STATUS_ACCURACY_LOW);

            locMan.setTestProviderEnabled(p, true);
            locMan.setTestProviderStatus(p, android.location.LocationProvider.AVAILABLE, Bundle.EMPTY,
                    java.lang.System.currentTimeMillis());
        }
    } catch (Exception ignored) {
        // here you should show dialog which is mean the mock location is not enable
    }
Ali
  • 31
  • 4
2

You can add additional check based on cell tower triangulation or Wifi Access Points info using Google Maps Geolocation API

The simplest way to get info about CellTowers

final TelephonyManager telephonyManager = (TelephonyManager) appContext.getSystemService(Context.TELEPHONY_SERVICE);
String networkOperator = telephonyManager.getNetworkOperator();
int mcc = Integer.parseInt(networkOperator.substring(0, 3));
int mnc = Integer.parseInt(networkOperator.substring(3));
String operatorName = telephonyManager.getNetworkOperatorName();
final GsmCellLocation cellLocation = (GsmCellLocation) telephonyManager.getCellLocation();
int cid = cellLocation.getCid();
int lac = cellLocation.getLac();

You can compare your results with site

To get info about Wifi Access Points

final WifiManager mWifiManager = (WifiManager) appContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);

if (mWifiManager != null && mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {

    // register WiFi scan results receiver
    IntentFilter filter = new IntentFilter();
    filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);

    BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                List<ScanResult> results = mWifiManager.getScanResults();//<-result list
            }
        };

        appContext.registerReceiver(broadcastReceiver, filter);

        // start WiFi Scan
        mWifiManager.startScan();
}
yoAlex5
  • 29,217
  • 8
  • 193
  • 205
1

Below approach is working for me getting proper detection of mock location

@Override
public void onLocationChanged (Location location){
    boolean isMockLocation = location.isFromMockProvider();
}
Aditya Nandardhane
  • 915
  • 1
  • 8
  • 22
0

Paste this in your activity/where you want to validate fake/mock gps

  try {
        if (areThereMockPermissionApps(mContext)) {
            Log.e(TAG, " - " + "Yup its use fake gps");
            List<String> mFakeList = new ArrayList<>();
            mFakeList = getListOfFakeLocationAppsInstalled(mContext); // this will return the fake app list
            for (int a = 0; a < mFakeList.size(); a++) { 
                Log.e(TAG, mFakeList.size() + "  - " + "NameList ----- " + mFakeList.get(a));
            }
        } else
            Log.e(TAG, " - " + "Nope its not use fake gps");
  } catch (Exception w) {
        w.printStackTrace();
  }

Here you can get the list of installed fake/mock app in your device.

    private List<String> getListOfFakeLocationAppsInstalled(Context context) {
    List<String> fakeApps = new ArrayList<>();
    try {
        List<String> runningApps = new ArrayList<>();
        final PackageManager pm = getPackageManager();
        List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);

        for (ApplicationInfo packageInfo : packages) {
            runningApps.add(packageInfo.packageName);
        } // the getLaunchIntentForPackage returns an intent that you can use with startActivity()

        for (String app : runningApps) {
            if (!isSystemPackage(context, app) && hasAppPermission(context, app, "android.permission.ACCESS_MOCK_LOCATION")) {
                fakeApps.add(getApplicationName(context, app));
            }
        }
    } catch (Exception w) {
        w.printStackTrace();
    }
    return fakeApps;
}

Paste this method in your Helper/same class

   public static boolean areThereMockPermissionApps(Context context) {
    int count = 0;
    try {
        PackageManager pm = context.getPackageManager();
        List<ApplicationInfo> packages =
                pm.getInstalledApplications(PackageManager.GET_META_DATA);

        for (ApplicationInfo applicationInfo : packages) {
            try {
                PackageInfo packageInfo = pm.getPackageInfo(applicationInfo.packageName,
                        PackageManager.GET_PERMISSIONS);
                // Get Permissions
                String[] requestedPermissions = packageInfo.requestedPermissions;

                if (requestedPermissions != null) {
                    for (int i = 0; i < requestedPermissions.length; i++) {
                        if (requestedPermissions[i]
                                .equals("android.permission.ACCESS_MOCK_LOCATION")
                                && !applicationInfo.packageName.equals(context.getPackageName())) {
                            count++;
                        }
                    }
                }
            } catch (PackageManager.NameNotFoundException e) {
                Log.e("MockDeductionAgilanbu", "Got exception --- " + e.getMessage());
            }
        }
    } catch (Exception w) {
        w.printStackTrace();
    }
    if (count > 0)
        return true;
    return false;
}
Agilanbu
  • 2,747
  • 2
  • 28
  • 33