禁用/检查模拟位置(防止 gps 欺骗)

寻找在 Android 上防止/检测 GPS 欺骗的最佳方法。对于如何实现这一目标以及如何阻止这一目标,有什么建议吗?我猜测用户必须打开模拟位置来伪造 GPS,如果这样做,那么他们可以伪造 GPS?

我想我只需要检测是否启用了模拟位置? 还有其他建议吗?

179526 次浏览

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;

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.

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.

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");
}

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.

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
}

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

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

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

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

@Override
public void onLocationChanged (Location location){
boolean isMockLocation = location.isFromMockProvider();
}