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

 

Немного теории

Как мы все знаем, в Android есть такое понятие — полномочия приложений (permissions, разрешения). Полномочия прописываются в файл Manifest.xml каждого приложения и определяют то, к каким функциям API сможет получить доступ приложение. Хочешь работать с камерой — добавь в Manifest.xml строку <uses-permission android:name="android.permission.CAMERA" />. Нужен доступ к карте памяти — android.permission.READ_EXTERNAL_STORAGE. Все просто и логично, к тому же все доступные приложениям полномочия хорошо документированы.

Есть, однако, в этой стройной схеме одна очень важная деталь, которую сами создатели Android называют уровень доступа (protection level). Чтобы понять, что это такое, попробуй добавить в Manifest.xml любого своего приложения следующую строку:

 <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />

По идее, данное полномочие должно открыть доступ к API, позволяющему переводить смартфон в режим полета, включать/выключать GPS и делать другие полезные вещи. Но IDE так не считает и поэтому сразу подчеркивает строку как ошибку с формулировкой «Permission is only granted to system apps». Это и есть предупреждение о нарушении того самого уровня доступа. IDE как бы говорит нам: да, ты можешь попробовать дать своему приложению полномочие WRITE_SECURE_SETTINGS, но Android все равно не разрешит тебе использовать закрепленный за ним API до тех пор, пока ты не сделаешь свое приложение системным. А что значит «системным» в данном случае? Это значит: подпишешь его тем же цифровым ключом, каким подписана сама прошивка (иди попробуй раздобыть такой ключ у какой-нибудь Samsung или LG!).

Упс...
Упс…

Официально в Android существует четыре уровня доступа:

  • normal — «обычные» полномочия, дающие приложению доступ к безобидным функциям, которые не получится зловредно использовать (примеры: SET_ALARM, ACCESS_NETWORK_STATE, VIBRATE). Система даже не скажет тебе, что приложение вообще их использует;
  • dangerous — «опасные» полномочия, юзер будет информирован о них при установке приложения либо увидит окошко с предупреждением в Android 6.0 (примеры: READ_SMS, SEND_SMS, CALL_PHONE, READ_CALL_LOG);
  • signature — доступны только приложениям, подписанным ключом прошивки (примеры: GET_TASKS, MANAGE_USERS, WRITE_SETTINGS, MOUNT_UNMOUNT_FILESYSTEMS);
  • privileged — доступны приложениям, располагающимся в каталоге /system/priv-app.

В большинстве случаев уровни доступа signature и privileged равноценны. Например, чтобы получить полномочие MANAGE_USERS, приложение должно быть либо подписано ключом прошивки, либо размещено в каталоге /system/priv-app. Но есть и исключения: например, полномочие MANAGE_DEVICE_ADMIN имеет уровень доступа signature, то есть единственный способ его получить — подписать приложение ключом прошивки.

Есть также набор внутренних уровней доступа, введенных в Android для решения определенных проблем: installer, development, preinstalled, appop, pre23. По сути, это костыли, и на данном этапе ты можешь о них не думать, однако к уровню доступа development мы еще вернемся, и он нам очень сильно пригодится. А пока поговорим о том, как получить нужные нам уровни доступа и что они дают.

 

Уровень доступа privileged

Privileged не самый высокий уровень доступа и позволяет использовать далеко не весь API Android. Однако в большинстве случаев он оказывается вполне достаточным, так как позволяет устанавливать и удалять приложения и пользователей (INSTALL_PACKAGES, DELETE_PACKAGES, MANAGE_USERS), управлять статусной строкой (STATUS_BAR), управлять некоторыми настройками питания (WRITE_SECURE_SETTINGS), читать и изменять настройки Wi-Fi (READ_WIFI_CREDENTIAL, OVERRIDE_WIFI_CONFIG), отключать приложения и их компоненты (CHANGE_COMPONENT_ENABLED_STATE) и многое другое.

Чтобы приложение получило уровень доступа privileged, оно должно быть установлено в каталог /system/priv-app, а это значит — поставляться предустановленным в составе прошивки. Однако, имея root, мы можем поместить свое приложение в данный каталог с помощью двух функций:

// Подсобная функция, которая просто выполняет shell-команду
static public boolean runCommandWait(String cmd, boolean needsu) {
  try {
    String su = "sh";
    if (needsu) { su = "su"; }

    Process process = Runtime.getRuntime().exec(new String[]{su, "-c", cmd});
    int result = process.waitFor();

    return (result == 0);

  } catch (IOException e) {
    throw new RuntimeException(e);
  } catch (InterruptedException e) {
    throw new RuntimeException(e);
  }
}

