Ломаємо софт для Android

Зміст статті
Обфускатори
Насправді ти вже маєш бути знайомим як мінімум з одним методом обфускації (заплутування) коду. Минулої статті ми впроваджували шкідливу функціональність у WhatsApp, і якщо ти уважно читав статтю і сам пробував декомпілювати WhatsApp, то напевно помітив, що більшість класів програми, майже всі його методи та змінні мають дивні імена: aa, ab або щось на зразок 2F3239 якщо дивитися код за допомогою декомпілятора jadx.
Це і є обфускація, і я можу з повною впевненістю стверджувати, що вона зроблена за допомогою інструменту ProGuard з комплекту Android Studio. Саме він видає на виході такі дивні імена класів, методів і змінних, а крім того, видаляє код, що не використовується, і оптимізує деякі ділянки програми за допомогою інлайнінгу методів.
Пропущений через ProGuard код компактніший, займає менше пам'яті і набагато складніший для розуміння. Але тільки в тому випадку, якщо це велика програма. Розібратися, що робить простий обфусцований код, дуже легко:
Але уяви, якщо з подібних буквених, цифрових або буквено-цифрових позначень (ProGuard дозволяє використовувати будь-який словник для генерації ідентифікаторів) складатиметься громіздкий додаток до десятків тисяч рядків коду:
І так на тисячі рядків вперед, а далі твій декомпілятор може поперхнутися кодом і видати замість Java щось на зразок цього:
Непогано, чи не так? А тепер уяви, що ці рядки складаються не із звичайних символів алфавіту, а із символів Unicode (так робить DexGuard, комерційна версія ProGuard) або наборів на кшталт l1ll1, що повторюються разів так п'ятдесят. Розробникцілком може застосувати і потужніші засоби обфускації, нашпигувавши додаток безглуздим кодом. Такий код не виконуватиме жодних корисних функцій, але направить тебе зовсім не в той бік, що загрожує як мінімум втратою часу.
Не бажаючи його втрачати, ти можеш почати з пошуку рядків, які приведуть тебе до мети: це можуть бути різні ідентифікатори, за допомогою яких програма реєструє себе на сервері, рядки, що записуються в конфіг при оплаті, паролі тощо. Однак замість рядків ти цілком можеш побачити щось на кшталт цього:
Це зашифрований рядок, який розшифровується під час виконання програми. Такий захист пропонують DexGuard, Allatory та багато інших обфускаторів. Вона дійсно здатна зупинити багатьох, але сіль у тому, що якщо є зашифрований текст, значить, у коді повинен бути і дешифратор. Його дуже легко знайти за допомогою пошуку на ім'я змінної (в даному випадку zb). При кожному її використанні завжди буде викликатись метод, що дешифрує рядок. Виглядати це може приблизно так:
Нерідко, щоправда, доведеться попітніти, щоби знайти дешифратор. Він може бути вбудований у цілком нешкідливу функцію і дешифрувати рядок неявно, може складатися з кількох функцій, що викликаються на різних етапах роботи з рядком. Сам зашифрований рядок може бути розбитий на кілька блоків, які збираються разом під час виконання програми. Але найшик — це клас-дешифратор усередині масиву!
DexGuard має функцію приховування класів, яка працює в такий спосіб. Байт-код класу, що приховується, витягується з програми, стискається за допомогою алгоритму Gzip і записується назад у додаток у формі масиву байтів (byte[]). Далі додаток впроваджується завантажувач, який витягує код класу з масивуі з допомогою рефлексії створює з його основі об'єкт, та був викликає потрібні методи. І звичайно, DexGuard використовує цей трюк для приховання дешифратора, а також коду інших класів за бажанням розробника. Більш того, приховані в масивах класи можуть бути зашифровані за допомогою прихованого в іншому масиві дешифратора!
З рефлексією замість прямого виклику методів об'єкта ти можеш зіткнутися і в інших обставинах, не пов'язаних із прихованням класів. Рефлексія може бути використана просто для обфускування (як у випадку з обфускатором Allatory). Тоді замість такого коду:
ти побачиш щось на кшталт цього:
А якщо використовується шифрування – це:
У цьому випадку я закодував рядки в Base64, тому їх легко «розкодувати» за допомогою команди
Але в реальному додатку тобі, швидше за все, доведеться розшифрувати всі ці рядки за допомогою описаного вище способу просто для того, щоб зрозуміти які об'єкти і методи викликає додаток у своїй роботі.
Пакувальники
А ще є пакувальники. Це інший вид захисту, заснований не на заплутуванні коду, а на повному прихуванні від очей реверсера. Працює він так. Оригінальний файл classes.dex (який містить код програми) перейменовується, шифрується і переміщається в інший каталог всередині пакету APK (це може бути каталог assets, res або будь-який інший). Місце оригінального classes.dex займає розпакувальник, завдання якого - завантажити в пам'ять оригінальний classes.dex, розшифрувати його та передати йому керування. Для ускладнення життя реверсера основна логіка розпакувальника реалізується мовою сі, що компілюється в нативний код ARM із застосуванням засобів обфускації та захисту від налагодження (gdb, ptrace).
Хороший пакувальник створює дуже великі проблеми для аналізу коду програми. У ряді випадківєдиний ефективний варіант боротьби з ними - це зняття дампа пам'яті процесу та витяг з нього вже розшифрованого коду classes.dex. Але є й хороші новини: пакувальник накладає серйозні обмеження на функціональність програми, призводить до несумісностей та збільшених витрат пам'яті. Так що розробники звичайних додатків використовують пакувальники рідко, зате їх дуже люблять творці різного роду троянів та вірусів.
Обчислити наявність пакувальника в APK дуже легко. Для цього достатньо глянути на вміст каталогу lib/armeabi. Якщо ти знайдеш у ньому файл libapkprotect2.so, значить, застосований пакувальник ApkProtect, файл libsecexe.so - Bangle, libexecmain.so - ljiami.