Is Your Kotlin Code Really Obfuscated? — статья, посвященная обфускации кода на языке Kotlin, а точнее тому, когда эта обфускация может не сработать.
Еще по теме: Как защитить нативную библиотеку
Автор приводит следующий пример кода:
1 2 3 4 5 6 7 8 9 |
class SomeClass { lateinit var importantVar: String fun funcWithParams(importantString: String, importantList: List<Int>) { } fun String.importantExtensionFunc() { } } |
Если скомпилировать этот код, а затем декомпилировать его в Java, мы получим следующую картину:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public final class SomeClass { @NotNull public String importantVar; @NotNull public final String getImportantVar() { String var10000 = this.importantVar; if (var10000 == null) { Intrinsics.throwUninitializedPropertyAccessException("importantVar"); } return var10000; } public final void funcWithParams(@NotNull String importantString, @NotNull List importantList) { Intrinsics.checkParameterIsNotNull(importantString, "importantString"); Intrinsics.checkParameterIsNotNull(importantList, "importantList"); } public final void importantExtensionFunc(@NotNull String $this$importantExtensionFunc) { Intrinsics.checkParameterIsNotNull($this$importantExtensionFunc, "$this$importantExtensionFunc"); } } |
Обратите внимание, что появилась проверка параметров функций на null с помощью методов объекта Intrinsics. Это часть особенности Kotlin под названием null safety, которая гарантирует, что вы не сможете обратиться к методам или полям null-объекта или случайно присвоить объекту значение null.
А теперь посмотрите на тот же код, но после обфускации с помощью ProGuard (в новых версиях Android Studio его работу выполняет оптимизатор R8):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public final class a { public String a; public final String a() { String var1 = this.a; if (var1 == null) { b.b("importantVar"); } return var1; } public final void a(String var1) { b.b(var1, "$this$importantExtensionFunc"); } public final void a(String var1, List var2) { b.b(var1, "importantString"); b.b(var2, "importantList"); } public final void b() { String var1 = this.a; if (var1 == null) { b.b("importantVar"); } this.a(var1); } } |
Снова обратите внимание на вызов функций Intrinsics, которые теперь имеют имя вроде b.b. Несмотря на то что в результате обфускации параметры функций получили имена var1 и var2, функция проверки на null все равно выдает их реальные имена (importantVar, importantList и так далее).
Побороть эту проблему можно, отключив саму функцию проверки на null в продакшен-коде. Это никак не повлияет на стабильность приложения, поскольку код проверки на null в рантайме нужен лишь в качестве средства отладки/диагностики, а не защиты от падения или обеспечения правильной работоспособности (фактически эти функции просто завершают приложение с исключением IllegalStateException).
Итак, открываем файл proguard-rules.pro приложения и добавляем в него следующие строки:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
-assumenosideeffects class kotlin.jvm.internal.Intrinsics { public static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String); public static void checkFieldIsNotNull(java.lang.Object, java.lang.String); public static void checkFieldIsNotNull(java.lang.Object, java.lang.String, java.lang.String); public static void checkNotNull(java.lang.Object); public static void checkNotNull(java.lang.Object, java.lang.String); public static void checkNotNullExpressionValue(java.lang.Object, java.lang.String); public static void checkNotNullParameter(java.lang.Object, java.lang.String); public static void checkParameterIsNotNull(java.lang.Object, java.lang.String); public static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String); public static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String, java.lang.String); public static void throwUninitializedPropertyAccessException(java.lang.String); } |
Полезные статьи по теме обфускации: