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


90

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

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


2
我认为他是在询问Eclipse的DDMS视图中可用的位置欺骗功能。
肖恩·沃尔​​顿

2
我有一个基于位置的游戏,我不想让人们在作弊,所以我想阻止欺骗,我的理解是它可以通过以下两种方式之一发生。并忽略设置应用中的欺骗设置。尝试查找MockLocations的Settings.System提供程序,或查看它是否已启用(在应用程序的中间有一个侦听器)。
Chrispix

Answers:


125

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

首先,我们可以检查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);

我已经看到removeTestProvider(〜)在Jelly Bean及更高版本上工作得很好。在冰淇淋三明治之前,此API似乎并不可靠。


非常有趣的观察。特别是最后一个+1分享。
ar-g

2
关于removeTestProvider方法的注释。如果您允许位置管理器在后台运行,则用户可以转到模拟应用并重新启动模拟位置。然后,您的位置管理器将开始接收模拟位置,直到您再次调用removeTestProvider为止。
Timur_C

4
另外,您的应用还必须具有工作android.permission.ACCESS_MOCK_LOCATION许可removeTestProvider,我认为这是最大的缺点。
Timur_C

18
谢谢你的回答!只是一点:在Android 6.0中,ALLOW_MOCK_LOCATION已被弃用。实际上,也没有用于模拟位置的复选框。可以从位置对象检查位置是否为伪造的:location.isFromMockProvider()
Silwester 2016年

2
@Blackkara我毕竟没用过。我使用了的自定义组合isMockSettingsON()Location.isFromMockProvider()areThereMockPermissionApps()使用了黑名单的应用程序。有很多预先安装的具有ACCESS_MOCK_LOCATION 权限的系统应用程序,例如在HTC和Samsung设备上。将所有合法应用程序列入白名单会更好,但就我而言,最流行的位置欺骗应用程序的黑名单效果很好。我还检查了设备是否已植根。
Timur_C

45

从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");
AjahnCharles

3
别客气。感谢您发布更现代的答案!到目前为止,这确实是正确的答案。
AjahnCharles '16

1
对于18岁以上的SDK,如何在没有“位置”对象的情况下做到这一点?
阿吉特·夏尔马

35

看来,做到这一点的唯一方法是防止位置欺骗防止MockLocations。不利的一面是,有些用户在使用蓝牙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不需要在有根设备上进行模拟位置设置。
Paul Lammertsma'3

总是可以检查以确认没有安装fake.gps应用程序:)
Chrispix 2012年

11
您可以使用return !x代替if(x) return false; else return true
CodesInChaos

实际上,伪造的位置甚至会在有根设备上更改模拟位置设置。
PageNotFound 2014年

25

几年后偶然发现了这个线程。在2016年,大多数Android设备的API级别将大于等于18,因此应依赖Fernando指出的Location.isFromMockProvider()

我在不同的Android设备和发行版上对假/模拟位置进行了广泛的实验。不幸的是.isFromMockProvider()并非100%可靠。每隔一段时间,虚假位置将不会被标记为模拟。这似乎是由于Google Location API中的某些内部融合逻辑所致。

如果您想了解更多信息,我写了一篇详细的博客文章。总而言之,如果您通过Location API订阅了位置更新,然后打开了一个伪造的GPS应用程序并将每个Location.toString()的结果打印到控制台,您将看到类似以下内容:

在此处输入图片说明

请注意,在位置更新流中,一个位置如何与其他位置具有相同的坐标,但是没有标记为模拟位置,并且位置精度更差。

为了解决这个问题,我编写了一个实用程序类,该类可靠地禁止所有现代Android版本(API级别15及更高版本)中的Mock位置:

LocationAssistant-Android上无障碍的位置更新

基本上,它“不信任”距离最后一个已知模拟位置1公里以内的非模拟位置,并将它们标记为模拟位置。它会执行此操作,直到到达大量的非模拟位置为止。该LocationAssistant不仅可以拒绝模拟地点,但也unburdens你最建立和订阅位置更新的麻烦。

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

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欺骗)的上下文中,基本上就是这样。

当然,有了扎根的操作系统,您仍然可以找到欺骗位置信息的方法,而普通应用程序则无法检测到。


您应该简要总结链接的博客文章(isFromMockProvider在哪里失败)。
AjahnCharles

@CodeConfident-感谢您的发言!不知道我应该添加什么。我的答案的第二段博客文章的摘要。.isFromMockProvider偶发且意外地失败。在本文中,我仅更详细地描述了发现和纠正此问题所采取的步骤。
KlaasNotFound '16

好吧,我被迫跳至您的文章,以了解我觉得这与SO的意图背道而驰。我最好的建议是:(1)插入显示躲闪位置的图片(未标记为模拟对象),以及(2)快速记录消除它们的逻辑(忽略模拟对象1公里之内)
AjahnCharles

好,知道了 我认为在OP的上下文中,为什么 .isFromMockProvider()不可靠的细节不太相关。但是,我将尝试添加您为大图所提及的细节。感谢您的反馈!
KlaasNotFound '16

1
如果用户未安装Google Play服务怎么办?
Yuriy Chernyshov

6

如果您偶然知道蜂窝塔的一般位置,则可以检查当前蜂窝塔是否与给定的位置相匹配(误差范围在10英里或10英里以上)。

例如,如果您的应用仅在用户位于特定位置(例如,您的商店)时才解锁功能,则可以检查gps以及手机信号塔。目前,没有gps欺骗应用程序还欺骗手机发射塔,因此您可以看到全国各地是否有人只是在试图将自己的方式欺骗​​到您的特殊功能中(例如,我想到的是Disney Mobile Magic应用程序)。

这是Llama应用程序默认情况下管理位置的方式,因为检查基站信号塔ID的电量比GPS少得多。它对非常特定的位置没有用,但是如果家和工作地点相距几英里,则可以很容易地区分两个位置。

当然,这将要求用户完全具有小区信号。而且您将必须知道该区域中所有网络提供商上的所有手机信号塔ID,否则您将冒着假阴性的风险。


1
谢谢,这是一个很好的主意。我可能需要调查一下。谢谢
Chrispix

3

试试这个代码,它非常简单和有用

  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:uid 11151中的程序包名称不允许执行MOCK_LOCATION。此方法将检测“您的应用程序是否可以模拟位置”。但不是“如果收到的位置被嘲笑”。
塞尔吉奥

@Sergio中的“如果您的应用程序可以模拟位置”,是指当前应用程序是否有权模拟位置,对吗?
维克多·拉尔特

@VictorLaerte我最后讲了一个主题的上下文:/对。但是问题是:“如何检测接收到的位置是模拟的还是来自模拟提供者”。或如何忽略虚假位置。
塞尔吉奥

2

此脚本适用于所有版本的android,经过多次搜索,我发现了它

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

您可以使用Google Maps Geolocation API根据手机发射塔三角测量或Wifi接入点信息添加其他检查

获取有关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();

您可以将结果与网站进行比较

获取有关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();
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.