В прошлой статье я делал обзор на OWASP Mobile TOP 10 и тогда у меня не было годного кейса для демонстрации необходимости защиты исходного кода. Интересный кейс для демонстрации появился только недавно и кому интересно посмотреть на наш опыт обхода проверок состояния среды, давайте под кат.
При проведении оценки работы одного из проектов, наша команда, сразу поняла, что кейс не будет лёгким. Разработчики хорошо подошли к вопросу защиты информации в программе и внедрили проверки состояния среды исполнения. Приложение не запускалось при любом из следующих условий:
- аппарат был рутованным;
- использовался эмулятор;
- наличие подключения через USB;
- использование режима разработчика.
Разработчики не обфуцировали исходный код и не была встроена проверка на модификацию кода, что позволило мне проанализировать способы, которыми выполнялись проверки и выполнить необходимые манипуляции с ними.
Итак, начнём. Согласно OWASP Mobile TOP 10, который мы используем в качестве базовой методологии тестирования в компании Hacken анализ исходного кода (Reverse Engineering) — эта уязвимость включает в себя анализ бинарных файлов для определения исходного кода, библиотек, алгоритмов и т.д. Программное обеспечение, такое как IDA Pro, Hopper, otool и другие инструменты реверс-инжиниринга могут дать представление о внутренней работе приложения. Это может быть использовано для поиска уязвимостей приложения, извлечения критичной информации, такой как бэкенд-сервера, ключей шифрования или интеллектуальной собственности.
Для проведения базового статического анализа я использовал такой интересный инструмент, как MobSF, который выполнил декомпиляцию и базовый статический анализ. После декомпиляции меня интересовал java- и smali-коды программы. Java-код нужен для анализа, а в smali-код будем вносить изменения. Более подробно, как smali и java соотносятся можно прочитать здесь.
Просмотрев список классов, я обнаружил файл, который отвечает за проверку рутованности телефона (см. Рис. 1) — rootingcheck/RootBeerNative.java.
Рис. 1. Список классов приложения
Проанализировав класс, стало понятно, что нужно дальше искать вызовы функций checkForRoot() и setLogDebugMessage() (см. Рис. 2).
Рис. 2. Исходный код класса проверки на рутованость
С помощью команды grep получим следующие результаты, которые нам показывают, в каких файлах есть вызов методов checkForRoot() и setLogDebugMessage().
Результаты выполнения поиска:
root@kali:~/Desktop# grep -nr 'RootBeerNative' ********_v_0.9.2/
********_v_0.9.2/smali/rootingcheck/RootBeerNative.smali:1:.class public Lrootingcheck/RootBeerNative;
********_v_0.9.2/smali/rootingcheck/RootBeerNative.smali:17: sput-boolean v0, Lrootingcheck/RootBeerNative;->?:Z
********_v_0.9.2/smali/rootingcheck/RootBeerNative.smali:28: sput-boolean v0, Lrootingcheck/RootBeerNative;->?:Z
********_v_0.9.2/smali/rootingcheck/RootBeerNative.smali:57: sget-boolean v0, Lrootingcheck/RootBeerNative;->?:Z
********_v_0.9.2/smali/o/CM.smali:591: new-instance v1, Lrootingcheck/RootBeerNative;
********_v_0.9.2/smali/o/CM.smali:593: invoke-direct {v1}, Lrootingcheck/RootBeerNative;-><init>()V
********_v_0.9.2/smali/o/CM.smali:685: new-instance v0, Lrootingcheck/RootBeerNative;
********_v_0.9.2/smali/o/CM.smali:687: invoke-direct {v0}, Lrootingcheck/RootBeerNative;-><init>()V
********_v_0.9.2/smali/o/CM.smali:689: invoke-static {}, Lrootingcheck/RootBeerNative;->?()Z
********_v_0.9.2/smali/o/CM.smali:753: new-instance v4, Lrootingcheck/RootBeerNative;
********_v_0.9.2/smali/o/CM.smali:755: invoke-direct {v4}, Lrootingcheck/RootBeerNative;-><init>()V
********_v_0.9.2/smali/o/CM.smali:764: invoke-virtual {v4, v3}, Lrootingcheck/RootBeerNative;->checkForRoot([Ljava/lang/Object;)I
********_v_0.9.2/smali/o/xZ$5.smali:257: new-instance v1, Lrootingcheck/RootBeerNative;
********_v_0.9.2/smali/o/xZ$5.smali:259: invoke-direct {v1}, Lrootingcheck/RootBeerNative;-><init>()V
********_v_0.9.2/smali/o/xZ$5.smali:261: invoke-static {}, Lrootingcheck/RootBeerNative;->?()Z
root@kali:~/Desktop# grep -nr 'setLogDebugMessages' ********_v_0.9.2/
********_v_0.9.2/smali/o/CM.smali:599: invoke-virtual {v1, v0}, Lrootingcheck/RootBeerNative;->setLogDebugMessages(Z)I
********_v_0.9.2/smali/o/CM.smali:761: invoke-virtual {v4, v0}, Lrootingcheck/RootBeerNative;->setLogDebugMessages(Z)I
Но это были не все проверки. Проанализировав класс MainActivity.java, был найдены вызовы функций, куда передаются строки “su”, “test-keys” и “which”, c помощью которых проводится проверка (см. Рис. 3).
Рис.3. Проверки на рутованость
И снова командой grep ищем в smali-файлах проверки рутованости:
Результаты выполнения поиска:
root@kali:~/Desktop# grep -nr 'su' ********_v_0.9.2/
********_v_0.9.2/smali/o/CM.smali:443: const-string v2, "su"
********_v_0.9.2/smali/o/CM.smali:706: const-string v2, "su"
********_v_0.9.2/smali/o/xZ$5.smali:172: const-string v1, "su"
********_v_0.9.2/smali/o/xZ$5.smali:347: const-string v0, "su"
root@kali:~/Desktop# grep -nr 'test-keys' ********_v_0.9.2/
********_v_0.9.2/smali/o/xZ$5.smali:141: const-string v1, "test-keys"
********_v_0.9.2/smali/o/xZ$5.smali:374: const-string v0, "test-keys"
root@kali:~/Desktop# grep -nr 'which' ********_v_0.9.2/
********_v_0.9.2/smali/o/CM.smali:437: const-string v2, "which"
В статье я покажу лишь одну из найденных модификаций проверок рутованности. После небольшой манипуляции, а именно смены 1 на 0 — проверки на рутованность убраны.
Рис. 4. Значение переменной равно единице, если телефон рутованый
Рис. 5. Теперь значение переменной равно нулю, если телефон рутованный
После этого — программу можно собрать, подписать своим релизным ключом и запустить на мобильном телефоне. Но если-бы не два НО! А именно:
- проверка подключения к USB;
- проверка включения Developer mode — подключение к USB и включенный Developer mode позволяют проводить динамический анализ.
Проверка Developer mode выключается тем же путём, что и проверка рутованости — сменой в проверках единицу на ноль
В классе MainActivity.java находим строку, которая отвечает за проверку Developer mode (см. Рис 6). После этого grep-ом ищем файлы в которых присутствует строка “development_settings_enabled” и модифицируем проверку — меняем 1 на 0 (см. Рис. 7 и 8).
Рис. 6. Проверка, включён ли Developer mode на телефоне
Результаты выполнения поиска:
grep -nr «development_settings_enabled» ********_v_0.9.2\
Binary file ********_v_0.9.2\/build/apk/classes.dex matches
********_v_0.9.2\/smali/o/xZ$1.smali:49: const-string v1, "development_settings_enabled"
Рис. 7. Место где нужно провести модификацию
Рис. 8. Модификация
После всех манипуляций можно запускать программу в режиме Developer mode.
Дальше отключаем проверку подключения по USB. Эта проверка находится в классе MainActivity.java (см. Рис. 9). Без применения grep, находим строку в MainActivity.smali, находим строку, которая содержит строку с проверкой USB — android.hardware.usb.action.USB_STATE. После этого, в smali-коде модифицируем строку на любой другой permissions, который возвращает “false” (см. Рис. 10).
Рис. 9. Проверка на подключение USB в коде MainActivity.java
Рис. 10. Строка кодa, которую нужно удалить в MainActivity.smali
Теперь осталось сгенерировать свой релизный ключ и подписать им приложение. Это делается следующим образом:
- Нужно установить два приложения: Keytool и Jarsinger.
- Выполнить команду на собрание приложения:
- apktool b C:\Users\User\Desktop\********
- Далее:cd ********\dist\
- Далее: Keytool.exe -genkey -alias key.keystore -keyalg RSA -validity 20000 -keystore key.keystore
- Далее:Jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore key.keystore ********.apk key.keystore
- Далее:jarsigner -verify -verbose -certs ********.apk
Вот в принципе все манипуляции и закончены. Теперь устанавливаем приложение на телефон с помощью adb install или из директории смартфона и можно проводить динамическое тестирование на уязвимости.
После установки приложения запускаем его (см. Рис. 11 и Рис. 12).
Рис. 11. Включаем режим Developer mode и подключаем USB | Рис. 12. Запуск приложения |
Выводы
На практическом примере я показал, как можно отключить некоторые проверки состояния среды исполнения. Дальше уже с помощью других тулзов провели анализ на уязвимость, но это другая история…
К чему может привести халатное отношение к защите кода:
- это обхождения тех или иных проверок, которые вложены в программу
- внедрение стороннего кода, после чего программа может быть опубликована и использоваться как вредоносная
Как можно защититься? Мы в Hacken решили не изобретать велосипед и предложили клиенту обфусцировать исходный код и использовать функции которые проверяют модификацию исходного кода.
P.S.
Можно использовать более продвинутый способ анализа — это smali debugging. Более подробнее можно почитать об этом в мануале.
Немного для справки, я сформулировал список строк который применяется для проверки на рутованость:
- “test-keys”;
- "/system/app/Superuser.apk";
- "/sbin/su";
- "/system/bin/su";
- "/system/xbin/su";
- "/data/local/xbin/su";
- "/data/local/bin/su";
- "/system/sd/xbin/su";
- "/system/bin/failsafe/su";
- "/data/local/su";
- "/su/bin/su";
- "/system/xbin/which";
- “su”;
- “which".
Комментарии (19)
arturdumchev
04.08.2018 12:57… предложили клиенту обфусцировать исходный код и использовать функции которые проверяют модификацию исходного кода.
Подскажите, что почитать по поводу функций проверки модификаций. Имеется в виду checksum-тесты?Skif_1993 Автор
04.08.2018 18:36Мы дали ему рекомендации из OWASP.
https://www.owasp.org/index.php/Mobile_Top_10_2016-M9-Reverse_Engineering
Zolg
04.08.2018 18:17Что там про отдельное место в аду, для постящих код картинками?
А по теме — на рутованом аппарате сколь либо серьезно усложнить жизнь шансов нет. (разве что совсем пионерам).
Зачем модифицировать исследуемый код, когда можно просто перехватить часть системных функций и творчески подменять результаты?
qw1
05.08.2018 13:48Вы анализируете приложение, декомпилируя его, а затем ищете по ключевым словам.
Если, следуя вашим советам, клиент заранее обфусцирует код (да ещё воспользуется таким обфускатором, который шифрует строки), что вы будете делать? Скажете — «наш grep-анализ ничего не нашёл, тут всё безопасно»?Skif_1993 Автор
05.08.2018 14:04Вы снизите свои риски. Это точно.
qw1
05.08.2018 16:56Так пока нет обфускации, вы находите уязвимости типа
или
Log.d(Class.TAG, "Generated Password=" + passwd);
или
MessageDigest.getInstance("MD5");
а так в коде было бы
а.b.aa(aa.b.a.decode("[encrypted 'MD5']"));
и никакой проблемы вы бы не нашли.
Artem_zin
06.08.2018 04:39+1Так и в чем здесь уязвимость?
Смогли украсть какие-то данные, или обмануть сервер? Нет.
То что происходит на устройстве пользователя — это дело пользователя, на остальных пользователей это никак не влияет.
Более того, нормальное приложение и не будет проверять есть ли рут или режим разработчика, отправить в аналитику на бекенд — почему нет, блокировать функционал — бред.
Prototik
И всё это обходится банальным Magisk (ну ладно, отладку и usb он не спрячет, но рут приложение не найдёт, зато пройдёт SafetyNet).
А вообще это дико раздражает, когда кто-то за меня решает, что и как я должен делать на моём устройстве.
Skif_1993 Автор
Спасибо за отзыв. Ознакомлюсь с этими программами. Не знал про них
grvelvet
Да, отличная утилита, плюс ещё хороша systemless модулями.
Skif_1993 Автор
Спасибо большое. Обязательно ознакомлюсь.
qw1
Я думаю, когда Magisk наберёт популярность, сравнимую с chainfire SuperSU, будут прицельно его детектировать.
Prototik
В том то и дело, что он systemless (ставится в boot, само приложение может перепаковываться под непонятную белиберду). system раздел не трогается, dm-verify проходит, safetynet проходит — его *мягко говоря* трудно задетектить, если не сказать, что невозможно.
qw1
Это так. Но boot разве не доступен приложениям на чтение? Можно распаковать initramfs и найти там следы Magisk.
Prototik
Конечно нет, все разделы — сплошное 0600 и доступны исключительно руту.