6 способів заховати дані в Android-додатку

Привіт, дорогий читачу, вже досить давно я вивчаю мобільні додатки. Більшість додатків не намагаються щось приховати від мене свій «секретний» функціонал. А я в цей час радію, адже мені не доводиться вивчати чийсь обфусцированный код.

 

 

У цій статті я хотів би поділитися своїм баченням обфускации, а також розповісти про цікавий метод приховування бізнес-логіки в додатках з NDK, який знайшов відносно недавно. Так що якщо вас цікавлять живі приклади обфусцированного коду Android — прошу під кат.

Під обфускацией в рамках цієї статті будемо розуміти приведення виконуваного коду Android-додатки до важкого для аналізу вигляді. Існує кілька причин ускладнювати аналіз коду:

  1. Жоден бізнес не хоче, щоб в її «нутрощах» колупалися.
  2. Навіть якщо у вас програма-пустушка, цікаве там можна знайти завжди (приклад з инстаграмом).

Багато розробників вирішують проблему простим форком конфига ProGuard. Це не найкращий спосіб захисту даних (якщо ви перший раз чуєте про це, то див. вікі).

Хочу привести показовий приклад, чому передбачувана “захист” з допомогою ProGuard не працює. Візьмемо будь простенький приклад з Google Samples.

 

 

Підключивши до нього ProGuard зі стандартним конфіг, отримаємо декомпилированный код:

 

 

«Тов, нічого незрозуміло» – скажемо ми і заспокоїмося. Але через пару хвилин перемикання між файлами знайдемо подібні шматочки коду:

 

 

У цьому прикладі код програми виглядає утрудненим досить слабко (логування даних, створення відео захоплення), тому деякі методи, використані в оригінальному коді, легко зрозумілі і після обробки конфіг ProGuard.

 

Далі більше, поглянемо на data-класи в Kotlin. Data-клас за замовчуванням створює метод “toString”, який містить у собі назви змінних екземпляра і назва самого класу.

 

Вихідний data-клас:

 

 

Він може перетворитися на ласий шматочок для реверсера:

 


(автогенерація методу toString в Kotlin)

 

З’ясовується, що ProGuard ховає далеко не весь вихідний код проекту.

 

Якщо я все ще не переконав вас в недоцільності захисту коду таким способом, то давайте спробуємо залишити у нашому проекті атрибут “.source”.

 

-keepattributes SourceFile

 

Цей рядок є у багатьох opensource проектах. Вона дозволяє переглядати StackTrace при падінні програми. Однак, витягнувши “.source” з smali-коду, ми отримаємо всю ієрархію проекту з повними назвами класів.

Читайте також  Застосування розширюваних політик Pull Request в VSTS для підтримки процесу розробки

 

За визначенням, обфускація – це “приведення вихідного коду в нечитаний вигляд для того, щоб протидіяти різним видам ресерча”. Однак, ProGuard (при використанні зі стандартним конфіг) не робить код нечитабельним – він працює як минификатор, стискає назви та выкидывающий зайві класи з проекту.

 

Таке використання ProGuard – це легке, але не зовсім підходяще для хорошої обфускации рішення на “авось”. Доброго розробнику потрібно змусити ресерчера (або зловмисника) злякатися “китайських символів”, які важко деобфусцировать.

 

Якщо вам цікаво детальніше дізнатися про ProGuard, то пропоную таку пізнавальну статтю.

 

Що ховаємо

 

Тепер давайте подивимося, що зазвичай ховають у додатках.

 

  • Ключі шифрування:

 

 

  • Специфічну логіку додатка:

 

 

У коді часто може бути заховано щось несподіване (спостереження з особистого досвіду), наприклад:

 

  • Імена розробників проекту
  • Повний шлях до проекту
  • “client_secret” для протоколу Oauth2
  • PDF-книга “Як розробляти під Android” (напевно, щоб завжди була під рукою)

 

Тепер ми знаємо, що може ховатися в Android-додатках і можемо переходити до головного, а саме до способів приховання цих даних.

 

Способи приховування даних

 

Варіант 1: Нічого не приховувати, залишити все на увазі

 

В такому випадку я просто покажу вам цю картинку 🙂

 

“Допоможіть Даші знайти бізнес-логіку”

 

 

Це нетрудозатратное і абсолютно безкоштовне рішення підійде для:

 

  • Простих застосувань, які не взаємодіють з мережею і не зберігають чутливу інформацію;
  • Додатків, які використовують тільки публічне API.

 

Варіант 2: Використовувати ProGuard з правильними налаштуваннями

 

Це рішення все-таки має право на життя, тому що, в першу чергу, воно є простим і безкоштовним. Незважаючи на вищезазначені недоліки, у нього є вагомий плюс: при правильному налаштуванні ProGuard-правил додаток може дійсно стати обфусцированным.

 

Однак, потрібно розуміти, що таке рішення після кожної збірки вимагає від розробника декомпіляції та перевірки, чи все нормально. Витративши декілька хвилин на вивчення APK файлу, розробник (і його компанія) можуть стати впевненіше в безпеці свого продукту.

Як вивчати APK-файл

Перевірити програму на наявність обфускации досить просто.

 

Для того, щоб дістати APK-файл з проекту існує кілька шляхів:
взяти з директорії проекту (в Android Studio зазвичай назва папки “build”);
встановити додаток на смартфон і дістати APK з допомогою програми “Апк Extractor”.

 

Після цього, користуючись утилітою Apktool, отримуємо Smali-код (інструкція по отриманню тут https://ibotpeaches.github.io/Apktool/documentation) і намагаємося знайти щось підозріло відчутний в рядках проекту. До речі, для пошуку читаються кодів можна запастися заздалегідь готовими bash-командами.

Читайте також  Повнолітня журналістика: від Росії до Кремля

 

Це рішення підійде для:

 

  • Додатків іграшок, додатків інтернет-магазинів і т. п.;
  • Додатків, які дійсно є тонкими клієнтами, і всі дані прилітають виключно з серверної сторони;
  • Додатків, які не пишуть на всіх своїх банерах “Безпечне додаток №1”.

 

Варіант 3: Використовувати Open Source Obfuscator

 

На жаль, реально хороших безкоштовних обфускаторов для мобільних додатків я не знаю. А обфускаторы, які можна знайти в мережі можуть принести вам багато головного болю, оскільки зібрати такий проект під нові версії API буде занадто складно.

 

Історично склалося, що існуючі круті обфускаторы зроблені під машинний код (для C/C++). Хороші приклади:

 

  • Obfuscator-LLVM, https://github.com/obfuscator-llvm/obfuscator
  • Movfuscator, https://github.com/xoreaxeaxeax/movfuscator

 

Наприклад, Movfuscator замінює всі opcodes mov-ами, робить код лінійним, прибираючи всі розгалуження. Однак, вкрай не рекомендується використовувати такий спосіб обфускации в бойовому проекті, тому що тоді код ризикує стати дуже повільним і важким.

 

Це рішення підійде для додатків, у яких основна частина коду — NDK.

 

Варіант 4: Використовувати пропрієтарне рішення

 

Це самий грамотний вибір для серйозних додатків, так як пропрієтарне ПЗ:
а) підтримується;
б) завжди буде актуально.

 

Приклад обфусцированного коду при використанні таких рішень:

 

 

У цьому фрагменті коду можна побачити:

 

  1. Максимально незрозумілі назви змінних (з наявністю російських букв);
  2. Китайські символи в рядках, що не дають зрозуміти, що реально відбувається в проекті;
  3. Дуже багато доданих в проект пасток (“switch”, “goto”), які сильно змінюють codeflow програми.

 

Це рішення підійде для:

 

  • Банків;
  • Страхових компаній;
  • Мобільних операторів, додатків для зберігання паролів і т. д.

 

Варіант 5: Використовувати React-Native

 

Я вирішив виділити цей пункт, так як написання кроссплатформенних додатків зараз стало дійсно популярним заняттям.

 

Крім дуже великого community, JS має дуже велику кількість відкритих обфускаторов. Наприклад, вони можуть перетворити ваш додаток в смайлики:

 

 

Мені б дуже хотілося порадити вам дане рішення, але тоді ваш проект буде працювати трохи швидше черепахи.

 

Натомість, зменшивши вимога до обфускации коду, ми можемо створити дійсно добре захищений проект. Так що гуглим “js obfuscator” і обфусцируем наш вихідний bundle-файл.

 

Це рішення підійде для тих, хто готовий писати багатоплатформовий додаток на React Native.

Xamarin

Було б дуже цікаво дізнатися про обфускаторы на Xamarin, якщо у вас є досвід їх використання – розкажіть, будь ласка, про нього в коментарях.

Читайте також  Julia. Скрипти і розбір аргументів командного рядка

 

Варіант 6: Використовувати NDK

 

Мені самому часто доводилося використовувати NDK в своєму коді. І я знаю, що деякі розробники вважають, що використання NDK рятує їх додаток від реверсеров. Це не зовсім так. Для початку потрібно зрозуміти, як саме працює приховування з допомогою NDK.

 

 

Виявляється, дуже просто. У коді є деяка JNI-домовленість, що при виклику C/C++ коду в проекті він змінюватиметься наступним чином.

 

Нативний клас NativeSummator:

 

 

Реалізація нативного методу sum:

 

 

Реалізація нативного статичного методу sum:

 

 

Стає зрозуміло, що для виклику нативного методу використовується пошук функції Java_<package name>_<Static?><class>_<method> у динамічній бібліотеці.

 

Якщо заглянути в Dalvik/ART код, то ми знайдемо наступні рядки:

 

 

(джерело)

 

Спочатку згенеруємо з Java-об’єкта наступний рядок Java_<package name>_<class>_<method>, а потім спробуємо разрезолвить метод динамічної бібліотеки з допомогою виклику “dlsym”, який спробує знайти потрібну нам функцію в NDK.

 

Так працює JNI. Його основна проблема в тому, що, декомпилировав динамічну бібліотеку, ми побачимо всі методи, як на долоні:

 

 

Значить, нам потрібно придумати таке рішення, щоб адреса функції був обфусцирован.

 

Спочатку я намагався записати дані безпосередньо в нашу JNI-таблицю, але зрозумів, що механізми ASLR і різні версії Android просто-напросто не дозволять мені зробити цей спосіб працює на всіх пристроях. Тоді я вирішив дізнатися, які методи NDK надає розробникам.

 

І, о диво, знайшовся метод “RegisterNatives”, який робить рівно те, що нам потрібно (викликає внутрішню функцію dvmRegisterJNIMethod).

 

Визначаємо масив, що описує наш нативний метод:

 

 

І реєструємо наш оголошений метод функції JNI_OnLoad (метод викликається після ініціалізації динамічної бібліотеки, тиць) :

 

 

Ура, ми самостійно сховали функцію “hideFunc”. Тепер застосуємо наш улюблений llvm-обфускатор і порадіємо безпеки коду в кінцевому вигляді.

 

Це рішення підійде для додатків, які вже використовують NDK (підключення NDK несе в проект велику кількість труднощів, тому для не-NDK додатків це рішення не так актуально).

 

Висновок

 

Насправді, в додатку не повинно зберігатися ніяких чутливих даних, або вони повинні бути доступні тільки після аутентифікації користувача. Проте, буває, що бізнес-логіка примушує розробників до зберігання токенів, ключів і специфічних елементів логіки коду усередині додатку. Сподіваюся, ця стаття допоможе вам, якщо ви не хочете ділитися такими чуттєвими даними і бути “відкритою книгою” для ресерчеров.

 

Я вважаю, що обфускація – важлива структурна частина будь-якого сучасного програми.

 

Обдумано підійти до питань приховування коду і не шукайте простих шляхів! 🙂

 

До речі, спасибі користувачеві miproblema за допомогу в деяких питаннях. Підписуйтесь на її телеграм-канал, там цікаво.

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

Вам також сподобається...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *