В Android существует замечательная возможность назначать поставщиком геокоординат любую программу, и вся система будет использовать те широту и долготу, которые она выдаст. В этой статье я покажу, как этим пользоваться и как самому написать программу для спуфинга координат GPS.
Идея родилась у меня в процессе написания статьи «Анализ приложения «Социальной мониторинг» » — именно тогда я обнаружил возможность менять поставщика координат в операционной системе, что открывает для пользователей много интересных возможностей.
С точки зрения юзера все очень просто: нужно лишь установить специальное приложение, затем включить в настройках режим разработчика и выбрать установленное приложение в качестве поставщика фиктивного местоположения. Таких программ великое множество — от простеньких до довольно развесистых, умеющих не только подменять координаты на заданные, но и менять их по расписанию или проигрывать заранее записанные треки, чтобы имитировать движение телефона по какому-то маршруту. В общем, вбивай запрос «Fake GPS» и выбирай по вкусу.
Сразу предупреждаю: надежность этого метода не очень высокая. При желании можно программно отследить наличие на телефоне такой программы-поставщика, и если программа серьезная, то просто так обдурить ее может не получиться.
Я же захотел разобраться, как именно работает этот механизм, и создать собственное приложение для спуфинга. А начал я с того, что посмотрел, как этот алгоритм реализован в одном из бесплатных приложений. Не читать же документацию, верно?
Реверс FakeGPS
В качестве подопытного кролика было взято приложение FakeGPS 5.0.0. Внешне приложение представляет собой карту, на которой можно установить маркер в произвольную точку и с помощью кнопок «Старт» и «Стоп» запускать или останавливать трансляцию координат выбранной точки. (см. также Подмена местоположения на Android)
Вооружившись JEB Decompiler, открываем и смотрим. Первое, что бросается в глаза, — это наличие в манифесте пермишена android.permission.ACCESS_MOCK_LOCATION.
1 2 3 4 5 6 7 8 9 |
<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" /> |
В основной активити ничего интересного не обнаружено, обычная инициализация и настройка, но есть сервис с говорящим названием FakeGPSService.
Попытаемся прорваться сквозь дебри обфускации и посмотреть, что в нем есть интересного.
В методе onCreate имеется такой код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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); } |
Если проще, то инициализируем LocationManager значением this.getSystemService(«location»), затем удаляем тестового провайдера
«gps» функцией removeTestProvider и добавляем заново с помощью функции addTestProvider, не забывая после этого включить его функцией setTestProviderEnabled(«gps», true). Всё, тестовый провайдер добавлен и включен. А далее при изменении пользователем координат создаем и устанавливаем новое местоположение в функции onEventMainThread:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Создаем long 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()); // И устанавливаем try { this.d.setTestProviderLocation(this.f, v3); Log.d("GpsMockProvider", v3.toString()); } catch(IllegalArgumentException unused_ex) { } |
Вроде бы все более-менее ясно, можно приступать к написанию своего провайдера фиктивных местоположений.
Еще по теме: Инструменты для взлома и реверсинга приложений Android
Пишем приложение для подмены местоположения Андроид
Сразу скажу, я не ставлю перед собой задачи создать готовое к практическому использованию приложение. Я буду делать макет с минимальным набором функций, который продемонстрирует работоспособность приведенного способа. Так что фиктивные координаты будем задавать жестко в коде и устанавливать их один раз при создании провайдера. Кому интересно, тот уже сам допилит до нужного уровня.
Итак, запускаем Android Studio и создаем проект с пустой активити.
Добавляем в манифест android.permission.ACCESS_MOCK_LOCATION, после чего «Студия» начинает ругаться, что это разрешение доступно только системным приложениям, да еще может быть добавлено только в тестовый манифест. Тут можно не заморачиваться, а просто понажимать кнопки Alt+Shift+Enter и Alt-Enter, следуя подсказкам, и «Студия» сама все сделает за нас. Затем добавляем на стартовую активити две кнопки.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<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="Удалить провайдер GPS" /> <Button android:id="@+id/btnAddGPS" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="AddGPS" android:text="Добавить провайдер GPS" /> </LinearLayout> |
И добавляем соответствующий код.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class MainActivity extends Activity { LocationManager mLocationManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Инициализируем LocationManager mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); } public void AddGPS(View view) { // Добавляем тестовый провайдер mLocationManager.addTestProvider(LocationManager.GPS_PROVIDER, false, false, false, false, true, true, true, android.location.Criteria.POWER_LOW, android.location.Criteria.ACCURACY_FINE); // Включаем тестовый провайдер mLocationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true); // Задаем фиктивную точку 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) { // Удаляем наш тестовый провайдер mLocationManager.removeTestProvider(LocationManager.GPS_PROVIDER); } } |
Компилируем и устанавливаем. Затем идем в настройки разработчика на телефоне, выбираем наше приложение в качестве поставщика фиктивных местоположений, трясущимися от волнения (как же, мы же только что написали собственный поставщик местоположений!) руками запускаем наше приложение и жмем кнопку «Добавить провайдер GPS».
Ничего не происходит. Ничего, собственно, и не должно происходить.
Если приложение запускается не первый раз, то возможно падение из-за того, что тестовый провайдер с таким именем уже создан. Тогда надо повторно запустить приложение и удалить провайдер кнопкой «Удалить провайдер GPS», а затем создать его заново кнопкой «Добавить провайдер GPS».
Я намеренно не стал добавлять обработку таких ошибок, чтобы не засорять код, мы же пишем макет, а не финальную версию. Впрочем, можешь присылать пулл-реквесты, ссылка на GitHub будет в конце статьи.
Тестируем приложение для подмены местоположения Андроид
Сворачиваем приложение и приступаем к тестам. Запускаем «Яндекс Карты» и попадаем точно на Красную площадь, в нулевой километр, как и было задумано. Ура, получилось!
Ну, почти получилось. Если попробовать запустить Google Maps, почему-то мы попадаем на площадь Комсомола в городе Урюпинске. Вернее, понятно, почему попадаем, непонятно, почему не работает наш TestProvider.
Скажу честно, ответ на этот вопрос я искал несколько дней, но все оказалось довольно просто — надо отключить в настройках телефона геолокацию Google. Эта настройка позволяет не заморачиваться с выбором провайдера: телефон сам решает, из какого источника брать координаты. Начинает с самых точных, то есть GPS, затем, если спутниковое позиционирование недоступно, переходит к базовым станциям, затем по сетям Wi-Fi, а затем якобы даже использует акселерометр, чтобы найти себя в пространстве.
Итак, пробуем — отключаем геолокацию Google и запускаем «Карты».
Сработало, мы снова на Красной площади.
Итак, этот способ позволяет подменять реальные координаты GPS фиктивными, но хотелось бы решить задачу полностью — подменять местоположение начисто, без всяких скидок. Что интересно, наш подопытный кролик FakeGPS работает корректно независимо от настроек геолокации Google. Что ж, будем ковырять дальше.
Исправление ошибок
Присмотревшись к сервису FakeGPSService чуть более внимательно, я заметил, что там еще используется некий GoogleApiClient. Признаюсь, при первичном анализе я с ходу решил, что он нужен для рекламы, и не стал больше обращать на него внимание. А еще там есть вот эти два метода:
- LocationServices.FusedLocationApi.setMockMode(),
- LocationServices.FusedLocationApi.setMockLocation().
Кажется, это то, что нужно. Погуглив документацию (от нее все же не уйти!), выясняем, что FusedLocationApi немножко устарел и вместо него рекомендуется использовать FusedLocationProviderClient.
Что же, попробуем. Добавляем в раздел dependencies файла build.gradle такую строчку:
1 |
implementation 'com.google.android.gms:play-services-location:17.0.0' |
Интересно, что после добавления этой строчки объем приложения вырастает с 11 Кбайт до 1 Мбайта с хвостиком.
В конец функции AddGPS дописываем пару строк.
1 2 |
LocationServices.getFusedLocationProviderClient(this).setMockMode(true); LocationServices.getFusedLocationProviderClient(this).setMockLocation(newLocation); |
Компилируем и запускаем — теперь нормально работает и в Google Maps с включенной геолокацией, и в «Яндекс Картах». Победа!