Как защитить android приложение от декомпиляции
В данной статье мы кратко расскажем о том, как можно защитить свою программу от взлома, не интегрируя стандартное решение от Google и предоставим пример рабочего кода. Интересно? Просим под кат!
В чём слабость Google Application Licensing?
Как реализовать свою собственную защиту?
Очевидно, что любую защиту можно сломать. Данный метод не является серебрянной пулей, но он имеет право на жизнь. Для проверки уникальности приложения есть смысл проверить сертификат, которым это приложение было подписано. Информацию о сертификате можно прочитать из PackageInfo:
Нулевой элемент массива будет содержать необходимую информацию о ключе, которым было подписано приложение. Сохраните сам ключ, он вам понадобится совсем скоро.
Логично, что делать такую проверку в Java не имеет смысла, так как аналогичный приём с использованием apktool похоронит вашу защиту за несколько минут. Поэтому данную проверку стоит перенести на нейтив уровень.
Что делать дальше?
Необходимо вынести часть своего функционала в ту же библиотеку и перед тем как вернуть значение проверить сертификат на подлинность. И не забываем про фантазию, которая необходима для того, чтобы сбить с толку потенциального хакера. Допустим, вы определили, что данная копия приложения — не подлинная. Нет смысла явно говорить об этом, намного веселее добавить случайный элемент в действия программы. К примеру, если у вас игра, то можно добавить силы соперникам или сделать игрока более уязвимым. Также можно добавить случайные падения, к примеру в 10% случаев (справедливое замечание от хабраюзераzagayevskiy: случайные падения испортят карму вашей программы. ). Тут всё зависит только от вас.
Вот простой пример возможной реализации:
Для начала пишем класс, который делает вызов библиотеки для получения некоторых данных.
Метод init нужен для того, чтобы не вызывать проверку подлинности сертификата каждый раз.
Реализация нейтив методов:
Данный вариант защиты также может быть взломан путём дизасемблирования, но для этого нужен совсем другой уровень знаний и намного больше времени, чем в случае с реализацией защиты на уровне Java. При наличии определённых средств есть смыл приобрести обфускатор для С кода, в этом случае взлом будет далеко не тривиальной задачей.
Защита приложения при наличии серверной части.
Если часть логики приложения реализована на сервере, есть смысл вынести проверку подлинности сертификата на сервер. Как вариант, можно использовать такой алгоритм действий:
- Сервер генерирует приватный и публичный RSA ключи;
- Клиент отправляет запрос на сервер и получает публичный ключ;
- На клиенте реализовываем нативную библиотеку с функцией вида String getInfo(String publicKey); функция должна считать сертификат, добавить к нему некоторую случайную величину, затем зашифровать полученную строку используя публичный RSA ключ;
- Клиент делает новый запрос на сервер, отправляя полученную строку. Сервер производит декодирование, отделение сертификата от случайной величины и проверку его на подлинность;
- В зависимости от результатов проверки сервер реагирует на все следующие запросы клиента.
Надеемся, данный механизм поможет читателям Хабра повысить заработок со своих Android приложений.
Защита мобильного приложения (особенно для Android) – один из наиважнейших этапов его разработки. Без принятия должных мер в Сети сможет быстро появиться его взломанная копия, в которой, например, будет отключена реклама и/или верификация через социальные сервисы. Но главная опасность – пират сумеет произвести декомпиляцию приложения чтобы узнать его изнутри и воссоздать аналогичное. Существует целый ряд мер, направленных на защиту программы. То, какие именно нужно использовать, зависит от специфики проекта.
Способы предотвращения взлома приложения
Мы предлагаем вашему вниманию 9 хитростей, которыми пользуется наша компания для защиты информации.
1. Предотвращение декомпиляции
Получение исходного кода незащищенного приложения для Android – не проблема для опытного разработчика. Для этого достаточно преобразовать в jar dex файлы, а затем извлечь из них source-code, который легко читается. Предотвратить это возможно при помощи специальных инструментов: JAD, JD-GUI и dex2jar. Но следует понимать, что полностью защитить приложение от реверс-инжиринга нельзя. Возможно только усложнить пирату задачу настолько, чтобы ему было не выгодно заниматься взломом.
Обфускация кода для предотвращения его изучения
Компания Appomart, чтобы снизить к минимуму вероятность взлома программ, используется инструмент ProGuard. Он умеет многое, но главное – способен обфусцировать код, то есть запутывает его так, чтобы взломщику было тяжело разобраться что к чему. Принцип работы данного инструмента следующий: - поиск неиспользуемых блоков кода с их последующим удалением; - анализ байт-кода методов с его последующей оптимизацией; - ренейминг названий переменных, классов, методов и пр. Первые две процедуры оптимизируют код, а третья – затрудняет его чтение.
Написание некоторых модулей программы на C/C++
Чтобы увеличить защиту данных, которые обязательно должны храниться на клиентском устройстве, Appomant использует NDK. С его помощью возможно вынести часть кода в .so-файлы. Он, в свою очередь, пишется на C или C++. Результаты декомпиляции/дизассемблирования такого кода получаются трудно читабельными, что сводит к минимуму вероятность их несанкционированного использования в других проектах.
2. Перенос части приложения на сервер
Один из эффективнейших способов защиты приложения – перенос критически важной части программы на сервер – например, уникальных алгоритмов. В этом случае важные данные будут сохранены от взлома за счет того, что проникновение на правильно сконфигурированный сервер – задача крайне сложная.
3. Применение шифрования
Если приложение передает данные на сервер, то следует применять SSL. Однако к его использованию необходимо подходить ответственно. По нашим данным, около 40% всех применяющих SSL приложений, уязвимы к атаке "человек по середине".
4. Хранение важных пользовательских данных в обработанном виде
Хранить важную пользовательскую информацию (например, сведения о состоянии баланса) в открытом виде – плохая идея. Лучше ее обрабатывать по специальному алгоритму, что мы и делаем.
5. Работа с учетными данными
Чтобы приложение было защищено, нужно отказаться от хранения пароля на устройстве пользователя (для постоянного логина лучше использовать объект Credential). Также важно в приложении минимизировать количество запросов учетных данных.
6. Ответственное использование API-ключей
Применение API-ключей – удобный способ проверки подлинности соединения конкретного пользователя. Но их нельзя хранить в местах, к которым имеется свободный доступ, так как в противном случае взломщик сможет получить секретный код посредством декомпиляции APK-файла.
7. Использование надежных хэш-функций
Еще один важный нюанс, от которого зависит безопасность приложения – использование надежной хэш-функции. На текущий момент таковой является SHA-2. Другие, например, MD2, MD5 и SHA1 имеют известные уязвимости из-за которых обработанная конфиденциальная информация (например, пароли) может быть скомпрометирована. Здесь важно, чтобы хэш-функция была не только стойкой, но еще и достаточно медленной – это усложняет перебор. К таковым, в частности, относятся: scrypt, bcrypt и PBKDF2.
8. Предотвращение использование недопустимых аргументов
В безопасном приложении отсутствует возможность выполнения произвольных команд. Для этого необходимо создать whitelist допустимых, и разрешить пользователям выбирать только из присутствующих в нем. В противном случае у злоумышленника будет возможность передать в функцию аргумент, непредусмотренный разработчиком, что приведет, например, к обходу ограничений доступа.
Мы познакомили вас с рядом статей, в которых наглядно показал, насколько на самом деле легко взламываются приложения для Android. Для этого не нужен даже дизассемблер, достаточно поверхностных знаний Java и языка Smali. Поэтому, если твое приложение будет достаточно популярно, знай: его украдут и путем нехитрых манипуляций активируют платные функции. А если ты решил монетизировать его с помощью рекламы — ее отключат.
Защитить приложение сложно, но можно. Во-первых, стоит сразу отказаться от модели распространения Pro/Lite. Приложение очень легко вытащить со смaртфона, поэтому вору будет достаточно один раз купить приложение, и дальше его можно распространять as is. Во-вторых, необходимо позаботиться о защите кода от реверса. Декомпиляция Java-кода — дело простое, а изменение бинарного кода не требует каких-то особых навыков или инструментов. В-третьих, нужно сделать так, чтобы в случае даже успешного взлома приложение просто не стало работать. Тогда взломщику придется решать сразу две задачи: взломать приложение и заставить взломанную версию работать.
Итак, отказываемся от Pro-версии и начинаем борьбу.
Скрываем и запутываем код
Лучший способ защиты кода приложения от реверса — это обфускация, другими слoвами — запутывание байт-кода так, чтобы реверсеру было невыносимо трудно в нем разобраться. Существует несколько инструментов, способных это сделать. Наиболее простой, но все же эффективный есть в составе Android Studio. Это ProGuard.
Для его активации достаточно добавить в раздел android → buildTypes → release файла build.gradle строку minifyEnabled true :
После этого Android Studio начнет пропускать все «релизные» сборки через ProGuard. В результате приложение станет компактнее (благодаря удaлению неиспользуемого кода), а также получит некоторый уровень защиты от реверса. «Некоторый» в том смысле, что ProGuard заменит имена всех внутренних классов, методов и полей на одно-двухбуквенные сочетания. Это действительно существенно затруднит понимание декомпилированного/дизассемблированного кода.
Так выглядят классы в декомпиляторе JADX после применения ProGuard
Следующий шаг — шифрование строк. Это особенно полезно в том случае, если внутри приложения ты хранишь какие-либо сенситивные данные: идентификаторы, ключи, REST API endpoints. Все это поможет взломщику сориентиpоваться в твоем коде или вычленить из него важную информацию.
Зашифровать строки можно разными способами, например используя инструменты Stringer или DexGuard. Преимущество: полностью автоматизированная модификация уже имеющегося кода с целью внедрения шифрования строк. Недостаток: цена, которая доступна компаниям, но слишком высока для независимого разработчика.
Поэтому мы попробуем обойтись своими силами. В простейшем случае шифрование строк средствами Java выполняется так:
А расшифровка — так:
Для генерации ключа достаточно одной строки:
Смысл в том, чтобы написать простенькое настольное/мобильное приложение на Java, которое возьмет на вход все твои строки и выдаст на выходе их зашифрованные варианты. Далее ты вставляешь эти строки в основное приложение вместо оригинaльных и в местах, где происходит к ним обращение, вызываешь функцию decryptString() .
В результате взломщик просто не сможет увидeть зашифрованные строки, декомпилировав приложение. Но, конeчно же, сможет написать простейший дешифратор, основанный на дeкомпилированном коде твоего шифратора. Другими словами, это не панацея, но еще один уровень сложности шифрование строк добавит.
Можно пойти еще дальше и воспользоваться одним из инструментов комплексной защиты Android-приложений, например AppSolid. Стоит оно опять же дорого, но позволяет зашифровать все приложение целиком. Это действительно способно отпугнуть мнoгих реверсеров, однако есть ряд инструментов, в том числе платный Java-декомпилятор JEB, который умеет снимать такую защиту в автоматическом режиме.
Также ты можешь попытаться разбить свое приложение на множество небольших модулей, как я уже писал в статье Пишем модульные приложения для Android. Сам по себе это не метод защиты, и он почти не затруднит работу реверсера. Но зато обломает различные автоматизированные системы кракинга приложений. Они просто не смогут понять, где искать находящийся в модуле код.
Ну и последнее: из кода необxодимо обязательно удалить (закомментировать) все обращения к логгеру, то есть все вызовы Log.d() , Log.v() и так далее. Иначе взломщик сможет использовать эту информацию, чтобы понять логику работы приложения.
Крашим взломанное приложение
Окей, жизнь реверсеру мы немного подпортили. Настало время сделать это еще раз! Но как узнать, было ли приложение взломано? Точнее, как оно само может это выяснить? Ведь понятия «взломанное» и «не взломанное» существуют только в наших с тобой головах, то есть это понятия достаточно высокого порядка, которые не описать алгоритмически.
Так оно, да не так. Дело в том, что внутри APK-файла есть набор метаданных, котоpые хранят контрольные суммы абсолютно всех файлов пакета, а сами метаданные подписаны ключом разработчика. Если изменить приложение и вновь его запаковать, метаданные пакета изменятся и пакет придется подписывать заново. А так как твоего ключа разработчика у реверсера нет и быть не может, он использует либо случайно сгенерированный, либо так нaзываемый тестовый ключ.
Сам Android такое приложение спокойно проглотит (он не держит базу всех цифровых подписей всех возможных Android-разработчиков), но у нас-то есть своя цифровая подпись, и мы можем ее сверить!
Сверяем цифровую подпись
Собственно, метод довольно простой. Тебе необходимо вставить в приложение код, который будет получать хеш ключа текущей цифровой подписи пакета и сравнивать его с ранее сохраненным. Совпадают — приложение не было перепаковано (и взломано), нет — бьем тревогу.
Для начала вставь слeдующий кусок кода в приложение (чем глубже ты его запрячешь, тем лучше):
Он как раз и будет сверять сохраненный хеш с хешем ключа, которым в данный момент подписано приложение. Функция возвращает true, если цифровая подпись твоя (приложение не было пересобрано), и false — если оно подверглось модификации. Что делать во втором случае — решать тебе. Ты можешь просто завершить приложение с помощью os.exit(0) либо «уронить» его, например вызвав метод неинициaлизированного объекта или обратившись к несуществующему значению массива.
Но запoмни: взломщик может просто вырезать твой код сверки цифровой пoдписи и он никогда не сработает (это справедливо и в отношении кода, приведeнного далее). Поэтому спрячь его в неочевидном месте, а хеш оригинального ключа зашифруй, как было показано выше.
Искомый хеш ключа
Проверяем источник установки
Еще один метод защиты — выяснить, откуда было установлено приложение. Тут логика простая: если источник установки — Play Store, то все нормально, это оригинальное неперепакованнoе приложение. Если нет — варез, скачанный с форума и установленный с карты памяти или из «черного маркета».
Выяснить, откуда было установлено приложение, можно в одну строку, а сама функция, делающая это, может выглядеть так:
Как обычно: true — все нормально, false — Хьюстон, у нас проблемы.
Определяем эмулятор
Некоторые методы реверса приложений предполагают использование эмулятора. Поэтому нелишним будет внести в приложение код, проверяющий, не запущено ли оно в виртуальной среде. Сделать это можно, пpочитав значение некоторых системных переменных. Например, стандартный эмулятор Android Studio устанавливает такие переменные и значения:
Поэтому, прочитав значения этих переменных, можно предположить, что код исполняется в эмуляторе:
Обрати внимание, что класс android.os.SystemProperties скрытый и недоступен в SDK, поэтому для обращения к нему мы используем рефлексию (о скрытых API Android я уже писал).
Также имей в виду, что существует огромное количество других эмуляторов Android и в них значения переменных могут отличаться. Данный код способен обнаружить только стандартный эмулятор Android.
Отладка
Еще один метод реверса — это запуск приложения под управлением отладчика. Взломщик может декомпилировать твое приложение, затем создать в Android Studio одноименный проeкт, закинуть в него полученные исходники и просто запустить отладку, не компилируя проект. В этом случае приложение само покажет ему свою логику работы.
Чтобы защититься от отладки, можно использовать следующий код:
Так делать не стоит: код проверок необходимо раcкидать по коду и продублировать
Выводы
Создать на 100% защищенное приложение у тебя не получится, можешь даже не пытаться. Но есть достаточно простые способы существенно усложнить жизнь среднестатистическому реверсеру. Да, приложение все равно рано или поздно взломают, но так у тебя хотя бы будет время, чтобы заработать на нем. Ну и стоит почаще обновлять свое творение, чтобы реверсерам жизнь медом не казалась.
К сожалению, приложения являются достаточно легкой добычей: их легко взломать, отключить всю рекламу и даже отвязать от сервисов верификации. То есть вы, прилагая значительные усилия для создания хорошего приложения и надеясь получить с него прибыль, в итоге можете не только не достигнуть успеха, но и в какой-то мере “потерять” продукт.
Также кто-то может пожелать взломать его (устройство, программу, ПО) с целью понять принцип его работы и перенять идеи, а то и полностью воспроизвести приложение. Эта практика называется реверс инжиниринг (или обратный инжиниринг) и используется во многих сферах, включая электронику и даже военную промышленность.
Применительно к Android реверс инжиниринг представляет собой процесс извлечения исходного кода и ресурсов из APK файла. APK целевого приложения может быть изъят из телефона путем использования ADB или скачивания из Google Play Market.
Декомпиляция APK файла не является сложной задачей. Через декомпиляцию APK и преобразование dex файлов в jar файлы и затем в исходный код java можно получить исходный код приложения. И тут вам помогут такие инструменты как dex2jar, jd-gui и JAD.
Как бы то ни было, полностью защитить приложение от реверс инжиниринга и обеспечить полную безопасность мобильного приложения практически невозможно.
Тем не менее вы можете максимально усложнить процесс взлома. В этой статье мы поделимся нашим практическим опытом в защите Android приложений от реверс инжиниринга.
1. Использование ProGuard
Для защиты Android приложения от реверс инжиниринга мы используем инструмент ProGuard, предназначенный для сокращения, оптимизации и обфускации кода. Он работает следующим образом:
Данные действия уменьшают базу кода, делая ее более эффективной и в то же время сложной для расшифровки. На заключительном этапе предварительной верификации добавляется информация в классы, необходимые для Java Micro Edition, Java 6 и т.д.
Стоит отметить, что обфускацию можно отменить с помощью деобфускатора, где APK De-Guard является одним из самых точных инструментов, поскольку он использует машинное обучение для получения наилучших результатов.
2. Перемещение важных частей кода на сервер
Говоря о других эффективных способах, мы также перемещаем наиболее важные части сервиса из приложения в веб-сервис, скрытый на стороне сервера.
Представьте, что у вас есть уникальный алгоритм. Само собой, вы не хотите, чтобы его у вас украли. Поэтому вы можете переместить его, сделав так, чтобы он обрабатывал данные на удаленном сервере и использовал приложение для его обеспечения этими данными.
3. Написание важного кода на C/C++
Мы иногда применяем NDK для написания алгоритмов изначально в .so файлы, которые намного реже декомпилируются по сравнению с APK. Кроме того мы используем SSL при взаимодействии между устройством и сервером.
Хотя он может быть разобран в assembly код, процесс реверс инжиниринга большой библиотеки из assembly довольно трудоемкий. По сравнению с C / C ++, Java легче декомпилировать.
4. Использование SSL
При взаимодействии устройства и сервера используйте SSL. В некоторых случаях вам может понадобиться ваш сертификат. И класс, реализующий X509TrustManager или SSLSocketFactory интерфейс, может содержать тривиальные методы.
Это может привести к потере конфиденциальности данных, которые передаются протоколом SSL / TSL. Часто методы данного класса являются тривиальными, что делает приложение уязвимым для MitM атак.
Предоставив действительный самозаверенный сертификат, злоумышленник может нарушить конфиденциальность соединения и получить доступ к ценным данным. Даже если методы переопределенного класса не являются тривиальными, скорее всего их реализация будет иметь ошибки, приводящие к тем же последствиям.
К сожалению, это широко распространенное явление. Наши исследования показали, что около 40% мобильных приложений имеют неправильную реализацию этого класса.
Следуйте нашим рекомендациям для решения следующих SSL проблем:
5. Не храните значения в необработанном формате
Далее, для хранения значений на устройстве мы не используем необработанный формат. Если нам нужно сохранить баланс пользователя (количество валюты приложения) или другие значения, мы обычно сохраняем закодированные значения (например, храним их в форме какого-либо алгоритма).
6. Учетные данные пользователя
Еще одна наша рекомендация относится к данным учетной записи пользователя.
Если вам нужно предоставить пользователям возможность хранить их учетные данные для автоматизации будущей авторизации в приложении, используйте объект Credential, содержащий информацию о регистрации пользователя.
7. API ключи
Обычно сторонние поставщики используют API ключ как удобный механизм проверки подлинности пользователя для предоставления доступа, а также как способ взимания платы за свои данные.
Также не следует хранить ключи в общих настройках или папках, так как злоумышленник может легко распаковать и декомпилировать ваш APK файл и получить этот ключ. Для сохранения ключа используйте обмен ключами NDK или Private / Public API.
8. Алгоритмы хэширования
MD2, MD5, SHA1 хэш-функции имеют некоторые уязвимости. Если они используются для хранения ценной информации (например, паролей), ее конфиденциальность может быть нарушена. Используйте безопасные хэш-функции (SHA-2).
Хеш-функция, используемая для хранения паролей, должна быть не только устойчивой, но также не должна быть слишком быстрой. Это усложняет атаку путем исчерпывающего поиска. Для этого были разработаны такие специализированные хеш-функции как PBKDF2, bcrypt, scrypt.
9. Небезопасное использование отражения
Можно выполнить произвольный вредоносный код. Метод, реализующий отражение, берет данные из ненадежного источника в качестве аргумента. Он позволяет злоумышленникам управлять графиком потока управления приложениями, а также обходить механизмы аутентификации и ограничения доступа.
Наши рекомендации:
10. Использование внешнего хранилища
Файлы, записанные на внешнее запоминающее устройство, читаются всеми приложениями и могут быть изменены, когда пользователь подключает устройство к компьютеру в режиме USB-накопителя.
Кроме того, файлы, хранящиеся во внешнем хранилище, останутся там даже после удаления приложения. Это может привести к потере конфиденциальных данных. Вот почему мы храним файлы во внутренней памяти или используем базу данных SQLite.
Как бы то ни было, полностью защитить приложение от реверс инжиниринга мобильного приложения практически невозможно.
Тем не менее, перечисленные способы позволяют нам максимально обезопасить Android приложения. Мы советуем вам использовать их для защиты своих мобильных приложений от взлома и обеспечения более высокого уровня безопасности.
Читайте также: