В этой статье я хочу поделиться своим опытом обхода проверки на рутованость своего устройства. Статья может рассматриваться не только как самостоятельный материал, но и как прямое продолжения моей работы из предыдущей статьи.
Сразу предупрежу, что люблю писать подобные статьи довольно подробно, не ради объема и многобукав, а ради максимального погружения в проблему и способ ее решения. Обратите внимание, что я работаю на 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():
Содержимое файла b.smali.method public a()Z
.locals 1
.line 43
invoke-virtual {p0}, Lcom/scottyab/rootbeer/b;->c()Z
move-result v0
if-nez v0, :cond_1
invoke-virtual {p0}, Lcom/scottyab/rootbeer/b;->d()Z
move-result v0
if-nez v0, :cond_1
const-string v0, "su"
invoke-virtual {p0, v0}, Lcom/scottyab/rootbeer/b;->a(Ljava/lang/String;)Z
move-result v0
if-nez v0, :cond_1
const-string v0, "busybox"
.line 44
invoke-virtual {p0, v0}, Lcom/scottyab/rootbeer/b;->a(Ljava/lang/String;)Z
move-result v0
if-nez v0, :cond_1
invoke-virtual {p0}, Lcom/scottyab/rootbeer/b;->f()Z
move-result v0
if-nez v0, :cond_1
invoke-virtual {p0}, Lcom/scottyab/rootbeer/b;->g()Z
move-result v0
if-nez v0, :cond_1
.line 45
invoke-virtual {p0}, Lcom/scottyab/rootbeer/b;->b()Z
move-result v0
if-nez v0, :cond_1
invoke-virtual {p0}, Lcom/scottyab/rootbeer/b;->h()Z
move-result v0
if-nez v0, :cond_1
invoke-virtual {p0}, Lcom/scottyab/rootbeer/b;->j()Z
move-result v0
if-nez v0, :cond_1
invoke-virtual {p0}, Lcom/scottyab/rootbeer/b;->e()Z
move-result v0
if-eqz v0, :cond_0
goto :goto_0
:cond_0
const/4 v0, 0x0
goto :goto_1
:cond_1
:goto_0
const/4 v0, 0x1
:goto_1
return v0
.end method
Можем сравнить его с неповторимым оригиналом:
Очень похожи. Итак, нас интересует return, то есть самый конец метод. Видим, что метод возвращает некоторое значение константы v0, значение которой определяется на основе выполнивших в методе условий. Нам это не подходит. Нам всегда нужно возвращать false или же как это будет в smali 0x0. Для этого добавим свою констант v1 со значением 0x0. Сделаем это прямо над return:
:goto_1
const/4 v1, 0x0
return v1
И конечно же заменим в return v0 на v1. Поднимемся в самое начало метода и изменим значение .locals с 1 на 2 потому, что так надо. Сохраняем изменения в файле и закрываем редактор.
Для этого воспользуемся командой apktool b app
, где app – папка с приложением, в котором мы редактировали smali файл. После завершения команды, в папке app появиться директория dist. Здесь и расположен наш заново собранный .apk. Однако при попытке установить его мы получим следующую ошибку:
The APK failed to install.
Error: INSTALLPARSEFAILEDNOCERTIFICATES: Failed collecting certificates for /data/app/vmdl164530405.tmp/base.apk: Failed to collect certificates from /data/app/vmdl164530405.tmp/base.apk: Attempt to get length of null array
Это из-за того, что после пересборки нужно еще и переподписать приложение. Для этого нам понадобиться любой keystore файл. Или можно создать новый при помощи команды:
keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
Подписать ключом приложение можно при помощи одного из двух инструментов: apksigner или jarsigner.
Я выбрал jarsigner используя следующую команду:
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore keystore app.apk cp_key
В этой команде мы указываем путь к keystore, путь к .apk, который хотим подписать и имя ключа (alias) из указанного keystore. После, вводим пароль от keystore и приложение успешно подписано.
Однако и это еще не все, теперь при попытке установить .apk мы будем получать другую ошибку:
The APK failed to install.
Error: INSTALL_FAILED_INVALID_APK: Failed to extract native libraries, res=-2
Это потому, что после подписи приложения нужно выполнить оптимизацию .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. И считаю это маленькой победой. Данной статьей я стремился описать не просто инструкцию о том, как расковырять приложение или свои способности по типу “смотрите как могу“. Этой статьей я хочу сказать (и напомнить тем, кто стал забывать) что большинство проблем решаемы, нужно только копать, копать и еще раз копать, не опускать руки и результат обязательно будет удовлетворительным. Даже если, в конце концов, баг я так и не обнаружил, я очень рад что расширил свой кругозор и в дальнейшем смогу взяться за более сложные задачи! Спасибо за внимание, обязательно буду писать еще про подобные танцы с бубном!
Центр управления связью общего пользования (ЦМУ ССОП) Роскомнадзора рекомендовал компаниям из реестра провайдеров ограничить доступ поисковых ботов к информации на российских сайтах.…
Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…
Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…
Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…
У частных продавцов на Авито появилась возможность составлять текст объявлений с помощью нейросети. Новый функционал доступен в категории «Обувь, одежда,…
24 апреля 2024 года в Москве состоялась церемония вручения наград международного конкурса Workspace Digital Awards. В этом году участниками стали…