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.
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.
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
.
Then enter the location you want to simulate and click to modify the location.
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:
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:
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
ofCLLocation
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#
-
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.
-
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.
-
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.
-
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.
Value | Meaning | Remarks |
---|---|---|
0 | unknown | Location data generated by the application, generally generated by virtual location programs on jailbroken devices. |
1 | gps | Location data generated by GPS |
2 | nmea | |
3 | accessory | Location data generated by Bluetooth and other external devices |
4 | wifi | Location data generated by WIFI |
5 | skyhook | Location data generated by WIFI |
6 | cell | Location data generated by mobile base stations |
7 | lac | Location data generated by LAC |
8 | mcc | |
9 | gpscoarse | |
10 | pipeline | |
11 | max |
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:
-
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 }
-
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 } }
-
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:
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.
References#
- iOS Virtual Location Monitoring
- Apple's Virtual Location Technology Principles and Detection
- Notes on Debugging Non-Jailbroken Virtual Location Plugins and Detection Solutions | B1nGzL
- iOS Anti-Fraud False Location Detection Technology
- Various Ways to Implement Virtual Location on iOS
- Research on Identifying Virtual Location on iOS
- Exploration of Virtual Location Detection on iOS