今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

Principles and Prevention of Virtual Location on iOS

iOS Virtual Location Principles and Prevention#

Background#

When it comes to virtual location, the common impression is often associated with cloning software on Android or even the built-in location spoofing feature (the author has previously used the location spoofing feature on the ZUK Z2 system), leading to the belief that virtual location on iOS is quite difficult. Before conducting research, I also held this belief; the known method for virtual location was to use Xcode to add GPX files and edit latitude and longitude to achieve virtual location. However, this operation can only be performed by those familiar with iOS development and requires a Mac computer, which contributed to my impression that virtual location on iOS is quite challenging.

However, after conducting research, I found that virtual location on iOS is not as difficult as it seems, and it is even simpler compared to Android. Below, I will introduce several methods for virtual location in iOS.

Methods and Principles of Virtual Location#

After my research, I found that virtual location on iOS can generally be categorized into four situations:

  • Using Xcode to simulate location through GPX files
  • Directly simulating location using the virtual location feature in 3uTools
  • Using external devices, such as connecting via Bluetooth to send virtual location data
  • Simulating location on jailbroken devices by hooking location methods

Let’s analyze and practice each method one by one:

Using Xcode to Simulate Location through GPX Files#

Using Xcode to simulate location through GPX files is generally familiar to iOS developers. The steps are as follows:

Create a new iOS project, then add a file and choose to create a GPX file.

wecom20210804-151237.png

Edit the content to change lat and lon to the desired latitude and longitude, as follows:


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

Then select Product -> Scheme -> Edit Scheme, select the Options tab, check Allow Location Simulation, and then run to simulate the location.

wecom20210804-152202.png

Note: Generally, when using Xcode to run virtual location, after stopping the run, the latitude and longitude will revert to the original. However, if the project is running and the data cable is directly unplugged (while running), the phone's latitude and longitude will remain as the simulated coordinates. To restore, you need to restart the phone or wait 2 to 5 days for it to automatically revert.

Principle: It calls the com.apple.dt.simulatelocation service in the iOS device. com.apple.dt.simulatelocation is a debugging feature provided by Apple for simulating GPS locations on devices since Xcode 6 and iOS 8.0. The principle is to obtain the device handle via USB, then enable the service ("com.apple.dt.simulatelocation") within the device, and simulate the location using fixed coordinates or GPX files.

Directly Simulating Location Using the Virtual Location Feature in 3uTools#

This method is the simplest. I previously did not know about this method. Download 3uTools, open it, connect the phone to the computer, and select the Toolbox tab under Virtual Location.

wecom20210804-142320@2x.png

Then enter the location you want to simulate and click to modify the location.

wecom20210804-142450@2x.png

Note: This method of virtual location is indeed simple. So far, I have found that DingTalk and WeChat Work have not detected this method.

Principle: It opens the com.apple.dt.simulatelocation service through the service module of libimobiledevice, thus achieving location simulation without using Xcode. libimobiledevice is an open-source cross-platform library for calling iOS protocols.

Simulating Location by Sending Virtual Location Data through External Devices#

I had no prior knowledge of this method of simulating location through external devices. I must say, it is truly impressive; the wisdom of the people is incredibly powerful, with the representative being displacement sprites. I did not purchase external devices, so I could not try it, but I can provide a video for reference:

Video link: https://haokan.baidu.com/v?pd=wisenatural&vid=17675944846390412165

Principle: Through MFi (Made For iOS) certified manufacturers, one can obtain the MFI Accessory Interface Specification document, which provides many hidden features, including time communication, music data communication, location functions, etc. The use of the location function only requires following the document and sending the corresponding location data according to the protocol format. MFI Accessory Interface Specification can be found at: https://usermanual.wiki/Document/MFiAccessoryInterfaceSpecificationR18NoRestriction.1631705096.pdf

Searching for Location in the document will reveal location-related information, as shown below:

wecom20210804-165553.png

Simulating Location on Jailbroken Devices by Hooking Location Methods#

Virtual location on jailbroken devices uses jailbreak plugins that have virtual location capabilities. In god mode, jailbreak plugins can freely intercept system functions. For example: GPS Location Manager, which can manage the GPS location of each iOS application..

