Skip to content

Latest commit

 

History

History
294 lines (223 loc) · 36.3 KB

android.md

File metadata and controls

294 lines (223 loc) · 36.3 KB

Языки: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية

Восстановление Java для Android

← Указатель документации

neverd mobile восстанавливает читаемый Java из APK, DEX и smali только встроенным движком NeverD. Независимо реализованные средства чтения используют общую типизированную модель Dalvik и генератор Java с ограниченным объёмом работы. Эта экспериментальная функция CLI не обещает полного восстановления произвольных APK. Контейнеры APK и вывод Java недоступны через нативный C SDK, SDK плагинов Python, загрузчик GUI и neverd decompile --language.

Восстановленный Java-код — реконструкция байткода. Исходные комментарии, форматирование, особенности выбора исходного языка и удалённые идентификаторы недоступны; байткод Kotlin также преобразуется в Java. Успешное выполнение не доказывает семантическую эквивалентность и не гарантирует, что каждый метод удастся скомпилировать. Этот процесс не запускает анализируемые приложения.

Быстрый старт

После сборки NeverD выберите новый каталог вывода:

neverd mobile app.apk -o recovered-app
neverd mobile classes.dex -o recovered-dex
neverd mobile MainActivity.smali -o recovered-class
neverd mobile decoded/smali -o recovered-java

Откройте recovered-app/sources/ для чтения файлов Java и recovered-app/report.json для просмотра перечня входных файлов и ограничений. Если классы smali ссылаются друг на друга, предпочтительнее передать каталог.

Настройка сред выполнения

Компонент Требование Выбор
NeverD Соберите цель neverd инструментами с поддержкой C++20. Мобильный процесс встроен в нативный CLI и не вызывает интерпретатор Python. Распространяйте исполняемый файл вместе с нативными библиотеками, необходимыми вашей сборке. build/bin/neverd / PATH

Восстановление Android использует только встроенный движок NeverD на C++20 и не требует среды выполнения Python или Java. Поддерживаются представимые обычные объявления и операции DEX 035, 037–040 и smali. DEX 041, динамические вызовы вроде invoke-custom, некоторые пути инициализации, неизвестные семантические аннотации или операции и идентификаторы, непредставимые в Java, приводят к явной ошибке. Приём формата файла не означает поддержку всех его инструкций и объявлений.

Разрешение имён Java различает заголовок и тело класса и учитывает известные случаи затенения имён в одном пакете; если без объявлений внешних суперклассов или интерфейсов нельзя однозначно определить типы либо ссылки в сгенерированном вспомогательном Java-коде, восстановление явно отклоняется, причём только для java.lang.Object без доступного объявления предполагается отсутствие наследуемых типов-членов. Литералы smali с плавающей точкой округляются непосредственно до целевой одинарной или двойной точности с сохранением полученного битового представления.

Встроенный движок C++ сохраняет и проверяет поддерживаемые метаданные Signature классов, полей и методов: переменные типов, массивы, подстановочные типы, ограничения и затенение имён на уровне метода. Результат стирания типов должен соответствовать идентичности исходного объявления DEX. Throws сохраняется; неподтверждённая иерархия исключений отклоняется. Обобщённое наследование или подстановка типов членов, повторное создание мостовых методов, вызовы обобщённых методов, параметризованные внутренние типы и сопоставление скрытых параметров конструкторов по-прежнему явно не поддерживаются без необходимых доказательств.

Пустая, видимая во время выполнения аннотация-маркер Java 8 @java.lang.Deprecated сохраняется у классов, полей, методов и конструкторов. Аннотации параметров, аннотированные статические инициализаторы, другие уровни видимости и значения элементов, например since или forRemoval, отклоняются. После повторной компиляции CI отдельно сравнивает атрибут Deprecated файла класса и аннотации времени выполнения, включая контрольные объявления без аннотаций и сгенерированные вспомогательные методы; вспомогательные методы не входят в число исходных методов.

DEX и smali также сохраняют платформенную аннотацию @android.annotation.SuppressLint у классов, полей, методов и конструкторов. Требуются видимость build и единственный массив строк value; сохраняются пустые массивы и строки, повторяющиеся значения, порядок и экранированные символы. Аннотации параметров и статических инициализаторов по-прежнему не поддерживаются. CI использует Android SDK, проверяет аннотации с политикой CLASS после повторной компиляции и сравнивает поведение программы.

