Запуск бинарных файлов из data/data на Android 10+ (Обход SELinux)

от автора

Всем привет! Как многие знают, с Android 10 (Target sdk 29) google ввели новую политику безопасности. Новая политика SELinux звучит просто: «Нельзя исполнять файлы из той директории, в которую можно записывать». Всё это очень хорошо, но многие проекты сломались (В том числе и мой). Termux из google play УМЕЕТ запускать бинарные файлы на target sdk 29+. Я решил поделится, как выполнить бинарный файл из data/data/com.ваш.пакет/files на новых версиях sdk БЕЗ полного клонирования Termux и БЕЗ С/C++ части. Сам метод запуска будет именно на java. Репозиторий termux, откуда был взят способ: https://github.com/termux‑play‑store/termux‑apps.

В чём суть: любой бинарный файл, который вы запускаете, имеет свой контекст. Если вы запускаете через нативную директорию (data/app/и так далее), перед этим положив бинарные файлы в jniLins — контекст у такого бинарного файла будет правильным, и SELinux даст разрешение на запуск (Granted), но в случае с data/data другой случай, оттуда SELinux УЖЕ ОТКАЖЕТСЯ запускать бинарный файл (Denied). Разрешил SELinux запуск или отклонил — можно смотреть в logcat. Однако, в системе существует системная утилита, которая может запустить бинарник, а самое главное — SELinux РАЗРЕШИТ ей запуститься, так как она системная. Её имя linker или linker64 (Зависит от разрядности, 32 бита или 64).

Запустив линкер и передав ему наш бинарный файл из data/data — SELinux разрешит ему выполниться и сразу разрешит исполнение нашего бинарного файла. Тут сразу возникает вопрос: а если бинарный файл попробует подключить so библиотеку? Ей будет отказано? Здесь тоже есть решение: существует termux‑exec. Это бинарный файл, который перехватывает попытку подключения чего‑либо и выполняет трюк с линкером. (linker или linker64 определяет автоматически). Вы можете собрать его из исходников (https://github.com/termux‑play‑store/termux‑exec), но лично я полностью скопировал среду (Где этот уже собранный файл идёт в usr/lib) termux, так как мне нужно было запускать OpenJDK 17 под termux.

Пишем сам метод.

// Устаналиваем пути File appFilesDir = context.getFilesDir(); // Путь к data/data/пакет/files/, нужен контекст, передайте его File usrDir = new File(appFilesDir, "usr"); // Путь к data/data/пакет/files/usr/ File javaExecutable = new File(appFilesDir, "usr/bin/java"); // ЗАМЕНИТЕ НА ВАШ БИНАРНИК  // Создаём окружение чтобы бинарник для термукса чувствовал себя как дома Map<String, String> envMap = new HashMap<>();  // Сам дом envMap.put("HOME", new File(appFilesDir, "home").getAbsolutePath());  // Папка пользователя envMap.put("PREFIX", usrDir.getAbsolutePath());  // Временнаяя папка envMap.put("TMPDIR", new File(appFilesDir, "tmp").getAbsolutePath());  // Путь к бинарникам envMap.put("PATH", new File(usrDir, "bin").getAbsolutePath() + ":" + System.getenv("PATH"));  // Папка для динамичкских библиотек File libDir = new File(usrDir, "lib"); envMap.put("LD_LIBRARY_PATH", libDir.getAbsolutePath());  // Наш перехватчик (Его нужно собрать либо взять пряом из термукса) File termuxExecSo = new File(libDir, "libtermux-exec.so"); envMap.put("LD_PRELOAD", termuxExecSo.getAbsolutePath());  // Системные переменные которые ВОЗМОЖНО будут нужны бинарнику, лучше их задать String[] systemVarsToCopy = {     "ANDROID_ART_ROOT", "ANDROID_ASSETS", "ANDROID_DATA", "ANDROID_I18N_ROOT",     "ANDROID_ROOT", "ANDROID_RUNTIME_ROOT", "ANDROID_STORAGE", "ANDROID_TZDATA_ROOT",     "BOOTCLASSPATH", "DEX2OATBOOTCLASSPATH", "EXTERNAL_STORAGE", "SYSTEMSERVERCLASSPATH" };  for (String var : systemVarsToCopy) {     String value = System.getenv(var);     if (value != null) {         envMap.put(var, value);     } }  // Формируем команду List<String> commandList = new ArrayList<>();  // Определяем, нам нужен linker или linker64 (Это зависит от архитектуры процессора) String linkerPath = "/system/bin/linker" + (android.os.Process.is64Bit() ? "64" : "");  // Заполняем саму команду commandList.add(linkerPath); // Запускаем линкер, а не бинарник. Это самый важный этап commandList.add(javaExecutable.getAbsolutePath()); // Ваш бинарник, у меня это java из OpenJDK commandList.add("-version"); // Ваши аргументы, у меня всего 1 аргумент чтобы узнать версию

Всё! Мы построили команду для запуска на новых targert sdk. Теперь осталось запустить этот процесс

Пример запуска (В try‑catch), если у вас простой бинарник — вам достаточно этого. Для более сложных сделайте получение вывода. Информация об этом есть в интернете.

// Создаём ProcessBuilder с нашей командой для линкера ProcessBuilder pb = new ProcessBuilder(commandList);  // Устаналиваем окружение pb.environment().putAll(envMap);  // Запускаем и ждём завершения pb.start().waitFor();

Дальше идёт обычная работа с процессами, никакой «Магии» дальше нет. Вывод вы можете организовать самостоятельно, главное — проверить, что бинарный файл запущен.

Вот, что мы видим в logcat:

Как тут видно, запускает наш бинарник linker64 и ему SELinux разрешил исполнение

Как тут видно, запускает наш бинарник linker64 и ему SELinux разрешил исполнение

Когда я добавил вывод — я получил вывод версии OpenJDK. Пользуйтесь. Работает даже на target sdk 36. Сам вывод никак не относится к теме статьи, она рассказывает про запуск бинарных файлов из директорий, куда можно записывать на новых версиях sdk.


ссылка на оригинал статьи https://habr.com/ru/articles/943188/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *