如何定位系统定位的链路呢?
目前我们不知道APP的定位是从哪里来的,所以需要先把系统的LocationManager的入口铺满
安卓APP获取自身定位有两个方法
根据gp服务提供的FusedLocationProviderClient(融合定位提供器),常用函数有两个:
1.获取一次当前位置
Java:fusedLocationProviderClient.getLastLocation(),它返回设备最后一次已知的缓存位置,适合"打开页面时定位到当前位置"的场景(如地图初始化)。
2.持续监听位置变化
requestLocationUpdates(LocationRequest, LocationCallback),在 onLocationResult 回调里拿最新坐标:
还有系统原生方案:
框架自带的LocationManager:
所以现在我们可以把LocationManager给铺满了
这运行之后我们就可以拿到两个东西了
5参数重载,并且高德同时向系统注册了两个provider的持续定位,gps和network
高德自己 new 了一条定位线程(b2$e.run → Looper.loopOnce),AmapLocationManager 的 Handler 收到消息(b2$d.handleMessage),转给 GpsLocation(u5.p → u5.K),最终调 LocationManager.requestLocationUpdates("gps", …)。
高德在拿到系统定位后干了什么呢?APP在哪一步介入的?
我们可以找一个数据必然经过的地方,打印调用栈
为什么选location.getlatitude呢?
不管上层怎么混淆、怎么抽象,坐标最终一定是 Location 或其子类的实例,读坐标必走 getter 或字段。getter 可 hook,字段不可 hook —— 所以选 getter。
Hook之后的输出为
到这一步我们就知道了是
系统投递 → u5$c.onLocationChanged → u5.g → AMapLocation.<init>。
现在我们要去找APP入口了,静态分析的时候找到了两条候选的线索
但是Hook的时候根本Hook不到,这是因为梆梆的加固
对于层A:

可以看到都传递到了cv函数中

所有的java2c都经过cv函数,而cv的末尾变参是方法表下标JID,所以
配合静态还原的 18851 条 JID → 方法名 映射,能实时看到 App 的完整执行流。
层B:编译态如何让Hook失效
ART里一次方法调用的路径:
调用方进行方法调用的时候,都是同一个动作:
拿到目标方法的ArtMethod,读取它的entrypoint,跳过去执行
每个 ArtMethod 有一个执行入口字段:
entry_point_from_quick_compiled_code_ 指向什么,决定这次调用怎么执行:
Frida的HOOK做了什么呢?
所以如果方法被JIT编译,并且调用点也被优化过,那么:
所以虽然改了ART里面的字段,但是bl根本不看它了
Hook对APP自身类失效了,因为梆梆java2c下这些方法被AOT/JIT编译,调用走已经生成的机器码,绕过了被替换的ArtMethod
entrypoint
层C
可以通过deoptimize才能hook到app的本身类
什么是deopt呢?
deoptimization 来自 JVM 世界的术语(HotSpot 的 deoptimization),指"从优化后的代码回退到未优化的解释执行状态",同时在回退时保留程序语义(通过 deopt 时构造解释器栈帧,把寄存器/栈上的值搬到解释器帧里)
ART里面的deopt的几种触发:
ArtMethod里面,entrypoint指向哪里,决定它怎么跑:
deopt就是把2放到3里面,deopt的入口是单独的,不能直接改成解释器入口,因为方法可能被其他线程执行,所以ART的做法为
deopt干的事情?
编译态下,调用方机器码可能是这样的
deopt做的就是让第一种形式失效
所以我们可以通过单方法降级让hook生效
现在我们就确定了整个定位的流程
什么是lbs,这不是系统的network provider
lbs = Location Based Service。这是一条完全独立于系统定位的通道 —— 高德自己发 HTTP 请求去查自己的数据库。
这个通道的输入是物理环节的指纹,周围有哪些wifi,哪个基站,输出是服务器数据库里面匹配到的参考点坐标
高德的libs定位:
在 u0.onLocationChanged 里连续 dump:
t一直在变,但是坐标不变,这证明回调是存货的,但是内容不对
为什么我们上面的已经成功了,但是坐标没有改变呢?是不是我们的hook根本没作用?
通过HOOK发现我们的数据成功被高德读取了,而且是反复读取,但是高德在做多源混合,读取了我们的gps数据,但是没有采用,而是选用了网络定位的结果
所以就是我们的gps模式一直被拒绝,所以我们需要先去切断网络定位这个通道,强制选取GPS,再观察GPS定位的错误
先 getEnumConstants()
这是知道了这个app选取的什么定位模式
现在我们需要强制app走gps模式了 App 会在两个地方设模式,都要堵:
最终得到的结果为
err=15
高德拒绝 test provider(errorCode=15)t
estprovider是系统的locationmanager允许测试提供者
一旦注册,所有监听 gps 的 App 收到的不再是真实 GNSS 数据,而是我们用 set-test-provider-location 塞进去的坐标。
但它有个先天限制:产出的每个 Location 都会被盖上 mock 标记。
但是注入后,虽然Location.getLatitude hook 读到了我们设的坐标,但是APP上面显示的距离不动
这说明我们的数据传入了应该传入的地方,但是不被接收
这里有个很重要的点:u0 收到的是 AMapLocation(高德自己的对象),不是原始 Location。所以 err=15 是高德生成的,不是系统给的。
系统的 Location 没有 errorCode 概念(Location 没有 errorCode 字段,它是 AMapLocation 才有的)。
所以这表示系统的location没问题,但是经过高德sdk处理后,报错了,当时传递出去的定位带有mock标志,怀疑是因为带有这个标志不行,于是通过Hook屏幕java mock getter
把 Location.isFromMockProvider()(旧版)和 isMock()(Android 12+,含 LocationManager/Location 的多个重载)的实现直接替换成恒返回 false。
所以证明高德的判定不读取这三个java的getter
然后我们hook setmock函数,发现从没有命中,这证明:
系统给 test provider 的 Location 打 mock 标记,不走 Location.setMock()。
那是如何打的呢?在安卓源码中,
在尝试了其他几个方法后发现从java层根本堵不住
于是考虑了重写对象
因为 AMapLocation 是高德自己生成的对象(err=15 也是它填的)。我们在它交给 App 的瞬间把字段全改掉
在出口篡改
(也可以试试 hook setmockenable,我没试)
内部转成消息投递给 b2:
b2 持有 LocationMode,分派表在静态块
网络通道的开关为一个独立判断
这就是为什么我们上面强制为Device_sensor之后prov=lbs就不行了,因为这里的if判错了
u5 启动时向系统注册
系统回调进 u5$c.onLocationChanged → 转发到 u5.g()。
u5.g
可以发现出口校验比较宽松,根本不查mock标志和satellites
通过上面可以发现有很多定位的渠道,但是高德是多源并存,然后给每个通道评分,谁的分高用谁
那么这个是如何判断谁的分数高呢?先到先用然后本地质量过滤
需要注意的是以下几点:
1.没有全局融合器,GPS结果和LBS结果都各自独立,谁先完成谁先交付
2.竞争不在通道之间,而是每个通道自己的过滤阈值里面
3.上面的案例之所以采用LBS的方案是因为LBS恒定且能通过自身的弱过滤,但是gps被发现了mock被过滤掉了
GPS
LBS
这两条通道都是有结果就交给P
u5.g中有一处规则
配合 f7994j = 240、f7995k = 80 两个常量,做的是运动连续性校验。
这两个常量从Bundle里被覆盖
阈值1: f7994j=240
这是逆地理刷新的距离门限
u5.o()`
作用:移动距离不到240m,就复用上一次的逆地理编码结果
阈值2: f7995k=30m
这是GPS上报门限
u5.t()
这条规则就是GPS定位必须和上一次定位的距离相差80m才会上报,而且有双重限制:
1.时间限制
2.距离限制
(需要注意的是,80和240是高德sdk的默认值,服务器可以更改)
更关键的是 u5 有 f(i10) 这类过滤器,配合 filters/a.java(定位滤波器)对结果做平滑/去噪。
GPS的判断首先会看卫星的数量有几个
这个值有两处作用:
1.参与mock判定
2.写进结果
同时还有其他的地方
b2.w() 在交付前组装质量报告
setInstallHighDangerMockApp(R) 值得注意——高德会检测设备上是否安装了已知的虚拟定位 App(如 Fake Location 之类),结果放进质量报告交给 App。App 可以用它做风控。
双存储:继承系统的Location但是重写全部的getter
这就代表这AmapLocation有自己的经纬度字段,与父类Location的字段并存,原生代码读取哪个取决于引用哪个对象
[招生]科锐逆向工程师培训(2026年11月13日实地,远程教学同时开班, 第57期)