Деобфускация строк Android малвари через Frida: извлекаем C2-адрес без запуска на устройстве

Свежий семпл с MalwareBazaar, банковский троян. Открываю в jadx (декомпилятор — превращает байт-код Android обратно в читаемую Java), вижу MainActivity с единственным вызовом статического метода a.b.c.a(). Строки — сплошные массивы байтов, ни одного читаемого символа. Ни URL, ни ключей, ни даже осмысленных имён переменных. Антивирусы детектят семейство Banker, но C2-адрес — сервер управления, куда малварь сливает украденные данные — не вытаскивается ничем, кроме рантайма. Хук через Frida на javax.crypto.Cipher, эмулятор Android — и адрес C2 в логах через 40 минут. Реальное устройство не понадобилось, весь анализ на виртуалке.
Ниже — конкретный workflow: от открытия APK в jadx до расшифрованных строк через Frida, с альтернативным путём через smali-гаджеты для случаев, когда малварь детектирует Frida.
Зачем малварь прячет строки: бизнес-логика обфускации
Прежде чем лезть в инструменты, разберёмся с мотивацией. Типичный Android-троян работает по цепочке: установка → закрепление → связь с C2 → выполнение команд (кража SMS, перехват банковских OTP, overlay-атаки поверх банковских приложений). Если C2-адрес лежит в коде открытым текстом, любой антивирус вытащит его за секунды. Строка https://evil-c2.example.com/gate.php в DEX-файле — приговор для малвари ещё до первого запуска.
В терминах MITRE ATT&CK (открытая база тактик и техник атак — каждая техника имеет идентификатор вида T-код, по которому можно искать описания и детекты) обфускация строк маппится так:
- T1027 — Obfuscated Files or Information (тактика: Defense Evasion) — малварь шифрует строки, чтобы статический анализ не дал результатов
- T1027.013 — Encrypted/Encoded File — подтехника: данные хранятся зашифрованными внутри APK
- T1140 — Deobfuscate/Decode Files or Information — обратная сторона: при запуске малварь расшифровывает строки в рантайме для собственного использования
Со стороны C2-коммуникации работают T1071.001 — Web Protocols (связь с C2 по HTTP/HTTPS), T1132.001 — Standard Encoding (кодирование данных при передаче, например Base64) и T1132.002 — Non-Standard Encoding (кастомное кодирование, например XOR). Финансовый импакт прямой: один живой C2-адрес управляет тысячами заражённых устройств. Пока аналитик не извлёк адрес — домен не заблокирован, IOC (индикаторы компрометации — конкретные артефакты: домены, IP, хэши файлов) не переданы другим командам, ботнет продолжает работать.
По данным исследования ARBDroid, значительная доля образцов реальной малвари скрывает вызовы чувствительных API через обфускацию. Самые ходовые техники — шифрование строк, шифрование целых классов и сокрытие API-вызовов через Java Reflection.
Требования к окружению для реверс-инжиниринга Android малвари
Перед переходом к практике — убедитесь, что окружение готово:
- ОС: Linux (Kali / Ubuntu 20.04+) или macOS; Windows — через WSL2
- RAM: минимум 8 ГБ, рекомендуется 16 ГБ (эмулятор + jadx + Frida одновременно съедают 6–10 ГБ)
- Android-эмулятор: Android Studio AVD с API Level 28+ (Android 9+), образ x86_64 с Google APIs, root-доступ включён. На образах Android 11+ может потребоваться настройка SELinux (
adb shell setenforce 0) для корректной работы frida-server — нужен userdebug/eng образ AVD (не production Google Play), иначе команда недоступна. Альтернатива — Genymotion - Python: 3.8+
- Инструменты:
jadx(декомпилятор DEX→Java),apktool(распаковка/сборка APK в smali), пакетыfrida-toolsиfrida(ставятся черезpip install frida-tools frida), утилитыkeytoolиapksignerдля подписи пересобранного APK
Для динамического анализа понадобится frida-server — бинарник, который запускается внутри Android и принимает команды от Frida-клиента на хосте. Качается с GitHub-релизов Frida под архитектуру эмулятора (обычно frida-server-*-android-x86_64), закидывается через adb push frida-server /data/local/tmp/, делается исполняемым (chmod 755) и запускается в фоне (adb shell /data/local/tmp/frida-server &).
Статический анализ вредоносного APK: находим метод расшифровки
[Применимо: любой анализ Android-малвари, полностью offline]
Цель первого прохода — не расшифровать строки, а найти метод, который это делает. Расшифровку выполнит Frida.
Шаг 1. Откройте APK в jadx: jadx-gui sample.apk. В дереве классов найдите MainActivity или класс, указанный в AndroidManifest.xml как точка входа (android.intent.action.MAIN). Нюанс: в банковских троянах MainActivity часто содержит лишь загрузчик, а реальная логика спрятана в отдельном DEX-файле в каталоге assets/ (описано в исследовании RedFenec). Если jadx показывает почти пустой onCreate — ищите вызовы DexClassLoader или обращения к файлам из assets/.
Шаг 2. Ищите характерные паттерны обфускации строк:
— Строковые константы выглядят как массивы байтов: byte[] b = {0x4a, 0x7f, 0x2e, ...} или Base64-блобы
— Есть метод, принимающий String (или byte[]) и возвращающий String — это и есть функция расшифровки
— В импортах присутствует javax.crypto.Cipher с алгоритмом AES, RC4 или DES — малварь расшифровывает строки стандартными средствами Java
Шаг 3. Зафиксируйте полное имя класса и метода расшифровки — например, com.fky.lblabjglab.a.a(String). Запишите также зашифрованные константы, которые передаются в этот метод из разных частей кода. Как понять, что нашли нужный метод: он вызывается из множества мест по всему приложению, принимает нечитаемую строку и возвращает String.
Когда статический анализ НЕ работает:
— Весь DEX зашифрован (class encryption) — jadx показывает пустоту или мусор
— Метод расшифровки реализован в нативной библиотеке (.so через JNI) — jadx его не увидит, потребуется Ghidra для разбора ARM-кода
— Многоуровневая упаковка: основной payload загружается динамически из зашифрованного файла в assets/
В таких случаях без динамического анализа не обойтись.
Frida-скрипт для деобфускации строк: два подхода
[Применимо: эмулятор или rooted-устройство, Android 7+. Не работает если: малварь детектирует Frida через проверку порта 27042, наличие frida-agent в /proc/self/maps или frida-server в списке процессов (T1622 — Debugger Evasion)]
Универсальный хук на javax.crypto.Cipher
Когда точное имя метода расшифровки неизвестно (вместо decrypt стоит a.b.c.a()), хукайте javax.crypto.Cipher.doFinal. Любой вызов стандартного шифрования в Java проходит через этот метод — малварь не исключение:
Java.perform(function() {
var Cipher = Java.use('javax.crypto.Cipher');
var doFinal = Cipher.doFinal.overload('[B');
doFinal.implementation = function(input) {
// вызываем оригинальную реализацию: .call() внутри .implementation обращается к оригинальному entry-point, а не к хуку
var result = doFinal.call(this, input);
var mode = this.getAlgorithm();
var str = Java.use('java.lang.String').$new(result);
console.log('[' + mode + '] → ' + str);
return result;
};
});
Сохраните скрипт в файл hook_cipher.js. Установите APK на эмулятор (adb install sample.apk) и запустите через Frida: frida -U -l hook_cipher.js -f com.malware.package. Флаг -f заставляет Frida запустить процесс с паузой — ни один вызов расшифровки при инициализации не пропустите. Ожидаемый вывод:
[AES/CBC/PKCS5Padding] → https://api.telegram-check.xyz/gate.php
[AES/CBC/PKCS5Padding] → device_admin_enable
[AES/CBC/PKCS5Padding] → sms_forward_start
Первая строка — кандидат на C2-адрес.
Прямой вызов метода расшифровки без запуска малвари
Более элегантный путь: вместо ожидания, пока малварь сама вызовет расшифровку, вы дёргаете метод напрямую на всех зашифрованных строках, найденных статически. Пример ниже предполагает, что метод расшифровки — статический (static). Если метод нестатический, придётся создать экземпляр через Dec.$new() или найти живой через Java.choose('com.malware.utils.a', { onMatch: function(inst) { ... } }). При запуске через frida -f процесс стартует в приостановленном состоянии (spawn-режим) — классы уже загружены в память, но вредоносная логика не запущена:
Java.perform(function() {
var Dec = Java.use('com.malware.utils.a');
var blobs = ['aGVsbG8=', 'd29ybGQ=', 'c21z', 'YzI='];
blobs.forEach(function(s) {
try {
// Если в классе несколько перегрузок a(), укажите сигнатуру: Dec.a.overload('java.lang.String')
var clear = Dec.a(s);
console.log(s + ' → ' + clear);
} catch(e) { console.log(s + ' ERR: ' + e); }
});
});
Массив blobs — зашифрованные константы, выписанные на этапе статического анализа. Скрипт вызывает метод a() класса-декриптора на каждой из них и выводит результат. Приложение при этом не выполняет ни одной строки своего onCreate — вы используете малварь как библиотеку расшифровки.
На бумаге формула понятна, но увидеть расшифрованные строки в рантайме — совсем другое ощущение. Если хочется потренироваться, на HackerLab.pro есть задачи в категории reverse, где нужно анализировать Android-приложения с обфускацией — потребуется регистрация, после неё доступны таски разных уровней.
Альтернатива: инъекция smali-гаджета для снятия обфускации строк
[Применимо: когда малварь детектирует Frida, любая версия Android. Не работает если: код расшифровки в нативной .so, а не в DEX]
Подход, описанный командой JPCERT/CC: вместо Frida вставляем диагностический код прямо в smali-файлы малвари. Smali — текстовое представление байт-кода Android, аналог ассемблера для виртуальной машины Dalvik/ART. Редактируется в обычном текстовом редакторе.
Шаг 1. Распакуйте APK: apktool d sample.apk. В каталоге sample/smali/ появятся smali-файлы.
Шаг 2. Откройте smali-файл с методом расшифровки (путь определили на этапе статического анализа: если jadx показал com.fky.lblabjglab.a, то файл — smali/com/fky/lblabjglab/a.smali). Перед инструкцией return добавьте две строки:
const-string v2, "DEOB" — записываем тег для Logcat.
invoke-static {v2, v0}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I — выводим результат расшифровки в системный лог.
Здесь v0 — пример регистра. Реальный номер определяется из smali-кода конкретного метода: найдите инструкцию return-object vX (или return-object/from16 vXXX при регистрах с индексом больше 15) в конце метода — этот регистр содержит результат. Замените v0 на найденный. Также проверьте директиву .locals N в начале метода — если регистр для тега (v2 в примере) выходит за пределы N, увеличьте .locals (например, с .locals 2 на .locals 3), иначе apktool b выдаст ошибку.
Шаг 3. Соберите модифицированный APK через apktool b sample, подпишите (keytool + apksigner) и установите на эмулятор.
Шаг 4. Запустите приложение и фильтруйте Logcat: adb logcat -s DEOB. Каждый вызов расшифровки покажет результат — URL, команды, ключи.
Преимущество: никакого Frida в процессе, малварь не обнаруживает инструментацию. Ограничение: нужно разбираться в smali-регистрах и точно знать, куда вставлять гаджет. Подобная техника применялась при анализе троянов с RC4-шифрованием строк — не требовалось идентифицировать алгоритм, достаточно было перехватить вход и выход метода.
Извлечение C2-адреса из малвари: что делать с результатом
После деобфускации — любым из описанных способов — в выводе окажутся URL, IP-адреса, пути API-эндпоинтов, команды управления. Отфильтруйте строки с http, ://, IP-паттернами, доменными именами — это кандидаты на C2.
Дальше: 1. Проверить домен/IP через VirusTotal, AbuseIPDB (Confidence Score выше 75 — высокая вероятность вредоносности), whois для определения возраста домена 2. Маппинг на MITRE ATT&CK для threat intelligence отчёта: обфускация → T1027, расшифровка → T1140, связь по HTTP → T1071.001, кодирование трафика → T1132.001/T1132.002 3. Передать IOC в SOC: добавить домен/IP в блоклист DNS/firewall, уведомить регистратора и хостера
Сравнение подходов: когда что применять
| Подход | Плюсы | Минусы | Когда использовать |
|---|---|---|---|
| Frida + frida-server | Быстрый старт, интерактивность, универсальный хук через Cipher | Требует root, детектируется малварью | Первый проход по новому семплу на эмуляторе |
| Frida Gadget | Не требует root | Нужна пересборка и переподпись APK | Анализ на устройстве без root |
| Smali-гаджет | Не детектируется как Frida | Ручная работа, нужно знание smali | Малварь активно ищет Frida |
| Статический реверс алгоритма | Полностью offline, повторяемый | Долго, алгоритм может быть в нативном коде | Нативное шифрование в .so |
На практике комбинация работает лучше всего: статика в jadx → прицельный Frida-хук → smali-гаджет для верификации, если Frida-результатам не доверяете.
Типичная ошибка новичка — пытаться реверсить алгоритм шифрования вручную: переписывать AES-дешифратор на Python, подбирать ключ и IV. Это работает, но занимает часы вместо минут. Малварь уже содержит собственный дешифратор — зачем писать второй, если можно заставить оригинал сделать работу за вас? В этом суть динамического анализа Android приложений через Frida: вы не реверсите алгоритм, вы реверсите результат. По OWASP MASVS-CODE, обфускация через ProGuard/R8 рекомендуется для защиты кода — но проектировалась она для легитимных приложений, а не для противодействия целенаправленному анализу. Малварь добавляет поверх шифрование строк и классов, и именно эти слои снимаются Frida за один хук.
Ещё один момент, который редко обсуждают: рынок автоматических деобфускаторов растёт. LLM уже восстанавливают семантику обфусцированного кода, исследовательские прототипы вроде ARBDroid автоматизируют расшифровку строк и API hiding на уровне рантайма Android. Через пару лет ручной хук на Cipher.doFinal может выглядеть архаикой. Но понимание того, что происходит под капотом — какой метод вызывается, какой регистр содержит результат, как малварь загружает зашифрованный класс — это фундамент, который автоматизация не заменит. Инструменты меняются, принципы — нет. Если ищешь джуниор-роль в ИБ и хочешь дойти до таких задач — на IB Basics показывают не теорию, а как джуны реально решают рабочие задачи.
Эту тему и смежные навыки разбирают на практике в курсе «Профессия Реверс-инженер» Codeby Academy.