// Функция делает указанное приложение системным и отправляет смартфон в мягкую перезагрузку
static public void makeAppSystem(String appName) {
  String systemPrivAppDir = "/system/priv-app/";
  String systemAppDir = "/system/app/";

  String appPath = "/data/app/" + appName;

  // Подключаем /system в режиме чтения-записи
  if (!runCommandWait("mount -o remount,rw /system", true)) {
    Log.e(TAG, "makeAppSystem: Can't mount /system");
    return;
  }

  int api = Build.VERSION.SDK_INT;
  String appDir = systemPrivAppDir;

  // Копируем приложение в /system/priv-app или /system/app в зависимости от версии Android
  if (api >= 21) {
    runCommandWait("cp -R " + appPath + "* " + appDir, true);
    runCommandWait("chown -R 0:0 " + appDir + appName + "*", true);
    runCommandWait("rm -Rf " + appPath + "*", true);
  } else {
    if (api < 20) { appDir = systemAppDir; }
    runCommandWait("cp " + appPath + "* " + appDir, true);
    runCommandWait("chown 0:0 " + appDir + appName + "*", true);
    runCommandWait("rm -f " + appPath + "*", true);
  }

  // Отправляем смартфон в мягкую перезагрузку
  Shell.runCommand("am restart", true);
}

Функцию runCommandWait я описывать не буду, она просто выполняет shell-команду и ждет ее завершения (подробнее читай в моей статье про написание приложений с правами root). Функция makeAppSystem, в свою очередь, принимает полное имя приложения (это то самое com.example.app, которое ты указываешь при создании нового проекта в Android Studio) и переносит его в /system/priv-app или /system/app, в зависимости от используемой версии Android. Код может показаться тебе несколько странным, на самом деле он абсолютно корректен и учитывает два фактора:

  • до Android 4.4 (API Level: 20) каталога /system/priv-app не существовало и все системные приложения размещались в /system/app;
  • начиная с Android 5.0 (API Level: 21) пакеты с приложениями не просто складируются в /data/app и /system/priv-app, а размещаются внутри своих обособленных подкаталогов.

Как использовать этот код? Очень просто: ты определяешь в Manifest.xml своего приложения все privileged-полномочия, которые ему нужны, не обращая внимания на ошибки IDE. Затем в самое начало кода приложения вставляешь вызов makeAppSystem с именем самого приложения в качестве аргумента, компилируешь и запускаешь. После запуска приложение перемещает само себя в /system/priv-app, перезагружает смартфон, и ему открываются все privileged API.

Список privileged-полномочий можно посмотреть в исходниках Android. Просто ищи по слову privileged. О том, как их использовать, — чуть позже.

 

Уровень доступа signature

Подпись ключом прошивки позволяет получить самый высокий уровень доступа к API — signature. Имеющее такой доступ приложение может делать практически все что угодно: манипулировать любыми настройками Android (WRITE_SETTINGS, WRITE_SECURE_SETTINGS), наделять приложения правами администратора (MANAGE_DEVICE_ADMINS), программно нажимать кнопки и вводить данные в любое окно (INJECT_EVENTS) и многое другое.

Получить такой уровень доступа на стоковой прошивке почти невозможно. Ни один производитель смартфонов не предоставит тебе ключ для подписи своих прошивок. Но если речь идет о кастомной прошивке, то все становится немного проще. Например, ночные сборки того же CyanogenMod (а я напомню, что пользователей у нее больше, чем юзеров всех версий Windows Phone, вместе взятых) подписываются тестовым ключом, а его особенность в том, что он есть в открытом доступе.

Но это еще не все, в CyanogenMod есть механизм безопасности, который, в отличие от чистого Android, позволяет получать уровень доступа signature не абсолютно всем приложениям, подписанным ключом прошивки, а только тем, что размещены в /system/priv-app. Поэтому, чтобы получить уровень доступа signature в CyanogenMod (не в Cyanogen OS, я подчеркиваю), необходимо:

  1. Добавить в Manifest.xml приложения необходимые полномочия.
  2. Добавить в приложение вызов функции makeAppSystem(), описанной в предыдущем разделе.
  3. Подписать релизную версию приложения ключом platform из репозитория CyanogenMod.

 

