- PVSM.RU - https://www.pvsm.ru -

Взлом одного Android приложения

Недавно я усиленно разрабатывал свое приложение под Android, и в процессе защиты платной версии понял, что довольно сложно обезопасить приложение от взлома. Ради спортивного интереса решил попробовать убрать рекламу из одного бесплатного приложения, в котором баннер предлагается скрыть, если заплатить денежку через In-App Purchase.
Взлом одного Android приложения

В этой статье я опишу, как мне удалось убрать рекламу бесплатно и в конце — несколько слов о том, как усложнить задачу взломщикам.

Шаг 1. Получаем «читаемый» код приложения.
Чтобы добыть APK приложения из телефона, нужны root права. Вытягиваем приложение из телефона с помощью adb (пусть, для конспирации, у нас будет приложение greatapp.apk):
adb pull /data/app/greatapp.apk

APK — это ZIP архив, достаем оттуда интересующий нас файл classes.dex со скомпилированным кодом.
Будем использовать ассемблер/дизассемблер smali/baksmali [1] для наших грязных дел.
java -jar baksmali-1.3.2.jar classes.dex

На выходе получаем директорию out с кучей файлов *.smali. Каждый из них соответствует файлу .class. Естественно, все обфусцированно по самое не хочу, выглядит эта директория вот так:
Взлом одного Android приложения

Попытаемся понять, где в этой обфусцированной куче «говорится» о рекламе. Сначала я просто сделал поиск с текстом "AdView" (View, отображающий рекламу из AdMob SDK) по всем файлам. Нашелся сам AdView.smali, R$id.smali и некий d.smali. AdView.smali смотреть не очень интересно, R.$id я как-то сначала проигнорировал, и пошел сразу в таинственный d.smali.

Шаг 2. Пойти по неверному пути.
Вот и метод a() в файле d.smali с первым упоминанием AdView (я решил, скриншотом лучше, а то без форматирования это очень уныло читать):
Взлом одного Android приложения

Метод ничего не возвращает, поэтому я, недолго думая, решил просто вставить поближе к началу return-void. Когда я все собрал и запустил, приложение радостно крэшнулось. Лог из adb logcat:

E/AndroidRuntime(14262): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.greatapp/com.greatapp.GreatApp}: android.view.InflateException: Binary XML file line #22: Error inflating class com.google.ads.AdView

Понятно, что наш AdView в результате манипуляций должным образом не создался. Забудем пока про d.smali.

Шаг 3. Откатываем назад все изменения и смотрим на пропущенный ранее R$id. Вот и строчка с AdView:
# static fields
.field public static final adView:I = 0x7f080006

Похоже, это идентификатор View с рекламой. Поищем, где он используется, сделав поиск по значению 0x7f080006. Получаем всего два результата: тот же R$id и GreatApp.smali. В GreatApp.smali текст уже гораздо интереснее (комментарии мои):
Взлом одного Android приложения

Видно, что этот идентификатор используется для поиска View (строка 588) и буквально сразу же AdView удаляется с экрана (строка 595). Видимо, удаляется, если пользователь заплатил за отсутствие рекламы? Если посмотреть немного выше, то взгляд цепляется за строчку 558 с «ключевыми словами»:
invoke-static {v7, v8}, Lnet/robotmedia/billing/BillingController;->isPurchased(Landroid/content/Context;Ljava/lang/String;)Z

robotmedia [2] — сторонняя (open source) библиотека, призванная упростить работу с in-app billing-ом в андроиде. Почему же она не была полностью обфусцирована? Ну да ладно, повезло.
Видно, что метод isPurchased() возвращает строку, которая с помощью Boolean.valueOf() преобразуется в объект Boolean и, наконец, в обычный boolean через booleanValue().
И тут самое интересное, в строке 572 мы переходим в некий :cond_32, если значение результата == false. А иначе начинается уже просмотренный код поиска и удаления AdView.

Шаг 4. Минимальное изменение, собрать и запустить.
Что ж, дело за малым — удаляем эту ключевую строку, собираем приложение и сразу инсталлируем на телефон:
java -jar ..smalismali-1.3.2.jar ..smaliout -o classes.dex
apkbuilder C:develgreatappgreatapp_cracked.apk -u -z C:develgreatappgreatapp_noclasses.apk -f C:develgreatappclasses.dex
jarsigner -verbose -keystore my-release-key.keystore -storepass testtest -keypass testtest greatapp_cracked.apk alias_name
adb install greatapp_cracked.apk

(greatapp_noclasses.apk — это оригинальный APK приложения, из которого удален classes.dex, сертификаты создаются с помощью Android SDK).
И ура, запускаем приложение, никакой рекламы!

Теперь о том, как усложнить задачу любителям халявы (это лишь то, что я запомнил из видео про пиратство с Google IO 2011, ссылка ниже):

  • Не осуществлять проверку оплаты или лицензирования в классах Activity и особенно методах onCreate() и ему подобных. Эти «точки входа» запускаются всегда в известное время и не обфусцируются, их всегда можно посмотреть и понять, что происходит с различными элементами UI
  • Лучше всего проводить проверку не в основном потоке и в случайные моменты времени
  • Проверять CRC файла classes.dex, причем хранить его зашифрованным
  • Хранить код проверки лицензии или покупки скомпилированным и зашифрованным как ресурс приложения, динамически его загружать и запускать через reflection

Надеюсь, было интересно. В заключение, несколько полезных ссылок по теме:

Автор: memkill


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/android/4918

Ссылки в тексте:

[1] smali/baksmali: http://code.google.com/p/smali/

[2] robotmedia: http://www.robotmedia.net/2011/06/android-billing-library-in-app-billing-made-simple/

[3] Отличное видео с Google IO 2011: http://www.google.com/events/io/2011/sessions/evading-pirates-and-stopping-vampires-using-license-verification-library-in-app-billing-and-app-engine.html

[4] Небольшая статья с блога Android Developers: http://android-developers.blogspot.com/2010/09/securing-android-lvl-applications.html

[5] Статья на Хабре: http://habrahabr.ru/post/127637/

[6] Dalvik VM bytecodes: http://www.netmite.com/android/mydroid/dalvik/docs/dalvik-bytecode.html

[7] http://androidcracking.blogspot.com/: http://androidcracking.blogspot.com/