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

103

寻找在Android上防止/检测GPS欺骗的最佳方法。有关如何实现此目标以及可以采取什么措施来阻止它的任何建议?我猜用户必须打开模拟位置才能欺骗GPS,如果这样做了,那么他们就可以欺骗GPS吗?

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


2
我认为他在询问Eclipse中DDMS视图中可用的位置欺骗功能。 - Shawn Walton
2
我有一个基于位置的游戏,不希望人们作弊,因此我想要阻止欺骗,我的理解是它可以通过两种方式之一发生。启用模拟位置,并构建一个自定义图像,进行低级欺骗并忽略设置应用程序中的欺骗设置。尝试查找MockLocations的Settings.System提供程序,或查看是否启用(使用应用程序中间的侦听器)。 - Chrispix
10个回答

147

我已经进行了一些调查,并在这里分享我的结果,这可能对其他人有用。

首先,我们可以检查MockSetting选项是否已打开。

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

其次,我们可以检查设备中是否有其他应用程序正在使用 android.permission.ACCESS_MOCK_LOCATION(位置欺骗应用程序)。

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;
}
如果以上两种方法都是真的,那么位置可能被欺骗或伪造。现在,我们可以通过使用位置管理器的API来避免欺骗。在从网络和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);

我已经看到,在Jelly Bean及以后的版本中,removeTestProvider(〜)非常有效。 直到Ice Cream Sandwich,该API似乎不可靠。

Flutter更新:使用Geolocator并检查Position对象的isMocked属性。


非常有趣的观察。特别是最后一个加1,感谢分享。 - ar-g
2
关于 removeTestProvider 方法的说明。如果允许位置管理器在后台工作,用户可以进入模拟应用程序并重新启动模拟位置。直到再次调用 removeTestProvider 方法之前,您的位置管理器将继续接收模拟位置。 - Timur_C
4
您的应用程序还必须具有android.permission.ACCESS_MOCK_LOCATION权限,以使removeTestProvider功能正常工作,我认为这是最大的不利因素。 - Timur_C
23
谢谢您的回答!只是想提醒一下:在Android 6.0中,ALLOW_MOCK_LOCATION已经被弃用。实际上,也没有用于模拟位置的复选框。可以通过检查位置对象中的**location.isFromMockProvider()**来验证位置是否为虚假的。 - Silwester
5
@Blackkara 最终我没用它。我使用了一个定制的组合,包括 isMockSettingsON()Location.isFromMockProvider()areThereMockPermissionApps(),以及一份应用黑名单。例如在HTC和三星设备上,有很多预安装的系统应用程序具有 ACCESS_MOCK_LOCATION 权限。白名单上列出所有合法应用程序是更好的选择,但在我的情况下,大多数受欢迎的位置欺骗应用程序的黑名单效果很好。我还检查了设备是否已root。 - Timur_C
显示剩余8条评论

53
自API 18以来,对象Location具有方法.isFromMockProvider(),因此您可以过滤掉虚假位置。
如果要支持18之前的版本,可以使用类似以下内容的东西:
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");
}

我相当确定你的第二个表达式是反过来的(应该返回false而不是true)。我认为它应该是:isMock =!Settings.Secure.getString(context.getContentResolver(),Settings.Secure.ALLOW_MOCK_LOCATION).equals(“0”); - charles-allen
3
不用客气。感谢您发布一篇更现代的答案!事实上,这是目前为止正确的答案。 - charles-allen
1
我们如何在SDK 18以上的情况下不使用“location”对象来实现它? - Ajit Sharma

34
似乎唯一的方法是防止位置欺骗和模拟位置。不过这种做法的缺点是,有些用户会使用蓝牙GPS设备以获得更好的信号,但由于需要使用模拟位置,他们将无法使用该应用程序。
我采取了以下措施:
// 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;

4
这并非绝对可靠。使用未Root的设备的用户仍然可以设置一个带有未来时间的虚假位置,然后禁用虚假位置,而该虚假位置仍然处于活动状态。更糟糕的是,他们可以将虚假位置命名为与网络/GPS相同的提供者名称,这样它似乎就会从那里获取信息。 - Chrispix
3
此外,Fake GPS 不需要在已获取 root 权限的设备上开启模拟位置设置。 - Paul Lammertsma
可以随时检查以验证未安装fake.gps应用程序 :) - Chrispix
13
你可以使用return !x代替if(x) return false; else return true - CodesInChaos
我们如何在Marshmallow操作系统中进行模拟位置启用检查? - Ajit Sharma
显示剩余2条评论

27

几年后我偶然发现了这个帖子。在2016年,大多数Android设备的API级别将>=18,因此应该依赖于Location.isFromMockProvider(),正如Fernando所指出的。