Встроенный движок также сохраняет видимые во время выполнения @Retention, @Target, @Documented и @Inherited у поддерживаемых объявлений аннотаций и создаёт настоящее объявление @interface. Этот поднабор не допускает полей, методов, параметров типов или вложенных объявлений. Поддерживаются аннотации верхнего уровня и статические аннотации-члены с подтверждёнными именами, доступностью и принадлежностью внешнему типу; локальные и анонимные области видимости отклоняются.

Применение пустого маркера к объявлению класса, интерфейса или аннотации требует соответствующего доступного определения маркера в том же наборе анализируемых классов. Retention и Target должны разрешать конкретное применение. Объявление SOURCE можно восстановить, но его применение, сохранившееся во входных данных, отклоняется; для CLASS или отсутствующего Retention нужна видимость DEX build (0), для RUNTIME — runtime (1). Отсутствующий Retention отличается от явно заданного CLASS, а отсутствующий Target — от пустого массива; порядок массива Target сохраняется. Значения Target ограничены Java 8; более новые значения, например MODULE и RECORD_COMPONENT, отклоняются. @Inherited сохраняется без копирования унаследованных применений в прямые аннотации подклассов.

Объявления элементов аннотаций и их значения по умолчанию, непустые пользовательские применения, пользовательские маркеры у полей/методов/параметров, внешние определения аннотаций, контейнеры повторяемых аннотаций и kotlin.Metadata по-прежнему не поддерживаются. Определение маркера выводится как объявление класса без методов элементов или вспомогательных методов и не увеличивает число восстановленных методов.

CI обрабатывает собственные примеры Java 8 через D8 и NeverD, повторно компилирует весь сгенерированный Java-код и сравнивает полные метаданные Signature, результаты рефлексии и поведение. Эти проверки не подтверждают полное восстановление реальных приложений.

Linux и macOS

cmake --build build --target neverd

./build/bin/neverd mobile app.apk -o recovered-app

Пути с пробелами заключайте в кавычки.

Windows PowerShell

& .\build\bin\neverd.exe mobile .\app.apk -o .\recovered-app

При сборке с несколькими конфигурациями исполняемый файл может находиться в build/bin/Release/. Соблюдайте обычные требования этой сборки к распространению нативных библиотек.

Поддерживаемые входы и ограничения

Таблица входов ниже описывает восстановление. Описанные далее режимы запросов используют более узкие границы проверки.

Вход Поведение Существенное ограничение
.apk Проверяется весь ZIP, затем совместно анализируются корневые classes.dex, classes2.dex и последующие пронумерованные DEX-файлы Только код; ресурсы и манифест не декодируются
.dex Проверка и разбор DEX 035 или 037–040 встроенным средством чтения DEX 041 и неподдерживаемые объявления или операции вызывают ошибку; переименование или обрезание файла не создаёт корректный байткод
.smali Анализируется переданный класс Соседние классы, на которые он ссылается, не загружаются неявно
Каталог smali Файлы .smali собираются рекурсивно и анализируются совместно Включите вложенные классы и зависимые корневые каталоги smali в каталог входа

Для анализа распакованного дерева APK с каталогами smali/ и smali_classes2/ передайте их общий каталог. Бэкенд получает только файлы .smali, но сначала проверяется и копируется всё переданное дерево; крупные посторонние ресурсы тоже учитываются в лимитах входа. Небольшой каталог только с нужными корневыми каталогами smali сокращает объём работы.

Разделённые APK обрабатываются как отдельные входы. Каждый APK с DEX можно обработать независимо, но команда не объединяет комплект APK; части только с ресурсами завершаются ошибкой из-за отсутствия корневого DEX. .aab, .apks, .xapk, .odex, .oat и .vdex не принимаются как мобильные входы.

Ресурсы APK, AndroidManifest.xml, файлы assets, JNI/нативные библиотеки и код, загружаемый во время выполнения, не восстанавливаются как Java. Извлеките нативную библиотеку .so отдельно и используйте neverd decompile library.so -o library.c. Для этого статического процесса зашифрованный или упакованный код должен уже быть доступен в виде обычного DEX/smali; распаковка защищённого кода, подключение к устройствам и обход защиты не выполняются.

