扣丁書屋

iOS虛擬定位原理與預防

iOS 虛擬定位原理與預防

背景

說到虛擬定位,常有印象都是安卓上的分身軟件甚至系統自帶的位置穿越(筆者曾經使用過ZUK Z2系統自帶的位置穿越),會認為iOS上虛擬定位比較困難。筆者沒調研之前也是這么認為,之前已知的虛擬定位是使用Xcode添加GPX文件,編輯經緯度,從而實現虛擬定位。但是這種操作也只有熟悉iOS開發的人才能操作,而且需要mac電腦,故而筆者印象中也是iOS上虛擬定位比較困難。

然而經過調研之后,筆者發現,iOS的虛擬定位沒有那么困難,甚至相比于安卓更簡單。下面就來介紹一下iOS中幾種虛擬定位的方法。

虛擬定位的幾種辦法及原理

筆者調研后,發現iOS上面虛擬定位大致可有4中情況:

  • 使用Xcode通過GPX文件虛擬定位
  • 使用愛思助手中的虛擬定位功能直接虛擬定位
  • 通過外接設備,比如藍牙和手機連接,發送虛擬定位數據來虛擬定位
  • 越獄設備中通過hook定位方法,來虛擬定位

下面就來一個個分析實踐:

使用Xcode通過GPX文件虛擬定位

使用Xcode通過GPX文件虛擬定位,iOS開發一般比較熟悉,操作步驟是:

新創建一個iOS項目,然后添加文件,選擇創建GPX文件

wecom20210804-151237.png

編輯其中內容,把lat、lon改為要模擬的經緯度,如下:


<wpt lat="31.2416" lon="121.333">
         <name>Shanghai</name>
    </wpt>
</gpx>

然后選擇Product -> Scheme -> Edit Scheme,選中Options tab,勾選Allow Location Simulation,然后運行即可虛擬定位

wecom20210804-152202.png

注意:通常情況下,使用Xcode運行虛擬定位,運行停止后,經緯度會恢復成原來的。但是當運行了項目,然后直接拔掉數據線(是運行狀態下拔掉),手機的經緯度就會一直是模擬的經緯度,想要恢復需要重啟手機或者等待2~5天自動恢復。

原理:調用iOS設備中的com.apple.dt.simulatelocation服務,com.apple.dt.simulatelocation服務是蘋果在Xcode 6、iOS 8.0之后提供的為設備模擬GPS位置的調試功能,原理是通過usb獲取設備句柄后開啟設備內的服務("com.apple.dt.simulatelocation"),再通過固定坐標或GPX文件進行位置模擬。

使用愛思助手中的虛擬定位功能直接虛擬定位

這個方法最是簡單,筆者之前不知道有這種方法,下載一個愛思助手,打開,連接手機到電腦,選擇工具箱tab下的虛擬定位

wecom20210804-142320@2x.png

然后輸入要定位的位置搜索,點擊修改定位即可

wecom20210804-142450@2x.png

注意:這種虛擬定位的方法,真的是簡單。就目前而言,筆者嘗試后發現釘釘、企業微信,均沒有對此類方法檢測處理。

原理:通過libimobiledevice的service模塊開啟com.apple.dt.simulatelocation服務,然后實現脫離通過Xcode來模擬定位。libimobiledevice是開源的跨平臺調用iOS協議的庫。

通過外接設備,發送虛擬定位數據來虛擬定位

通過外接設備,發送虛擬定位這個方法筆者之前完全都沒了解到,不得不說,真的是雙擊666,人民智慧無限強大,其中代表是位移精靈。筆者沒有購買外設,所以也沒辦法嘗試,但是可以附上一個視頻,供大家參考:

視頻鏈接:https://haokan.baidu.com/v?pd=wisenatural&vid=17675944846390412165

原理:通過MFi(Made For iOS)認證廠商,可以獲得MFI Accessory Interface Specification文檔,其中提供了很多隱藏功能,包含時間通訊、音樂數據通訊、定位功能等等。其中定位功能的使用只需要照著文檔,按照協議格式發送對應位置數據,即可。

MFI Accessory Interface Specification地址見:https://usermanual.wiki/Document/MFiAccessoryInterfaceSpecificationR18NoRestriction.1631705096.pdf

文檔中搜索Locaiton即可看到定位相關信息,如下:

wecom20210804-165553.png

越獄設備中通過hook定位方法,來虛擬定位

越獄設備虛擬定位,是越獄之后使用具備虛擬定位功能的越獄插件。在上帝模式下,越獄插件可以隨意劫持系統函數。比如:GPS定位管家,能夠管理每個iOS應用的GPS位置。。

原理:越獄后,通過注入庫,hook了CLLocationManager中的定位代理方法,從而篡改正常定位信息。

總結如下:

wecom20210804-171650.png

虛擬定位幾種方法的檢測

作為開發,知道了有哪些虛擬定位的,還不夠,還需要知道怎么這些虛擬定位的方法怎么解決。尤其是OA應用和游戲應用的開發,需要特別注意。下面就來一步步看下:

使用Xcode通過GPX文件虛擬定位的檢測 和 使用愛思助手中的虛擬定位功能直接虛擬定位的檢測

使用Xcode通過GPX虛擬定位和使用愛思助手虛擬定位的,其最終的原理是一樣的,都是通過調用com.apple.simulatelocation服務,從而實現虛擬定位。

筆者統計了一下,網上說的驗證方式大致有兩種:

  • 根據特征值判斷

  • 定位的精度:虛擬定位的經緯度的精度不如真實定位的精度高,所以可以通過定位經緯度的精度來判斷

  • 定位的海拔值和海拔精度:虛擬定位的海拔值為0,海拔垂直精度為-1;所以可以通過這兩個值來判斷

  • 定位回調調用次數:虛擬定位的回調只會調用一次,而真實定位的會多次觸發;所以可以通過觸發次數來判斷

  • 函數響應時間:虛擬定位的響應是立馬返回,而真實的不會;所以可以通過響應時間判斷

  • 根據CLLocation的私有屬性_internal中的type來判斷

上面是筆者總結的網上給出的檢測方案,下面來實踐,看事實是否如上所描述,下面筆者采用的是使用愛思助手虛擬經緯度的方法來模擬。**強烈注意:**使用第三方地圖的定位和系統定位回對下面對方法也有影響?。?!筆者這里吃了很大的虧,好家伙。

根據特征值判斷

  1. 定位的精度 獲取經緯度的精度,不能使用"%f"來直接格式化,因為格式化字符串默認“%f”,默認保留到小數點后第6位,對比不出來差異。代碼如下:
  /// 定位經緯度的精度
  /// @param location 定位Item
  - (void)testLocationAccuracy:(CLLocation *)location {
    NSString *longitudeStr = [NSString stringWithFormat:@"%@", 
           @(location.coordinate.longitude)];
    // NSString *longitudeStr = [[NSDecimalNumber numberWithDouble:
     location.coordinate.longitude] stringValue]; // 這種方法取到的是17位
    NSString *lastPartLongitudeStr = [[longitudeStr
          componentsSeparatedByString:@"."] lastObject];

    NSString *latitudeStr = [NSString stringWithFormat:@"%@", @(location.coordinate.latitude)];
    NSString *lastPartLatitudeStr = [[latitudeStr 
         componentsSeparatedByString:@"."] lastObject];

    NSLog(@"定位精度的 longitude位數:%d, latitude位數:%d", 
     lastPartLongitudeStr.length, lastPartLatitudeStr.length);
  }

使用正常定位時,結果如下:

  定位精度的 longitude位數:13, latitude位數:14

使用愛思助手搜索虹橋火車站地鐵站,自動定位到經緯度是6位,輸入框中最多可輸入小數點后8位,開啟虛擬定位后,結果如下:

  定位精度的 longitude位數:13, latitude位數:14

筆者這里測試了很久,由于小數精度的問題,筆者換了好幾種方式,最終結論是使用此方法無法判斷。雖然愛思助手設置的經緯度有個數限制,但是最終定位出來的經緯度和定位出來的并不相同,且由于小數精度問題,無法準確判斷。故而此方法行不通。 2. 定位的海拔值和海拔精度 通過altitude和verticalAccuracy來判斷,CLLocation的altitude的屬性表示海拔。verticalAccuracy的屬性來判斷海拔的精確度。海拔數值可能會有verticalAccuracy大小的誤差,當verticalAccuracy為負值時,表示不能獲取海拔高度。

代碼如下:

   /// 定位海拔、海拔垂直精度
  /// @param location 定位Item
  - (void)testLocationAltitudeAccuracy:(CLLocation *)location {
      NSLog(@"海拔高度:%f", location.altitude);
      NSLog(@"海拔垂直精度:%f", location.verticalAccuracy);
  }

使用正常定位時,結果如下:

  海拔高度:9.224902
海拔垂直精度:16.078646

使用愛思助手開啟定位后,結果如下:

  海拔高度:0.000000
海拔垂直精度:-1.000000

從上面可以看出,正常定位和模擬定位的海拔和海拔垂直精度是不同的,故而可以用來區分。但是真正的海拔高度為0的地方會不會被誤殺,需要納入考慮,待測試驗證。 3. 定位回調調用次數 筆者在定位類中,聲明一個回調次數的屬性,在調用開始定位的方法中賦值為0,回調成功的方法中每次都加1,且打印出結果。大致代碼如下:

@TestLocationManager()

@property (nonatomic, assign) NSInteger callbackCount;

@end

@implementation TestLocationManager()

- (void)startLocation {
    self.callbackCount = 0;
}

- (void)BMKLocationManager:(BMKLocationManager * _Nonnull)manager 
  didUpdateLocation:(BMKLocation * _Nullable)location 
     orError:(NSError * _Nullable)error
{
  self.callbackCount += 1;
  NSLog(@"百度地圖單次定位回調次數: %ld", self.callbackCount);
}

- (void)locationManager:(CLLocationManager *)manager
  didUpdateLocations:(NSArray<CLLocation *> *)locations {
  self.callbackCount += 1;
  NSLog(@"系統定位單次定位回調次數: %ld", self.callbackCount);
}

@end

使用正常定位時,結果如下:

  百度地圖單次定位回調次數: 1
系統定位單次定位回調次數: 1
系統定位單次定位回調次數: 2

使用愛思助手模擬定位時,結果如下:

  百度地圖單次定位回調次數: 1
系統定位單次定位回調次數: 1

筆者這邊測試出來的結果,使用第三方地圖定位時虛擬定位和正常定位的回調次數沒有區別,故而,使用第三方地圖定位時此方法也行不通。使用系統定位時,虛擬定位和正常定位的回調次數不同,故而理論上使用系統定位時可以用此方法區分;但是使用這個判斷的準確度并不高,因為系統定位偶爾也會只回調一次,而且,假如使用回調來判斷,如何區分回調結束,是一個問題,比如筆者寫了一個延時0.5s后,來查看回調次數;所以建議可以用作參考,但是不建議作為判斷依據 4. 函數響應時間 筆者在定位類中,聲明一個開始時間的屬性,在開始調用定位的方法調用,然后定位結果的回調里取到結束時間,最后得出的時間差即是響應時間。代碼大致如下:

  @TestLocationManager()

@property (nonatomic, strong) NSDate *locateBeginDate;

@end

@implementation TestLocationManager()

- (void)startLocation {
    self.locateBeginDate = [NSDate date];
}

- (void)BMKLocationManager:(BMKLocationManager * _Nonnull)manager 
   didUpdateLocation:(BMKLocation * _Nullable)location 
      orError:(NSError * _Nullable)error
{
  NSDate *locateEndDate = [NSDate date];
  NSTimeInterval gap = [locateEndDate timeIntervalSinceDate:
        self.locateBeginDate];
  NSLog(@"單次定位時間:%lf", gap);
}

@end

使用正常定位時,結果如下:

  單次定位時間:0.332915

使用愛思助手模擬定位時,結果如下:

  單次定位時間:0.298709

根據筆者測試出的結果,定位的間隔網絡好的時候并沒有明顯差距,無法用固定的值區分,故而此方法也行不通。

根據CLLocation的私有屬性_internal中的type來判斷

參考iOS防黑產虛假定位檢測技術,按照文章描述,定位對象CLLocation中有一個私有屬性type,在不同定位來源的情況下,值是不同的。

所表示的意義 備注
0 unknown 應用程序生成的定位數據,一般在越獄設備下,通過虛擬定位程序來生成。
1 gps GPS生成的定位數據
2 nmea
3 accessory 藍牙等外部設備模擬定位生成的定位數據
4 wifi WIFI定位生成的數據
5 skyhook WIFI定位生成的數據
6 cell 手機基站定位生成的數據
7 lac LAC生成的定位數據
8 mcc
9 gpscoarse
10 pipeline
11 max .

筆者驗證步驟如下:在定位成功的回調中,判斷CLLocation的type屬性。


- (void)testLocationIntervalProperty:(CLLocation *)location {
    NSString *type = [location valueForKey:@"type"];
    NSLog(@"定位來源類型:%@", type);
    return;
    // 如果想要看location的全部屬性,可以通過下面的方法
    unsigned int intervalCount;
    objc_property_t *properties = class_copyPropertyList([location class],
                &intervalCount);
    for (unsigned int y = 0; y < intervalCount; y++) {
        objc_property_t property = properties[y];
        NSString *propertyName = [[NSString alloc] initWithCString:
               property_getName(property)];
        if ([propertyName containsString:@"type"]) {
            id propetyValue = [location valueForKey:propertyName];

            NSLog(@"屬性名稱:%@, 屬性信息:%@", propertyName, propetyValue);
        }
        else {
//            NSLog(@"屬性名稱:%@", propertyName);
        }
    }
}

正常定位時,Wi-Fi打開時,結果為4;Wi-Fi關閉時,結果為6;結果如下:

// Wi-Fi打開時,結果為4;Wi-Fi關閉時,結果為6;移動網絡也關閉時,結果為6;
// 但是網絡不好時,結果為1;
定位來源類型:4

使用虛擬定位時,結果如下:

定位來源類型:1

但是,當使用第三方地圖定位時,不論是虛擬定位,還是正常定位,結果如下:

定位來源類型:0

筆者這邊對比結果如下:使用系統定位時,正常網絡下虛擬定位和正常定位結果不同,但是網絡不好時,定位來源類型都是1,故而區分不準確;使用第三方系統定位時,虛擬定位和正常定位結果相同,無法區分。

通過外接設備,發送虛擬定位數據來虛擬定位的檢測

這種虛擬定位的筆者沒有設備實踐,但通過網上的資料,可以看出外接設備是通過藍牙和手機連接,故而筆者推測,也可以根據CLLocation的私有屬性_internal中的type來判斷。因為type=3的定義是藍牙等外部設備模擬定位生成的定位數據,所以這種虛擬定位應該可以通過此type值區分。

越獄設備中通過hook定位方法,來虛擬定位的檢測

這種方法,筆者目前調研到的有兩種,一種是直接針對越獄設備,判斷出iPhone已越獄,就提示,從而避免;另外一種是判斷代理方法是否被hook,以及代理方法被hook的實現是否在APP中。

方法一:判斷設備是否已越獄,參考用代碼判斷iOS 系統是否越獄的方法

判斷設備已越獄有下面幾種方法

  1. 判斷常見越獄文件,維護一份常見越獄文件,判斷其中一個存在,則說明已越獄
   /// 根據白名單判斷設備是否已越獄
 /// - Returns: true-已越獄;false-未越獄
 class func isJailedDevice() -> Bool {
     let jailPaths = ["/Applications/Cydia.app", 
         "/Library/MobileSubstrate/MobileSubstrate.dylib", 
         "/bin/bash", 
        "/usr/sbin/sshd",
          "/etc/apt"]
     var isJailDevice = false
     for item in jailPaths {
         if FileManager.default.fileExists(atPath: item) {
             isJailDevice = true
             break
         }
     }
     return isJailDevice
 }
  1. 判斷cydia的URL Scheme,通過識別手機是否安裝了Cydia.app,來判斷是否已越獄。
  /// 根據cydia scheme能否打開判斷是否已越獄
/// - Returns: true-已越獄;false-未越獄
class func isJailedDevice() -> Bool {
    let cydiaSchemeStr = "cydia://"
    if let url = URL(string: cydiaSchemeStr),
       UIApplication.shared.canOpenURL(url) {
        return true
    }
    else {
        return false
    }
}
  1. 根據能否讀取系統所有應用來判斷,已越獄的設備可以讀取,未越獄的設備不可以
  /// 根據能否獲取到所有應用來判斷是否越獄
/// - Returns: true-已越獄;false-未越獄
class func isJailedDevice() -> Bool {
    let appPathStr = "/User/Applications"
    if FileManager.default.fileExists(atPath: appPathStr) {
        do {
            let appList = 
            try FileManager.default.contentsOfDirectory(atPath: 
            appPathStr)
            if appList.count > 0 {
                return true
            }
            else {
                return false
            }
        } catch {
            return false
        }
    }
    else {
        return false
    }
}

方法二判斷代理方法是否被hook,以及hook的實現是否在app中,參考iOS上虛擬定位檢測的探究

注入dylib方法的實現虛擬定位,大部分會利用MethodSwizzling去hook定位方法的目標函數,method被替換的新implemention所在的module,不會與原始implemention所在的module一致。越獄插件的方式新implemention所在的module通常是插件本身的dylib;對ipa砸殼做動態庫注入的方式,新implemention所在的module通常是被注入 的dylib?!獊碜詇ttp://devliubo.com/2016/2016-12-23-iOS%E4%B8%8A%E8%99%9A%E6%8B%9F%E5%AE%9A%E4%BD%8D%E6%A3%80%E6%B5%8B%E7%9A%84%E6%8E%A2%E7%A9%B6.html

實踐:

代碼如下:

#import <objc/runtime.h>
#import <dlfcn.h>

static void logMethodInfo(const char *className, const char *sel)
{
    Dl_info info;
    IMP imp = class_getMethodImplementation(objc_getClass(className),
               sel_registerName(sel));
    if(dladdr(imp,&info)) {
        NSLog(@"method %s %s:", className, sel);
        NSLog(@"dli_fname:%s",info.dli_fname);
        NSLog(@"dli_sname:%s",info.dli_sname);
        NSLog(@"dli_fbase:%p",info.dli_fbase);
        NSLog(@"dli_saddr:%p",info.dli_saddr);
    } else {
        NSLog(@"error: can't find that symbol.");
    }
}

比如,筆者這里驗證使用如下,筆者的項目中對UIView的layoutSubviews做了MethodSwizzling,而viewDidLayoutSubviews方法沒有,對比如下:


- (void)testViewDidLayoutSubviews {
    const char *className = object_getClassName([UIView new]);
    SEL selector = @selector(viewDidLayoutSubviews);
    const char *selName = sel_getName(selector);
    logMethodInfo(className, selName);
}

- (void)testLayoutSubviews {
    const char *className = object_getClassName([UIView new]);
    SEL selector = @selector(layoutSubviews);
    const char *selName = sel_getName(selector);
    logMethodInfo(className, selName);
}

結果對比:

wecom20210805-085332@2x.png wecom20210805-103558@2x.png

從上面的結果對比,發現未使用MethodSwizzling方法的dli_fname/usr/lib/libobjc.A.dylib,dli_sname_objc_msgForward;而使用了MethodSwizzling方法的dli_fname/private/var/containers/Bundle/Application/0106942C-7D3F-45A9-BB1B-2C0FBD994744/xxx.app/xxx,dli_sname-[UIView(MSCorner)ms_layoutSubviews],可以看出,從dli_sname可以判斷方法是否被hook過,從dli_fname可以知道方法的implemention是否在項目的module中。(筆者手頭沒有越獄的手機,也沒有做過砸殼注入,大家如果有興趣可以驗證一下試試。)

總結

筆者驗證的結果,通過特征值中的海拔和海拔精度可以判斷出使用愛思助手或者 Xcode 模擬定位,通過 type 可以區分不同定位來源。為了準確,筆者通過海拔、海拔精度、type 三個字段結合起來判斷。

筆者寫了一個檢測的代碼,倉庫地址如下:https://github.com/mokong/DetectFakeLocation

原理是:使用swizzlemethod hook了CLLocationManagerstartUpdatingLocation方法,以及CLLocationManagerDelegatelocationManager:didUpdateLocations:方法,然后檢測越獄、海拔和海拔精度、定位類型,根據這幾個方面判斷是否是虛擬定位。

ios_虛擬定位的方法.png

參考

  • iOS 虛擬定位監測
  • 蘋果虛擬定位技術原理和檢測
  • 免越獄虛擬定位外掛的調試小記與檢測方案 | B1nGzL 著
  • iOS防黑產虛假定位檢測技術
  • iOS實現虛擬定位的多種玩法
  • iOS 識別虛擬定位調研
  • iOS上虛擬定位檢測的探究

https://mp.weixin.qq.com/s/5ivzxYjiZ74px6ibwFCH8g

最多閱讀

iOS 性能檢測新方式?——AnimationHitches 8月以前  |  19828次閱讀
快速配置 Sign In with Apple 2年以前  |  5527次閱讀
APP適配iOS11 3年以前  |  4466次閱讀
App Store 審核指南[2017年最新版本] 3年以前  |  4296次閱讀
所有iPhone設備尺寸匯總 3年以前  |  4219次閱讀
使用 GPUImage 實現一個簡單相機 3年以前  |  3946次閱讀
開篇 關于iOS越獄開發 3年以前  |  3813次閱讀
在越獄的iPhone設置上使用lldb調試 3年以前  |  3739次閱讀
給數組NSMutableArray排序 3年以前  |  3668次閱讀
UITableViewCell高亮效果實現 3年以前  |  3367次閱讀
使用ssh訪問越獄iPhone的兩種方式 3年以前  |  3363次閱讀
關于Xcode不能打印崩潰日志 3年以前  |  3262次閱讀
使用ssh 訪問越獄iPhone的兩種方式 3年以前  |  3101次閱讀
為對象添加一個釋放時觸發的block 3年以前  |  2877次閱讀
使用最高權限操作iPhone手機 3年以前  |  2849次閱讀

手機掃碼閱讀
18禁止午夜福利体验区,人与动人物xxxx毛片人与狍,色男人窝网站聚色窝
<蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>