我在不同的Android设备和发行版上广泛实验了虚假/模拟位置。不幸的是,.isFromMockProvider()并不是100%可靠的。不时会出现一次虚假定位未被标记为模拟。这似乎是由于Google位置API中的一些错误的内部融合逻辑所致。

如果您想了解更多信息,我写了一篇详细的博客文章。总之,如果您从位置API订阅位置更新,然后打开一个虚拟GPS应用程序并将每个Location.toString()的结果打印到控制台,则会看到像这样的内容:

enter image description here

请注意,在位置更新流中,有一个位置坐标与其他位置相同,但未被标记为模拟,并且其位置精度要差得多。

为解决这个问题,我编写了一个实用程序类,可以可靠地抑制所有现代Android版本(API级别15及以上)的模拟位置:

LocationAssistant - 无忧无虑地在Android上更新位置

基本上,它会“不信任”上一个已知的模拟位置以内1km的非模拟位置,并将它们标记为模拟位置。直到到达了大量的非模拟位置。 LocationAssistant不仅可以拒绝模拟位置,还可以减轻设置和订阅位置更新的大部分麻烦。

要只接收真实位置更新(即抑制模拟位置),请按以下方式使用:

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()现在只会被调用以获取真实的位置信息。还有一些其他的监听器方法需要实现,但是就你的问题(如何防止GPS欺骗)而言,基本上就这些了。

当然,在rooted的操作系统中,你仍然可以找到一些无法被普通应用程序检测到的欺骗位置信息的方法。


你应该简要概括链接博客文章的内容(isFromMockProvider失败的原因)。 - charles-allen
@CodeConfident - 感谢您的评论!不过我不确定我应该添加什么。我的回答的第二段已经是博客文章的摘要了。**.isFromMockProvider** 会偶尔和不可预测地失败。在文章中,我只是更详细地描述了我采取的措施来发现和解决这个问题。 - KlaasNotFound
我被迫跳转到您的文章才能理解,这对我来说似乎与 SO 的意图相悖。我最好的建议是:(1)插入显示可疑位置的图片(不要标记为模拟),并(2)快速记录消除它们的逻辑(忽略在模拟范围内的 1 公里)。 - charles-allen
1
如果用户没有安装Google Play服务,该怎么办? - Yuriy Chernyshov
干得好! :) 谢谢! - Daksh Gargas
显示剩余2条评论

7
如果您知道基站的大致位置,您可以检查当前的基站是否与给定的位置相符(误差范围可大可小,例如10英里或更多)。
例如,如果您的应用程序仅在用户位于特定位置(例如您的商店)时解锁功能,则可以检查GPS以及基站。目前,没有GPS欺骗应用程序也欺骗基站,因此您可以看到是否有人试图通过欺骗方式进入您的特殊功能(我想到了迪士尼手机魔法应用程序,这是一个例子)。
这是Llama应用程序默认管理位置的方式,因为检查基站ID比GPS更省电。对于非常具体的位置,它并不有用,但是如果家和工作地点相距几英里,它可以很容易地区分两个一般位置。
当然,这需要用户有手机信号。您还需要知道该地区所有网络提供商的所有基站ID,否则您将面临虚假负面的风险。

1
谢谢,这是一个相当不错的想法。我可能需要进一步研究一下。谢谢。 - Chrispix

4

试试这段代码,它非常简单而且有用.

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

这是比上面的isMockLocationEnabled方法更好的版本。 - setzamora
AppOpsManager.checkOp()会抛出SecurityException异常,如果应用程序已被配置为在此操作上崩溃。例如:java.lang.SecurityException: packagename from uid 11151 not allowed to perform MOCK_LOCATION。 该方法旨在检测“您的应用程序是否可以模拟位置”,而不是“接收到的位置是否被模拟”。 - Sergio
@Sergio,“如果您的应用程序可以模拟位置”,您是指当前应用程序是否具有模拟位置的权限,对吗? - Victor Laerte
@VictorLaerte 我丢失了一个话题的上下文 :/ 好吧。但问题是:“如何检测接收到的位置是否是模拟的或来自模拟提供程序”。或者如何忽略虚假位置。 - Sergio

2
您可以使用Google Maps Geolocation API基于基站三角定位或WiFi接入点信息添加额外的检查。
获取有关基站的信息的最简单方法是:
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();

您可以将您的结果与网站进行比较。
获取有关WiFi接入点的信息。
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();
}

2

这个脚本适用于所有版本的安卓系统,我经过多次搜索才找到它。

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
    }

1
以下方法适用于我,可以正确检测模拟位置。
@Override
public void onLocationChanged (Location location){
    boolean isMockLocation = location.isFromMockProvider();
}

0

将此粘贴到您的活动/您想要验证虚假/模拟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();
  }

在这里,您可以获取设备中安装的虚假/模拟应用程序列表。

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

将此方法粘贴到您的 Helper/同一类中

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

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