Сергей Попов
05.04.2026, 16:18
https://forum.antichat.xyz/attachments/4951391/img_ba4d527159.png
Когда на аудите тебе прилетает APK финтех-приложения, первый вопрос - не «какой инструмент открыть», а «что именно я ищу». Декомпиляция Android приложений - не магия и не однокнопочный процесс. Это методичная работа: jadx покажет основную часть картины, apktool дополнит ресурсами и smali, а часть логики придётся восстанавливать вручную из байткода и нативных библиотек. Именно там обычно прячутся захардкоженные API-ключи, обходы проверок и прочие артефакты, которые разработчик считал надёжно скрытыми (спойлер: нет).
Дальше - полный цикл реверс-инжиниринга APK: от вытаскивания пакета с устройства до восстановления логики сквозь обфускацию ProGuard и R8. С командами, конкретными ловушками, в которые я сам влетал на реальных проектах, и без пересказа документации.
Анатомия APK: что лежит внутри пакета
Прежде чем запускать инструменты, разберись, с чем работаешь. APK - это ZIP-архив с фиксированной структурой:
Файл/директорияСодержимое
classes.dex
(classes2.dex, ...)Байткод Dalvik - скомпилированный Java/Kotlin
AndroidManifest.xml
Декларация компонентов, permissions, intent-фильтры (в бинарном XML)
res/
Ресурсы - layouts, strings, drawables
lib/
Нативные библиотеки (.so) под разные архитектуры (arm64-v8a, armeabi-v7a, x86)
assets/
Произвольные файлы - часто конфиги, базы данных, JS-бандлы
META-INF/
Подписи и сертификаты
resources.arsc
Скомпилированная таблица ресурсов
Ключевой момент:
classes.dex
содержит байткод для виртуальной машины Dalvik/ART - промежуточное представление, далёкое от машинного кода. Именно поэтому декомпиляция Android приложений даёт результат, близкий к исходникам. Сравни с нативными бинарниками, где восстановление логики - на порядок более мучительное занятие.
Инструменты: jadx, apktool, dex2jar и когда что использовать
Одного инструмента, который решает все задачи, не существует. Вот рабочий арсенал для статического анализа Android:
jadx - декомпилятор для быстрого анализа
Jadx конвертирует DEX-байткод напрямую в Java-код. Основной инструмент для чтения логики приложения. Лично я начинаю любой анализ APK именно с него - на сегодняшний день это самый популярный декомпилятор, и читаемость результата у него лучшая в классе.
Bash:
# CLI: декомпиляция в директорию
jadx -d output_dir target.apk
# GUI: визуальный анализ с поиском и навигацией
jadx-gui target.apk
Когда использовать: первичный анализ, поиск строк, навигация по классам, понимание общей архитектуры.
Ограничение: jadx иногда генерирует невалидный Java-код. Увидишь комментарий вроде
/ jadx WARN: ... /
или
/ Code decompiled incorrectly /
. Это не баг jadx - байткод просто не маппится чисто на Java-конструкции. В таких случаях переключайся на smali.
apktool - распаковка и пересборка
Apktool работает на другом уровне: декодирует ресурсы (бинарный XML обратно в читаемый) и конвертирует DEX в smali - ассемблер для Dalvik VM.
Bash:
# Распаковка APK с декодированием ресурсов
apktool d target.apk -o target_src
# Пересборка после модификации
apktool b target_src -o modified.apk
Когда использовать: анализ
AndroidManifest.xml
, модификация ресурсов и поведения приложения, патчинг smali-кода.
dex2jar + JD-GUI - альтернативный путь
Конвертирует DEX в JAR, который можно открыть в любом Java-декомпиляторе:
Bash:
d2j-dex2jar target.apk -o target.jar
jd-gui target.jar
Когда использовать: если jadx не справился с конкретным классом. Разные декомпиляторы обрабатывают edge-кейсы по-разному. Я не раз видел ситуации, когда JD-GUI корректно декомпилировал метод, который jadx пометил как ошибочный. Так что держи оба под рукой.
Сравнение подходов
Критерийjadxapktooldex2jar + JD-GUIВыходJava-кодsmali + ресурсыJAR (Java-классы)ЧитаемостьВысокаяН зкая (smali)ВысокаяПересборка APKНетДаНетАнализ ресурсовЧастичноПолныйНет Работа с обфускациейСреднеТочный smaliСредне
Практический workflow: декомпиляция Android приложений шаг за шагом
Шаг 1. Получение APK с устройства
Если приложение уже установлено на устройстве (или эмуляторе):
Bash:
# Найти пакет
adb shell pm list packages
|
grep
-i
"targetapp"
# Узнать путь к APK
adb shell pm path com.example.targetapp
# package:/data/app/com.example.targetapp-randomhash==/base.apk
# Стянуть на хост
adb pull /data/app/com.example.targetapp-randomhash
==
/base.apk ./target.apk
Нюанс, на котором спотыкаются: с 2021 года Google Play принимает только Android App Bundle (AAB), и на устройство приходят split APK. Если получаешь APK из сторонних источников, можешь встретить форматы XAPK или APKS - это ZIP-архивы с набором split APK внутри (для объединения используй
APKEditor
или
bundletool
). Современные приложения часто используют split APK - вместо одного файла получишь
base.apk
плюс несколько
split_config.*.apk
. Забирай все:
Bash:
# Путь покажет несколько файлов
adb shell pm path com.example.targetapp
# package:/data/app/.../base.apk
# package:/data/app/.../split_config.arm64_v8a.apk
# package:/data/app/.../split_config.xxhdpi.apk
# Забрать всю директорию
adb pull /data/app/com.example.targetapp-randomhash
==
/ ./target_splits/
Основная логика лежит в
base.apk
, но нативные библиотеки могут оказаться в
split_config.arm64_v8a.apk
. Потеряешь их - потеряешь половину интересного.
Шаг 2. Первичная разведка через jadx
Открываем APK в jadx-gui и начинаем с общей картины:
Bash:
jadx-gui target.apk
Первые действия в jadx-gui:
Поиск строк (Navigation → Text Search): ищи
api_key
,
secret
,
password
,
token
,
http://
,
https://
Анализ пакетов: структура покажет архитектуру - чистый нативный Android, React Native (
com.facebook.react
), Flutter (
io.flutter
) или Xamarin
Навигация по
BuildConfig
: часто содержит debug-флаги и endpoint-ы
Шаг 3. Распаковка через apktool для глубокого анализа
Параллельно распаковываем через apktool - нам нужны ресурсы и манифест в читаемом виде:
Bash:
apktool d target.apk -o target_src
Структура на выходе:
Код:
target_src/
├── AndroidManifest.xml ← читаемый XML
├── smali/ ← smali-код (classes.dex)
├── smali_classes2/ ← smali-код (classes2.dex)
├── res/ ← декодированные ресурсы
├── assets/ ← файлы as-is
├── lib/ ← нативные .so
└── original/ ← оригинальный манифест и META-INF
Шаг 4. Анализ AndroidManifest.xml
Манифест - карта приложения. Вот что грепать для мобильного пентеста:
Bash:
# Экспортированные компоненты (доступны другим приложениям)
grep
-n
'android:exported="true"'
target_src/AndroidManifest.xml
# Кастомные permission-ы (часто с protectionLevel="normal" - небезопасно)
grep
-n
'android:protectionLevel'
target_src/AndroidManifest.xml
# Backup разрешён? (утечка данных через adb backup)
grep
-n
'android:allowBackup'
target_src/AndroidManifest.xml
# Deeplinks и intent-фильтры (поверхность атаки)
grep
-A5
'android:scheme'
target_src/AndroidManifest.xml
Компоненты с
android:exported="true"
без protection - классика на аудитах. Activity, которая принимает произвольные intent-ы, может стать вектором для обхода аутентификации или доступа к внутренним функциям. На одном проекте мы так нашли экспортированную Activity, которая сбрасывала PIN-код - без какой-либо проверки вызывающего приложения.
Статический анализ Android: охота на секреты
Исходный код из APK - полдела. Дальше начинается целенаправленный поиск уязвимостей.
Захардкоженные секреты
Разработчики регулярно оставляют ключи прямо в коде. Чек-лист поиска:
Bash:
# В декомпилированном Java-коде (jadx output)
grep
-rn
"API_KEY\|api_key\|apiKey\|ApiKey"
output_dir/
grep
-rn
"SECRET\|secret\|Secret"
output_dir/
grep
-rn
"Bearer \|Basic "
output_dir/
grep
-rn
"AIza[0-9A-Za-z\-_]{35}"
output_dir/
# Google API keys
# В ресурсах
grep
-rn
"api_key\|secret\|token\|password"
target_src/res/values/strings.xml
# В assets - конфиги, JSON, JS-бандлы
find
target_src/assets/ -name
"*.json"
-o -name
"*.js"
-o -name
"*.config"
|
\
xargs
grep
-l
"key\|secret\|token\|password"
2>
/dev/null
Отдельная история - Firebase. Файл
google-services.json
в assets или строки вида
firebaseio.com
в коде. Если Firebase Realtime Database открыта на чтение без авторизации - это P1-находка в bug bounty. Проверяется элементарно:
curl https://project-id.firebaseio.com/.json
- и если в ответ прилетает JSON с данными, а не 401, то кто-то крупно облажался.
Небезопасная работа с сетью
Ищи в декомпилированном коде:
Java:
// Игнорирование SSL-ошибок (критическая уязвимость)
// Паттерн в коде:
TrustManager
[
]
trustAllCerts
// доверяет любому сертификату
HostnameVerifier
allHostsValid
// не проверяет hostname
.
hostnameVerifier
(
SSLSocketFactory
.
ALLOW_ALL_HOSTNAME_VERIFIER
)
В
res/xml
ищи
network_security_config.xml ('https://developer.android.com/privacy-and-security/security-config')
- там может быть
cleartextTrafficPermitted="true"
или кастомные trust anchors.
Анализ SharedPreferences и SQLite
В коде ищи работу с локальным хранилищем - что приложение сохраняет на устройство:
Bash:
grep
-rn
"SharedPreferences\|getSharedPreferences\|MODE_WORL D_READABLE"
output_dir/
grep
-rn
"SQLiteDatabase\|openOrCreateDatabase\|execSQL"
output_dir/
MODE_WORLD_READABLE
- устаревший и небезопасный флаг, но он до сих пор попадается в легаси-приложениях. Казалось бы, 2025 год на дворе, а оно живёт.
Обфускация Android приложений: ProGuard, R8 и что с ними делать
Когда вместо читаемых
LoginActivity.java
и
PaymentProcessor.java
ты видишь
a.java
,
b.java
,
c.java
- перед тобой обфускация. В экосистеме Android это почти всегда ProGuard или его замена - R8 (используется по умолчанию с Android Gradle Plugin 3.4.0).
Что делает обфускация
Переименование: классы, методы, поля получают однобуквенные имена
Удаление неиспользуемого кода (tree shaking)
Оптимизация байткода: инлайнинг методов, упрощение control flow
Чего обфускация НЕ делает:
Не шифрует строки (по умолчанию)
Не скрывает структуру вызовов Android SDK
Не защищает ресурсы и манифест
Не меняет логику - только имена
По сути, это красивый фантик. Имена спрятали, а содержимое - на месте. Подробнее о том, как выстраивается комплексная защита мобильных приложений от реверс-инжиниринга (https://forum.antichat.xyz/threads/592577/), - в отдельном разборе.
Практические приёмы работы с обфусцированным кодом
1. Строки - твой лучший друг.
Обфускатор переименовал класс в
com.a.b.c
, но строка
"Invalid password"
внутри метода осталась. Ищи строки через jadx (Text Search) и от них раскручивай логику.
2. Android SDK вызовы не обфусцируются.
Классы из
android.
,
java.
,
javax.*
сохраняют имена. Если видишь:
Java:
// Обфусцированный код в jadx
public
class
a
extends
AppCompatActivity
{
private
void
b
(
String
str
)
{
(
(
SharedPreferences
)
this
.
f123c
)
.
edit
(
)
.
putString
(
"auth_token"
,
str
)
.
apply
(
)
;
}
}
Сразу понятно: класс
a
- Activity, метод
b
сохраняет auth-токен в SharedPreferences. Имена обфусцированы, а семантика восстанавливается по API-вызовам.
3. Функция «Rename» в jadx-gui.
Правый клик → переименовать обфусцированный класс/метод. Jadx запомнит переименование и обновит все ссылки. При анализе больших приложений это экономит часы - без преувеличения.
4. Проверяй
mapping.txt
.
Иногда разработчики забывают исключить ProGuard mapping file из сборки. Ищи в assets или res что-то вроде
mapping.txt
,
proguard-rules.pro
,
r8-mapping.txt
. Этот файл содержит полное соответствие оригинальных имён обфусцированным - джекпот. Я встречал такое дважды на коммерческих аудитах - оба раза в финтехе.
Обфускация строк: DexGuard и коммерческие решения
Серьёзные приложения (банкинг, финтех) используют коммерческие обфускаторы вроде DexGuard, которые шифруют строковые литералы. В декомпилированном коде вместо
"https://api.bank.com"
видишь вызов:
Java:
String
url
=
SomeClass
.
decrypt
(
new
byte
[
]
{
0x4a
,
0x2f
,
.
.
.
}
)
;
Подход: найди метод дешифровки, пойми алгоритм (обычно простой XOR или AES с захардкоженным ключом), напиши скрипт для массовой расшифровки. Или - что часто быстрее - используй Frida для перехвата результата в рантайме. Зачем реверсить криптографию, если можно просто подождать, пока приложение само всё расшифрует?
Smali-код: когда jadx декомпилятор не справляется
Smali - ассемблер для Dalvik VM. Когда jadx помечает метод как
/ Code decompiled incorrectly /
, smali покажет точный байткод. Он не врёт - показывает ровно то, что записано в DEX.
Базовый пример - логирование значения переменной для отладки:
Код:
# Добавляем логирование в метод
const-string v0, "REVERSE_TAG"
# p1 - параметр, значение которого хотим увидеть
invoke-static {v0, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
Приём незаменим при анализе: модифицируешь smali, пересобираешь APK через apktool, ставишь на устройство - и видишь в logcat реальные значения, которые приложение обрабатывает в рантайме. Грубо, но работает.
Навигация по smali
Основные моменты для чтения smali:
Lcom/example/MyClass;
- ссылка на класс (L-нотация)
invoke-virtual
- вызов виртуального метода (обычный вызов)
invoke-static
- вызов статического метода
iget-object
/
iput-object
- чтение/запись поля объекта
v0, v1, ...
- локальные регистры
p0
- this (для нестатических методов),
p1, p2, ...
- параметры
Свободно читать smali ты не обязан. Но понимать базовые инструкции при анализе мобильных приложений нужно - хотя бы чтобы верифицировать то, что jadx показал корректно.
Анализ нативных библиотек (.so)
Если в
lib/
лежат
.so
-файлы - часть логики реализована на C/C++ через JNI. Реверсить сложнее, но не невозможно.
Bash:
# Посмотреть экспортированные функции
nm -D lib/arm64-v8a/libnative.so
|
grep
Java_
# Java_com_example_app_CryptoUtils_decrypt
# Java_com_example_app_CryptoUtils_getApiKey
Функции с префиксом
Java_
- JNI-методы, вызываемые из Java-кода. Имя функции содержит полный путь пакета и класса.
getApiKey
в нативной библиотеке - серьёзный повод залезть внутрь с дизассемблером.
Для дизассемблирования
.so
используй Ghidra (бесплатный, от NSA) или IDA Free:
Bash:
# Ghidra: импорт .so через File → Import File
# Выбери архитектуру AARCH64 для arm64-v8a
# После автоанализа ищи JNI-функции в Symbol Tree
Если в нативной библиотеке реализована криптография, часто проще перехватить входы и выходы через Frida, чем препарировать сам алгоритм:
JavaScript:
// Frida: перехват JNI-метода
Java
.
perform
(
function
(
)
{
var
CryptoUtils
=
Java
.
use
(
"com.example.app.CryptoUtils"
)
;
CryptoUtils
.
decrypt
.
implementation
=
function
(
input
)
{
console
.
log
(
" decrypt input: "
+
input
)
;
var
result
=
this
.
decrypt
(
input
)
;
console
.
log
(
" decrypt output: "
+
result
)
;
return
result
;
}
;
}
)
;
Модификация и пересборка APK
После анализа и патчинга smali-кода нужно собрать APK обратно. Полный цикл:
Bash:
# 1. Пересборка
apktool b target_src -o modified.apk
# 2. Генерация ключа для подписи (один раз)
keytool -genkey -v -keystore my-release-key.keystore
\
-alias alias_name -keyalg RSA -keysize
2048
-validity
10000
# 3. Выравнивание (обязательно перед подписью через apksigner)
zipalign -v
4
modified.apk modified-aligned.apk
# 4. Подпись (apksigner автоматически применяет v2-схему подписи)
apksigner sign --ks my-release-key.keystore modified-aligned.apk
# 5. Установка
adb
install
modified-aligned.apk
Если приложение уже установлено с другой подписью - удали старую версию (
adb uninstall com.example.targetapp
). Android не позволит обновить приложение с другим сертификатом - тупо откажет.
Типичная ошибка при пересборке: apktool падает на ресурсах, которые не может декодировать. Используй флаг
--no-res
:
Bash:
# Распаковка без декодирования ресурсов (только smali)
apktool d --no-res target.apk -o target_src
Теряешь читаемые ресурсы, но получаешь гарантированно пересобираемый проект. Для чего хватит за глаза, если тебе нужен только патчинг логики.
Типичные ловушки и как их избежать
За годы аудитов мобильных приложений я собрал набор ситуаций, которые регулярно ставят в тупик:
jadx показывает не тот код. Если метод содержит сложный control flow (вложенные try-catch, switch по enum), jadx может неправильно восстановить логику. Правило простое: если результат декомпиляции выглядит нелогично - проверь smali. Smali не врёт.
Multidex. Современные приложения часто содержат
classes.dex
,
classes2.dex
, ...,
classesN.dex
. Jadx обрабатывает их все автоматически. Но при работе с apktool убедись, что проверяешь все директории
smali/
,
smali_classes2/
,
smali_classes3/
- искомый класс может валяться в любой из них.
React Native, Flutter, Xamarin. Если приложение на кросс-платформенном фреймворке, основная логика может быть не в DEX:
React Native: бизнес-логика в
assets/index.android.bundle
(JavaScript) - открываешь текстовым редактором и читаешь
Flutter: скомпилированный Dart в
lib/libflutter.so
и
lib/libapp.so
- реверс значительно сложнее, тут придётся попотеть
Xamarin: .NET-сборки в
assemblies/
- используй dnSpy/ILSpy
Root detection и SSL pinning. Приложение может крашиться или менять поведение при обнаружении root. Для обхода на этапе динамического анализа используй objection или скрипты Frida:
Bash:
# objection: автоматический обход root detection и SSL pinning
objection -g com.example.targetapp explore
# Внутри objection:
android sslpinning disable
android root disable
Итоговый чек-лист мобильного пентеста (статика)
Собери всё вместе в воспроизводимый процесс анализа мобильных приложений:
Получить APK:
adb pull
или скачать из стороннего сервиса
Открыть в jadx-gui - общая картина, архитектура, фреймворк
Поиск строк: ключи, токены, URL-ы, пароли
Распаковать через apktool - манифест, ресурсы, smali
Анализ
AndroidManifest.xml
- экспортированные компоненты, permissions, backup
Проверить
res/values/strings.xml
и
assets/
- конфиги, секреты
Проанализировать network security config - cleartext, trust anchors
Если есть
.so
- проверить экспортированные JNI-функции через
nm -D
При обфускации - искать по строкам и Android SDK вызовам, использовать rename в jadx
Документировать находки с привязкой к конкретным классам и методам
Реверс-инжиниринг APK - фундамент любого серьёзного аудита мобильных приложений (https://forum.antichat.xyz/threads/591943/). Статический анализ Android даёт тебе карту приложения до запуска единственного запроса. Дальше - динамический анализ через Frida, перехват трафика через Burp, фаззинг интентов. Но без качественной декомпиляции и понимания кода всё остальное - стрельба вслепую. Попробуй прогнать чек-лист на любом APK из своего телефона - гарантирую, найдёшь хотя бы один захардкоженный ключ.
Когда на аудите тебе прилетает APK финтех-приложения, первый вопрос - не «какой инструмент открыть», а «что именно я ищу». Декомпиляция Android приложений - не магия и не однокнопочный процесс. Это методичная работа: jadx покажет основную часть картины, apktool дополнит ресурсами и smali, а часть логики придётся восстанавливать вручную из байткода и нативных библиотек. Именно там обычно прячутся захардкоженные API-ключи, обходы проверок и прочие артефакты, которые разработчик считал надёжно скрытыми (спойлер: нет).
Дальше - полный цикл реверс-инжиниринга APK: от вытаскивания пакета с устройства до восстановления логики сквозь обфускацию ProGuard и R8. С командами, конкретными ловушками, в которые я сам влетал на реальных проектах, и без пересказа документации.
Анатомия APK: что лежит внутри пакета
Прежде чем запускать инструменты, разберись, с чем работаешь. APK - это ZIP-архив с фиксированной структурой:
Файл/директорияСодержимое
classes.dex
(classes2.dex, ...)Байткод Dalvik - скомпилированный Java/Kotlin
AndroidManifest.xml
Декларация компонентов, permissions, intent-фильтры (в бинарном XML)
res/
Ресурсы - layouts, strings, drawables
lib/
Нативные библиотеки (.so) под разные архитектуры (arm64-v8a, armeabi-v7a, x86)
assets/
Произвольные файлы - часто конфиги, базы данных, JS-бандлы
META-INF/
Подписи и сертификаты
resources.arsc
Скомпилированная таблица ресурсов
Ключевой момент:
classes.dex
содержит байткод для виртуальной машины Dalvik/ART - промежуточное представление, далёкое от машинного кода. Именно поэтому декомпиляция Android приложений даёт результат, близкий к исходникам. Сравни с нативными бинарниками, где восстановление логики - на порядок более мучительное занятие.
Инструменты: jadx, apktool, dex2jar и когда что использовать
Одного инструмента, который решает все задачи, не существует. Вот рабочий арсенал для статического анализа Android:
jadx - декомпилятор для быстрого анализа
Jadx конвертирует DEX-байткод напрямую в Java-код. Основной инструмент для чтения логики приложения. Лично я начинаю любой анализ APK именно с него - на сегодняшний день это самый популярный декомпилятор, и читаемость результата у него лучшая в классе.
Bash:
# CLI: декомпиляция в директорию
jadx -d output_dir target.apk
# GUI: визуальный анализ с поиском и навигацией
jadx-gui target.apk
Когда использовать: первичный анализ, поиск строк, навигация по классам, понимание общей архитектуры.
Ограничение: jadx иногда генерирует невалидный Java-код. Увидишь комментарий вроде
/ jadx WARN: ... /
или
/ Code decompiled incorrectly /
. Это не баг jadx - байткод просто не маппится чисто на Java-конструкции. В таких случаях переключайся на smali.
apktool - распаковка и пересборка
Apktool работает на другом уровне: декодирует ресурсы (бинарный XML обратно в читаемый) и конвертирует DEX в smali - ассемблер для Dalvik VM.
Bash:
# Распаковка APK с декодированием ресурсов
apktool d target.apk -o target_src
# Пересборка после модификации
apktool b target_src -o modified.apk
Когда использовать: анализ
AndroidManifest.xml
, модификация ресурсов и поведения приложения, патчинг smali-кода.
dex2jar + JD-GUI - альтернативный путь
Конвертирует DEX в JAR, который можно открыть в любом Java-декомпиляторе:
Bash:
d2j-dex2jar target.apk -o target.jar
jd-gui target.jar
Когда использовать: если jadx не справился с конкретным классом. Разные декомпиляторы обрабатывают edge-кейсы по-разному. Я не раз видел ситуации, когда JD-GUI корректно декомпилировал метод, который jadx пометил как ошибочный. Так что держи оба под рукой.
Сравнение подходов
Критерийjadxapktooldex2jar + JD-GUIВыходJava-кодsmali + ресурсыJAR (Java-классы)ЧитаемостьВысокаяН зкая (smali)ВысокаяПересборка APKНетДаНетАнализ ресурсовЧастичноПолныйНет Работа с обфускациейСреднеТочный smaliСредне
Практический workflow: декомпиляция Android приложений шаг за шагом
Шаг 1. Получение APK с устройства
Если приложение уже установлено на устройстве (или эмуляторе):
Bash:
# Найти пакет
adb shell pm list packages
|
grep
-i
"targetapp"
# Узнать путь к APK
adb shell pm path com.example.targetapp
# package:/data/app/com.example.targetapp-randomhash==/base.apk
# Стянуть на хост
adb pull /data/app/com.example.targetapp-randomhash
==
/base.apk ./target.apk
Нюанс, на котором спотыкаются: с 2021 года Google Play принимает только Android App Bundle (AAB), и на устройство приходят split APK. Если получаешь APK из сторонних источников, можешь встретить форматы XAPK или APKS - это ZIP-архивы с набором split APK внутри (для объединения используй
APKEditor
или
bundletool
). Современные приложения часто используют split APK - вместо одного файла получишь
base.apk
плюс несколько
split_config.*.apk
. Забирай все:
Bash:
# Путь покажет несколько файлов
adb shell pm path com.example.targetapp
# package:/data/app/.../base.apk
# package:/data/app/.../split_config.arm64_v8a.apk
# package:/data/app/.../split_config.xxhdpi.apk
# Забрать всю директорию
adb pull /data/app/com.example.targetapp-randomhash
==
/ ./target_splits/
Основная логика лежит в
base.apk
, но нативные библиотеки могут оказаться в
split_config.arm64_v8a.apk
. Потеряешь их - потеряешь половину интересного.
Шаг 2. Первичная разведка через jadx
Открываем APK в jadx-gui и начинаем с общей картины:
Bash:
jadx-gui target.apk
Первые действия в jadx-gui:
Поиск строк (Navigation → Text Search): ищи
api_key
,
secret
,
password
,
token
,
http://
,
https://
Анализ пакетов: структура покажет архитектуру - чистый нативный Android, React Native (
com.facebook.react
), Flutter (
io.flutter
) или Xamarin
Навигация по
BuildConfig
: часто содержит debug-флаги и endpoint-ы
Шаг 3. Распаковка через apktool для глубокого анализа
Параллельно распаковываем через apktool - нам нужны ресурсы и манифест в читаемом виде:
Bash:
apktool d target.apk -o target_src
Структура на выходе:
Код:
target_src/
├── AndroidManifest.xml ← читаемый XML
├── smali/ ← smali-код (classes.dex)
├── smali_classes2/ ← smali-код (classes2.dex)
├── res/ ← декодированные ресурсы
├── assets/ ← файлы as-is
├── lib/ ← нативные .so
└── original/ ← оригинальный манифест и META-INF
Шаг 4. Анализ AndroidManifest.xml
Манифест - карта приложения. Вот что грепать для мобильного пентеста:
Bash:
# Экспортированные компоненты (доступны другим приложениям)
grep
-n
'android:exported="true"'
target_src/AndroidManifest.xml
# Кастомные permission-ы (часто с protectionLevel="normal" - небезопасно)
grep
-n
'android:protectionLevel'
target_src/AndroidManifest.xml
# Backup разрешён? (утечка данных через adb backup)
grep
-n
'android:allowBackup'
target_src/AndroidManifest.xml
# Deeplinks и intent-фильтры (поверхность атаки)
grep
-A5
'android:scheme'
target_src/AndroidManifest.xml
Компоненты с
android:exported="true"
без protection - классика на аудитах. Activity, которая принимает произвольные intent-ы, может стать вектором для обхода аутентификации или доступа к внутренним функциям. На одном проекте мы так нашли экспортированную Activity, которая сбрасывала PIN-код - без какой-либо проверки вызывающего приложения.
Статический анализ Android: охота на секреты
Исходный код из APK - полдела. Дальше начинается целенаправленный поиск уязвимостей.
Захардкоженные секреты
Разработчики регулярно оставляют ключи прямо в коде. Чек-лист поиска:
Bash:
# В декомпилированном Java-коде (jadx output)
grep
-rn
"API_KEY\|api_key\|apiKey\|ApiKey"
output_dir/
grep
-rn
"SECRET\|secret\|Secret"
output_dir/
grep
-rn
"Bearer \|Basic "
output_dir/
grep
-rn
"AIza[0-9A-Za-z\-_]{35}"
output_dir/
# Google API keys
# В ресурсах
grep
-rn
"api_key\|secret\|token\|password"
target_src/res/values/strings.xml
# В assets - конфиги, JSON, JS-бандлы
find
target_src/assets/ -name
"*.json"
-o -name
"*.js"
-o -name
"*.config"
|
\
xargs
grep
-l
"key\|secret\|token\|password"
2>
/dev/null
Отдельная история - Firebase. Файл
google-services.json
в assets или строки вида
firebaseio.com
в коде. Если Firebase Realtime Database открыта на чтение без авторизации - это P1-находка в bug bounty. Проверяется элементарно:
curl https://project-id.firebaseio.com/.json
- и если в ответ прилетает JSON с данными, а не 401, то кто-то крупно облажался.
Небезопасная работа с сетью
Ищи в декомпилированном коде:
Java:
// Игнорирование SSL-ошибок (критическая уязвимость)
// Паттерн в коде:
TrustManager
[
]
trustAllCerts
// доверяет любому сертификату
HostnameVerifier
allHostsValid
// не проверяет hostname
.
hostnameVerifier
(
SSLSocketFactory
.
ALLOW_ALL_HOSTNAME_VERIFIER
)
В
res/xml
ищи
network_security_config.xml ('https://developer.android.com/privacy-and-security/security-config')
- там может быть
cleartextTrafficPermitted="true"
или кастомные trust anchors.
Анализ SharedPreferences и SQLite
В коде ищи работу с локальным хранилищем - что приложение сохраняет на устройство:
Bash:
grep
-rn
"SharedPreferences\|getSharedPreferences\|MODE_WORL D_READABLE"
output_dir/
grep
-rn
"SQLiteDatabase\|openOrCreateDatabase\|execSQL"
output_dir/
MODE_WORLD_READABLE
- устаревший и небезопасный флаг, но он до сих пор попадается в легаси-приложениях. Казалось бы, 2025 год на дворе, а оно живёт.
Обфускация Android приложений: ProGuard, R8 и что с ними делать
Когда вместо читаемых
LoginActivity.java
и
PaymentProcessor.java
ты видишь
a.java
,
b.java
,
c.java
- перед тобой обфускация. В экосистеме Android это почти всегда ProGuard или его замена - R8 (используется по умолчанию с Android Gradle Plugin 3.4.0).
Что делает обфускация
Переименование: классы, методы, поля получают однобуквенные имена
Удаление неиспользуемого кода (tree shaking)
Оптимизация байткода: инлайнинг методов, упрощение control flow
Чего обфускация НЕ делает:
Не шифрует строки (по умолчанию)
Не скрывает структуру вызовов Android SDK
Не защищает ресурсы и манифест
Не меняет логику - только имена
По сути, это красивый фантик. Имена спрятали, а содержимое - на месте. Подробнее о том, как выстраивается комплексная защита мобильных приложений от реверс-инжиниринга (https://forum.antichat.xyz/threads/592577/), - в отдельном разборе.
Практические приёмы работы с обфусцированным кодом
1. Строки - твой лучший друг.
Обфускатор переименовал класс в
com.a.b.c
, но строка
"Invalid password"
внутри метода осталась. Ищи строки через jadx (Text Search) и от них раскручивай логику.
2. Android SDK вызовы не обфусцируются.
Классы из
android.
,
java.
,
javax.*
сохраняют имена. Если видишь:
Java:
// Обфусцированный код в jadx
public
class
a
extends
AppCompatActivity
{
private
void
b
(
String
str
)
{
(
(
SharedPreferences
)
this
.
f123c
)
.
edit
(
)
.
putString
(
"auth_token"
,
str
)
.
apply
(
)
;
}
}
Сразу понятно: класс
a
- Activity, метод
b
сохраняет auth-токен в SharedPreferences. Имена обфусцированы, а семантика восстанавливается по API-вызовам.
3. Функция «Rename» в jadx-gui.
Правый клик → переименовать обфусцированный класс/метод. Jadx запомнит переименование и обновит все ссылки. При анализе больших приложений это экономит часы - без преувеличения.
4. Проверяй
mapping.txt
.
Иногда разработчики забывают исключить ProGuard mapping file из сборки. Ищи в assets или res что-то вроде
mapping.txt
,
proguard-rules.pro
,
r8-mapping.txt
. Этот файл содержит полное соответствие оригинальных имён обфусцированным - джекпот. Я встречал такое дважды на коммерческих аудитах - оба раза в финтехе.
Обфускация строк: DexGuard и коммерческие решения
Серьёзные приложения (банкинг, финтех) используют коммерческие обфускаторы вроде DexGuard, которые шифруют строковые литералы. В декомпилированном коде вместо
"https://api.bank.com"
видишь вызов:
Java:
String
url
=
SomeClass
.
decrypt
(
new
byte
[
]
{
0x4a
,
0x2f
,
.
.
.
}
)
;
Подход: найди метод дешифровки, пойми алгоритм (обычно простой XOR или AES с захардкоженным ключом), напиши скрипт для массовой расшифровки. Или - что часто быстрее - используй Frida для перехвата результата в рантайме. Зачем реверсить криптографию, если можно просто подождать, пока приложение само всё расшифрует?
Smali-код: когда jadx декомпилятор не справляется
Smali - ассемблер для Dalvik VM. Когда jadx помечает метод как
/ Code decompiled incorrectly /
, smali покажет точный байткод. Он не врёт - показывает ровно то, что записано в DEX.
Базовый пример - логирование значения переменной для отладки:
Код:
# Добавляем логирование в метод
const-string v0, "REVERSE_TAG"
# p1 - параметр, значение которого хотим увидеть
invoke-static {v0, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
Приём незаменим при анализе: модифицируешь smali, пересобираешь APK через apktool, ставишь на устройство - и видишь в logcat реальные значения, которые приложение обрабатывает в рантайме. Грубо, но работает.
Навигация по smali
Основные моменты для чтения smali:
Lcom/example/MyClass;
- ссылка на класс (L-нотация)
invoke-virtual
- вызов виртуального метода (обычный вызов)
invoke-static
- вызов статического метода
iget-object
/
iput-object
- чтение/запись поля объекта
v0, v1, ...
- локальные регистры
p0
- this (для нестатических методов),
p1, p2, ...
- параметры
Свободно читать smali ты не обязан. Но понимать базовые инструкции при анализе мобильных приложений нужно - хотя бы чтобы верифицировать то, что jadx показал корректно.
Анализ нативных библиотек (.so)
Если в
lib/
лежат
.so
-файлы - часть логики реализована на C/C++ через JNI. Реверсить сложнее, но не невозможно.
Bash:
# Посмотреть экспортированные функции
nm -D lib/arm64-v8a/libnative.so
|
grep
Java_
# Java_com_example_app_CryptoUtils_decrypt
# Java_com_example_app_CryptoUtils_getApiKey
Функции с префиксом
Java_
- JNI-методы, вызываемые из Java-кода. Имя функции содержит полный путь пакета и класса.
getApiKey
в нативной библиотеке - серьёзный повод залезть внутрь с дизассемблером.
Для дизассемблирования
.so
используй Ghidra (бесплатный, от NSA) или IDA Free:
Bash:
# Ghidra: импорт .so через File → Import File
# Выбери архитектуру AARCH64 для arm64-v8a
# После автоанализа ищи JNI-функции в Symbol Tree
Если в нативной библиотеке реализована криптография, часто проще перехватить входы и выходы через Frida, чем препарировать сам алгоритм:
JavaScript:
// Frida: перехват JNI-метода
Java
.
perform
(
function
(
)
{
var
CryptoUtils
=
Java
.
use
(
"com.example.app.CryptoUtils"
)
;
CryptoUtils
.
decrypt
.
implementation
=
function
(
input
)
{
console
.
log
(
" decrypt input: "
+
input
)
;
var
result
=
this
.
decrypt
(
input
)
;
console
.
log
(
" decrypt output: "
+
result
)
;
return
result
;
}
;
}
)
;
Модификация и пересборка APK
После анализа и патчинга smali-кода нужно собрать APK обратно. Полный цикл:
Bash:
# 1. Пересборка
apktool b target_src -o modified.apk
# 2. Генерация ключа для подписи (один раз)
keytool -genkey -v -keystore my-release-key.keystore
\
-alias alias_name -keyalg RSA -keysize
2048
-validity
10000
# 3. Выравнивание (обязательно перед подписью через apksigner)
zipalign -v
4
modified.apk modified-aligned.apk
# 4. Подпись (apksigner автоматически применяет v2-схему подписи)
apksigner sign --ks my-release-key.keystore modified-aligned.apk
# 5. Установка
adb
install
modified-aligned.apk
Если приложение уже установлено с другой подписью - удали старую версию (
adb uninstall com.example.targetapp
). Android не позволит обновить приложение с другим сертификатом - тупо откажет.
Типичная ошибка при пересборке: apktool падает на ресурсах, которые не может декодировать. Используй флаг
--no-res
:
Bash:
# Распаковка без декодирования ресурсов (только smali)
apktool d --no-res target.apk -o target_src
Теряешь читаемые ресурсы, но получаешь гарантированно пересобираемый проект. Для чего хватит за глаза, если тебе нужен только патчинг логики.
Типичные ловушки и как их избежать
За годы аудитов мобильных приложений я собрал набор ситуаций, которые регулярно ставят в тупик:
jadx показывает не тот код. Если метод содержит сложный control flow (вложенные try-catch, switch по enum), jadx может неправильно восстановить логику. Правило простое: если результат декомпиляции выглядит нелогично - проверь smali. Smali не врёт.
Multidex. Современные приложения часто содержат
classes.dex
,
classes2.dex
, ...,
classesN.dex
. Jadx обрабатывает их все автоматически. Но при работе с apktool убедись, что проверяешь все директории
smali/
,
smali_classes2/
,
smali_classes3/
- искомый класс может валяться в любой из них.
React Native, Flutter, Xamarin. Если приложение на кросс-платформенном фреймворке, основная логика может быть не в DEX:
React Native: бизнес-логика в
assets/index.android.bundle
(JavaScript) - открываешь текстовым редактором и читаешь
Flutter: скомпилированный Dart в
lib/libflutter.so
и
lib/libapp.so
- реверс значительно сложнее, тут придётся попотеть
Xamarin: .NET-сборки в
assemblies/
- используй dnSpy/ILSpy
Root detection и SSL pinning. Приложение может крашиться или менять поведение при обнаружении root. Для обхода на этапе динамического анализа используй objection или скрипты Frida:
Bash:
# objection: автоматический обход root detection и SSL pinning
objection -g com.example.targetapp explore
# Внутри objection:
android sslpinning disable
android root disable
Итоговый чек-лист мобильного пентеста (статика)
Собери всё вместе в воспроизводимый процесс анализа мобильных приложений:
Получить APK:
adb pull
или скачать из стороннего сервиса
Открыть в jadx-gui - общая картина, архитектура, фреймворк
Поиск строк: ключи, токены, URL-ы, пароли
Распаковать через apktool - манифест, ресурсы, smali
Анализ
AndroidManifest.xml
- экспортированные компоненты, permissions, backup
Проверить
res/values/strings.xml
и
assets/
- конфиги, секреты
Проанализировать network security config - cleartext, trust anchors
Если есть
.so
- проверить экспортированные JNI-функции через
nm -D
При обфускации - искать по строкам и Android SDK вызовам, использовать rename в jadx
Документировать находки с привязкой к конкретным классам и методам
Реверс-инжиниринг APK - фундамент любого серьёзного аудита мобильных приложений (https://forum.antichat.xyz/threads/591943/). Статический анализ Android даёт тебе карту приложения до запуска единственного запроса. Дальше - динамический анализ через Frida, перехват трафика через Burp, фаззинг интентов. Но без качественной декомпиляции и понимания кода всё остальное - стрельба вслепую. Попробуй прогнать чек-лист на любом APK из своего телефона - гарантирую, найдёшь хотя бы один захардкоженный ключ.