First, you have to have your bootloader.img unlocked

Техника

Внедряемся в систему без установленных root-прав

Первое приходит на ум мысль о том, что мы можем просто взять устройство с которого хотим извлечь данные, прошить в него magisk используя TWRP, а затем, сразу же следом прошить наш бэкдор. Технически это сработает, т.к. вместе с magisk установятся и его политики SELinux, благодаря которым он сможет работать, но в этом случае, пользователь сразу же поймёт, что что-то не так. Он не устанавливал magisk, а magisk на устройстве есть. Значит, в то время как устройство было изъято злоумышленником, он что-то в него прошивал. Пользователь не сможет заметить этого до того как введёт код разблокировки, однако проблема в том что во время разблокировки интернет на устройстве пользователя может быть выключен, мы не получим удалённый доступ, а пользователь, обнаружив то что в его устройство пытались что-то прошить может удалить необходимую информацию, удалить magisk, начать разбираться что не так, обнаружить бэкдор, или просто сбросить телефон до заводских настроек вследствие чего интересующие нас данные будут уничтожены. Если на устройстве пользователя стоит какое-либо антивирусное решение, то оно может поднять тревогу, если обнаружит что в системе появились root-права полученные через magisk. 

Нам нужно постараться любой ценой избежать обнаружения, поскольку от этого зависит получится у нас изъять данные или нет. По сути, нам, в общем-то, не нужны root-права в обычном понимании, нам не нужен терминал с uid=0 для того, чтобы вводить какие-то команды. Нам не нужен исполняемый файл su, т.к. uid=0 мы можем получить и от процесса init. Нам не нужны и сторонние инструменты, которые поставляются с magisk. Нам не нужно приложение MagiskManager. Всё что нас интересует – это контекст u:r:magisk:s0. Получим контекст – получим удалённый доступ. 

Нам не только не нужно всё вышеперечисленное, нам очень желательно ничего из этого не устанавливать, т.к. это – маркеры компрометации. Если пользователь запустит какую-нибудь популярную проверку на root, то она нас обнаружит, это же может случиться и в одном из приложений, установленных на его устройстве, оно обнаружит что на телефоне установлены root-права и уведомит пользователя об этом. 

Обнаружить root-права на устройстве, в частности magisk, можно по-разному. Можно банально проверить наличие установленного менеджера в системе или попытаться найти испоняемый файл su или magisk (magisk создаёт символическую ссылку su которая на самом деле указывает на исполняемый файл magisk)

Интересный факт: начиная с android 10 в системе появилась служба APEX отвечающая за более простой подход к доставке обновлений системных компонентов. Её идея в том, чтобы добавить в android возможность выборочно доставлять обновления частей системы: добавлять новые и заменять существующие системные библиотеки и части android фреймворка, и главное делать это небольшими пакетами, без необходимости загружать и устанавливать полные образы всех разделов целиком. Более того всё это ложится в стандартную модель управления пакетами в android. То есть идея в том, что это нечто вроде apk, но не для приложений, а для самой ОС. Это критически важно для безопасности, например для того, чтобы в случае обнаружения какой-нибудь новой серьёзной уязвимости в системной библиотеке, как это например случалось с libstagefright когда 95% устройств на рынке были подвержены уязвимости, а обновления до многих устройств шли долгие месяцы, Google мог в течение нескольких часов доставить обновление с заплаткой на 100% устройств которые поддерживают apex. Иронично то, что этот механизм ну очень сильно похож по принципу действия на работу модулей magisk, и на то, как они монтируются поверх системы через зеркала. Я могу только предполагать это, но не исключено, что ребята, которые игрались с безопасностью android устройств и «хакали» их по фану, вдохновили своими подходами системных разработчиков android, которые построили на этом систему обновлений, которая сделает каждое из наших устройств неприступнее для злоумышленников. По-моему, это прекрасно. 

Возвращаясь к magisk, особенностью такого подхода является то, что magisk создаёт множество лишних точек монтирования, особенно если установлено много magisk-модулей.

$ cat /proc/mounts | grep magisk                                                                                             
/sbin/.magisk/block/system /sbin/.magisk/mirror/system ext4 ro,seclabel,relatime,block_validity,discard,delalloc,barrier,user_xattr 0 0
/sbin/.magisk/block/vendor /sbin/.magisk/mirror/vendor ext4 ro,seclabel,relatime,block_validity,discard,delalloc,barrier,user_xattr 0 0
/sbin/.magisk/block/data /sbin/.magisk/mirror/data ext4 rw,seclabel,relatime,discard,noauto_da_alloc,data=ordered 0 0
/sbin/.magisk/block/data /sbin/.magisk/modules ext4 rw,seclabel,relatime,discard,noauto_da_alloc,data=ordered 0 0

Можно поискать в файловой системе файлы, содержащие в названии magisk, и обнаружить исполняемые файлы:

$ find / -name "magisk" 2>/dev/null
/sbin/magiskpolicy
/sbin/magiskhide
/sbin/magisk
/sbin/magiskinit
/sbin/.magisk

Ещё больше можно увидеть с root-правами:

