Android 定位方式
GPS定位
定义:全球卫星定位系统,直接和卫星交互,获取设备经纬度
优点:
- 走卫星通信通道,无需打开Wifi或流量就能获得位置信息
- 精确度最高,几米到几十米
缺点:
- 需要手机支持GPS模块(大部分智能机基本都有)
- 耗电量大
- 大部分用户可能关闭GPS
- 定位慢,特别是从GPS模块启动到获取到第一次定位数据,需要的时间较长
- 室内、地下室基本无法使用
var listener: LocationListener? = null
fun gpsLocation(completion: (Boolean, String?, Location?) -> Unit) {
PermissionUtil.guardPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) { success ->
if (success) {
val locationManager = AppUtils.application.applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
listener = object : LocationListener {
override fun onLocationChanged(location: Location?) {
completion(true, null, location)
listener?.let {
locationManager.removeUpdates(it)
listener = null
}
}
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
}
override fun onProviderDisabled(provider: String?) {
}
override fun onProviderEnabled(provider: String?) {
}
}
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0.0f, listener)
} else {
completion(false, "Permission Denied", null)
}
}
}
Network定位
基站定位
方式:
- 三角定位:由于每个基站的位置是固定的,手机通过扫描到附近的三个基站的信号,因其必然在这三个基站的共同覆盖范围内,从而利用电磁波在这三个基站间中转所需要时间,来算出手机所在的准确坐标。但因大部分手机都只能获取到单个基站的ID,所以此方法基本无法实行
- 单基站定位:利用获取最近的基站的信息,其中包括基站ID,lac、mcc、mnc和信号强度,将这些数据发送到Google的定位Web服务里,就能拿到当前所在的位置信息,误差一般在几十米到几百米之内。其中信号强度这个数据很重要
| mcc | mobile country code | 移动电话基站的国家代码,中国460 |
| mnc | mobile network code | 移动电话基站的网络类型:0移动GSM,1联通WCDMA(电信CDMA对应sid),十进制 |
| lac | location area code | 电信对应nid,十进制 |
优点:
- 只要网络通畅即可定位
- 可以在室内使用
缺点:
- 精确度很差,一般有几百米,甚至上千米的误差。误差和基站的信号覆盖半径有关,Wifi覆盖半径大概100米左右,而基站则到千米级别
WIFI定位
定义:根据一个固定的WifiMAC地址,通过收集到的该Wifi热点的位置,然后访问网络上的定位服务以获得经纬度坐标。因为它和基站定位其实都需要使用网络,所以在Android也统称为Network方式。
一说到wifi定位,很多人觉得奇怪,Wifi怎么能知道我的位置呢,Wifi硬件会返回位置吗?其实这些Wifi都不能做到。Wifi定位的原理是,我们在室外的时候,手机能接收到GPS位置信息,也能扫描到Wifi,当手机把GPS位置和Wifi传到后端服务器,Wifi和GPS位置就建立了映射关系,当手机在室内无法接收到GPS时,却能扫描Wifi,手机把Wifi传到服务器查询出对应的GPS位置,然后进行计算可以得到位置结果(经纬度),其中映射和计算是很复杂的过程。
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0.0f, listener)
优点:
- 精度相对GPS差一点,但也能到十几米、几十米的误差
- 低耗电
被动定位
读取其他应用保存的定位信息。当其它应用使用定位更新定位信息后,系统会被保存下来,被动定位就是读取的这些信息。如果系统中已经安装了百度地图、高德地图之类的应用,你只要使用他们定位过后,使用被动定位就可以拿到比较精确的定位信息了。局限性很大,依赖于别的应用。
locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0.0f, listener)
IP定位
定义:根据设备上获得的IP地址,访问网络上的定位服务以获得经纬度坐标
AGPS定位
定义:辅助全球卫星定位系统,结合GSM或GPRS与传统卫星定位,利用基站代送辅助卫星信息,缩减GPS芯片获取卫星信号的延迟时间,受遮盖的室内也能借基站讯号弥补,减轻GPS芯片对卫星的依赖度
优点:
- 在室外等空旷地区,其精度在正常的GPS工作环境下,可达10米左右,堪称目前定位精度最高的一种定位技术
- 首次捕获GPS信号的时间一般仅需几秒,不像GPS的首次捕获时间可能要2~3分钟
缺点:
- 需要在手机内增加GPS接收机模块,并改造手机天线,同时要在移动网络上加建位置服务器、差分GPS基准站等设备。如果要提高该方案在室内等GPS信号屏蔽地区的定位有效性,该方案还提出需要增添类似于EOTD方案中的位测量单元(LMU)。
Android 模拟位置方式
GPS伪造
GPS信号可以通过编程修改GPS芯片驱动,来改变获取到的位置,此方法成本非常昂贵。
GSM伪造
通过修改设备硬件,或者伪造基站来伪造定位,成本也相对较高。并且一般通信都是加密的,也会造成不少困扰。
编译时就修改NLP结果
思路:修改NLP部分源码,重编系统
难度:非常大
运行时修改LocationManager结果
1. 使用Android自带调试Api,模拟定位结果
打开开发者选项,然后在设置中打开允许模拟位置的开关。
要模拟位置时,使用以下代码:
fun mock(providerName: String, location: Location) {
val locationManager = AppUtils.application.applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
locationManager.addTestProvider(providerName, false, true, true, false, true, true, true, android.location.Criteria.POWER_HIGH, android.location.Criteria.ACCURACY_FINE)
locationManager.setTestProviderEnabled(providerName, true)
locationManager.setTestProviderStatus(providerName, LocationProvider.AVAILABLE, null, System.currentTimeMillis())
locationManager.setTestProviderLocation(providerName, location)
}
有一些应用会在定位前检测是否开启了虚拟定位,如果想欺骗他们,需要在虚拟定位之前,把开关打开,定位后立即把开关关上,这样短暂的时间让其他程序基本看不到ALLOW_MOCK_LOCATION的变化。或者是直接Hook是否开启虚拟定位的接口,始终返回False。
private fun restoreMockLocationSettings(restore_value: Int) {
try {
Settings.Secure.putInt(getContentResolver(),
Settings.Secure.ALLOW_MOCK_LOCATION, restore_value)
} catch (e: Exception) {
e.printStackTrace()
}
}
当然,这两种方法都需要Root权限。
2. 使用xposed,对app_process进行注入
xposed号称Android上最强大的神器,它通过替换/system/bin/app_precesss程序控制zygote进程,使得它在系统启动的过程中会加载Xposed framework的一个jar文件即XposedBridge.jar,从而完成对Zygote进程及其创建的Dalvik虚拟机的劫持,并且能够允许开发者独立的替代任何class,例如framework本身,系统UI又或者随意的一个app。
安装xposed框架需要Root权限。
Android 定位反作弊方式
使用removeTestProvider
在定位前先调用removeTestProvider方法,移除虚拟定位。
检测NMEA data
NMEA data是用于通信或接收GPS数据的标准,在真实的GPS定位中,每次取得位置信息,都会回调NmeaListener的onNmeaReceived方法,而虚拟定位则不会。
我们可以通过监听NmeaListener是否改变来判断此次的定位是否是真实的:
locationManager.addNmeaListener(nmeaListener);
检测虚拟位置开关
开发者选项中有模拟位置的选项可选,开发者可以用过模拟位置来伪造地理位置信息。这种方式可以被很多方法检测到:
fun isMockLocationOn(location: Location, context: Context): Boolean {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return location.isFromMockProvider
} else {
var mockLocation = "0"
try {
mockLocation = Settings.Secure.getString(context.contentResolver, Settings.Secure.ALLOW_MOCK_LOCATION)
} catch (e: Exception) {
e.printStackTrace()
}
return mockLocation != "0"
}
}
检测安装的应用中是否有使用了ACCESS_MOCK_LOCATION权限
fun areThereMockPermissionApps(context: Context): Boolean {
var count = 0
val pm = context.packageManager
val packages = pm.getInstalledApplications(PackageManager.GET_META_DATA)
for (applicationInfo in packages) {
try {
val packageInfo = pm.getPackageInfo(applicationInfo.packageName,
PackageManager.GET_PERMISSIONS)
// Get Permissions
val requestedPermissions = packageInfo.requestedPermissions
if (requestedPermissions != null) {
for (i in requestedPermissions.indices) {
if (requestedPermissions[i] == "android.permission.ACCESS_MOCK_LOCATION" && applicationInfo.packageName != context.packageName) {
count++
}
}
}
} catch (e: NameNotFoundException) {
Log.e("Got exception " + e.getMessage())
}
}
return count > 0
}
检测手机是否Root
对于root机型,几乎无法从根源上反作弊,对于出来四年的Ingress也几乎无法避免作弊的问题,目前可知的Ingress对于位置欺骗的反作弊方式有:
- 速度限制, 35 miles per hour 大概是 55km/h
- 交叉验证 SSID,通过匹配数据库中记录的 SSID 来确定位置
而对于这两种方式Ingress官方并没有明确的文档指出,基本通过玩家黑盒测试得出。而对于最新出的Pokemon Go,在全球作弊成风的情况下官方直接禁用了Root机型。可见目前对于Root机型反作弊还没有一个切实有效的方法检测。