cryflmind@zephterm: ~/notes/virtual-location-of-gps/
READ SESSION
返回笔记
猫猫の技术文章15 分钟阅读

GPS的数据可以被“劫持”喵:借着晚打卡聊聊虚拟定位问题(一)

在一次晚打卡死活打不上后,猫猫萌生了一个想法:既然就连手机GPS都会有误差,那能否继续“放大”这个误差,甚至将GPS定位完全偏离原定位置呢……?

首先前排叠个甲:

猫猫是几乎0基础的移动应用开发者,写这篇博客更多的只是想记录一下这次小开发遇到的问题和解决方案,估摸着也没什么硬核东西,各位看个热闹好了(?)
以及千万不要学猫猫没事总想着干坏事,用学到的技术造福他人才是硬道理。

然后就是,限于可以讲的东西实在太多,为了避免篇幅过长本文应该会分成好几篇来发(?绝对不是因为猫猫想要水一下文章数(点头))


前情概要

这是一个普普通通的晚上,又到了9:50的晚打卡时间了。
猫猫熟练的打开XX校园,打开打卡通知,点击拍照签……嗯?
不是,什么叫不在签到范围,我也没离开学校啊???
再一看:
qq_pic_merged_1792950726407

气笑了.jpg
没招,在努力尝试了大概20分钟,包括但不限于走到楼的另一侧随机roll定位误差等方式后,猫猫果断选择了:

放弃挣扎.jpg

跟安全委员和辅导员都请了个假,缓了口气的猫猫这才在打卡还剩10分钟的时候成功打上了卡。


但是roll随机定位的时候猫猫也忽然意识到了一个问题,GPS定位的信号似乎是有一定误差的,所以 只要运气好猫猫就能凭手速打上卡(实际上中间还真成功roll出来一次只是因为需要填理由被猫猫取消了) 是不是可以利用这种误差,假定引入一个中间人,实现对定位系统的欺骗呢?

于是猫猫启动了AI并让他生成一个实现自动“劫持”GPS请求的APP,并开始尝试了解里面的技术。

免责声明:本文仅供技术交流与学习,严禁模仿!
遵守规定的才是好猫猫哦~

定位

手机是怎么知道自己在哪的呢?

想要了解定位系统,首先肯定是要了解手机了解自己位置的方式:
QQ20261026-130054

  1. 卫星定位(GPS/GNSS,Android里对应GPS_PROVIDER):也就是我们最为著名的定位方式,比如美丽国的GPS啊我国的北斗啊这些东西(统称为GNSS),手机通过接收天上卫星的信号,靠信号传播时间算出距离,4 颗以上就能解出自己的坐标。

GNSS 定位的本质是测距交会:接收机测出信号从卫星飞到自己花了多久(伪距),然后解四元方程(x、y、z + 接收机钟差)。但方程里的“卫星坐标 (xₛ, yₛ, zₛ)”不是现成的——必须先知道每颗卫星此刻在天上的精确位置,这个位置就是拿星历里的轨道参数算出来的。
什么是星历呢?星历(Ephemeris)是每颗卫星自己持续广播的“我的轨道参数表”,这东西就像是公交车的时刻表:3 号车 12:00 从 A 站发车,12:05 经过 B 站……你在公交车站甚至不用盯着车看,只需要拿时刻表 + 当前时间就能算出车此刻在哪。
当然,星历除了等卫星自己播(导航电文仅 50 bps,星历子帧约 30 秒重播一轮,这是冷启动慢的根源),还能走网络提前下载——这就是 A-GPS/A-GNSS(辅助定位)。服务器把未来数小时的预测星历打包成几十 KB 文件,手机经任意可上网的数据连接(蜂窝数据或 WiFi 均可)下载后即可注入 GNSS 芯片之中。
不过,这种定位方式在没有遮挡的时候效果最好,也就是在室外的空旷区域的时候(精度甚至可以达到3~10m),室内的效果就要大打折扣(严格来说几乎都收不到)了。

  1. 网络定位(WiFi + 基站,Android里对应NETWORK_PROVIDER):可能很多人都不知道,其实 WiFi 也是可以用来进行定位的,简单来说就是把附近扫到的 WiFi “编号”(BSSID,可以简单理解为网卡的mac地址,注意和 WiFi 名称的那个SSID区分)和基站编号上传到服务器,然后如果服务器有一张巨型数据库记录了这些数据出现的位置,只需要通过一些计算就能估计出大概的位置了。

