В этой статье я хочу поделиться своим опытом обхода проверки на рутованость своего устройства. Статья может рассматриваться не только как самостоятельный материал, но и как прямое продолжения моей работы из предыдущей статьи.  Сразу предупрежу, что люблю писать подобные статьи довольно подробно, не ради объема и многобукав, а ради максимального погружения в проблему и способ ее решения. Обратите внимание, что я работаю на macOS, поэтому все команды в терминале будут ориентированы под данную ОС. Мне выслали .apk файл приложения. Где-то внутри программы есть баг, который мне нужно  воспроизвести. Но пройти вглубь приложения у меня не получается. Мое устройство не проходит проверку на рутованность.  Так как я уже потратил немало усилий, чтобы все же запустить это приложение (о каких именно усилиях идет речь можно узнать в прошлой статье), то и здесь я решил не сдаваться и попробовать обойти проверку на рутованность устройства. Первое, что пришло мне в голову, так это попробовать выполнить декомпиляцию и поправить кое-где код, а потом собрать все обратно. Основное сомнение по поводу такого подхода вызывало то, что у меня не нативное android-приложение, а flutter-приложение. То есть как такового основного кода приложения в виде байт-кода в .apk найти не получится. Или получится? И нужен ли мне код самого приложения? Небольшое отступление о том, как во флаттере запускается нативный код, то есть родной код для Android или iOS платформы. Например, перед flutter-разработчиком стоит задача: не давать возможность работать с приложением на рутованном устройстве. Аналогом андроид root-устройства является jailbreak для iOS. Для проверки на рутованность или jailbreak необходимо воспользоваться нативными средствами обеих платформ. Для этого разработчик пишет плагин, который вызывает соответствующие нативные методы в зависимости от платформы на которой запущено приложение.  Таким образом, я сделал вывод, что в имеющемся .apk файле есть как минимум 2 интересующих меня места в виде байт-кода. Первое место - это нативная часть приложения, которая слушает команды из flutter части приложения, чтобы вызвать соответствующие нативные методы. И второе место - это собственно те самые нативные методы, а в данном случае - библиотека для проверки на рутованность устройства. Я решил, что буду менять байт-код во втором месте. Честно говоря, сначала я работал с первым местом, то есть с плагином. Все потому, что кода там намного меньше и запутаться было сложнее. Всего лишь метод, который вызывает другой метод - это пара строк кода. Но в таком случае статья была бы еще сильнее завязана под конкретный .apk, и не было б такой абстрактности. Поэтому будем работать напрямую с кодом библиотеки. Итак, посмотрим на внутренности .apk. Для этого можно воспользоваться инструментом, предоставляемый Android Studio. В меню выбираем Build -> Analyze APK… В окне выбора файла находим интересующий нас .apk.  Весь скомпилированный байт-код хранится в файле classes.dex. Выберем его и взглянем на содержимое. В первую очередь, нас интересуют не файлы java или androidx, а именно файлы приложения. Поэтому выбираем первый package в структуре файлов android проекта, в моем случае это папка com. Из любопытства к содержимому каждой из папок я затригерился на слово rootbeer. Погуглив в гугле оказалось, что это весьма популярная библиотека для проверки устройства на предмет рутованности.  Исследовав документацию и исходники библиотеки стало ясно о том, как пользоваться библиотекой и где располагается ключевой метод isRooted().  Даже не смотря на то, что большинство классов приложения были обфусцированы, по прежнему получается сориентироваться и найти нужный файл. Не хочу сказать, что обфускация бесполезна. Ведь будь необходимый мне файл где-то в глубине приложения, то я б скорее всего его не нашел.  Судя по исходникам библиотеки, нужный мне файл должен находится рядом с классом RootBeerNative, и как мы видим тут таких, два a и b. Эмпирическим методом я понял, что это файл b. Правый клик по файлу b и в выпадающем меню выбираем Show Bytecode. К сожалению, имена всех методов обфусцированы. Снова обращаясь к исходному файлу на github становится ясно, что метод isRooted() находится на строке 42. Побегав по файлу с байт-кодом мне удалось найти этот метод. Только теперь он называется a() и почему-то начинает работу с .line 43. Вот этот метод я и буду редактировать. Но не так быстро. К сожалению здесь только Read-only доступ.  Для этого придется декомпилировать весь .apk при помощи известного инструмента apktool. Работать с ним довольно просто. После его установки нам понадобятся всего 2 команды: apktool d appname.apk, которая декомпилирует указанный .apk и apktool b directory_with_app, которая соберет .apk обратно (в указанной directory_with_app должен располагаться файл apktool.yml). Итак, выполняю декомпиляцию при помощи команды в терминале: apktool d app.apk После выполнения команды появляется папка с файлами приложения. Нас интересует папка smali. Smali - это язык которым описан байт-код (вот классная статья по основам smali). Именно эта папка и есть содержимое файла classes.dex. К только что выполненной команде можно добавить параметр --skip-sources или просто -s тогда вместо папки smali мы увидим тот самый файл classes.dex.  Далее, в папке smali находим интересующий и уже известный нам файл b.smali: Открываем его любым текстовым редактором и переходим к месту, где располагается метод isRooted():  Можем сравнить его с неповторимым оригиналом:  Очень похожи. Итак, нас интересует return, то есть самый конец метода. Видим, что метод возвращает некоторое константу v0, значение которой определяется на основе выполнивших в методе условий. Нам это не подходит. Нам всегда нужно возвращать false или же, как это будет в smali, 0x0. Для этого добавим свою констант v1 со значением 0x0. Сделаем это прямо над return:  И конечно же заменим в return v0 на v1. Поднимемся в самое начало метода и изменим значение .locals с 1 на 2, потому что так надо. Сохраняем изменения в файле и закрываем редактор. Для этого воспользуемся командой apktool b app, где app - папка с приложением, в котором мы редактировали smali файл. После завершения команды, в папке app появится директория dist. Здесь и расположен наш заново собранный .apk. Однако, при попытке установить его, мы получим следующую ошибку:  Это из-за того, что после пересборки нужно еще и переподписать приложение. Для этого нам понадобиться любой keystore файл. Или можно создать новый при помощи команды: Подписать ключом приложение можно при помощи одного из двух инструментов: apksigner или jarsigner. Я выбрал jarsigner используя следующую команду: В этой команде мы указываем путь к keystore, путь к .apk, который хотим подписать и имя ключа (alias) из указанного keystore. После, вводим пароль от keystore и приложение успешно подписано. Однако и это еще не все. Теперь, при попытке установить .apk, мы будем получать другую ошибку: Это потому, что после подписи приложения нужно выполнить оптимизацию .apk файла при помощи инструмента zipalign.  Для выполнения оптимизации нужно ввести следующую команду: ~/Library/Android/sdk/build-tools/30.0.3/zipalign -v -f -p 4 app.apk rdy.apk 30.0.3 - я выбрал самую последнюю версию на момент написания статьи. После завершения команды на выходе получаем файл rdy.apk, который можно успешно установить! Проверим, получилось ли обойти проверку на рутованность устройства: И да, это успех! Вызов принят - вот, что я подумал получив этот .apk. И считаю это маленькой победой. Данной статьей я стремился написать не просто инструкцию о том, как расковырять приложение. И не про свои способности по типу "смотрите как могу". Этой статьей я хочу сказать (и напомнить тем, кто стал забывать), что большинство проблем решаемы, нужно только копать, копать и еще раз копать, не опускать руки и результат обязательно будет положительным. Даже если, в конце концов, баг я так и не обнаружил, я очень рад, что расширил свой кругозор и в дальнейшем смогу взяться за более сложные задачи! Спасибо за внимание, обязательно буду писать еще про подобные танцы с бубном! Lead Senior Solution Architect