Principle: After jailbreaking, it injects a library and hooks the location delegate methods in CLLocationManager to tamper with normal location information.

In summary:

wecom20210804-171650.png

Detection of Various Virtual Location Methods#

As developers, knowing what virtual location methods exist is not enough; it is also necessary to understand how to address these virtual location methods. This is especially important for the development of OA applications and game applications. Let's take a step-by-step look:

Detection of Using Xcode to Simulate Location through GPX Files and Using 3uTools for Direct Virtual Location#

The final principle of using Xcode for GPX virtual location and using 3uTools for virtual location is the same; both achieve virtual location by calling the com.apple.simulatelocation service.

I have summarized that there are generally two verification methods mentioned online:

  • Judging based on characteristic values
    • Location accuracy: The accuracy of virtual location latitude and longitude is not as high as that of real location, so it can be judged based on the accuracy of the latitude and longitude.
    • Location altitude and altitude accuracy: The altitude value of virtual location is 0, and the vertical accuracy is -1; thus, these two values can be used for judgment.
    • Number of callback calls for location: Virtual location callbacks will only be called once, while real location callbacks will be triggered multiple times; thus, the number of triggers can be used for judgment.
    • Function response time: The response of virtual location is immediate, while real location is not; thus, response time can be used for judgment.
  • Judging based on the private property _internal of CLLocation which contains the type.

The above is the detection scheme summarized from the internet. Now let's practice to see if the facts are as described. Below, I will use the method of simulating latitude and longitude with 3uTools.

Strongly note: Using third-party map location and system location will also affect the methods below!!! I learned this the hard way.

Judging Based on Characteristic Values#

  1. Location Accuracy
    To obtain the accuracy of latitude and longitude, do not use "%f" for direct formatting, as the default formatting string "%f" retains up to six decimal places, making it impossible to compare differences.
    The code is as follows:

       /// Location latitude and longitude accuracy
       /// @param location Location Item
       - (void)testLocationAccuracy:(CLLocation *)location {
         NSString *longitudeStr = [NSString stringWithFormat:@"%@", 
         							@(location.coordinate.longitude)];
         // NSString *longitudeStr = [[NSDecimalNumber numberWithDouble:
         	location.coordinate.longitude] stringValue]; // This method retrieves 17 digits
         NSString *lastPartLongitudeStr = [[longitudeStr
         				 	componentsSeparatedByString:@"."] lastObject];
    
         NSString *latitudeStr = [NSString stringWithFormat:@"%@", @(location.coordinate.latitude)];
         NSString *lastPartLatitudeStr = [[latitudeStr 
         					componentsSeparatedByString:@"."] lastObject];
    
         NSLog(@"Location accuracy longitude digits: %d, latitude digits: %d", 
         	lastPartLongitudeStr.length, lastPartLatitudeStr.length);
       }
    

    When using normal location, the result is as follows:

    Location accuracy longitude digits: 13, latitude digits: 14
    

    When using 3uTools to search for Hongqiao Railway Station Subway Station, the automatically located latitude and longitude is 6 digits, and the input box can input up to 8 decimal places. After enabling virtual location, the result is as follows:

    Location accuracy longitude digits: 13, latitude digits: 14
    

    I tested this for a long time, and due to the issue of decimal precision, I tried several methods. The final conclusion is that this method cannot determine accurately. Although 3uTools has a limit on the number of digits for latitude and longitude, the final latitude and longitude obtained do not match the actual location, and due to decimal precision issues, it cannot be accurately judged. Therefore, this method is not feasible.

  2. Location Altitude and Altitude Accuracy
    By using altitude and verticalAccuracy, the altitude property of CLLocation represents altitude. The verticalAccuracy property is used to determine the accuracy of altitude. The altitude value may have an error of the size of verticalAccuracy, and when verticalAccuracy is negative, it indicates that altitude cannot be obtained.

    The code is as follows:

       /// Location altitude and vertical accuracy
       /// @param location Location Item
       - (void)testLocationAltitudeAccuracy:(CLLocation *)location {
           NSLog(@"Altitude: %f", location.altitude);
           NSLog(@"Vertical accuracy: %f", location.verticalAccuracy);
       }
    

    When using normal location, the result is as follows:

    Altitude: 9.224902
    Vertical accuracy: 16.078646
    

    When using 3uTools to enable location, the result is as follows:

    Altitude: 0.000000
    Vertical accuracy: -1.000000
    

    From the above, it can be seen that the altitude and vertical accuracy of normal location and simulated location are different, thus it can be used to distinguish. However, whether places with a true altitude of 0 will be mistakenly identified needs to be considered and tested.

  3. Number of Callback Calls for Location
    I declared a property for the number of callback calls in the location class, initialized it to 0 when starting the location method, and incremented it by 1 each time the callback succeeds, printing the result.
    The approximate code is as follows:

    
    @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(@"Baidu Map single location callback count: %ld", self.callbackCount);
     }
    
     - (void)locationManager:(CLLocationManager *)manager
     		didUpdateLocations:(NSArray<CLLocation *> *)locations {
       self.callbackCount += 1;
       NSLog(@"System location single callback count: %ld", self.callbackCount);
     }
    
     @end
    
    

    When using normal location, the result is as follows:

    Baidu Map single location callback count: 1
    System location single callback count: 1
    System location single callback count: 2
    

    When using 3uTools to simulate location, the result is as follows:

    Baidu Map single location callback count: 1
    System location single callback count: 1
    

    The results I tested showed that when using third-party map location, the callback counts for virtual and normal locations are the same, thus this method is not feasible when using third-party map location. When using system location, the callback counts for virtual and normal locations are different, therefore theoretically, this method can be used to distinguish when using system location; however, the accuracy of this judgment is not high because system location may occasionally only trigger once. Additionally, if using callback to judge, how to distinguish the end of the callback is a problem. For example, I wrote a delay of 0.5s to check the callback count; thus, it is recommended to use this as a reference but not as a judgment basis.

  4. Function Response Time
    I declared a property for the start time in the location class, recorded the start time when calling the location method, and then obtained the end time in the location result callback. The time difference obtained is the response time.
    The approximate code is as follows:

    
    @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(@"Single location time: %lf", gap);
     }
    
     @end
    
    

    When using normal location, the result is as follows:

    Single location time: 0.332915
    

    When using 3uTools to simulate location, the result is as follows:

    Single location time: 0.298709
    

    Based on my tests, there is no significant difference in the interval of location when the network is good, thus this method is not feasible.

