Обфускация — важная часть защиты Android-приложений. Однако даже если вы используете R8 или ProGuard - это не гарантия высокой устойчивости к тому что вас код не взломают. Стандартная настройка обфускации довольно простая и нацелена на уменьшение размера сборки, а не повышение защиты от взлома. В этой статье разберу как происходит обфускация в стандартной конфигурации, как можно её сделать сложнее для разбора, а по итогу и уникальной для каждой сборки.
Больше полезных материалов по Android разработке вы найдете в Telegram канале Android Broadcast
Что такое обфускация

Обфускация — это процесс преобразования имён в коде. Зачастую заменяются читаемые имена на нечитаемые, чтобы усложнить разбор кода. Например, UserManager → a
, getUser() → b()
. На вход обфускатор принимает словари и конфигурацию, которая содержит описание какие части кода не стоит переименовывать и другие настройки.
Инструменты ProGuard и R8 включают в себя этап обфускации, но также выполняют минификацию (удаление неиспользуемых частей кода) и оптимизацию (изменение кода с целью улучшения его работы по скорости, потреблению памяти и пр.).
Обфускаторы в ProGuard и R8 используют словари - набор слов из которых формируется новое имя. По умолчанию используется словарь состоящих из букв английского алфавита:
# Стандартный словарь R8/ProGuard
a
b
c
...
z
Когда слова в словаре заканчиваются, то начинается комбинирование с начала: a, b, c, ..., z, aa, ab, ac,..., az, ba, bb, ..., zzzzzz, ...
Словарь из всех возможных букв английского алфавита выбран потому, что он самый простой и позволяет делать имена элементов кода короткими, что положительно влияет на финальный размер файлов.
При этом стандартный словарь имеет несколько минусов:
Малое разнообразие имён ведет к тому, что проще угадать назначение
При повторной сборке будет полное соответствие прошлой сборке
Автоматические инструменты реверс инжениринга кода легко строят шаблоны связи кода в разных сборках
Всё это ведет к ослаблению защиты вашего кода, но усилить её можно с помощью задания собственного словаря.
Как использовать собственный словарь
R8/ProGuard позволяют задать собственные словари для обфускации кода через файл конфигурации. Если не указать словарь, то будет использоваться стандартный
# Добавьте в файл конфигурации R8/ProGuard
# Задание словаря для обфускации переменных и методов
-obfuscationdictionary obfuscation-dictionary.txt
# Словать для имек классов
-classobfuscationdictionary obfuscation-dictionary.txt
# Словарь для пакетов
-packageobfuscationdictionary obfuscation-dictionary.txt
Продвинутые словари для обфускации
Словарь из ключевых слов Java
Один из интересных словарей который можно взять - ключевые слова Java. Их запрещено использовать в Java как имена методов/классов, но R8/ProGuard работают на уровне байткода и там такие имена полностью валидны. Будет полезен для имен переменны и методов
# Пример из словаря из ключевых слова Java
do
if
for
int
new
...
instanceof
synchronized
Пример восстановленного Java кода после обфускации с применением словаря ключевых слов
// Код будет с ошибками из-за использования ключевых слов в названиях
package class;
public class static {
private String final;
private String volatile;
public static(String interface, String extends) {
this.final = interface;
this.volatile = extends;
}
public boolean if() {
return "admin".equals(final) && "1234".equals(volatile);
}
}
Словарь из запрещенных названий файлов
В Windows есть запрещенные названия файлов и при попытке назвать так файл будет ошибка. Хорошая идея чтобы использовать такой словарь для названия классов, чтобы при попытке реверс инжиниринга привести к ошибке
# Пример из словаря с запрещенными именами файлов в Windows
aux
Aux
aUx
...
Все словари сохранил файлами в Gist
Словари с длинными именами ключей могут приводить к увеличению размера APK и размера приложения на диске после установки
Теперь у вас есть собственный словарь, но все мы не избавились от одной проблемы - словарь один и тот же каждый раз. Давайте теперь сделаем генерацию словаря для каждой сборки уникальной
Уникальный код после обфускации. Рандомизация словаря
Хорошей практикой защиты является использование уникального словаря для каждой обфускации вашего кода. Самый простой способ - взять ваш собственный словарь и перед запуском обфускации случайным образом менять в нем слова. Если вы используете Gradle, то это будет удобно сделать с помощью добавления Gradle task
// Указываем путь к вашему словарю
val inputDictionary = file("tools/base-dictionary.txt")
// Сохраняем сгенерированный словарь, желательно в Gradle Build папку
val outputDictionary = layout.buildDirectory
.file("outputs/mapping/generated-dictionary.txt")
// Добавляем рандомизацию заданного словаря
tasks.register("generateObfuscationDictionary") {
outputs.file(outputDictionary)
doLast {
val lines = inputDictionary.readLines()
.filter { it.isNotBlank() }
.shuffled(Random(System.currentTimeMillis()))
val outputDictFile = outputDictionary.get().asFile
outputDictFile.parentFile.mkdirs()
outputDictFile.writeText(lines.joinToString("\n"))
}
}
// Добавляем в зависимость Релизной сборки рандомизацию словаря
// Если у вас несколько сборок - добавьте их во все необходимые
androidComponents {
onVariants { variant ->
if (variant.buildType == "release") {
val assembleTask = tasks.named("assembleRelease")
assembleTask.configure {
dependsOn(generateObfuscationDictionary)
}
}
}
}
Затем в конфигурации R8/ProGuard надо указать путь к сгенерированому словарю и использовать этот конфиг для обфускации.
# Указываем в файле конфигурации R8/ProGuard
-obfuscationdictionary build/outputs/mapping/generated-dictionary.txt
-classobfuscationdictionary build/outputs/mapping/generated-dictionary.txt
-packageobfuscationdictionary build/outputs/mapping/generated-dictionary.txt
Вы также можете взять уже готовые плагины для рандомизации словаря, например Proguard dictionary generator Gradle Plugin
Теперь вы сможете обеспечить лучшую защиту своих проектов. Рекомендую вам при сборке релизного билда также добавить сохранять сгенерированный словарь.