В прошлой статье я делал обзор на 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. Запуск приложения |
Выводы
На практическом примере я показал, как можно отключить некоторые проверки состояния среды исполнения. Дальше уже с помощью других тулзов провели анализ на уязвимость, но это другая история…
К чему может привести халатное отношение к защите кода:
- это обхождения тех или иных проверок, которые вложены в программу
- внедрение стороннего кода, после чего программа может быть опубликована и использоваться как вредоносная
Как можно защититься? Мы в ByteCode решили не изобретать велосипед и предложили клиенту обфусцировать исходный код и использовать функции которые проверяют модификацию исходного кода.
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".