$ su                                                                                                                         
# find / -name "*magisk*" 2>/dev/null
/storage/emulated/0/Android/data/com.topjohnwu.magisk
/storage/emulated/0/Android/media/com.topjohnwu.magisk
/sbin/magiskpolicy
/sbin/magiskhide
/sbin/magisk
/sbin/magiskinit
/sbin/.magisk
/sbin/.magisk/mirror/data/system/package_cache/1/com.topjohnwu.magisk-DkH9A9_cUz6YvCX-YbQs4Q==-0
/sbin/.magisk/mirror/data/system/graphicsstats/1612051200000/com.topjohnwu.magisk
/sbin/.magisk/mirror/data/system/graphicsstats/1611964800000/com.topjohnwu.magisk
/sbin/.magisk/mirror/data/misc/profiles/cur/0/com.topjohnwu.magisk
/sbin/.magisk/mirror/data/misc/profiles/ref/com.topjohnwu.magisk
/sbin/.magisk/mirror/data/user_de/0/com.topjohnwu.magisk
/sbin/.magisk/mirror/data/magisk_backup_5063aa326352068974a1a161a798cd606e05dd12
/sbin/.magisk/mirror/data/app/com.topjohnwu.magisk-DkH9A9_cUz6YvCX-YbQs4Q==
/sbin/.magisk/mirror/data/data/com.topjohnwu.magisk
/sbin/.magisk/mirror/data/adb/magisk.db
/sbin/.magisk/mirror/data/adb/magisk
/sbin/.magisk/mirror/data/adb/magisk/magiskinit64
/sbin/.magisk/mirror/data/adb/magisk/magiskboot
/sbin/.magisk/mirror/data/adb/magisk/magiskinit
/sbin/.magisk/mirror/data/media/0/Android/data/com.topjohnwu.magisk
/sbin/.magisk/mirror/data/media/0/Android/media/com.topjohnwu.magisk
/mnt/runtime/write/emulated/0/Android/data/com.topjohnwu.magisk
/mnt/runtime/write/emulated/0/Android/media/com.topjohnwu.magisk
/mnt/runtime/read/emulated/0/Android/data/com.topjohnwu.magisk
/mnt/runtime/read/emulated/0/Android/media/com.topjohnwu.magisk
/mnt/runtime/default/emulated/0/Android/data/com.topjohnwu.magisk
/mnt/runtime/default/emulated/0/Android/media/com.topjohnwu.magisk
/data/system/package_cache/1/com.topjohnwu.magisk-DkH9A9_cUz6YvCX-YbQs4Q==-0
/data/system/graphicsstats/1612051200000/com.topjohnwu.magisk
/data/system/graphicsstats/1611964800000/com.topjohnwu.magisk
/data/misc/profiles/cur/0/com.topjohnwu.magisk
/data/misc/profiles/ref/com.topjohnwu.magisk
/data/user_de/0/com.topjohnwu.magisk
/data/magisk_backup_5063aa326352068974a1a161a798cd606e05dd12
/data/app/com.topjohnwu.magisk-DkH9A9_cUz6YvCX-YbQs4Q==
/data/data/com.topjohnwu.magisk
/data/adb/magisk.db
/data/adb/magisk
/data/adb/magisk/magiskinit64
/data/adb/magisk/magiskboot
/data/adb/magisk/magiskinit
/data/media/0/Android/data/com.topjohnwu.magisk
/data/media/0/Android/media/com.topjohnwu.magisk
/config/sdcardfs/com.topjohnwu.magisk
/cache/magisk.log

Ещё magisk добавляет права на запись в некоторые места файловой системы, где этих прав явно быть не должно, и некоторые приложения для обнаружения root-прав обнаруживают его из-за этого.

Вообще-то, magisk очень хорошо умеет прятаться от других процессов с помощью сервиса MagiskHide, который умеет прятать все точки монтирования и даже заменять некоторые свойства в системе, однако от человека, который будет исследовать файловую систему устройства, особенно до загрузки системы, спрятаться не получится. Поэтому технически подкованный пользователь быстро обнаружит наличие magisk на своём устройстве. Для наших целей это не подходит, т.к. если мы будем обнаружены, данные будет не извлечь.

Это значит, что вместо грубой установки magisk нужно поступить красиво – необходимо разобраться с тем, как именно он закрепляется в системе и как заставляет init загрузить ненастоящие политики SELinux.

План таков: мы возьмём исходники magisk и соберём из них инструмент, который будет внедрять в систему всемогущий контекст u:r:magisk:s0, но больше не будет делать ничего. То есть наша задача сводится к тому, чтобы вместо magisk установить на устройство только политики magisk.

Для начала нам нужно понять как именно magisk внедряется в систему. Суть установки magisk в следующем:

  • Установщик находит среди разделов на диске раздел boot

  • Дампит раздел boot через nanddump в файл-образ и распаковывает его 

  • Извлекает из него образ ramdisk

  • Заменяет в образе ramdisk оригинальный исполняемый файл init на свой, заранее подготовленный – magiskinit

  • Складывает оригинальный ramdisk с оригинальным init в бэкап который ложится рядом

  • Если необходимо применяет дополнительные патчи, которые зависят от устройств и версии android

  • Запаковывает образ boot раздела и прошивает его на место оригинального boot

  • Бэкапит оригинальный boot раздел в /data

По окончанию мы получаем раздел boot, в котором во время запуска системы, между тем как ядро вызывает запуск процесса init и реальным запуском процесса init появляется окно, в котором magisk подготавливает всё необходимое для своей работы во время уже запущенной системы. 

Если очень грубо, то работает это так: magiskinit запускается, находит файл с политиками, патчит его добавляя в него политики необходимые для работы magisk после запуска системы, добавляет в init.rc записи которые запустят сервис magiskd во время загрузки системы после чего запускает оригинальный init, который загружает уже пропатченные политики вместо оригинальных, а далее загрузка происходит привычным образом. На деле, в этом процессе есть огромное множество нюансов.

Во-первых, ramdisk у нас доступен только на чтение. Мы не можем взять и переписать boot раздел по своему желанию, и применить изменения на постоянной основе, поэтому все манипуляции над файлами выполняются в ОЗУ, заново, при каждом запуске системы. 

Во-вторых, начиная с android 9, и далее в 10 и 11 очень сильно менялся подход к организации файловой системы во время работы устройства, организации хранения файла с политиками и вообще самого процесса запуска.

До android 9 скомпилированные политики SELinux всегда упаковывались в boot раздел и лежали прямо рядом с ядром, затем появился механизм split-policy, когда для каждого из основных разделов (system, vendor, иногда бывает ещё product), политики компилируются и хранятся отдельно. 

Для magiskinit это значит то, что при запуске ему нужно смонтировать все эти разделы, собрать оттуда отдельные файлы с политиками, распарсить, упаковать и сложить в единый файл, найти ему место в файловой системе (которое тоже зависит от многих факторов и версии android), после чего брутально пропатчить прямо в бинарном виде исполняемый файл init – найти место где в условной конструкции выбирается тип политики, принудительно заменить его со split-policy на mono-policy и заменить путь к файлу с политиками на тот что был получен в предыдущем шаге.

Бинарного файла init, пригодного для модификации может и не быть, потому что на некоторых устройствах есть 2SI – two-stage-init или двухэтапный запуск init. Это подход, в котором исходный init файл из ramdisk не запускает систему, вместо этого он монтирует раздел с системой и уже из него запускает /system/bin/init. В этом случае magiskinit придётся не менее брутально прямо в бинарном виде патчить libselinux в системном разделе.

А есть ещё в android подход system-as-root, который обязателен для сборок android 10+. От него зависит что именно будет корнем файловой системы ramdisk или system. И оба этих случая magiskinit обязан учитывать. А ещё в некоторых условиях на некоторых устройствах ramdisk может вообще отсутствовать.

Если интересно узнать подробнее, то разработчик magisk очень хорошо и доступно описал как устроены все эти хитросплетения с запуском init. Я полагаю, что для разработчиков magisk это чистая боль, если раньше процесс загрузки был достаточно единообразным и бесхитростным, то теперь разработчики magisk тратят огромные усилия чтобы подстраиваться под это, а учитывая темп выхода новых версий android, делать это очень непросто.

Тем не менее, для нашей задачи внутреннее устройство magiskinit интересует нас только для того, чтобы понять, как именно внедрить нужные нам изменения в скомпилированные политики и отбросить всё остальное. 

В исходниках нас будут интересовать в основном только файлы из директории init. Вкратце, список изменений которые были внесены в код:

  • В методе main() в init.cpp удаляем вызовы методов dumpmagisk() и dumpmanager().

  • В init.hpp обратим внимание на вызовы execinit() – это вызовы оригинального init. Перед ними во всех случаях кроме FirstStageInit добавим rmrf(«/.backup») чтобы скрыть соответствующую директорию, которая будет торчать в файловой системе работающего устройства. В FirstStageInit этого делать не нужно, т.к. этот вызов всё равно будет совершён во время второй стадии init.

  • В mount.cpp нас будет интересовать метод setuptmp() который отвечает за создание tmpfs в файловой системе где будут храниться линки на исполняемые файлы magisk. Обычно в файловой системе это директория /sbin. Мы можем полностью удалить этот вызов для RootFSInit, т.к. моно-файл с политиками SELinux в этом случае находится прямо в ramdisk, и патчится прямо там же, но в андроид 10 и выше с приходом механизма split-policy именно туда в единый файл будут складываться собранные из всех разделов политики, поэтому нам, похоже, обязательно придётся её оставить, но мы можем вынести её в /dev. Начиная с android 11 этот подход становится в magisk основным, т.к. с android 11 наличие директории /sbin в файловой системе не гарантируется. Меняем режим tmpfs с 755 на 700 чтобы содержимое не мог посмотреть непривилегированный пользователь и не сработали root-чекеры которые проверяют наличие доступа на запись в подозрительных местах. Удаляем создание файлов и линков magisk в tmpdir. У меня не получилось полностью избавиться от tmpdir в android 10+ и сохранить работоспособность системы, но оно вроде бы и не проблема. Прочитать tmpfs без рута не получится, а название служебной директории можно поменять с .magisk на любое случайное.

  • В rootdir.cpp удаляем код который патчит init.rc для запуска демона magisk в системе 

Дополнительно:  Как бороться с чёрным экраном после обновления до Windows 10?

На выходе получаем нечто, что можно назвать magisk без magisk. Он будет патчить политики SELinux до запуска init, подкладывая туда всемогущий контекст u:r:magisk:s0, но на этом всё – никакого функционала root-прав и всего такого прочего.

Теперь можно начинать.

Как устроено шифрование хранилища

Для начала разберёмся как устроено шифрование хранилища, потому что это самое труднопреодолимое препятствие для изъятия данных. Шифрование применяется на уровне файловой системы. Существует два основных подхода к организации шифрования:

  • FDE – full-device-encryption – полнодисковое шифрование. Это значит что всё устройство хранения зашифровано, и даже загрузка операционной системы невозможна до его расшифровки. Поэтому в этом случае владельцу устройства для работы с ним сначала, ещё до загрузки операционной системы, необходимо ввести ключ с помощью которого хранилище будет расшифровано и только потом произойдёт загрузка системы. Такой подход требовал «двойной» загрузки системы, сначала в минимальном варианте для показа формы ввода пароля, а потом в полноценном, после расшифровки хранилища. Он применялся на некоторых устройствах с версиями android 5-7, однако на современной версии ОС и современных устройствах не используется

  • FBE – file-based-encryption – шифрование отдельных частей файловой системы. Применительно к android зашифрованы только те части системы, где хранятся данные пользователя. Незашифрованным остаются ядро, системный раздел, и т.д. Строго говоря, проще перечислить то, что зашифровано, а зашифрованы только /data/data и /data/media. Все остальные части системы остаются незашифрованными. Это позволяет операционной системе успешно загружаться до экрана авторизации пользователя, стартовать системные сервисы и accessibility сервисы, принимать SMS. Начиная с android 7 и переходом на FBE появилось Directboot API, которое даёт приложениям возможность запускаться и в ограниченном режиме работать до ввода кода разблокировки и расшифровки файловой системы. FBE позволяет сочетать высокие стандарты защиты данных в хранилище и отличный пользовательский опыт. Пользователь не отвлекается на ввод дополнительного пароля до запуска системы, система не тратит ресурсы на шифрование и расшифровку частей файловой системы где не содержится личных данных владельца. Это современный подход, который используется на современных устройствах и является обязательным для всех новых устройств, начиная с android 9.

  • BFU (before first unlock) – до первого ввода кода разблокировки

  • AFU (after first unlock) – после первого ввода кода разблокировки

До первого ввода кода разблокировки данные пользователя всегда зашифрованы, после первого ввода – всегда расшифрованы. Даже когда пользователь в дальнейшем нажимает кнопку питания или система сама уходит в сон, то данные, с точки зрения ОС, больше не переходят в зашифрованное состояние, теперь их защищает только экран блокировки. В android пока не предусмотрен механизм очищения ключей для расшифровки файловой системы из памяти после определённого периода неактивности.

Это значит, что если после того, как устройство было разблокировано хотя бы однажды подключиться к нему, например по adb, то пользовательские данные будут доступны и можно получить к ним доступ даже если экран заблокирован.

Вот так выглядит общее хранилище до первого ввода кода разблокировки:

# ls -la /data/media/0/                                                                                                                                  
total 100
drwxrwx--- 13 media_rw media_rw 4096 2021-01-29 10:45 .
drwxrwx---  4 media_rw media_rw 4096 2021-01-29 10:43 ..
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 3aIg6706qnt+JRerXQc,9B
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 5RxSnwRfzXH5JsgykyuneB
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 9QCg2626EAEHNRc,IpjzjC
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 XLrhnulSzxYVPwgkHhs8YC
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:45 ZC6kM5uXi6,coHL+OYgLCB
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 kJJ0DN8Tmhcs7hicwcEZ3A
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 mPaCm6PJHF9,MyimVTRozC
drwxrwxr-x  3 media_rw media_rw 4096 2021-01-29 10:43 qIkgta78EOvsfnjupFXQ+C
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 uAP,C13tjXpxdP8PWVeMRD
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 v33cOjp,wu+hlgBIWnQdjB
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 xxjD9tk7bDh9XZUzoDwMbB

А вот так после:

# ls -la /data/media/0/                                                                                                                                   
total 100
drwxrwx--- 13 media_rw media_rw 4096 2021-01-29 10:45 .
drwxrwx---  4 media_rw media_rw 4096 2021-01-29 10:43 ..
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 Alarms
drwxrwxr-x  3 media_rw media_rw 4096 2021-01-29 10:43 Android
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 DCIM
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 Download
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 Movies
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 Music
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 Notifications
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 Pictures
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 Podcasts
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:43 Ringtones
drwxrwxr-x  2 media_rw media_rw 4096 2021-01-29 10:45 bluetooth