那问题来了……这个数据库是哪来的呢?(细思鼻孔.jpg)

首先先来简单介绍一下我们要计算什么,注意到 WIFI 信号也满足距离越远信号越弱,只要参照目标足够多,理论上我们就能计算出来一个大概的位置出来,而且参照目标越多理论上精度也会越精确。(当然,墙体、反射会让这个换算相当粗糙,所以网络定位的精度通常只有几十到几百米,而不是理论上的精确值)

而目前这些数据库的来源主要有3种:采集车扫街,众包,以及运营商与第三方数据。

采集

采集车扫街,最出名的可能就是比如说百度全景那种,你可以在百度全景中看到采集车所处的环境。但采集车采集的数据可能并不只是当前坐标和环境的扫描结果,还有附近的 WiFi 信号和基站,只要采集车记录的对应关系数据足够多,到达的地方足够多,是不是就可以把数据库建起来了?
唯一的问题是,这么搞显然是没把个人隐私放在眼里的(bushi),早些年 Google 还因为扫街时顺带收集了 WiFi 数据吃了官司,所以现在扫街收集这些数据还是收敛了很多的。

而众包和采集车的情况差不多,都是把”我在哪”和”我看到了哪些 AP(网络接入点)“绑在一起,只是采集者从专业车变成了每个人的手机:当你的手机 GPS 定位成功的那一刻,如果 WiFi 开着,系统或定位 SDK 会顺手上传一条类似于:“服务器你好,我在 (纬度, 经度),周围看到了这些 BSSID ,已经得到了ta们的信号强度”。全球几亿台设备就这么日积月累下来,数据库自然而然的就长出来了。这也是今天最主要、更新最快的数据源。而且就个人隐私而言,厂商会把这条写进隐私条款并要求授权,所以在授权范围内收集一般不会出问题;不过嘛,“告知过”并不等于没有争议,这也是这类数据一直被讨论的原因。

最后就是运营商与第三方数据了,对于基站来说运营商肯定是清清楚楚的(毕竟就是他们自己维护的基础设施,基站的 Cell ID 以及对应位置这些数据,运营商自己就有安装记录)。同时为了保持数据的流通性,不同的厂商之间彼此也会进行数据交换,这样数据库就能建起来了。

  1. 融合定位(fused,这个Android中同名的定位源主要是由GMS(也就是谷歌服务)提供的,其自家实现是FusedLocationProvider,但是国内因为没有GMS,所以就比较百花齐放了):看名字其实也能猜出来,大概就是把上面两者融合在一起,实际上我们还会加上加速度计、陀螺仪之类的侦测出来的数据一起算,通过取长补短来得到更精确的数值。

APP:我?问卫星要位置???

是谁在帮助APP获取位置?

显然,直接让APP去问卫星要位置的想法看起来就挺抽象的,而且对移动应用开发者非常不友好(应该没人想要学习卫星数据的接收与处理吧?)
所以,一个更优的想法就是对位置数据进行封装,通过一个统一的接口来获取位置数据。
在Android中,这个东西叫位置管理服务(LocationManagerService, LMS),顾名思义就是给系统里的各种应用和服务统一提供位置数据的系统服务。
当你下拉快捷设置,按下位置开关(很多人习惯叫 GPS 按钮)时,你就成功通过 LocationManager 接口通知了位置管理服务:“喂喂,位置管理服务请注意,位置许可状态已经改变,请检查 Settings.Secure / Global 里的 location 开关来确认许可状态。”
这里需要额外的提一嘴,快捷设置里的位置开关在不同的操作系统下效果还不太一样:

  • 如果你是较新的 Android(12/13+),这个按钮就相当于一个”应用能否使用位置”的权限总闸,关闭之后绝大部分 App 都拿不到位置(返回空),但紧急呼叫定位仍可使用;而硬件层行为则是由厂商来决定。
  • 如果你是 Android 11 甚至更早,这个开关对定位服务 provider 启用状态的影响通常更直接,部分系统/厂商实现下会停用定位源,甚至让 GNSS 硬件进入低功耗或断电状态。不过从 Android 9/10 开始,Google 就在逐步移除对 GPS_PROVIDER 的细粒度用户控制,所以关闭后到底是“应用拿不到位置”还是“provider 被停用”,还需要具体机型具体分析。

总而言之,关闭位置开关最确定的效果是阻断普通应用的位置访问权限;在系统服务层,GNSS provider的启用控制权在近年Android版本中已被逐步收归系统;硬件是否真正断电,取决于AOSP的低功耗建议和厂商的具体实现,不能一概而论。
所以,当你在手机上关掉位置开关的时候,更准确的理解应该是”吊销普通应用的位置许可”,而不是”拔掉 GPS 电源”。值得一提的是,国产 ROM(比如 HyperOS)通常会把这一权限拆得更细一点,比如他们会给WiFi 扫描 蓝牙扫描 AGPS 辅助这些权限设置一些额外的子开关。

焦虑:位置开关很耗电吗?

上面我们提到了功耗的问题,这里不知道大伙儿日常生活中会不会焦虑这么一件事情:每次手机快要没电的时候,总想着要把位置开关给关上,就总觉得位置开关很耗电的样子。
事实果真如此吗?
在 LocationManagerService 接收到变更后,他会首先调整各 provider 的启用状态。
之后呢?

反正没人喊我,睡大觉!

是的,为了省电,GPS芯片(GNSS芯片, 由 GNSS HAL 控制)在设计时采用了这样一套逻辑:

  • 在我们打开定位开关后,provider 此时“可用”,但没人发起请求 → 芯片保持原状态,定位图标不亮。

状态栏那个图标本质是使用中指示器(“有没有活跃请求”),不是“开关是否打开”。它反映的是‘有没有应用在活跃地使用位置’;系统对‘活跃’有自己的判定(例如前台使用、最近若干秒内的访问等),并不严格等于 GNSS 芯片每一毫秒都在出数据。

qq_pic_merged_1793013568768

  • 芯片真正上电,是第一个 App 对 gps(或含 GPS 的 fused)发起 requestLocationUpdates 时,框架调 GNSS HAL 的 start(),HAL 才去唤醒引擎、注入辅助数据、开始搜星。

qq_pic_merged_1793013548690

  • 关掉最后一个定位请求后,系统对 GNSS HAL 执行 stop(),停止本次导航会话;芯片不再持续跟踪与解算,进入厂商定义的低功耗状态。进一步的掉电/深度休眠取决于厂商在 cleanup() 或底层电源管理中的实现。

我们可以用TTFF(首次定位时间)来给启动分个类(表情符号只是助记来着zwz):

  • 🔥热启动(星历仍有效、时间与概略位置可用):几秒内就能出定位
  • 温启动(星历过期但时间/概略位置还在):需要数十秒时间
  • ❄️冷启动(无辅助):30 秒以上,甚至可能有数分钟

其中,星历有效期一般 2~4 小时,所以隔几小时再开定位的话,“搜星”时间将会明显变得更久。
同时,重开秒定位其实不全靠芯片保留状态。框架在 start() 时会主动注入辅助数据——网络时间(NITZ)、基站/WiFi 算出的概略位置、以及 XTRA/PSDS(服务器下载的星历扩展)。哪怕芯片彻底失忆,有这套辅助也能给冷启动“加热”。
于是我们现在可以展示系统定位的时序图了:
时序图

受篇幅限制,我们将在下一篇文章中介绍“虚拟定位”软件的作用原理,先到这啦~
话说真的有人会看到这里喵zwz~

EOF / 记录结束,探索继续。
ZEPHTERM / NOTESUTF-8

搜索笔记 / notes --search

全部笔记 3 篇