Быстрая инвентаризация классов

neverd mobile app.apk --list-classes
neverd mobile classes.dex --list-classes --class-prefix com.example
neverd mobile app.apk --list-classes --class-prefix Lcom/example/ --json
neverd mobile app.apk --list-classes -o classes.txt

Этот запрос читает идентификаторы классов без декодирования тел методов и генерации Java. Промежуточный каталог и внешняя среда выполнения не нужны. Текстовый вывод содержит по одному точному дескриптору DEX на строку, сначала в порядке каталога ZIP, затем в порядке определений DEX. --class-prefix принимает префикс дескриптора или имя пакета с точками; выполняется буквальное сопоставление префикса без проверки границы пакета. Дублирующиеся определения классов, в том числе между DEX-файлами, явно вызывают ошибку. Идентификаторы классов, которые невозможно без потерь представить в UTF-8, также вызывают ошибку.

Без -o запрос пишет в stdout. В режиме запроса -o задаёт новый файл, а не каталог восстановления. Существующие файлы остаются нетронутыми. Результаты накапливаются до успешной проверки всех выбранных DEX: некорректный DEX, обнаруженный позже, предотвращает публикацию частичного инвентаря. --json включает число совпавших классов, общее число классов, число DEX и validation_scope: "dex-envelope-and-class-identities".

Проверяются все имена ZIP, заголовки, диапазоны и заявленные ограничения ресурсов. Распаковываются и проверяются по CRC только корневой classes.dex и пронумерованные classesN.dex; целостность содержимого посторонних ресурсов не проверяется. Для каждого DEX сохраняются проверки заголовка, SHA-1, Adler-32, границ карты и используемых метаданных классов. Тела методов и неиспользуемые метаданные не проверяются. Это запрос инвентаризации, а не проверка целостности всего архива и не доказательство восстановления Java. DEX 041, разделы method-handle и custom-call-site по-прежнему не поддерживаются. Обычный путь полного восстановления по-прежнему проверяет содержимое всех записей архива.

Действуют --timeout, --max-files и --max-bytes. Лимит классов учитывает все определения во всех DEX до фильтрации; невыбранные записи ZIP также учитываются в ограничениях архива. Операция инвентаризации не принимает параметры iOS и входы smali.

Запросы ссылок в коде

Поиск прямых операндов инструкций без генерации Java:

neverd mobile app.apk --find-refs string --query 'login failed' --json
neverd mobile classes.dex --find-refs type --query 'Lcom/example/Service;' --exact
neverd mobile app.apk --find-refs method --query '->connect(' --owner 'Lcom/example/Client;'
neverd mobile app.apk --find-refs field --query 'Lcom/example/State;->ready:Z' --exact -o refs.jsonl

Поиск выполняет буквальное сопоставление подстрок с учётом регистра. --exact сопоставляет цель целиком: содержимое строки, дескриптор типа, идентификатор метода вида Lpkg/Type;->name(I)V или поля вида Lpkg/Type;->name:I. --owner ограничивает владельца целевого метода или поля точным дескриптором. Он не фильтрует метод, содержащий ссылку. Пустые запросы, входы smali и сочетание параметров запроса с параметрами восстановления не поддерживаются; метасимволы регулярных выражений считаются обычными буквальными символами.

По умолчанию выводится один компактный объект JSON на вхождение (JSON Lines). --json возвращает отчёт со счётчиками и массивом references. Каждая строка содержит dex_entry, полный идентификатор содержащего метода method, pc_code_units (16-битные единицы от первой инструкции метода), opcode, kind, target_index (локальный для DEX) и target. Сохраняются все вхождения, включая общий код, отнесённый к нескольким определениям методов. Строковые записи также содержат точные единицы target_utf16; target равен null, если одиночный суррогат не позволяет опубликовать UTF-8 без потерь. Остальные идентификаторы должны допускать представление в UTF-8.

Сканер использует границы инструкций, проверки операндов, обработку payload и проверки потока управления средства чтения для восстановления. Непосредственные значения и данные payload переключателей/массивов не могут стать ссылками. Перед установкой code_scan_complete: true проверяется каждое определённое тело кода, даже если нет совпавших целей. defined_method_count включает нативные и абстрактные объявления; scanned_method_count считает определения с телами, а scanned_code_item_count — различные физические тела в каждом DEX. matching_pool_entries считает совпавшие цели, в том числе неиспользуемые.

