Содержание статьи
Каждый день в мобильных устройствах выявляют уязвимости, которые могут быть проэксплуатированы злоумышленниками. Они могут отправить СМС на платный номер, могут собрать большую базу данных контактов и продать ее, а могут и скомпрометировать конкретного человека. Удачная эксплуатация уязвимости обычно предполагает соблюдение целого ряда условий. Но ведь можно пойти и другим путем! Дать пользователю вполне нужное приложение (игрушку с птичками), у которого в манифесте будет записан список интересной нам информации на устройстве. В данной статье мы рассмотрим способы получения и сохранения важной информации с Android-устройства.
Архитектура ОС Android построена таким образом, что позволяет обмениваться разного рода информацией между приложениями. Приложению, работающему с картами, нужно местоположение, диктофону — доступ к микрофону. Таким образом, с виду все ясно и прозрачно.
Мы открыто прописываем в манифесте приложения требуемые данные или возможности и получаем их при установке. Никто никого не обманывает, все добровольно. Но проблема состоит в том, что пользователи ужасно неграмотны в информационных технологиях. Мало кто задумывается, для чего тому же диктофону требуется твое местоположение или доступ к СМС. Приложение открыто заявляет о своих намерениях в манифесте, и странно было бы ожидать от него другого поведения.
Задолго до всем известного разоблачения я понимал, что игрушка со злыми птичками на твоем устройстве — это стукач, так как оно, помимо всего прочего, хочет читать идентификатор устройства и данные о вызовах. Простой вопрос «Тебе эти данные зачем?» обнажает истинные намерения ее создателей.
Пользователь при установке приложения ставится в положение «или разрешай все, что оно хочет, или останешься без программы». Только единицы пойдут в магазине искать приложение со сходной функциональностью, но с меньшими запросами (аналогов может вовсе не быть), поэтому у пользователей быстро входит в привычку жать «да-да-да» на все вопросы. Согласись, легко привыкать, когда за долгие годы офлайн-жизни у пользователей вырабатывался рефлекс автоматически подписывать многостраничные договоры, по принципу «ну, все же подписывают, наверное, тут ничего плохого, да и выход всего один — либо я подписываю тут, либо не получаю того, за чем пришел».
После установки шпионское приложение не надо даже запускать, так как в нем может быть прописан механизм приема широковещательных сообщений (BroadcastReceiver) , который при получении сообщения от системы запустит сервис сбора информации.
Если мы отберем все разрешения у приложения, ОС во избежание падения программы может просто отдать ему пустые значения. Можно обмануть приложение, подсунув ему заведомо ложные данные (местоположение Северного полюса) или просто нули. Например, приложение может спросить список контактов на устройстве, и разработчик предполагает в своей архитектуре, что он может быть пустым (совсем новое устройство). Тут даже и заподозрить нечего — и данные спасены, и приложение не сломалось.
На такие ухищрения приходилось идти вплоть до версии Android 6.0 Marshmallow. В ней появился новый механизм работы с разрешениями.
Он позволяет давать и забирать разрешения во время работы самого приложения. Для обратной совместимости старых аппликух (то есть у которых значение targetSdkVersion меньше 23) работает старый механизм запроса разрешений при установке. Обновленные приложения должны запрашивать разрешения в процессе работы. В настройках приложения мы можем посмотреть, к чему у приложения есть доступ, и при желании отозвать этот самый акцесс.
Рассмотрим работу данного механизма на устройстве с версией Android 6.0.
Давай установим птичек, но перед первым запуском отберем у них все права. При запросе прав при установке из гуглплея мы видим, что targetSdkVersion у приложения меньше 23. Экран настроек говорит нам о несколько завышенных интересах создателей приложения.
Другие статьи в выпуске:
Хакер #204. Шифровальщик для Android |
Как насчет того, чтобы немного их укоротить?
После отзыва разрешений я запустил игру, и оказалось, что нормальной работе это ничуть не помешало. Видимо, фоновый сервис сбора данных никак не влияет на основной игровой интерфейс.
Теперь давай рассмотрим работу с обновленным приложением Skype. Вот перед нами часть манифеста «похожего» приложения. Список разрешений и требований из манифеста приложения вдохновляет:
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<uses-feature
android:name="android.hardware.microphone"
android:required="false" />
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.wifi"
android:required="false" />
<uses-feature
android:name="android.hardware.location"
android:required="false" />
<uses-feature
android:name="android.hardware.location.network"
android:required="false" />
<uses-feature
android:name="android.hardware.location.gps"
android:required="false" />
<permission
android:name="com.skype.android.permission.READ_CONTACTS"
android:permissionGroup="android.permission-group.PERSONAL_INFO"
android:protectionLevel="normal" />
<permission
android:name="com.skype.android.permission.WRITE_CONTACTS"
android:protectionLevel="signature" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.READ_SYNC_STATS" />
<permission
android:name="com.skype.raider.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="com.skype.raider.permission.C2D_MESSAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<permission
android:name="com.skype.raider.permission.RECEIVE_ADM_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="com.skype.raider.permission.RECEIVE_ADM_MESSAGE" />
<uses-permission android:name="com.amazon.device.messaging.permission.RECEIVE" />
<uses-permission android:name="com.nokia.pushnotifications.permission.RECEIVE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<permission
android:name="com.skype.permission.ACCOUNT"
android:protectionLevel="signature" />
<uses-permission android:name="com.skype.permission.ACCOUNT" />
<uses-feature
android:glEsVersion="0x20000"
android:required="true" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.sec.android.provider.badge.permission.READ" />
<uses-permission android:name="com.sec.android.provider.badge.permission.WRITE" />
<uses-permission android:name="com.htc.launcher.permission.READ_SETTINGS" />
<uses-permission android:name="com.htc.launcher.permission.UPDATE_SHORTCUT" />
<uses-permission android:name="com.sonyericsson.home.permission.BROADCAST_BADGE" />
<uses-permission android:name="com.anddoes.launcher.permission.UPDATE_COUNT" />
<uses-permission android:name="com.majeur.launcher.permission.UPDATE_BADGE" />
Если бы только пользователи это видели...
Раз мы хотим сохранить хотя бы малую долю частной жизни, остается уповать только на операционную систему. Рассмотрим процесс установки приложения. При установке из маркета никаких диалоговых окон не показывается — все разрешения приложение будет спрашивать потом. Проверим в настройках.
Запустим приложение. Последовательно идут семь запросов.
Последовательно отклоняем все запросы, и скайп превращается в интернет-чат :).
Постой, я сказал «в интернет-чат»? А почему он не спросил разрешения на доступ в интернет? А все потому, что разрешения делятся на две группы: обычные и опасные. Доступ теперь должны запрашивать только последние. Список обычных разрешений можно посмотреть тут. А вот — опасные разрешения (и их группы). Для создания запросов на разрешения есть подробные материалы: шаблоны использования запросов на доступ к разрешениям, рекомендации для доступа к разрешениям, а вот и полный список разрешений.
С теорией мы более-менее разобрались, теперь перейдем к практике.
Проникаем в список контактов (по-хорошему)
Обогащенные этим новым знанием, давай напишем небольшое приложение, в котором будем читать данные о контактах пользователя через запросы в интерфейсе и в сервисе. Как недавно выяснилось, некоторые приложения очень часто любят читать эти данные (оно и неудивительно — вдруг там что-то изменилось, а создатели «хороших» приложений и не знают).
Нажатием на кнопку мы проверяем версию ОС и, если она 23 и выше, делаем запрос на получение прав:
public void setRequestReadContacts(View view) {
// Проверка на уже данные разрешения
if (hasSelfPermission(this, Manifest.permission.READ_CONTACTS)) {
// Разрешение есть, показываем контакты
showContacts();
} else {
// Разрешения нет, посылаем запрос
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_READ_CONTACTS);
}
}
}
После диалога в методе onRequestPermissionsResult нужно обработать ответ:
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == REQUEST_READ_CONTACTS) {
if (verifyAllPermissions(grantResults)) {
// Разрешение получено, показываем контакты
showContacts();
} else {
Toast.makeText(this, "Разрешение REQUEST_READ_CONTACTS не было получено", Toast.LENGTH_SHORT).show();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
Читаем контакты в одну строчку с переходами на новую строку:
private String readContacts() {
Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
String names = "";
while (phones.moveToNext()) {
names = names + phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)) + "\n";
}
phones.close();
return names;
}
Показываем контакты в окне, при клике по окну оно скроется:
public void createWindow(String contacts) {
LayoutInflater layoutInflater
= (LayoutInflater) getBaseContext()
.getSystemService(LAYOUT_INFLATER_SERVICE);
View popupView = layoutInflater.inflate(R.layout.popup_window, null);
if (mPopupWindow != null)
mPopupWindow.dismiss();
mPopupWindow = new PopupWindow(
popupView,
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mPopupWindow.setAnimationStyle(android.R.style.Animation_Dialog);
LinearLayout ll = (LinearLayout) popupView.findViewById(R.id.PopUp);
TextView tv_message = (TextView) popupView.findViewById(R.id.tv_message);
tv_message.setText(contacts);
ll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPopupWindow.dismiss();
}
});
mPopupWindow.showAtLocation(mMainLayout, Gravity.CENTER, 0, (int) (mSidePopup * 0.3));
}
А вот результат работы чтения контактов:
Проникаем в список контактов (по-плохому)
Теперь проделаем то же самое, но в сервисе, как и положено плохому приложению. Сервис никаких запросов слать не будет, а будет пытаться просто в лоб читать контакты.
Запуск сервиса сделаем по событию подключения к Wi-Fi .
Код сервиса:
public class ReadContactsService extends Service {
public ReadContactsService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
private String readContacts() {
Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
String names = "";
while (phones.moveToNext()) {
names = names + phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)) + "\n";
}
phones.close();
return names;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(getBaseContext(), readContacts(), Toast.LENGTH_SHORT).show();
return START_STICKY;
}
}
Код приемника:
public class WifiReceiver extends BroadcastReceiver {
public WifiReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
if (info != null && info.isConnected()) {
// Делаем свою работу
Toast.makeText(context, "Подключен", Toast.LENGTH_LONG).show();
Intent myIntent=new Intent(context,ReadContactsService.class);
context.startService(myIntent);
}
}
}
Если у приложения есть права, то сервис покажет Toast сообщение со списком контактов, если прав нет — вызовет ошибку с сообщением. Теперь исследуем поведение приложения, если выставить ему targetSdkVersion ниже 23.
По умолчанию приложению были выданы права, и все работает штатно без запросов. После отключения прав в настройках приложения список контактов стал приходить пустым. Никаких ошибок мы не получили, но и контактов тоже. Значит, без нашего ведома приложения будут получать неактуальную информацию.
Заключение
Новая версия Android включает в себя более надежные и гибкие настройки прав для приложений. Можно сказать, что в новой версии наши данные защищены более эффективно, но это не значит, что теперь тебе можно вслепую соглашаться со всеми предложениями, поступающими из всплывающих окошек. Будь бдителен!