Проверка APK на скрытый код — это поиск в установочном пакете Android логики, о которой вам не сказали: догрузки сторонних модулей из интернета, тихой отправки данных или чтения идентификаторов устройства. Гайд для стажёра, который уже умеет распаковывать архивы и запускать команды в терминале, но никогда не разбирал DEX (скомпилированный байт-код приложения) и smali (читаемый текстовый аналог этого байт-кода). Подходит для APK с Android 5.0 и выше (minSdk 21+), проверяется на ПК с Windows, macOS или Linux — root на телефоне не нужен.


1. Создайте рабочую папку и скопируйте туда APK. Сразу считайте хеш файла: `sha256sum app.apk` (на Windows — `certutil -hashfile app.apk SHA256`).
**Пример:** вывод вида `3f2a...9c1b app.apk`. Запишите его: если хеш изменится, значит файл подменили, и все выводы ниже недействительны.
Почему это работает: хеш — отпечаток содержимого, любая правка байтов его меняет.
2. Распакуйте APK: `apktool d -f -o out app.apk`.
**Пример:** в папке `out` появляются `AndroidManifest.xml`, `res/`, `smali/`, `smali_classes2/`. Если приложение многомодульное, папок `smali_classesN` будет несколько.
Ожидаемый результат: `apktool` пишет «I: Copying assets and libs…» и завершается без ошибок.
3. Сделайте вторую копию — в Java: `jadx -d out_java app.apk`.
**Пример:** в `out_java/sources/com/example/app/` лежат `.java`-файлы с читаемыми именами методов.
Почему это работает: jadx умеет восстанавливать имена классов из отладочной информации, а smali — нет. Два представления дополняют друг друга.
4. Соберите индекс smali в SQLite — скрипт ниже кладёт каждую строку каждого `.smali` в таблицу.
import pathlib, sqlite3
db = sqlite3.connect("smali.db")
db.execute("CREATE TABLE IF NOT EXISTS lines (file TEXT, line INTEGER, code TEXT)")
for path in pathlib.Path("out").rglob("*.smali"):
for i, text in enumerate(path.read_text(encoding="utf-8", errors="replace").splitlines(), 1):
db.execute("INSERT INTO lines VALUES (?,?,?)", (str(path), i, text))
db.commit()
**Пример:** запуск `python index_smali.py`, на выходе — файл `smali.db` размером в десятки мегабайт.
Почему это работает: SQLite превращает поиск по тысячам файлов в один запрос, а главное — позволяет группировать и считать совпадения.
5. Ищите динамическую загрузку кода — главный признак скрытой логики.
SELECT file, line, code FROM lines WHERE code LIKE '%DexClassLoader%';
SELECT file, line, code FROM lines WHERE code LIKE '%Ljava/lang/reflect/Method%';
**Пример:** строка вида `invoke-virtual {v0, v1}, Ljava/lang/DexClassLoader;->loadClass:(...)`. Если рядом есть `const-string` с `http`, приложение скачивает и исполняет чужой код.
Ожидаемый результат: пустая выборка — признак нормы; 5–20 совпадений в одном классе — повод читать его целиком.
6. Проверьте сбор данных об устройстве.
SELECT file, COUNT(*) AS hits FROM lines
WHERE code LIKE '%getDeviceId%' OR code LIKE '%getImei%' OR code LIKE '%getSubscriberId%'
GROUP BY file ORDER BY hits DESC LIMIT 10;
**Пример:** вывод `smali/com/example/app/Analytics.smali | 7` означает, что класс собирает идентификаторы семь раз. Смотрите, куда эти значения передаются дальше.
Почему это работает: `GROUP BY` и `ORDER BY` показывают самый «жадный» до данных класс, а не разбросанные одиночные вызовы.
7. Отдельно посмотрите сетевые строки в smali и в Java-выгрузке.
SELECT DISTINCT file FROM lines WHERE code LIKE '%const-string%http%';
**Пример:** `smali/com/example/app/net/Uploader.smali` — откройте файл и посмотрите, какой URL собирается.
Ожидаемый результат: список доменов, которые приложение реально использует.
8. Разберите `AndroidManifest.xml`: разрешения (`INTERNET`, `READ_PHONE_STATE`, `READ_SMS`), экспортированные компоненты (`android:exported="true"`), список `activity`, `service`, `receiver`.
**Пример:** приложение-фонарик с разрешением `READ_SMS` — явное несоответствие заявленной функции.
Почему это важно: скрытый код начинается с лишнего права.
9. Проверьте встроенные базы приложения — они тоже SQL, и в них часто лежат конфиги, endpoints и токены. Файлы ищите в `out/assets/` и `out/res/raw/` с расширениями `.db`, `.sqlite`, `.sqlite3`.
sqlite3 out/assets/config.db ".tables"
sqlite3 out/assets/config.db "SELECT * FROM settings LIMIT 5;"
**Пример:** вывод `.tables` — `settings telemetry_queue`. Вторая таблица с очередью отправки — сигнал о фоновой передаче данных.
10. Прогоните строки на подозрительный контент: `strings app.apk | grep -iE "base64|exec|su |chmod"`.
**Пример:** длинная base64-строка внутри smali, которая декодируется в `.dex`. Это классическая упаковка скрытого модуля.
Ожидаемый результат: короткий список, каждую строку проверяете вручную в smali.
11. Сведите выводы в чек-лист и решайте по нему:

Симптом: `apktool` падает с ошибкой вида `Invalid resource` или «java.lang.OutOfMemoryError».У больших APK не хватает памяти. Решение: закройте браузер и IDE, увеличьте лимит — `java -Xmx4g -jar apktool.jar d app.apk`; если ошибка осталась, ищите более новую версию apktool. Проверьте, что распаковка дошла до конца: папка `smali` должна существовать, иначе шаг 4 проиндексирует пустоту.
Симптом: скрипт из шага 4 падает с `sqlite3.OperationalError: no such table` или создаёт базу на 0 строк.Вы запустили его не из той папки. Решение: команда `pwd` (или `cd`) должна показать папку, где лежит `out`; путь `pathlib.Path("out")` относительный. Проверьте, что индекс реально наполнился: `sqlite3 smali.db "SELECT COUNT(*) FROM lines;"` — счётчик должен быть в сотнях тысяч.
Симптом: SQL-запрос возвращает сотни совпадений, но среди них нет ничего опасного.Слишком широкий шаблон: `LIKE '%http%'` ловит и строки лицензий, и подсказки интерфейса. Решение: сужайте до конкретного класса — добавьте `AND file LIKE '%net%'`, или ищите пары: строка с `http` должна встречаться в одном файле с `invoke` на сетевой метод. Так вы отсекаете шум. Если задача — понять, ведёт ли найденный код себя автономно, пригодится отдельная методика проверки: [тесты автономности ИИ-агента и риски](https://ai-release.org/guides/kak-proverit-mozhet-li-ii-agent-rabotat-avtonomno-i-bez.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide).
Что такое smali простыми словами?
Это текстовое представление байт-кода: каждая строка — одна инструкция виртуальной машины Android. Читается тяжелее Java, но именно в smali видно всё, что реально исполняется, включая код без исходников.
Можно ли проверить APK без root и без установки приложения?
Да. Всё в гайде делается на ПК над файлом: распаковка, декомпиляция и SQL-запросы root не требуют. Root нужен только если вы хотите снять дамп памяти уже запущенного приложения.
Заменяет ли такой разбор антивирус?
Нет. Антивирус ищет известные сигнатуры, а SQL-запросы по smali показывают поведение: загрузку кода, сбор идентификаторов, лишние права. Это разные слои проверки, и их стоит совмещать.
Почему SQL, если DEX — не база данных?
Мы сами переносим строки smali в SQLite на шаге 4. Дальше SQL даёт то, чего не даёт обычный поиск по файлам: `GROUP BY` по классам, счётчики совпадений и сортировку `ORDER BY` — то есть приоритет для ручного чтения.