validation_scope: "dex-code-references" охватывает оболочку DEX, таблицы идентификаторов, принадлежность классов и членов, а также прочитанные данные кода, исключений и отладки. Аннотации, статические кодированные значения, использования только в объявлениях и неиспользуемые метаданные находятся вне границ запроса. Это не восстановление Java и не верификатор ART. Неподдерживаемый код и некорректные прочитанные данные явно вызывают ошибку. Проверка APK имеет ту же границу выбранного содержимого, что и инвентаризация классов; целостность содержимого невыбранных ресурсов не проверяется.

Результаты накапливаются по всем выбранным DEX до публикации, включая проверку дублирующихся классов между DEX. Необязательный -o задаёт новый файл. --max-files отдельно ограничивает суммарные количества определений классов, определений методов и вхождений в результатах, а также записи ZIP. --max-bytes ограничивает вход, сохраняемые данные запроса, рабочую память каждого тела и вывод (с консервативным запасом на расширение JSON). Это бюджеты операции; RSS процесса также включает входные буферы, накладные расходы аллокатора и среду выполнения. Обычные бюджеты времени и работы тоже действуют.

Параметры и приоритет

neverd mobile app.apk -o recovered-app --platform=android \
  --timeout=600 --max-files=30000 --max-bytes=4294967296 --json
Параметр По умолчанию Значение
-o PATH Обязателен для восстановления Новый каталог восстановления; в обоих режимах запроса — необязательный новый файл вывода
--list-classes Выключен Запрос идентификаторов классов APK/DEX без восстановления Java
--class-prefix PREFIX Все классы Буквальный префикс дескриптора или имени с точками; требует --list-classes
--find-refs KIND Выключен Запрос прямых ссылок инструкций на строки, типы, методы или поля
--query TEXT Обязателен для ссылок Буквальная подстрока идентификатора цели
--exact Выключен Сопоставить полный идентификатор цели ссылки
--owner DESCRIPTOR Любой владелец Точный владелец цели для запросов методов/полей
--platform=auto|android auto Явно выбрать Android или определить платформу по входу
--timeout N 300 Положительный бюджет времени анализа в секундах
--max-files N 20000 Положительный лимит записей, включая создаваемые каталоги
--max-bytes N 2147483648 Положительный лимит байтов для входа, извлечённых данных и итогового вывода
--json Выключен Вывести отчёт в JSON; иначе запросы ссылок выводят JSON Lines

Нестандартное значение --arch, --artifact, --metadata-only и ненулевой --max-func предназначены для iOS и отклоняются для Android; явный --arch=auto допустим.

Для входов, распакованных данных и конечного вывода сохраняются ограничения на число файлов и байты. Встроенные средства чтения и генератор также проверяют объём работы и прошедшее время. Увеличение одного лимита не отключает остальные.

Структура вывода и отчёт JSON

recovered-app/
  sources/                       восстановленные пакеты и классы Java
  metadata/android-methods.json  покрытие методов встроенным движком
  report.json                    версионированный перечень и ограничения

Временные входы удаляются. Вложенные классы могут использовать файл внешнего класса, поэтому число файлов Java не равно числу классов DEX. Созданные методы могут использовать диспетчерский цикл Java; они не выполняют исходный DEX и не вызывают его через мост времени выполнения.

Встроенный отчёт содержит android_method_recovery; те же данные записываются в metadata/android-methods.json, и каждый исходный метод остаётся в перечне. Выполняется method_count = recovered_method_count + projected_method_count + declaration_only_method_count + unrecovered_method_count; отсутствующий projected_method_count означает ноль, а unrecovered_method_count до публикации остаётся нулевым. Исходные методы native и abstract получают статус declaration-only и не учитываются как восстановленные тела.

Ограниченное подмножество именованных локальных классов без захвата переменных можно вывести внутри точно установленного внешнего статического метода. Требуются обычный метод со скалярными типами, класс без полей с непосредственным предком Object, настоящий конструктор без аргументов, скалярные методы экземпляра и проверенное использование объектов без выхода за поддерживаемую область. Анонимные классы, захват переменных, неподдерживаемые модификаторы и недоказанные способы использования по-прежнему вызывают явную ошибку.