На самом деле, шифрование хранилища – самая трудно преодолимая часть нашей задачи по извлечению данных. Обойти шифрование с ключами в аппаратном ТЕЕ никак не представляется возможным, но зато из этой информации следует несколько полезных выводов, а наша задача начинает формироваться в конкретные технические требования.

  • Во-первых, получать доступ к личным файлам пользователя в любом случае придётся только после ввода кода разблокировки. 

  • Во-вторых, разблокированный загрузчик даёт возможность переписывать системный раздел, а он никогда не зашифрован, даже в BFU состоянии.

Это подводит нас к мысли о том, что можно воспользоваться возможностью модифицировать никогда не шифруемую часть системы, внедрить в неё агента, который никак не затронет и не испортит зашифрованные данные, а в последствие даст нам доступ к ней после того как устройство будет возвращено пользователю и он сам первый раз введёт код разблокировки. Поскольку пользователь явно не будет вводить код разблокировки подключив устройство к нашему usb кабелю или находясь в нашей локальной сети, то использование adb нам не подходит и необходимо организовать удалённый доступ в формате reverse-shell.

Нелегальные root-права и magisk

Я назвал предыдущий подход к получению root-прав «легальным», потому что всё необходимое для этого было намеренно заложено в систему на этапе сборки. Совершенно иной подход использует инструмент для получения root-прав magisk, ставший, де-факто, стандартным инструментом для этих целей в сообществе любителей android. Magisk устанавливается на любые устройства, на любые сборки, на любые версии android, и не только на отладочные варианты сборки, но и на релизные, и даже на те устройства, на которых применяются дополнительные защиты от несанкционированного получения root-прав. Magisk по полной эксплуатирует разблокированный загрузчик и, по сути, совершает настоящий изощрённый взлом за что и будем его называть его «нелегальным». Для нас наиболее важно то, что magisk, по сути, делает всё тоже что хотим сейчас сделать мы. Он закрепляется в системе также, как хотим закрепиться мы, а значит, похоже, что нам с ним по пути.

Для начала постараемся выяснить как magisk получает root-права в рантайме. На устройстве выполняем:

$ adb shell
$ id
uid=2000(shell) gid=2000(shell) groups=2000(shell),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc),3011(uhid) context=u:r:shell:s0
$ su
# id
uid=0(root) gid=0(root) groups=0(root) context=u:r:magisk:s0
# ps -Zef
LABEL                          UID            PID  PPID C STIME TTY          TIME CMD
u:r:init:s0                    root             1     0 1 09:17 ?        00:00:01 init
u:r:magisk:s0                  root           658     1 0 09:24 ?        00:00:00 magiskd
u:r:zygote:s0                  root           695     1 1 09:24 ?        00:00:01 zygote64
u:r:zygote:s0                  root           696     1 0 09:24 ?        00:00:00 zygote
u:r:adbd:s0                    shell          956     1 1 09:25 ?        00:00:01 adbd --root_seclabel=u:r:su:s0
u:r:platform_app:s0:c512,c768  u0_a39        2800   695 4 09:35 ?        00:00:07 com.android.systemui
u:r:priv_app:s0:c512,c768      u0_a120       3909   695 1 10:26 ?        00:00:01 com.android.launcher3
u:r:untrusted_app:s0:c113,c25+ u0_a113       5218   695 1 10:48 ?        00:00:00 com.topjohnwu.magisk
u:r:shell:s0                   shell         5473   956 0 10:56 pts/0    00:00:00 sh -
u:r:magisk_client:s0           shell         5602  5473 0 10:59 pts/0    00:00:00 su
u:r:magisk_client:s0:c113,c25+ u0_a113       5629  5218 0 10:59 ?        00:00:00 su --mount-master
u:r:magisk:s0                  root          5633   658 0 10:59 ?        00:00:00 busybox sh
u:r:magisk:s0                  root          5708   658 0 11:02 pts/1    00:00:00 sh
u:r:magisk:s0                  root          5795  5708 7 12:49 pts/1    00:00:00 ps -Zef

Я сократил вывод команды ps только до тех значений, которые нас интересуют.

Во-первых, мы видим что magisk имеет специальный контекст для своих процессов — u:r:magisk:s0. Наша оболочка с root-правами имеет терминал pts/1 и запущена с этим контекстом. Это явно не встроенный в систему контекст, а значит magisk смог его отредактировать и внедрить дополнительные правила перед запуском процесса init. Поскольку, наши root-права работают, и мы действительно можем делать в системе всё что угодно, контекст u:r:magisk:s0 должен иметь как минимум все те же разрешения, которые прописаны для u:r:su:s0, а может и больше.

Во-вторых, magisk имеет свой демон запущенный в системе – magiskd, он породил наш процесс с рутовым шеллом, а значит именно через него magisk и даёт другим процессам доступ с оболочке с root-правами, этот демон (PID 658) порождён процессом init (PPID 1), т.е. запущен как системный сервис. Демон также работает в контексте u:r:magisk:s0.

Мы подключились по adb и получили шелл на устройстве, терминал pts/0. Видно что процесс sh имеет контекст u:r:shell:s0, PID 5473 и PPID 956 который равен значению PID adbd, а сам adbd уже был порождён процессом init.

Мы вызываем исполняемый файл su и видим что его контекст – u:r:magisk_client:s0, следовательно magisk использует отдельный контекст для того, чтобы знать какие именно исполняемые файлы могут запрашивать доступ к root-правам. Исполняемый файл провоцирует открытие окна подтверждения доступа к root-правам для обычного shell, оно находится в пакете приложения MagiskManager — com.topjohnwu.magisk, получив результатат magiskd (PID 658) породил нам нашу оболочку sh с новым терминалом pts/1 (PID 5708, PPID 658), от которой мы отнаследовали и пользователя root (uid=0), и всемогущий контекст u:r:magisk:s0. 

Для нас интересно вот что: если init запускается со своим ограниченным со всех сторон контекстом u:r:init:s0 из которого разрешены transition’ы только в прописанные в *.te файлах контексты для системных служб, а демон маджиска имеет контекст u:r:magisk:s0, значит magisk смог внедрить правило разрешающее прямой transition из u:r:init:s0 в u:r:magisk:s0. Это значит что и мы можем использовать контекст u:r:magisk:s0 в своём сервисе!

Root

Так называемый root-доступ — это возможность выполнять код от имени «пользователя root» (UID 0, также известного как суперпользователь). Напомню, что root — это специально выделенный Unix-пользователь, которому — за несколькими интересными исключениями — разрешён полный доступ ко всему в системе, и на которого не распространяются никакие ограничения прав.

Как и большинство других современных операционных систем, Android спроектирован с расчётом на то, что обыкновенному пользователю ни для чего не требуется использовать root-доступ. В отличие от более закрытых операционных систем, пользователи которых называют разрушение наложенных на них ограничений буквально «побегом из тюрьмы», в Android прямо «из коробки», без необходимости получать root-доступ и устанавливать специальные сторонние «твики», есть возможность:

  • устанавливать произвольные приложения — как из множества существующих магазинов приложений, так и произвольные APK из любых источников,
  • выбирать приложения по умолчанию: веб-браузер, email-клиент, камеру, файловый менеджер, лончер, приложение для звонков, приложение для СМС, приложение для контактов и так далее (это работает за счёт красивой системы activity и intent’ов, которую я описал в прошлой статье),
  • устанавливать и использовать пакеты значков (icon packs),
  • получать прямой доступ к файловой системе, хранить в ней произвольные файлы,
  • подключать устройство к компьютеру (или даже к другому Android-устройству) и напрямую передавать между ними файлы по кабелю,
  • и даже, во многих дистрибутивах Android, настраивать цвета и шрифты системной темы.

Итак, для обыкновенных задач, с которыми может столкнуться простой пользователь, root-доступ не нужен. В то же время использование root-доступа неизбежно сопряжено со многими проблемами с безопасностью (подробнее о них ниже), поэтому в большинстве случаев Android не позволяет пользователю работать от имени root. Приложения, в том числе эмуляторы терминалов, выполняются от имени своих ограниченных Unix-пользователей; а shell, который запускается при использовании команды adb shell, работает от имени специально для этого предназначенного Unix-пользователя shell.

Тем не менее, бывает, что root доступен пользователю:

  • Во-первых, root-доступ обычно разрешён на сборках Android для эмуляторов — поставляемых вместе с Android Studio виртуальных машин на базе QEMU, которые изображают реальные устройства и обычно используются разработчиками для отладки и тестирования приложений.
  • Во-вторых, root-доступ включён по умолчанию во многих сторонних дистрибутивах Android (pre-rooted ROMs).
  • В-третьих, при разблокированном bootloader’е root-доступ на любой сборке Android можно включить напрямую, просто установив исполняемый файл, реализующий команду su, через bootloader.
  • Ну и наконец, в-четвёртых, на старых версиях системы, содержащих известные уязвимости, часто можно получить root-доступ, проэксплуатировав их (обычно для получения root-доступа эксплуатируются сразу несколько уязвимостей в разных слоях и компонентах системы). Например, если система не обновлялась с первой половины 2016 года, для получения root-доступа можно воспользоваться получившей широкую известность уязвимостью Dirty Cow.
Дополнительно:  English Community-Lenovo Community

Зачем это может быть нужно? Конечно, root-доступ полезен для отладки и исследования работы системы. Кроме того, обладая root-доступом, можно неограниченно настраивать систему, изменяя её темы, поведение и многие другие аспекты. Можно принудительно подменять код и ресурсы приложений — например, можно удалить из приложения рекламу или разблокировать его платную или скрытую функциональность. Можно устанавливать, изменять и удалять произвольные файлы, в том числе в разделе system (хотя это почти наверняка плохая идея).

С более философской точки зрения, root-доступ позволяет пользователю полностью контролировать своё устройство и свою систему — вместо того, чтобы устройство и разработчики ПО контролировали пользователя.

Проблемы root-доступа

With great power comes great responsibility.

С большими возможностями приходит и большая ответственность, и совсем не всегда эту ответственность можно доверить пользователю и приложениям. Другими словами, с root-доступом связано большое количество проблем в области безопасности и стабильности системы.

Напомню, что пользователь почти всегда взаимодействует с устройством не напрямую, а через приложения. А это значит, что и root-доступ он будет использовать в основном через приложения, которые, можно надеяться, будут добросовестно пользоваться root-доступом для хороших целей. Но если на устройстве доступен root, это, в принципе, означает, что приложения могут воспользоваться им и для нехороших целей — навредить системе, украсть ценные данные, заразить систему вирусом, установить кейлоггер и т.п.

Как я уже говорил во второй статье серии, в отличие от традиционной модели доверия программам в классическом Unix, Android рассчитан на то, что пользователь не может доверять сторонним приложениям — поэтому их и помещают в песочницу. Тем более нельзя доверять приложениям root-доступ, надеясь, что они будут использовать его только во благо. Фактически, root-доступ разрушает аккуратно выстроенную модель безопасности Android, снимая с приложений все ограничения и открывая им права на доступ ко всему в системе.

С другой стороны, и приложения не могут доверять устройству, на котором подключен root-доступ, поскольку на таком устройстве в его работу имеют возможность непредусмотренными способами вмешиваться пользователь и остальные приложения. Например, разработчики приложения, содержащего платную функциональность, естественно, не захотят, чтобы проверку совершения покупки можно было отключить. Многие приложения, работающие с особенно ценными данными — например, Google Pay (бывший Android Pay) — явно отказываются работать на устройствах с root-доступом, считая их недостаточно безопасными.

Аналогично, онлайн-игры вроде популярной Pokémon GO тоже отказываются работать на root-ованных устройствах, опасаясь, что игрок сможет с лёгкостью нарушить правила игры.

Google Play и root-доступ

Несмотря на ограничение для производителей, Google разрешает пользователям сборок, в которых разрешён root-доступ, самостоятельно устанавливать Google Play (обычно это делается путём установки готовых пакетов от проекта Open GApps); при этом Google требует, чтобы после установки пользователь вручную зарегистрировал устройство на специально предназначенной для этого странице регистрации.

Device manufacturers work with Google to certify that Android devices with Google apps installed are secure and will run apps correctly. To be certified, a device must pass Android compatibility tests. If you are unable to add a Google Account on your Android device, your Android device software might not have passed Android compatibility tests, or the device manufacturer has not submitted the results to Google to seek approval. As a result, your device is uncertified. This means that your device might not be secure.

Ограничение доступа к root

В «обычном» Linux, как и в других Unix-системах, для получения root-доступа (с помощью команд sudo и su) пользователю требуется ввести пароль (или свой, или пароль Unix-пользователя root, в зависимости от настройки системы и конкретной команды) или авторизоваться другим способом (например, с помощью отпечатка пальца). В дополнение к собственно авторизации это служит подтверждением того, что пользователь доверяет этой программе и согласен предоставить ей возможность воспользоваться root-доступом.

