Перейти к содержимому

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

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

Свежий семпл с 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.