Локальные методы и внешний метод получают source-projected с projection_kind: "named-method-local"; покрытие остаётся partial, даже если общий отчёт сообщает success. Двоичные имена и флаги доступа после повторной компиляции остаются непроверенными. Компилятор Java может выбрать другое двоичное имя, поэтому class_source_bindings сохраняет исходный класс, точный внешний метод, путь исходника и локальное имя с binary_name_status: "unverified".

generated_source_helpers перечисляет дополнительные методы с точными типами throw-helper, constant-helper, default-constructor и field-initializer. Последний обозначает дополнительно сгенерированный <clinit>, которого не было в исходном списке методов. Эти методы не входят в исходное общее количество. Успешная компиляция или разовое совпадение имён не доказывают полного восстановления. Следующий сокращённый пример не содержит проецируемых методов:

{
  "schema_version": 1,
  "status": "success",
  "platform": "android",
  "source": "app.apk",
  "input_kind": "apk",
  "backend": {
    "name": "neverd",
    "version": "1",
    "execution": "builtin"
  },
  "input_code_files": [
    "classes.dex",
    "classes2.dex"
  ],
  "dex_count": 2,
  "smali_count": 0,
  "java_source_count": 2,
  "java_sources": [
    "sources/example/Main.java",
    "sources/example/Peer.java"
  ],
  "logs": [],
  "android_method_recovery": {
    "schema_version": 1,
    "status": "recovered",
    "class_count": 2,
    "method_count": 6,
    "recovered_method_count": 5,
    "declaration_only_method_count": 1,
    "unrecovered_method_count": 0
  }
}

input_kind принимает значение apk, dex, smali или smali-directory. input_code_files перечисляет имена входного байткода или пути smali, а java_sources и logs задаются относительно корня вывода. source — базовое имя входного файла или каталога. Реальные отчёты содержат дополнительные ограничения реконструкции; сохраняйте их при передаче результатов другим инструментам.

При автоматизации проверяйте код завершения процесса, прежде чем использовать status, и при перенаправлении stdout сохраняйте отчёт вне нового каталога вывода:

neverd mobile app.apk -o recovered-app --json > recovery-result.json

Нативный CLI возвращает ноль при успехе и ненулевой код при ошибке восстановления. С --json обработанные ошибки содержат schema_version, status: "error" и error. Ошибки разбора аргументов, запуска нативного файла или библиотек и прерывания могут сообщаться только через stderr. Сначала проверяйте код завершения.

Обработка ошибок и устранение неполадок

Публикация транзакционна: существующий вывод сохраняется, а временные результаты неудачного запуска удаляются. Неподдерживаемые операции, неразрешённые потоки регистров, непредставимые объявления, некорректная обработка исключений и исчерпанные бюджеты завершают встроенный анализ ошибкой вместо публикации пропущенных тел методов. Успешное восстановление не доказывает семантическую эквивалентность.

Симптом Действие
DEX, инструкция, объявление или инициализация не поддерживаются Прочитать диагностику и проверить поддерживаемое подмножество
Некорректный вход или дублирующийся класс Исправить байткод или набор классов; неподдерживаемые тела не пропускаются молча
Превышен срок или бюджет Сократить вход или изменить --timeout, --max-files и --max-bytes с учётом доступных ресурсов
Вывод уже существует Выбрать новый выходной каталог

Проверка и глубина поддержки

Python используется только в приведённых ниже тестовых сценариях разработки; встроенное мобильное восстановление выполняется нативным CLI на C++20.

cmake --build build --target check-neverd-mobile
ctest --test-dir build -L NeverDMobileTests --output-on-failure
python3 scripts/test_mobile_android_internal.py --d8 PATH --neverd build/bin/neverd

Тесты компонентов и CLI проверяют разбор, контракты вывода и очистку после сбоев. Внутренний исполнитель сравнений использует JDK (java и javac) и D8 для создания независимых примеров DEX/APK, затем компилирует и запускает восстановленный Java. Это зависимости тестирования, а не требования встроенного восстановления. Запустите его с текущей сборкой и изучите результаты, прежде чем считать случай проверенным. Успех примеров не доказывает полного восстановления произвольных приложений.

Связанный процесс для iOS описан в обзоре мобильных приложений.