Стандартная версия команды su из Android Open Source Project не запрашивает подтверждения пользователя явно, но она доступна только Unix-пользователю shell (и самому root), что полностью отрезает приложениям возможность получать root-права. Многие сторонние реализации su позволяют любому приложению получать права суперпользователя, что, как я объяснил выше, очень плохо в плане безопасности.

В качестве компромисса многими пользователями используются специальные программы, которые управляют доступом к su. При попытке приложения вызвать su они запрашивают подтверждение у пользователя, который может разрешить или запретить приложению получить root-доступ. Несколько лет назад была популярна одна из таких программ, SuperSU; в последнее время её вытеснил новый открытый проект под названием Magisk.

Magisk

Основная особенность Magisk — возможность проведения широкого класса модификаций системы, в том числе связанных с изменением файлов, расположенных в папке /system, на самом деле не изменяя сам раздел system (это называют словом systemless-ly), путём использования нескольких продвинутых «фич» Linux. В сочетании с Magisk Hide — возможностью не просто не давать некоторым приложениям root-доступ, а полностью прятать от них сам факт наличия root-доступа и установки Magisk в системе — это позволяет устройству по-прежнему получать обновления системы от производителя и использовать приложения вроде того же Google Pay, которые отказываются работать на root-ованных системах. Magisk Hide способен обходить даже такие достаточно продвинутые технологии обнаружения root-а, как SafetyNet от Google.

Хотя это действительно невероятно круто, нужно осознавать, что возможность использовать приложения вроде Google Pay на root-ованных устройствах несмотря на встроенные в них проверки — это не решение связанных с root-доступом проблем с безопасностью. Приложения, которым пользователь доверил root-доступ, по-прежнему могут решить вмешаться в работу системы и украсть деньги пользователя через Google Pay. Проблемы с безопасностью остаются, нам лишь удаётся закрыть на них глаза.

Magisk поддерживает systemless-ную установку готовых системных «твиков» в виде Magisk Modules, специальных пакетов для установки с использованием Magisk. В таком виде доступны, например, известный root-фреймворк Xposed и ViPER, набор продвинутых драйверов и настроек для воспроизведения звука.

Опасности root-прав

Не буду утверждать, что root-права это безопасно — любой необкатанный magisk модуль может привести систему в нерабочее состояние, при неумелом редактировании системных файлов система также придет в негодность, а функционал программ, запрашивающих root не всегда прозрачен. Не давайте root права приложениям, которым, по вашему мнению, они не нужны! Периодически такие права запрашивают Яндекс Карты, статистики ради или для чего-то еще — неизвестно, но проверять не хочется.

Говорить о том, что наличие root как-то сильно ослабляет защиту системы тоже не буду — доступ вполне себе контролируемый, и если вы будете соблюдать элементарную цифровую гигену, никаких проблем не будет.

Самое опасное, наверное — потеря гарантии производителя, что логично.

Мой набор Magisk-модулей

  • Busybox — дает доступ приложениям к встроенному busybox от Magisk

  • No Storage Restricts — убирает ограничения в выборе папок в файловом менеджере

  • LuckyPatcher — его модуль нужен для переноса приложений в системный раздел

  • Move Certificates — перенос пользовательских сертификатов в систему

  • NFC Screen Off — работа NFC при выключенном экране

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Получали ли вы Root на своем устройстве?

Нет и не планирую

Нет, но хочу попробовать

Да, но сейчас не имею

Да, пользуюсь root-правами

Проголосовали 127 пользователей.

Воздержались 9 пользователей.

Как устроено хранилище

Если мы посмотрим на структуру разделов на хранилище смартфона, то увидим что их на устройстве довольно много. 

# ls -la /dev/block/by-name                                                                                                                                    
total 0
drwxr-xr-x 2 root root 1480 1973-02-10 03:40 .
drwxr-xr-x 4 root root 2160 1973-02-10 03:40 ..
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 LOGO -> /dev/block/sde18
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 abl -> /dev/block/sde16
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 ablbak -> /dev/block/sde17
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 apdp -> /dev/block/sde31
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 bluetooth -> /dev/block/sde24
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 boot -> /dev/block/sde19
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 boot_aging -> /dev/block/sde20
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 cache -> /dev/block/sda3
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 cdt -> /dev/block/sdd2
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 cmnlib -> /dev/block/sde27
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 cmnlib64 -> /dev/block/sde29
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 cmnlib64bak -> /dev/block/sde30
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 cmnlibbak -> /dev/block/sde28
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 config -> /dev/block/sda12
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 ddr -> /dev/block/sdd3
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 devcfg -> /dev/block/sde39
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 devinfo -> /dev/block/sde23
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 dip -> /dev/block/sde14
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 dpo -> /dev/block/sde33
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 dsp -> /dev/block/sde11
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 frp -> /dev/block/sda6
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 fsc -> /dev/block/sdf4
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 fsg -> /dev/block/sdf3
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 fw_4g9n4 -> /dev/block/sde45
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 fw_4j1ed -> /dev/block/sde43
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 fw_4t0n8 -> /dev/block/sde46
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 fw_8v1ee -> /dev/block/sde44
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 hyp -> /dev/block/sde5
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 hypbak -> /dev/block/sde6
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 keymaster -> /dev/block/sde25
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 keymasterbak -> /dev/block/sde26
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 keystore -> /dev/block/sda5
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 limits -> /dev/block/sde35
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 logdump -> /dev/block/sde40
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 logfs -> /dev/block/sde37
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 md5 -> /dev/block/sdf5
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 mdtp -> /dev/block/sde15
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 mdtpsecapp -> /dev/block/sde12
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 mdtpsecappbak -> /dev/block/sde13
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 minidump -> /dev/block/sde47
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 misc -> /dev/block/sda4
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 modem -> /dev/block/sde10
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 modemst1 -> /dev/block/sdf1
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 modemst2 -> /dev/block/sdf2
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 msadp -> /dev/block/sde32
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 oem_dycnvbk -> /dev/block/sda7
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 oem_stanvbk -> /dev/block/sda8
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 param -> /dev/block/sda9
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 persist -> /dev/block/sda2
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 pmic -> /dev/block/sde8
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 pmicbak -> /dev/block/sde9
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 recovery -> /dev/block/sde22
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 reserve -> /dev/block/sdd1
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 reserve1 -> /dev/block/sda10
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 reserve2 -> /dev/block/sda11
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 reserve3 -> /dev/block/sdf7
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 rpm -> /dev/block/sde1
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 rpmbak -> /dev/block/sde2
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 sec -> /dev/block/sde7
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 splash -> /dev/block/sde34
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 ssd -> /dev/block/sda1
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 sti -> /dev/block/sde38
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 storsec -> /dev/block/sde41
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 storsecbak -> /dev/block/sde42
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 system -> /dev/block/sde21
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 toolsfv -> /dev/block/sde36
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 tz -> /dev/block/sde3
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 tzbak -> /dev/block/sde4
lrwxrwxrwx 1 root root   16 1973-02-10 03:40 userdata -> /dev/block/sda13
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 vendor -> /dev/block/sdf6
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 xbl -> /dev/block/sdb1
lrwxrwxrwx 1 root root   15 1973-02-10 03:40 xblbak -> /dev/block/sdc1

Большинство из них небольшие, и содержат, например, логотип производителя девайса, который отображается сразу после подачи питания на плату устройства. Есть раздел содержащий прошивку, работающую на baseband процессоре и отвечающую за мобильную связь, звонки и интернет по стандартам 2G, 3G, LTE и т.д. Есть разделы содержащие BLOBы необходимые для работы с некоторыми устройствами. Нас будет интересовать всего несколько разделов, содержание которых монтируется в файловую систему и напрямую используется во время работы ОС:

  • Раздел boot содержит ядро операционной системы. 

  • Раздел system содержит саму операционную систему, все её исполняемые файлы, неизменяемые конфиги, системные библиотеки, android-фреймворк и другие jar файлы необходимые для работы виртуальной машины, в которой исполняются android приложения. Начиная с android 10 на многих устройствах вместо обычного system раздела используется раздел system-as-root, он отличается точкой монтирования, но его суть та же – он содержит файлы ОС.

  • Раздел vendor содержит проприетарные компоненты ОС от производителя устройства. Например, драйвера Qualcomm и т д

Дополнительно:  Why is crond failing to run a non-root crontab on alpine linux?

Данные приложений, «internal storage», находятся по пути /data/data. В этой директории сложены директории-песочницы отдельных приложений, соответствующие их полным именам пакетов. Например:

drwx------   8 u0_a69         u0_a69          4096 2021-01-29 13:31 com.google.android.youtube

Общее хранилище, «external storage», находится по пути /data/media/0, внешние SD-карты соответственно будут называться /data/media/1. Во время работы оно линкуется в /storage.

Файловая система

Устройство файловой системы — один из самых важных и интересных вопросов в архитектуре операционной системы, и устройство файловой системы в Android — не исключение.

Интерес представляет, во-первых, то, какие файловые системы используются, то есть в каком именно формате содержимое файлов сохраняется на условный диск (в случае Android это обычно flash-память и SD-карты) и как обеспечивается поддержка этого формата со стороны ядра системы. Ядро Linux, используемое в Android, в той или иной степени поддерживает большое количество самых разных файловых систем — от используемых в Windows FAT и NTFS и используемых в Darwin печально известной HFS+ и современной APFS — до сетевой 9pfs из Plan 9. Есть и много «родных» для Linux файловых систем — например, Btrfs и семейство ext.

Стандартом де-факто для Linux уже долгое время является ext4, используемая по умолчанию большинством популярных дистрибутивов Linux. Поэтому нет ничего неожиданного в том, что именно она и используется в Android. В некоторых сборках (и некоторыми энтузиастами) также используется F2FS (Flash-Friendly File System), оптимизированная специально для flash-памяти (впрочем, с её преимуществами всё не так однозначно).

Во-вторых, интерес представляет так называемый filesystem layout — расположение системных и пользовательских папок и файлов в файловой системе. Filesystem layout в «обычном Linux» заслуживает более подробного описания (которое можно найти, например, по этой ссылке); я упомяну здесь только несколько наиболее важных директорий:

  • /home хранит домашние папки пользователей; здесь же, в различных скрытых папках (.var.cache.config и других), программы хранят свои настройки, данные и кэш, специфичные для пользователя,
  • /boot хранит ядро Linux и образ initramfs (специальной загрузочной файловой системы),
  • /usr (логичнее было бы назвать /system) хранит основную часть собственно системы, в том числе библиотеки, исполняемые файлы, конфигурационные файлы, а также ресурсы — темы для интерфейса, значки, содержимое системного мануала и т.п.,
  • /etc (логичнее было бы назвать /config) хранит общесистемные настройки,
  • /dev хранит файлы устройств и другие специальные файлы (например, сокет /dev/log),
  • /var хранит изменяемые данные — логи, системный кэш, содержимое баз данных и т.п.

Android использует похожий, но заметно отличающийся filesystem layout. Вот несколько самых важных из его частей:

  • /data хранит изменяемые данные,
  • ядро и образ initramfs хранятся на отдельном разделе (partition) flash-памяти, который не монтируется в основную файловую систему,
  • /system соответствует /usr и хранит систему,
  • /vendor — аналог /system, предназначенный для файлов, специфичных для этой сборки Android, а не входящих в «стандартный» Android,
  • /dev, как и в «обычном Linux», хранит файлы устройств и другие специальные файлы.

Наиболее интересные из этих директорий — /data и /system. Содержимое /system описывает систему и содержит большинство составляющих её файлов. /system располагается на отдельном разделе flash-памяти, который по умолчанию монтируется в режиме read-only; обычно данные на нём изменяются только при обновлении системы. /data также располагается на отдельном разделе и описывает изменяемое состояние конкретного устройства, в том числе пользовательские настройки, установленные приложения и их данные, кэши и т.п. Очистка всех пользовательских данных, так называемый factory reset, при такой схеме заключается просто в очистке содержимого раздела data; нетронутая система остаётся установлена в разделе system.

# mount | grep /system
/dev/block/mmcblk0p14 on /system type ext4 (ro,seclabel,relatime,data=ordered)
# mount | grep /data
/dev/block/mmcblk0p24 on /data type ext4 (rw,seclabel,nosuid,nodev,noatime,noauto_da_alloc,data=writeback)

Приложения — а именно, их APK, файлы odex (скомпилированный ahead-of-time Java-код) и ELF-библиотеки — устанавливаются в /system/app (для приложений, поставляемых с системой) или в /data/app (для установленных пользователем приложений). Каждому предустановленному приложению при создании сборки Android выделяется папка с именем вида /system/app/Terminal, а для устанавливаемых пользователем приложений при установке создаются папки, имена которых начинаются с их имени пакета. Например, приложение YouTube сохраняется в папку с названием вроде /data/app/com.google.android.youtube-bkJAtUtbTuzvAioW-LEStg==/.

Про этот суффикс

