前言
测试位置改变的方式:
- 通过GPX文件修改经纬度
- 直接hook CLLocation修改
通过GPX文件修改经纬度信息的原理:
苹果在Xcode 6、iOS 8.0开始提供了一个为设备模拟GPS位置的调试功能,其原理是通过USB获取设备句柄后开启设备内的服务"com.apple.dt.simulatelocation"再通过固定坐标或GPX文件进行位置模拟。
应用场景: 在开发测试时尽量模拟真实用户的位_置。
相关产品类型:基于地理位置的推荐(消息推送)
I、 例子
修改经纬度并逆地理编码来获取位_置信息
使用系统API获取到经纬度,再使用系统API进行逆地理编码,以获取详细的位置信息。例如,我想要模拟的经纬度为:-122.030237,37.331705,然后获取的详细位置信息为:United States CA Cupertino。
步骤:
- 使用Xcode模拟iOS设备的位置:
通过GPX文件修改 - 通过逆地理编码来获取位置信息;
1.1 准备gpx 文件( “iOS“选项下的”GPX File”)
虚拟坐标的获取之前,先了解下坐标系,以便进行转换
- iOS,原生坐标系为 WGS-84
- 高德以及国内坐标系:GCS-02https://lbs.amap.com/console/show/picker
- 百度的偏移坐标系:BD-09
- 例子
<gpx version="1.1" creator="GMapToGPX 6.4j - http://www.elsewhere.org/GMapToGPX/" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"> <wpt lat="31.255713422688655" lon="121.51010557702513"> <cmt>北外滩</cmt> </gpx>
1.2 gpx文件的使用
在调试App运行起来后,勾选要模拟的位置
- 效果
1.3 通过逆地理编码来获取位置信息;
- iOS平台开发高德SDK:定位AMapServices 及 Web服务 API :地理/逆地理编码geocodes的使用
iOS定位、地理/逆地理编码geocodes的使用、判断目标经纬度是否在大陆
///逆地理编码 @interface AMapReGeocode : AMapSearchObject
常见问题:使用系统API进行逆地理编码国外的经纬度时获取不到位置信息时, 解决方案:地图服务改为Apple在国外的地图服务
1.4 检测方法
simulate location模拟的数据仅模拟longitude和latitude,不涉及altitude、course、speed。
II hook CLLocation
2.1 修改方案
在iOS中获取当前定位位置的框架叫CoreLocation.framework。我们可以通过CLLocation来得到一个经纬度坐标信息,CLLocation对象明面上所携带的信息包括经纬度、海拔、方向、速度、精确度等信息。
/** 1. 修改定_位; hook 原生API,直接替换为自己的即可 */ #import <CaptainHook/CaptainHook.h> #import "WechatPodForm.h" #import <UIKit/UIKit.h> CHDeclareClass(CLLocation); CHOptimizedMethod0(self, CLLocationCoordinate2D, CLLocation, coordinate){ CLLocationCoordinate2D coordinate = CHSuper(0, CLLocation, coordinate); if(pluginConfig.location.longitude || pluginConfig.location.latitude ){ coordinate = pluginConfig.location; } return coordinate; } CHConstructor{ CHLoadLateClass(CLLocation); CHClassHook(0, CLLocation, coordinate); }
CLLocation 头文件
API_AVAILABLE(macos(10.6), ios(2.0)) @interface CLLocation : NSObject <NSCopying, NSSecureCoding> { @private id _internal; } struct CLLocationCoordinate2D { CLLocationDegrees latitude; CLLocationDegrees longitude; }; @property(readonly, nonatomic) CLLocationCoordinate2D coordinate; @property(readonly, nonatomic) CLLocationDistance altitude;//海拔 /* * horizontalAccuracy * * Discussion: * Returns the horizontal accuracy of the location. Negative if the lateral location is invalid. */ @property(readonly, nonatomic) CLLocationAccuracy horizontalAccuracy;//水平精确度 /* * verticalAccuracy * * Discussion: * Returns the vertical accuracy of the location. Negative if the altitude is invalid. */ @property(readonly, nonatomic) CLLocationAccuracy verticalAccuracy;//垂直精确度
最好也hook代理的回调 locationManager:didUpdateLocations:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations API_AVAILABLE(ios(6.0), macos(10.9));
2.2 检测方法
检测是否越狱
III 其他修改和检测方案
更多内容请关注公众号:iOS逆向,查看私密文章部分
3.1 其他修改方案
- 在未越狱的设备上通过电脑和手机进行 USB 连接,电脑通过特殊协议向手机上的DTSimulateLocation 服务发送模拟的坐标数据来实现,目前 Xcode 上内置位置模拟就是借助这个技术来实现的。(爱思助手)
- 在未越狱的设备上通过符合MFI认证的蓝牙设备和手机连接,并通过MFI蓝牙设备向手机发送虚假的数据
3.2 其他检测方案
根据数据的特征综合判断
CLLocation对象中有一个内部的私有数据成员:_internal 这个内部的私有属性是一个内部类CLLocationInternal的实例。而这个内部类CLLocationInternal中有一个结构体CLLocationInfo指针数据成员:fLocation。
struct CLLocationInfo { #if defined(__i386__) || defined(__x86_64__) int padding1; #endif int suitability; //定位的magic数 CLLocationCoordinate2D coordinate; //经纬度 CLLocationAccuracy horizontalAccuracy; //水平精确度 CLLocationDistance altitude; //海拔 CLLocationAccuracy verticalAccuracy; //垂直精确度 #if defined(__i386__) || defined(__x86_64__) double padding2; double padding3; #endif CLLocationSpeed speed; //速度 CLLocationAccuracy speedAccuracy; //速度精确度 CLLocationDirection course; //方向 CLLocationAccuracy courseAccuracy; //方向精确度 double timestamp; //时间戳 int confidence; //置信值? double lifespan; //有效期限? int type; //定位数据来源类型 CLLocationCoordinate2D rawCoordinate; //原始经纬度 CLLocationDirection rawCourse; //原始方向 int floor; //楼层 #if !defined(__i386__) unsigned int integrity; //信息完整度? :75=High 50=Medium 25=Low 0=None int referenceFrame; //参考坐标系:2=ChinaShifted 1=wgs84 0=unknown int rawReferenceFrame; //原始参考坐标系 #endif };
结构体中有一个数据成员type,用来标明当前定位的数据是怎么产生的,具体的取值如下:
| 值 | 所表示的意义 | 备注 |
| 0 | unknown | 应用程序生成的定位数据 |
| 1 | gps | GPS生成的定位数据 |
| 2 | nmea | |
| 3 | accessory | 蓝牙等外部设备模拟生成的数据 |
| 4 | wifi | WIFI定位生成的数据 |
| 5 | skyhook | WIFI定位生成的数据 |
| 6 | cell | 手机基站定位生成的数据 |
| 7 | lac | LAC生成的定位数据 |





