Playful Xamarin. Researching and hacking a C# mobile app
Blindfold game. Manage your Android smartphone via ABD
Controlling Android. Dangerous APIs enable hackers to intercept data and reset smartphone settings
Fake address. How to change geolocation on Android devices and fool apps
From the user’s perspective, the task is pretty simple: you install a special app, then enable developer mode in the settings, and make the installed application the provider of fictitious coodrinates. There are plenty of such programs: from very primitive to sophisticated ones that not only substitute coordinates with arbitrary values, but can also change them according to a schedule or play prerecorded tracks, thus, simulating movement along a certain route. So, all you have to do is search Google Play for “Fake GPS” and select an app to your taste.
Too bad, this method is not very reliable. The presence of such an app on your phone can be detected at the software level, and some programs won’t be fooled by it.
I decided to find out how the GPS substitution mechanism works and create my own spoofing application. First of all, I checked how this algorithm is implemented in one of the free apps.
Reversing FakeGPS
I used the FakeGPS 5.0.0 app. It looks like a map: you set a marker at an arbitrary point of this map and start or stop the transmission of coordinates of the selected point using the “Start” and “Stop” buttons.
I launch JEB Decompiler and start examining the program. The presence of android.
in the manifest immediately attracts my attention.
<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WAKE_LOCK" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" /><uses-permission android:name="android.permission.WRITE_SETTINGS" /><uses-permission android:name="com.android.vending.BILLING" />
Nothing of interest can be found in the main activity (just the ordinary initialization and setup procedures), but there is also a service called FakeGPSService
.
I try to break through the obfuscation and see what it’s all about.
The onCreate
method includes the following code:
this.f = "gps";this.d = (LocationManager)this.getSystemService("location");try { if(this.d == null) { goto label_46; } this.d.removeTestProvider(this.f); goto label_46;} catch(IllegalArgumentException | NullPointerException unused_ex) { goto label_46;}label_46:if(this.d != null) { this.d.addTestProvider(this.f, false, false, false, false, true, false, false, 0, 5); this.d.setTestProviderEnabled(this.f, true);}
In other words, LocationManager
is initialized by the this.
value; then the test "gps"
provider is removed by the removeTestProvider
function; and then I add the provider again using theaddTestProvider
function and enable it with the setTestProviderEnabled(
function. Voila, the test provider has been added and enabled! Now I can create and set new locations using the onEventMainThread
function:
// Create locationlong v1 = System.currentTimeMillis();Location v3 = new Location("");v3.setProvider("gps");v3.setLatitude(arg10.latitude);v3.setLongitude(arg10.longitude);v3.setAltitude(((double)FakeGPSService.p));v3.setBearing(((float)FakeGPSService.q));v3.setTime(v1);v3.setAccuracy(((float)FakeGPSService.o));v3.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());// Set locationtry { this.d.setTestProviderLocation(this.f, v3); Log.d("GpsMockProvider", v3.toString());} catch(IllegalArgumentException unused_ex) {}
Everything seems to be clear, and I start writing my own GPS spoofer.
Coding
Important: my goal wasn’t to produce a ready-to-use app. I just wanted to create a mockup with a minimum set of functions in order to demonstrate the efficiency of the selected method. Accordingly, the fake coordinates will be hardcoded in the GPS provider and set once and forever in the course of its creation.
I launch Android Studio and create a project with an empty activity.
Then I add android.
to the manifest – and Android Studio immediately tells me that this permission is available only for system apps and can be added only to the test manifest. I follow the tips by pressing the [Alt+Shift+Enter] and [Alt-Enter] combinations, and Android Studio does all the job for me. Then I add two buttons to the start activity…
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <Button android:id="@+id/btnDelGPS" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="DelGPS" android:text="Remove GPS provider" /> <Button android:id="@+id/btnAddGPS" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="AddGPS" android:text="Add GPS provider" /></LinearLayout>
…and add the respective code.
public class MainActivity extends Activity { LocationManager mLocationManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Initialize LocationManager mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); } public void AddGPS(View view) { // Add test provider mLocationManager.addTestProvider(LocationManager.GPS_PROVIDER, false, false, false, false, true, true, true, android.location.Criteria.POWER_LOW, android.location.Criteria.ACCURACY_FINE); // Enable test provider mLocationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true); // Set fake location Location newLocation = new Location(LocationManager.GPS_PROVIDER); newLocation.setLatitude(55.75578); newLocation.setLongitude(37.61786); newLocation.setTime(System.currentTimeMillis()); newLocation.setAccuracy(25); newLocation.setElapsedRealtimeNanos(System.nanoTime()); mLocationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, newLocation); } public void DelGPS(View view) { // Remove my test provider mLocationManager.removeTestProvider(LocationManager.GPS_PROVIDER); }}
Finally, I compile and install the program. Then I go to developer options on my phone, select my app as the provider of fake locations, launch it, and press the “Add GPS provider” button.”
Expectedly, nothing happens.
If an app is launched not for the first time, it may crash because a test provider of the same name already exists. In such a situation, you have to relaunch the app, delete the provider using the “Remove GPS provider” button, and then create it again using the “Add GPS provider” button.
I deliberately didn’t include handling of such errors into my program to avoid cluttering up its code. Again, I am writing a mockup, not the final version. However, you are welcome to send pull requests; the link to the project page on GitHub can be found in the end of the article.
Testing
I minimize the app and start testing it. First, I launch Yandex.Maps and get right to the Red Square – exactly as I wanted. Success!
However, when I use Google Maps, I somehow get to Komsomol Square in Uryupinsk. In fact, I know why I ended up there; what I don’t know is why my TestProvider isn’t working properly…
I had searched for the answer to this question for several days, and it turned out to be quite simple: all I had to do was disable the Google geolocation in the phone settings. This function relieves you from the need to specify the GPS provider manually: the phone automatically selects the source of coordinates. It starts with the most accurate sources (i.e. GPS); if satellite positioning is not available, the phone switches to base stations, then to Wi-Fi networks, and then it reportedly uses an accelerometer to determine the location.
I disable Google geolocation and launch Maps.
It’s working, I am on the Red Square again!
So, this method allows to substitute real GPS coordinates with fake ones, but I want to solve the problem fundamentally (i.e. be able to change the location without any additional tricks). Interestingly, the tested app, FakeGPS, operates correctly regardless of the Google geolocation settings. Therefore, I continue my research.
Fixing errors
I examine FakeGPSService
in more detail and notice that it uses some GoogleApiClient
. During the initial analysis, I thought that this client was used for advertisement and ignored it. But now I see that it includes two methods:
LocationServices.FusedLocationApi.setMockMode()LocationServices.FusedLocationApi.setMockLocation()
Apparently, this is what I need. I search Google for documentation, review it, and find out that FusedLocationApi
is considered obsolete, and FusedLocationProviderClient
should be used instead of it.
So, I add the following string to the dependencies
section of the build.
file:
implementation 'com.google.android.gms:play-services-location:17.0.0'
Interestingly, after the addition of this string, the app size increased from 11 KB to more than 1 MB.
I also add two strings in the end of the AddGPS
function:
LocationServices.getFusedLocationProviderClient(this).setMockMode(true);LocationServices.getFusedLocationProviderClient(this).setMockLocation(newLocation);
Then I compile and run the program, and it operates correctly both in Google Maps (with enabled geolocation) and in Yandex.Maps. Victory!