Суффикс в названии папок приложений — 16 случайных байт, закодированных в Base64. Использование такого суффикса не позволяет другим приложениям «угадать» путь к приложению, о существовании которого им знать не следует. В принципе, список установленных на устройстве приложений и путей к ним не является секретом — его можно получить через стандартные API — но в некоторых случаях (а именно, для Instant apps) на доступ к этим данным накладываются ограничения.

Этот суффикс служит и другой цели. Каждый раз при обновлении приложения новая APK устанавливается в папку с новым суффиксом, после чего старая папка удаляется. До версии 8.0 Oreo в этом и состояло назначение суффиксов, и вместо случайных байт поочерёдно использовались -1 и -2 (например, /data/app/com.google.android.youtube-2 для YouTube).

Полный путь к папке приложения в /system/app или /data/app можно получить с помощью стандартного API или команды pm path org.example.packagename, которая выводит пути всех APK-файлов приложения.

# pm path com.android.egg
package:/system/app/EasterEgg/EasterEgg.apk

Поскольку предустановленные приложения хранятся в разделе system (содержимое которого, напомню, изменяется только при обновлении системы), их невозможно удалить (вместо этого Android предоставляет возможность их «отключить»). Тем не менее, поддерживается обновление предустановленных приложений — при этом для новой версии создаётся папка в /data/app, а поставляемая с системой версия остаётся в /system/app. В этом случае у пользователя появляется возможность «удалить обновления» такого приложения, вернувшись к версии из /system/app.

Другая особенность предустановленных приложений — они могут получать специальные «системные» разрешения. Например, сторонним приложениям не получить разрешения DELETE_PACKAGES, которое позволяет удалять другие приложения, REBOOT, позволяющего перезапустить систему, и READ_FRAME_BUFFER, позволяющего получить прямой доступ к содержимому экрана. Эти разрешения имеют уровень защиты signature, то есть приложение, пытающееся получить доступ к ним, должно быть подписано тем же ключом, что и приложение или сервис, в котором они реализованы — в этом случае, поскольку эти разрешения реализованы системой, ключом, которым подписана сама сборка Android.

# ls /data/data/com.google.android.youtube/
cache  code_cache  databases  files  lib  no_backup  shared_prefs
# cat /data/data/com.google.android.youtube/shared_prefs/youtube.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="user_account">username@gmail.com</string>
    <boolean name="h264_main_profile_supported7.1.2" value="true" />
    <int name="last_manual_video_quality_selection_max" value="-2" />
    <...>
</map>

Система знает о существовании папки cache и может очищать её самостоятельно при нехватке места. При удалении приложения вся папка этого приложения полностью удаляется, и приложение не оставляет за собой следов. Альтернативно, и то, и другое пользователь может явно сделать в настройках:

Это выделяемое каждому приложению хранилище данных называется внутренним хранилищем (internal storage).

Кроме того, в Android есть и другой тип хранилища — так называемое внешнее хранилище (external storage — это название отражает изначальную задумку о том, что внешнее хранилище должно было располагаться на вставляемой в телефон внешней SD-карте). По сути, внешнее хранилище играет роль домашней папки пользователя — именно там располагаются такие папки, как Documents, Download, Music и Pictures, именно внешнее хранилище открывают файловые менеджеры в качестве папки по умолчанию, именно к содержимому внешнего хранилища Android позволяет получить доступ компьютеру при подключении по кабелю.

# ls /sdcard
Alarms      Download        Podcasts
Android     Movies          Ringtones
Books       Music           Subtitles
DCIM        Notifications   bluetooth
Documents   Pictures

В отличие от внутреннего хранилища, разделённого на папки отдельных приложений, внешнее хранилище представляет собой «общую зону»: к нему есть полный доступ у любого приложения, получившего соответствующее разрешение от пользователя. Как я уже упоминал в прошлой статье, это разрешение стоит запрашивать таким приложениям, как файловый менеджер; а большинству остальных приложений лучше использовать intent с действием ACTION_GET_CONTENT, предоставляя пользователю возможность самому выбрать нужный файл в системном файловом менеджере.

Многие приложения предпочитают сохранять и некоторые из своих внутренних файлов, имеющие большой размер (например, кэш загруженных изображений и аудиофайлов) во внешнем хранилище. Для этого Android выделяет приложениям во внешнем хранилище папки с названиями вида Android/data/com.google.android.youtube. Самому приложению для доступа к такой папке не требуется разрешение на доступ ко всему внешнему хранилищу (поскольку в качестве владельца этой папки устанавливается его UID), но к этой папке может получить доступ любое другое приложение, имеющее такое разрешение, поэтому её, действительно, стоит использовать только для хранения публичных и некритичных данных. При удалении приложения система удалит и его специальную папку во внешнем хранилище; но файлы, созданные приложениями во внешнем хранилище вне их специальной папки считаются принадлежащими пользователю и остаются на месте после удаления создавшего их приложения.

Как я упомянул выше, исходно предполагалось, что внешнее хранилище действительно будет располагаться на внешней SD-карте, поскольку в то время объём SD-карт значительно превышал объём встраиваемой в телефоны памяти (в том же самом HTC Dream её было лишь 256 мегабайта, из которых на раздел data выделялось порядка 90 мегабайт). С тех пор многие условия изменились; в современных телефонах часто нет слота для SD-карты, зато устанавливается огромное по мобильным меркам количество встроенной памяти (например, в Samsung Galaxy Note 9 её может быть до 512 гигабайт).

Поэтому в современном Android практически всегда и внутреннее, и внешнее хранилища располагаются во встроенной памяти. Настоящий путь, по которому располагается внешнее хранилище в файловой системе, имеет форму /data/media/0 (для каждого пользователя устройства создаётся отдельное внешнее хранилище, и число в пути соответствует номеру пользователя). В целях совместимости до внешнего хранилища также можно добраться по путям /sdcard, /mnt/sdcard, /storage/self/primary, /storage/emulated/0, нескольким путям, начинающимся с /mnt/runtime/, и некоторым другим.

С другой стороны, у многих устройств всё-таки есть слот для SD-карты. Вставленную в Android-устройство SD-карту можно использовать как обычный внешний диск (не превращая её во внутреннее или внешнее хранилище системы) — сохранять на неё файлы, открывать хранящиеся на ней файлы, использовать её для перенесения файлов на другие устройства и т.п. Кроме того, Android позволяет «заимствовать» SD-карту и разместить внутреннее и внешнее хранилище на ней (это называется заимствованным хранилищем — adopted storage). При этом система переформатирует SD-карту и шифрует её содержимое — хранящиеся на ней данные невозможно прочесть, подключив её к другому устройству.

Подробнее почитать про всё это можно, например, вот в этом посте и в официальной документации для разработчиков приложений и для создателей сборок Android.

Оцените статью
Master Hi-technology
Добавить комментарий