Уровень доступа development

В Android есть специальный уровень доступа development, отличие которого заключается в том, что приложения получают его не по факту размещения в /system/priv-app или использования цифровой подписи прошивки, а динамически. То есть система может дать такой уровень доступа любому приложению, а может и отозвать обратно. Но самое важное, что, имея права root, приложение может наделить себя таким уровнем доступа самостоятельно.

Чтобы это сделать, достаточно использовать примерно такой код:

runCommandWait("pm grant " + appName + " android.permission.WRITE_SECURE_SETTINGS", true);

В данном случае приложение appName получит полномочие WRITE_SECURE_SETTINGS вне зависимости от того, где оно размещено и каким ключом подписано. Круто? Вне сомнения, однако WRITE_SECURE_SETTINGS — фактически единственное полезное полномочие с уровнем доступа development. Остальные четырнадцать — это полномочия для отладки и тестирования (чтение логов, дампы памяти и так далее).

Полномочия development в исходниках Android
Полномочия development в исходниках Android

 

Как использовать системный API?

Основная проблема, с которой ты столкнешься при работе с системным API, — это полное (за небольшими исключениями) отсутствие документации. Ни в официальных руководствах, ни в неофициальных ты не найдешь почти никаких упоминаний об этом. Информацию придется собирать по крупицам, прошаривая сотни страниц форумов и читая тысячи страниц исходников Android. Однако хоть и небольшую, но отправную точку в виде парочки полезных примеров мы тебе дадим.

 

WRITE_SECURE_SETTINGS

Полномочие WRITE_SECURE_SETTINGS появилось в Android 4.2 для защиты некоторых критически важных настроек Android. Среди таких настроек: включение/выключение режима полета, управление настройками местоположения и передачи данных. Оно защищено сразу тремя уровнями доступа: signature, privileged и development. То есть ты можешь использовать любой из перечисленных выше способов получения уровня доступа, чтобы наделить свое приложение полномочием WRITE_SECURE_SETTINGS.

Как использовать открывшиеся возможности? Например, так:

// Читаем текущее значение настройки
boolean isEnabled = Settings.System.getInt(
getContentResolver(),
Settings.System.AIRPLANE_MODE_ON, 0) == 1;

// Переключаем настройку
Settings.System.putInt(
getContentResolver(),
Settings.System.AIRPLANE_MODE_ON, isEnabled ? 0 : 1);

// Отправляем интент для переключения
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intent.putExtra("state", !isEnabled);
sendBroadcast(intent);

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

 

INSTALL_PACKAGES

Как ясно из названия, полномочие INSTALL_PACKAGES позволяет «втихую» устанавливать в систему APK-пакеты. Использовать эту возможность могут либо подписанные ключом прошивки приложения (signature), либо установленные в /system/priv-app. При этом даже не обязательно использовать Java API, достаточно вызвать консолью команду pm (Package Manager) с нужными параметрами:

runCommandWait("pm install " + apkPath, false);

После отработки команды пакет apkPath будет установлен в систему. Ты можешь возразить, что то же самое можно сделать и с правами root, и будешь прав: в данном случае достаточно изменить последний аргумент функции runCommandWait() на true. Однако стоит иметь в виду, что приложения с правами root, во-первых, приводят к появлению окна запроса соответствующих полномочий у юзера, а во-вторых, логируются тем же SuperSU. А так достаточно один раз прописать свою софтину в /system/priv-app, и она сможет устанавливать сколько угодно софта без всяких вопросов.

 

Вместо выводов

Вот и все. Доступ к закрытому API в Android не так уж и сложно получить. С другой стороны, с легитимным софтом использовать его в большинстве случаев не имеет смысла, проще получить права root и вызывать соответствующие консольные команды: settings для изменения настроек, pm для установки/удаления приложений, setprop для изменения низкоуровневых настроек и так далее. Однако если речь идет о не совсем обычном программном обеспечении...

1 комментарий

  1. Аватар

    Ov3r1oad

    25.04.2016 at 16:15

    Небольшая ремарка: мне кажется, что priv-app папка появилась ещё в API 19. Так что, код надо исправить.

Оставить мнение

Check Also

Хроники битвы при Denuvo. Как «непробиваемая» игровая защита EA Origin оказалась пробиваемой

Защита от пиратства Denuvo пришла на смену SecuROM и связана с одним действующим лицом – Р…