Judging Based on the Private Property _internal of CLLocation#

Referencing iOS Anti-Fraud False Location Detection Technology, according to the article, the CLLocation object has a private property type, which has different values under different location sources.

ValueMeaningRemarks
0unknownLocation data generated by the application, generally generated by virtual location programs on jailbroken devices.
1gpsLocation data generated by GPS
2nmea
3accessoryLocation data generated by Bluetooth and other external devices
4wifiLocation data generated by WIFI
5skyhookLocation data generated by WIFI
6cellLocation data generated by mobile base stations
7lacLocation data generated by LAC
8mcc
9gpscoarse
10pipeline
11max

My verification steps are as follows:
In the successful location callback, check the type property of CLLocation.


- (void)testLocationIntervalProperty:(CLLocation *)location {
    NSString *type = [location valueForKey:@"type"];
    NSLog(@"Location source type: %@", type);
    return;
    // If you want to see all properties of location, you can use the following method
    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 propertyValue = [location valueForKey:propertyName];
            
            NSLog(@"Property name: %@, Property info: %@", propertyName, propertyValue);
        }
        else {
//            NSLog(@"Property name: %@", propertyName);
        }
    }
}

When using normal location, with Wi-Fi on, the result is 4; with Wi-Fi off, the result is 6; the results are as follows:

// With Wi-Fi on, the result is 4; with Wi-Fi off, the result is 6;
// When mobile network is also off, the result is 6;
// But when the network is poor, the result is 1;
Location source type: 4

When using virtual location, the result is as follows:

Location source type: 1

However, when using third-party map location, regardless of whether it is virtual location or normal location, the result is as follows:

Location source type: 0

My comparison results are as follows: When using system location, under normal network conditions, the results for virtual and normal locations are different, but when the network is poor, the location source type is both 1, thus making it inaccurate to distinguish. When using third-party system location, the results for virtual and normal locations are the same, making it impossible to distinguish.

Detection of Simulating Location by Sending Virtual Location Data through External Devices#

I have not practiced this virtual location method with devices, but based on online information, it can be inferred that external devices connect via Bluetooth to the phone, so I speculate that it can also be judged based on the private property _internal of CLLocation. Since the definition of type=3 is location data generated by Bluetooth and other external devices, this virtual location should be distinguishable by this type value.

Detection of Simulating Location on Jailbroken Devices by Hooking Location Methods#

Currently, I have researched two methods for this: one is to directly target jailbroken devices and prompt if the iPhone is jailbroken to avoid issues; the other is to check whether the delegate methods have been hooked and whether the implementations of the hooked methods are in the app.

Method 1: Check if the device is jailbroken, refer to Methods to Determine if iOS System is Jailbroken

There are several methods to determine if a device is jailbroken:

  1. Check for common jailbreak files, maintain a list of common jailbreak files, and if one exists, it indicates that the device is jailbroken.

    
     /// Determine if the device is jailbroken based on a whitelist
     /// - Returns: true- jailbroken; false- not jailbroken
     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
     }
    
    
  2. Check the Cydia URL Scheme to determine if the phone has Cydia.app installed, indicating it is jailbroken.

    
    /// Determine if the device is jailbroken based on whether the Cydia scheme can be opened
    /// - Returns: true- jailbroken; false- not jailbroken
    class func isJailedDevice() -> Bool {
        let cydiaSchemeStr = "cydia://"
        if let url = URL(string: cydiaSchemeStr),
           UIApplication.shared.canOpenURL(url) {
            return true
        }
        else {
            return false
        }
    }
    
    
  3. Determine if all system applications can be read; jailbroken devices can read them, while non-jailbroken devices cannot.

    /// Determine if the device is jailbroken based on whether all applications can be accessed
    /// - Returns: true- jailbroken; false- not jailbroken
    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
        }
    }
    

Method 2: Check if delegate methods have been hooked and whether the implementations of the hooked methods are in the app, refer to Exploration of Virtual Location Detection on iOS

The implementation of virtual location through dylib injection often utilizes Method Swizzling to hook the target function of the location method. The method of the new implementation that has been replaced will not be consistent with the original implementation. For jailbreak plugins, the new implementation is usually the dylib of the plugin itself; for dynamic library injection through IPA cracking, the new implementation is usually the injected dylib. — from http://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

Practice:

The code is as follows:


#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.");
    }
}

For example, I verified using the following; I performed Method Swizzling on the layoutSubviews of UIView in my project, while viewDidLayoutSubviews was not, comparing as follows:


- (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);
}

The comparison results are as follows:

wecom20210805-085332@2x.png

wecom20210805-103558@2x.png

From the comparison results above, it can be seen that the dli_fname of the method that did not use Method Swizzling is /usr/lib/libobjc.A.dylib, and dli_sname is _objc_msgForward; while the dli_fname of the method that used Method Swizzling is /private/var/containers/Bundle/Application/0106942C-7D3F-45A9-BB1B-2C0FBD994744/xxx.app/xxx, and dli_sname is -[UIView(MSCorner)ms_layoutSubviews]. It can be seen that from dli_sname, it can be determined whether the method has been hooked, and from dli_fname, it can be known whether the method's implementation is in the project's module. (I do not have a jailbroken phone or have done cracking injection, but if anyone is interested, you can verify it.)

Conclusion#

The results I verified show that the altitude and altitude accuracy in the characteristic values can determine whether the location is simulated using 3uTools or Xcode. The type can distinguish different location sources. For accuracy, I combined altitude, altitude accuracy, and type to make judgments.

I wrote a detection code, and the repository address is as follows: https://github.com/mokong/DetectFakeLocation

The principle is: using swizzlemethod to hook the startUpdatingLocation method of CLLocationManager and the locationManager:didUpdateLocations: method of CLLocationManagerDelegate, then detecting jailbreak status, altitude and altitude accuracy, and location type, based on these aspects to determine whether it is virtual location.

ios_virtual_location_methods.png

References#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.