3

iOS 识别虚拟定位调研

 2 years ago
source link: https://mp.weixin.qq.com/s/ZbZ4pFzzyfrQifmLewrxsw
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Swift社区
做最好的 Swift 社区,我们的使命是做一个最专业最权威的 Swift 中文社区,我们的愿景是希望更多的人学习和使用Swift。我们会分享以 Swift 实战、SwiftUI、Swift 基础为核心的技术干货,不忘初心,牢记使命。
54篇原创内容
Official Account

最近业务开发中,有遇到我们的项目 app 定位被篡改的情况,在 android 端表现的尤为明显。为了防止这种黑产使用虚拟定位薅羊毛,iOS 也不得不进行虚拟定位的规避。

在做技术调研后,发现在苹果手机上,单凭一部手机,真正要实现虚拟定位,是比较难实现的,但还是有存在的可能性,公司的一个项目 appbugly 记录反馈用户存在使用越狱苹果手机,这就着实让人这种行为实在有大嫌。

本人和公司伙伴的共同努力下,大致调研了以下使用虚拟定位的情况(使用 Xcode 虚拟定位的方式本文忽略):

第一种:使用越狱手机

一般 app 用户存在使用越狱苹果手机的情况,一般可以推断用户的行为存在薅羊毛的嫌疑(也有 app 被竞品公司做逆向分析的可能),因为买一部越狱的手机比买一部正常的手机有难度,且在系统升级和 appstore 的使用上,均不如正常手机,本人曾经浅浅的接触皮毛知识通过越狱 iPhone5s 进行的 app 逆向。

建议一刀切的方式进行,通过识别手机是否安装了 Cydia.app,如果安装了直接判定为越狱手机,并向后台上报“设备异常”的信息。如果不使用这种方式的方式,请继续看,后面会有其他方式解决。

专业的逆向人员是绝对可以避免 app 开发者对 Cydia 的安装检测的,当然这种情况是 app 在市场上有很大的份量,被竞争对手拿来进行逆向分析,对这种情况,虚拟的识别基本毫无意义。个人建议,直接锁死停掉此手机 app 的接口服务。这里推荐一篇开发者如何识别苹果手机已经越狱[1]的文章。

/// 判断是否是越狱设备
/// - Returns: true 表示设备越狱
func isBrokenDevice() -> Bool {

var isBroken = false

let cydiaPath = "/Applications/Cydia.app"

let aptPath = "/private/var/lib/apt"

if FileManager.default.fileExists(atPath: cydiaPath) {
        isBroken = true
    }

if FileManager.default.fileExists(atPath: aptPath) {
        isBroken = true
    }

return isBroken
}

第二种:使用爱思助手

对于使用虚拟定位的场景,大多应该是司机或对接人员打卡了。而在这种场景下,就可能催生了一批专门以使用虚拟定位进行打卡薅羊毛的黑产。对于苹果手机,目前而言,能够很可以的实现的,当数爱思助手的虚拟定位功能了。

使用步骤: 下载爱思助手 mac 客户端,连接苹果手机,工具箱中点击虚拟定位,即可在地图上选定位,然后点击修改虚拟定位即可实现修改地图的定位信息。

原理: 在未越狱的设备上通过电脑和手机进行 USB 连接,电脑通过特殊协议向手机上的 DTSimulateLocation 服务发送模拟的坐标数据来实现虚假定位,目前 Xcode 上内置位置模拟就是借助这个技术来实现的。(文章来源[2])

一、通过多次记录爱思助手的虚拟定位的数据发现,其虚拟的定位信息的经纬度的高度是为 0 且经纬度的数据位数也是值得考究的。真实定位和虚拟定位数据如下图:

640?wx_fmt=png
640?wx_fmt=png

仔细观察数据,不难发现,如果我们比对获取定位信息的高度,以及对经纬度的 double 位数也进行校验,虚拟定位的黑帽子就会轻易被破了。

那么如果我们比对虚拟定位的高度为 0 时,就认定为虚拟定位,那么就会产生一个疑问,真实海拔就是零的地点,如何解决?这里科普下中国的海拔零度位置,中国水准零点位于青岛市东海中路银海大世界内的“中华人民共和国水准零点”,是国内唯一的水准零点。唯一的水准零点。

同时,因为比对经纬度的 double 位数,发现虚拟定位的位数很明显不对,核对 swiftfloatdouble 的位数精度发现,虚拟定位的经纬度数据只是敷衍的满足 double 精度位数,swiftfloat 有效位数是 7double 的有效位数是 15

当然这个比较的权重是相对高度比较低的,笔者刚刚更新爱思助手版本发现新版本经纬度有更详细,但是还是达不到 double 的有效位数级别。相对于目前的爱思助手的高度比较识别为虚拟定位,已经完全可以做到。

640?wx_fmt=png
if location.altitude == 0.0 {
    print("虚拟定位")
}

//位数作为判定的权重比,如果位数小于12(假定值,目前爱思助手的虚拟定位的此数据的位数是9),判断为虚拟定位,
//危险慎用,但是作为小权重的异常数据记录还是可以的
let longitude = location.coordinate.longitude
let longitudeStr = "\(longitude)".components(separatedBy: ".").last ?? ""

print("经度的有效位数:\(longitudeStr.count)")
if longitudeStr.count < 12 {

print("虚拟定位")
}

二、把定位后的数据的经纬度上传给后台,后台再根据收到的经纬度获取详细的经纬度信息,对司机的除经纬度以外的地理信息进行深度比较,优先比较 altitudehorizontalAccuracyverticalAccuracy 值,根据值是否相等进行权衡后,确定。

(一)通过获取公网 ip,大概再通过接口根据 ip 地址可获取大概的位置,但误差范围有点大。

//获取公网ip地址
var ipAddress: String? {

let ipUrl = URL(string: "https://ipof.in/txt")!
    let ip = try? String.init(contentsOf: ipUrl, encoding: .utf8)

return ip
}

(二)通过 Wi-Fi 热点来读取 app 位置[3]

(三)利用 CLCircularRegion 设定区域中心的指定经纬度和可设定半径范围,进行监听。

代码简略实现:

manager = CLLocationManager()
//设置定位服务管理器代理
manager?.delegate = self
//设置定位模式
manager?.desiredAccuracy = kCLLocationAccuracyBest
//更新距离
manager?.distanceFilter = 100
//发送授权申请
manager?.requestWhenInUseAuthorization()

let latitude = 115.47560123242931
let longitude = 29.9757535600194
let centerCoordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
let locationIDStr = ""
let clRegion = CLCircularRegion(center: centerCoordinate, radius: 100, identifier: locationIDStr)
manager?.startMonitoring(for: clRegion)

代理方法

func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {

}

func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {

}

(四)通过 IBeacon 技术,使用 CoreBluetooth 框架下的 CBPeripheralManager 建立一个蓝牙基站。这种定位直接是端对端的直接定位,省去了 GPS 的卫星和蜂窝数据的基站通信。

代码简略实现:

func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {

for beacon in beacons {
        var proximityStr: String = ""
        switch beacon.proximity {
        case .far:
            proximityStr = "Unknown"
        case .immediate:
            proximityStr = "Immediate"
        case .near:
            proximityStr = "Near"
        case .unknown:
            proximityStr = "Unknown"
        }

var beaconStr = "信号:" + beacon.proximityUUID.uuidString + "major:" + beacon.major.stringValue + "minor:" + beacon.minor.stringValue + "距离:" + beacon.accuracy + "信号:" + "\(Int64(beacon.rssi))" + "接近度:" + proximityStr

print("beacon信息: \(beaconStr)")
    }

}

func locationManager(_ manager: CLLocationManager, rangingBeaconsDidFailFor region: CLBeaconRegion, withError error: Error) {

}

----------------------------------------------------------------------------------

//不能单独创建一个类遵守CBPeripheralManagerDelegate协议,需要先遵守NSObjectProtocol协议,这里直接继承NSObject
class CoreBluetoothManager:NSObject, CBPeripheralManagerDelegate { 

//建立一个蓝牙基站。
    lazy var peripheralManager: CBPeripheralManager =  CBPeripheralManager(delegate: self, queue: DispatchQueue.main, options: nil)

lazy var region: CLBeaconRegion = {

guard let uuid = UUID(uuidString: "xxx") else {
            return CLBeaconRegion()
        }
        let major: CLBeaconMajorValue = 1
        let minor: CLBeaconMajorValue = 1
        let id = "创建的蓝牙基站的名称"
        let region = CLBeaconRegion(proximityUUID: uuid, major: major, minor: minor, identifier: id)
        return region
    }()

func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {

switch peripheral.state {
        case CBManagerState.poweredOn:

if let data = self.region.peripheralData(withMeasuredPower: nil) as? [String : Any] {

self.peripheralManager.startAdvertising(data)
            }

case CBManagerState.poweredOff,
             CBManagerState.resetting,
             CBManagerState.unauthorized,
             CBManagerState.unsupported,
             CBManagerState.unknown:

break
        }
    }

func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {

}

}

四(待完善)、 iOS防黑产虚假定位检测技术 文章的末尾附的解法本人有尝试过,一层一层通过 kvc 读取 CLLocation_internalfLocation,只能读取到到此。再通过 kvc 读取会报以下错误:

Expression can't be run, because there is no JIT compiled function

深入研究,在苹果的官方开发文档上发现了这个解释[4],也有说设置 debug+ 优化策略的,但 iOS 默认 bug 环境就是 -Onone 级别的。其实主要原因貌似因为 JIT 的设置是在开发 mac 客户端的时候,才能在 Signing&CapabilitiesHardened Runtime 中找到。关于 Allow Execution of JIT-compiled Code 的设置(官方文章[5])。最终只能卡到这里,若有大神能通过其他方式读取 CLLocation 的真实定位(这是极其完美的解决方案),还请不吝赐教。

附:

CLLocation 对象私有变量 _internal 实例对象的官方定义[6]:

@interface CLLocationInternal : NSObject {
    struct {
        int suitability;
        struct {
            double latitude;
            double longitude;
        } coordinate;
        double horizontalAccuracy;
        double altitude;
        double verticalAccuracy;
        double speed;
        double speedAccuracy;
        double course;
        double courseAccuracy;
        double timestamp;
        int confidence;
        double lifespan;
        int type;
        struct {
            double latitude;
            double longitude;
        } rawCoordinate;
        double rawCourse;
        int floor;
        unsigned int integrity;
        int referenceFrame;
        int rawReferenceFrame;
    }  fLocation;
    CLLocationMatchInfo * fMatchInfo;
    double  fTrustedTimestamp;
}
@class NSData;

@interface CLLocationMatchInfo : NSObject <NSCopying, NSSecureCoding> {

id _internal;
}
@property (nonatomic,readonly) long long matchQuality;
@property (nonatomic,readonly) CLLocationCoordinate2D matchCoordinate;
@property (nonatomic,readonly) double matchCourse;
@property (nonatomic,readonly) int matchFormOfWay;
@property (nonatomic,readonly) int matchRoadClass;
@property (getter=isMatchShifted,nonatomic,readonly) BOOL matchShifted;
@property (nonatomic,copy,readonly) NSData * matchDataArray;
[1]

用代码判断 iOS 系统是否越狱的方法: https://www.huaweicloud.com/articles/7c6b8027253c4a97196d359840f638d9.html

[2]

iOS 防黑产虚假定位检测技术: https://cloud.tencent.com/developer/article/1800531

[3]

Wifi 定位原理及 iOS Wifi 列表获取: http://www.caojiarun.com/2017/01/iOS_Wifilist/

[4]

Allow Execution of JIT-compiled Code Entitlement: https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-jit

[5]

Hardened Runtime: https://developer.apple.com/documentation/security/hardened_runtime

[6]

_internal 实例对象的官方定义: https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks/CoreLocation.framework/CLLocationInternal.h


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK