SQLite — очень популярное решение хранения данных, и операционная система Android не исключение. Сама система и многие программы используют для хранения информации базы данных - файлы с расширением db. Какие именно данные содержатся в базах, как их посмотреть, что с ними можно сделать и чем это грозит устройству с правами суперпользователя — обо всем этом я расскажу в статье.
WARNING
Автор статьи не является знатоком баз данных, равно как и SQLite, понимает, что некоторые действия можно совершать проще, а также не несет ответственности за возникновение паранойи после прочтения данной статьи. Также настоятельно рекомендуется перед любыми манипуляциями сделать бэкап редактируемой базы.
Работа с базами
Для работы с базами существует немало различного софта как для компа, так и для Android-устройств. Базы приложений обычно находятся по пути /data/data/НАЗВАНИЕПАКЕТАПРИЛОЖЕНИЯ/databases. Узнать название пакета интересующего приложения можно зайдя в «Настройки -> Приложения -> Все» и выбрав нужное (откроется вкладка «О приложении»), или в адресной строке браузера на странице приложения в Play Market.
Чтобы попасть в сам каталог /data/data, необходимы права суперпользователя, а с просмотром содержимого отлично справится Root Explorer. Для более удобной работы, а также для редактирования баз на устройстве можно использовать, например, SQLite Debugger, а на компе — DB Browser for SQLite. Для работы с базами также необходим установленный BusyBox с апплетом sqlite3. Все манипуляции в статье проводятся на Nexus 5 с прошивкой 5.1. Доступные для просмотра и редактирования базы, разбитые по соответствующим программам, на устройстве можно посмотреть в той же программе SQLite Debugger, нажав на главном экране меню App. Так чем же могут быть полезны базы в первую очередь тебе и что может украсть злоумышленник? Попробуем разобраться.
INFO
Огромная благодарность demosfenus за помощь в написании SQL-запросов.
accounts.db
Находится в /data/system/ или /data/system/users/0 в зависимости от версии прошивки и содержит данные обо всех аккаунтах, зарегистрированных на устройстве. Как видно на скриншоте «Структура accounts.db» в таблице accounts, к моему устройству привязано пятнадцать аккаунтов различных программ. Почти для всех указаны логины, для части есть пароли (на рисунке часть удалена) в зашифрованном виде.
Xakep #197. Социальная инженерия
В таблице authtokens содержатся токены авторизации от приложений, всех сервисов Google, GMS и других приложений. На вкладке extras — дополнительные ключи и значения, такие как GoogleUserId и список подключенных приложений/сервисов. У меня их около пятидесяти, включая Talk, YouTube, URL shortener, Wallet и другие.
Не скажу, может ли злоумышленник расшифровать пароли из базы, но получить доступ к сервисам можно, просто подкинув базу на другое устройство. Попробуем провести такой эксперимент. Возьмем базу со смартфона Nexus 5 и планшет Nexus 7 с чистой системой (свежеустановленная 5.1 через flash-all.bat с ключом -w, затем root). После загрузки чистой системы нажимаем «Пропустить» при запросе добавления аккаунта, далее устанавливаем весь софт, прописанный в accounts.db (WhatsApp официально не поддерживает работу на планшетах, поэтому качаем APK с варезников или 1mobile.com). Далее скидываем базу со смартфона, помещаем в каталог /data/system/users/0 на планшете и перезагружаемся.
После загрузки видим, что на вкладке «Настройки -> Аккаунты» появился наш аккаунт Google и дал нам полный доступ ко всем связанным программам. Почта, с помощью которой можно поменять пароль от аккаунта, все контакты с номерами телефонов, Google+, фотографии, файлы Google Drive, заметки, сохраненные авторизации в мобильном Хроме и так далее. Единственный неприятный момент — нерабочий Play Market, который выдает: «Ошибка при получении данных с сервера rpc:s-7:aec-7». Погуглив текст ошибки, можно легко его реанимировать.
Остальные приложения вели себя по-разному:
- Viber — базы ему недостаточно, открывается на странице ввода телефона;
- Facebook — показал логин на экране авторизации, но пароль оказался пустым;
- WhatsApp — также предлагает ввести номер телефона;
- ICQ — подставляет номер телефона, после чего отсылает код на телефон;
- LinkedIn и ВКонтакте — открывают стартовую страницу с запросом на авторизацию;
- Pebble — после коннекта с часами автоматом зацепил аккаунт и добавил в локер все установленные программы;
- Dropbox — нормально заработал и показал все файлы;
- Яндекс.Почта — загрузилась и показала все письма. К слову, это был корпоративный ящик, хостящийся у Яндекса.
Вывод: к последним трем программам легко получить доступ, если увести данные из accounts.db или саму базу.
mmssms.db
А вот и вся наша СМС-переписка. Находится она по пути /data/data/com.android.providers.telephony/databases/. Попробуем что-либо поменять. Для примера возьмем СМС с номера 900 — это информатор Сбербанка. На скриншоте «СМС от Сбербанка до и после вмешательства в mmssms.db» слева, последнее сообщение: «ECMC6844 02.05.15 12:49 покупка 450р 210009 KARI Баланс: 3281.16р». Поменяем его на более интересное сообщение, показанное справа. Для этого открываем базу на устройстве в SQLite Debugger. Нас интересует таблица sms. Выделим необходимые поля запросом:
> SELECT _id, thread_id, address, date, body FROM sms WHERE address = 900
Программа имеет тач-ориентированный интерфейс, и сами команды можно не писать вручную. Они подставляются автоматически после вызова меню долгим тапом на значении или после нажатия на соответствующее слово в верхней строке. Команды показаны для удобства использования в дальнейшем (например, при работе с консолью или вызове скриптов Таскером).
Итак, нажав на SELECT и отметив галочками нужные поля, получим таблицу, содержащую номер записи, номер ветки разговора, номер отправителя, дату в UNIX time и собственно текст СМС (см. скриншот «Изменяем значения в базе mmssms.db»). Долгий тап на последней записи. Программа предлагает на выбор действия. Выбираем Update value. Вводим необходимый нам текст. По аналогии с предыдущими СМС сделаем себе зачисление денег через банкомат. Изменим текст на «ECMC6844 05.05.15 10:18 зачисление 1000000р ATM 367700 Баланс: 1003731.16р». Сам запрос будет выглядеть так:
> UPDATE sms SET body = 'ECMC6844 05.05.15 10:18 зачисление 1000000р ATM 367700 Баланс: 1003731.16р' WHERE _id = 196
Нажав на треугольник в правом верхнем углу, внесем изменение в строку. Подгоним время из текста СМС (05.05.15 10:18) в поле date. Можно использовать любой UNIX time конвертер, например Онлайн калькулятор unix time stamp. Соответствующая дата будет 1430810300. Добавим в конец три произвольных цифры для миллисекунд и вставим полученное значение в поле date.
> UPDATE sms SET date = 1430810300000 WHERE _id = 196
Две команды можно объединить в одну, вписав редактируемые поля с данными через запятую. Нажимаем в правом нижнем углу кнопку Commit и вносим изменения в базу. Смотрим, что получилось. На том же скриншоте «СМС от Сбербанка до и после вмешательства в mmssms.db» справа видно, что теперь мы богатые люди и на счету у нас больше миллиона. Иногда требуется очистить данные приложения, обрабатывающего СМС, чтобы изменения вступили в силу.
Попробуем добавить в базу новую СМС. Для этого нам понадобятся две таблицы в базе: threads, которая хранит порядковый номер и заголовок (последнее сообщение) разговора/нити, и sms, которая хранит всю оставшуюся информацию. Вариантов событий тут два.
Вариант 1: добавляем СМС в существующий разговор. Для этого ищем в таблице sms номер ветки разговора — thread_id, соответствующий номеру отправителя. Как видно на скриншоте «Изменяем значения в базе mmssms.db», для информатора Сбербанка это цифра 7. Добавим новую строку в разговор, показанный на предыдущем скриншоте. Заполняем следующие поля: thread_id — ветка/нить разговора; address — номер отправителя; person — если отправитель есть в списке контактов; date — время прихода СМС; read — 1 для прочитанного сообщения, 0 для непрочитанного; type — 1 входящее, 2 исходящее (есть еще 0 — отправляемое и 4 — черновик); body — текст сообщения. Для добавления новой строки в таблицу необходимо выполнить следующую команду:
INSERT INTO sms (threadid, address, date, read, type, body) VALUES (7, 900, strftime('%s', 'now')*1000, 1, 1, "Текст сообщения")
Значение strftime('%s', 'now')*1000 используется для вставки текущего времени. Для вставки конкретной даты и времени необходимо использовать UNIX time с тринадцатью цифрами. Результат можно увидеть на скриншоте «Добавляем новую СМС в существующий разговор».
Вариант 2: добавляем новую СМС и создаем новую ветку разговора. Если по аналогии добавить строку с новым номером +7123456789, которого нет в записной книге и с которым ранее не было переписки, то в отправителях будет значиться «Неизвестный отправитель» без указания номера (см. скриншот «Добавляем новую СМС и создаем новую ветку» слева). Чтобы этого избежать, необходимо увязать еще таблицы threads и canonical_addresses. Сначала добавляем строку с номером в canonical_addresses, попутно проверяя наличие этого номера в таблице:
INSERT INTO canonical_addresses (address) SELECT '+7123456789' WHERE NOT EXISTS (SELECT 1 FROM canonical_addresses WHERE address = '+7123456789')
Затем создаем новый разговор/нить в таблице threads. В этой таблице recipient_ids соответствует порядковому номеру _id, а также номеру в таблице canonical_addresses, который создали предыдущей командой.
> INSERT INTO threads (message_count, recipient_ids, read) SELECT 1, MAX(_id)+1, 0 from threads
Далее добавляем само сообщение с уже созданным thread_id равным recipient_ids, который получили увеличением последнего номера recipient_ids в threads на 1 (MAX(_id)+1).
> INSERT INTO sms(thread_id, address, date, read, type, body) SELECT max(_id), "+7123456789", strftime('%s', 'now')*1000, 0, 1, 'Текст_сообщения' from threads
После этой вставки сработают триггеры и обновят информацию в ячейках всех таблиц базы. Результат можно увидеть на скриншоте «Добавляем новую СМС и создаем новую ветку» справа.
Для удобства все указанные команды можно вводить через консоль с компа, эмулятор терминала на устройстве или вызывать из Таскера. Proof of concept для Таскера:
- Создаем сцену с полями для ввода данных или действия Variable Querry для каждой переменной.
- Присваиваем переменным вводимые данные: номер отправителя/получателя, тип сообщения — входящее/исходящее (1/2), точное время сообщения.
- После нажатия кнопки «Добавить» в сцене или ввода последней переменной проверяется наличие введенного номера в таблице canonical_addresses. При его отсутствии добавляется новая строка с номером. Введенное время переводится в UNIX time. При отсутствии данных в переменной %Time или если введен 0 подставляется текущее время.
- Срабатывает скрипт, вносящий остальные данные в базу.
Само действие изменения базы для примера из скриншота «Добавляем новую СМС в существующий разговор» будет выполняться через Script — Run Shell с чекбоксом Root:
$ system/xbin/sqlite3 /data/data/com.google.android.providers.telephony/databases/mmssms.db " INSERT INTO sms(thread_id, address, date, read, type, body) VALUES (7, 900, strftime('%s', 'now')*1000, 1, 1, "Текст_сообщения")";
contacts2.db
Находится в /data/data/com.android.providers.contacts/databases/ и содержит всю записную книжку и логи звонков. В базе находятся все контакты, для аккаунтов которых в разделе «Настройка -> Аккаунты» включена синхронизация. Указаны они в таблице accounts (например, facebook, vk, whatsapp). Посмотрим, что можно выжать из этой базы.
Предположим, что мы позвонили кому-то и не дозвонились. Можно найти в истории звонков любой номер, но для удобства сделаем дозвон и скинем звонок. Заглянем в историю звонков и увидим сделанный звонок с датой (на момент написания статьи) — 6 мая 2015 года, временем 10:23 и длительностью 0 мин 0 с (см. скриншот «История звонков до и после изменений в contacts2.db» слева).
Откроем в базе таблицу calls и увидим, что в конце есть строка (в моем случае 364) с номером, датой, длительностью, типом и так далее. Дата традиционно в UNIX time, длительность в секундах, тип 2 соответствует исходящему звонку. Поменяем дату на вчерашнюю и поправим длительность. Для этого введем команду (см. скриншот «Изменяем время и длительность звонка»):
> UPDATE calls SET date = 1430829536000, duration = 1524 WHERE _id = 364
Заглядываем в историю звонков (см. скриншот «История звонков до и после изменений в contacts2.db» справа) и показываем начальнику, что мы вчера очень мило поболтали с заказчиком и за двадцать пять минут утрясли все рабочие моменты (каждый подставит свою историю). Можно также добавить новый звонок с произвольного номера. Для этого добавим в таблицу новую строку:
> INSERT INTO calls(number, date, duration, type) VALUES ("+71234567890", strftime('%s', 'now')*1000, 89, 1)
Заглядываем в историю (см. скриншот «Добавляем новый звонок») и видим, что нам только что звонили. Можем смело показывать звонок заинтересованным и ссылаться на срочный вызов, чтобы покинуть текущее место. Для того чтобы номер вписывался в текущий вид (остальные незнакомые номера показаны с пробелами и дефисами), нужно заполнять больше ячеек и увязывать эту базу с dialer.db.
К слову сказать, после некоторых экспериментов с добавлением строк, из диалера пропали все сведения, включая историю звонков и избранные контакты, а сама база стала весить в четыре раза меньше. Поэтому пришлось восстанавливать сохраненную копию базы.
msgstore.db
База сообщений WhatsApp, которая находится по пути /data/data/com.whatsapp/databases. В таблице chat_list содержатся ветки разговоров, по аналогии с таблицей threads в СМС-переписке. Сами сообщения находятся в таблице messages в столбце data. Отдельно контакты приложения лежат в базе wa.db. Попробуем поменять сообщение, которое видно на скриншоте «Редактируем сообщения WhatsApp» слева. Для этого в таблице messages найдем нужное нам сообщение и выполним следующую SQL-команду:
> UPDATE messages SET data = "Новый_текст" WHERE _id = номер_строки_с_сообщением
Результат показан на скриншоте «Редактируем сообщения WhatsApp» справа. Как видно, сообщения отлично редактируются.
settings.db
Расположена в /data/data/com.android.providers.settings/databases и хранит все настройки системы, включая некоторые недоступные пользователю из меню. Для разных моделей устройств содержит разные настройки. Забрать данные в этой базе легко, а вот менять настройки напрямую крайне не рекомендуется. Это может вызвать как неработающие переключатели, так и бутлуп. Содержит три основные таблицы: global, system и secure. Остановлюсь на самых интересных записях.
- adb_enabled — включение отладки по USB.
- airplane_mode_radios — перечисленные через запятую модули, которые подлежат выключению в режиме самолета.
- always_finish_activities — при выставленной единице агрессивно завершает процессы (activities), как только они перестают быть нужны.
- usb_mass_storage_enabled — возможность подключения USB-накопителя.
- wifi_sleep_policy — политика поведения Wi-Fi при засыпании (отключение или перевод в спящий режим).
- wifi_watchdog_on — включает/выключает сервис Wi-Fi Watchdog (автоматический выбор точки доступа с выходом в интернет).
- bluetooth_discoverability_timeout — длительность обнаружения устройства по Bluetooth в секундах.
- end_button_behavior — поведение физической кнопки «Закончить разговор», не во время разговора.
- font_scale — масштаб шрифта.
- setup_wizard_has_run — был или нет запущен менеджер первоначальной настройки после прошивки/сброса до заводских настроек.
- android_id — уникальный идентификатор устройства, 64-битное число (hex-строка), которая рандомно генерируется при первичной настройке и остается постоянной на протяжении всего жизненного цикла устройства.
- location_mode — режим определения местоположения пользователя.
- skip_first_use_hints — если значение равно 1, Android запустит режим ознакомления пользователя с функциями при первом запуске приложения.
- status_bar_show_battery_percent — отображение процента заряда батареи.
- audio_safe_volume_state — уровень безопасной громкости в наушниках.
- bugreport_in_power_menu — кнопка отправки баг-репорта в меню выключения.
Полный список значений зависит от версии прошивки устройства (версии API) и доступен на developer.android.com. Для изменения значений рекомендуется использовать утилиты settings и content. На примере последней, включение отображения процентов у значка батареи из консоли будет выглядеть так (буквы s и i здесь означают тип значения — строка или число соответственно):
$ adb shell content insert --uri content://settings/system --bind name:s:status_bar_show_battery_percent --bind value:i:1
Причем сработает эта команда даже без наличия прав суперпользователя на устройстве c прошивкой 4.4+. Именно это делает программа из маркета «Батарея с процентом». Для работы с данной базой необходимо точно знать структуру таблиц, так как, кроме вариантов 1 — включено, 0 — выключено, могут встречаться другие цифры и наборы букв и параметров через запятую.
Полезной для нас база также может быть, если необходимо сбросить пин-код/пароль для разблокирования экрана. Подробнее можно найти в многочисленных инструкциях в инете по запросам lockscreen.password_type и lock_pattern_autolock. В зависимости от версии прошивки пароль может находиться в /data/system/locksettings.db.
Выводы
Как видно на примерах выше, многие базы «уязвимы» и позволяют с легкостью доставать и менять данные. Пин-код, графический ключ, разблокировка по лицу, сканер отпечатков пальцев — все это недостаточный способ сохранения конфиденциальных данных. Знающий и заинтересованный человек, имея провод, комп и нужную прогу, за небольшой промежуток времени может легко увести всю интересующую его информацию. С момента подключения устройства к компу/ноуту процесс, включающий в себя разблокировку загрузчика (для многих устройств с прошивкой ниже 5.0 можно это сделать без потери данных), установку кастомного рекавери, перезагрузку в рекавери, поиск и вытягивание всех баз с помощью команды adb pull, установку стокового рекавери и блокировку загрузчика, может занять минут 15–20. Да и дав права суперпользователя с виду безобидному приложению, можно никогда не узнать, что еще оно делает в фоне, кроме заявленных функций.
Блок-врезка: Остальные базы
Приведу еще несколько баз, заслуживающих внимания. Их можно использовать, например, в Таскере, доставая информацию SQLite-запросами.
- telephony.db — база, содержащая настройки точек доступа к интернету, прокси, порты ММS и дополнительную сервисную информацию всех известных гуглу операторов. Именно поэтому на всех современных устройствах интернет работает из коробки. Может пригодиться особенно в международном роуминге для старых устройств, чтобы не гуглить настройки местных операторов (пакет com.android.telephony).
- barcode_scanner_history.db — содержит историю всех сканирований программы Barcode Scanner / Сканер штрих-кодов. Возможно, кому-то пригодится для быстрого ввода штрих-кодов и последующего экспорта данных (пакет com.google.zxing.client.android).
- btopp.db — история переданных по Bluetooth файлов с названиями, временем передачи и MAC’ом сопряженных устройств (пакет com.android.bluetooth).
- calendar.db — собственно база календаря со всеми мероприятиями (пакет com.android.providers.calendar).
- external.db и internal.db — список всех файлов, находящихся в /sdcard, включая скрытые и в корне: system, data и прочие (пакет com.android.providers.media).
- google_analytics — история всех кликов, отсылаемых в Google Analytics.
- keep.db — заметки Google Keep. В выпуске № 191 я показал, как, читая и меняя эту базу, просматривать заметки на часах Pebble, а также помечать выполненные пункты списков прямо с часов (пакет com.google.android.keep).
- mail.db — вся переписка из Яндекс.Почты (пакет ru.yandex.mail).
- music.db — база, содержащая песни/плей-листы из Google Play Music (пакет com.google.android.music).
- reminders.db — напоминания, созданные в Google Now. В той же папке лежит много интересных баз, связанных с сервисами Google Play (пакет com.google.android.gms).
- user_dict.db — словарь пользователя (для клавиатуры). Можно забить вручную слова или перенести на другое устройство (пакет com.android.providers.userdictionary).
- viber_messages.db — сообщения всем известного мессенджера (пакет com.viber.voip).
- threads_db2.db и contacts_db2.db — сообщения и контакты из Facebook (пакет com.facebook.katana).
- vk.db — все данные из приложения «ВКонтакте». Друзья, дни рождения, сообщения. В переписке используются номера пользователей, определить которых можно, подставив в адресную строку браузера ссылку вида http://vk.com/idXXXX. Сообщения не правятся, так как постоянно синхронизируются с сервером и без интернета просто не отображаются в приложении (пакет com.vkontakte.android).