Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ITT 2014 - Eric Lafortune - ProGuard, Optimizer and Obfuscator in the Android SDK

ITT 2014 - Eric Lafortune - ProGuard, Optimizer and Obfuscator in the Android SDK

Eric presents ProGuard - the open-source optimizer and obfuscator that is integrated in the Android SDK. ProGuard reduces the size of applications, improves their performance, and makes them more difficult to reverse-engineer. Eric presents some typical results on what to expect from ProGuard, discuss the latest developments and provide some background that should help mobile developers get the best out of ProGuard.

Istanbul Tech Talks

May 12, 2014
Tweet

More Decks by Istanbul Tech Talks

Other Decks in Programming

Transcript

  1. ProGuard Optimizer and Obfuscator in the Android SDK Eric Lafortune

    Developer of ProGuard Technical director at Saikoa
  2. Topics ProGuard – Techniques – Results More application protection –

    Goals – Attackers – Types of attacks – Protection
  3. ProGuard history Java applications Applets 2002 Midlets 2010 2012 Android

    apps • May 2002 First release • Sep 2010 Recommended for protecting LVL • Dec 2010 Part of Android SDK • Jan 2012 Startup Saikoa
  4. ProGuard in Android builds Application Java bytecode ProGuard Libraries Java

    bytecode Processed Java bytecode Dex Dalvik bytecode Javac Application Java source Libraries Java bytecode XML resources Assets Assets Compiled XML resources Aapt
  5. Why use ProGuard? • Application size • Performance • Remove

    logging, debugging, testing code • Protection
  6. Application size classes.dex size .apk size Without ProGuard With ProGuard

    Reduction ApiDemos 716 K 482 K 33 % 2.6 M 2.5 M 4 % GoogleIO App 3.4 M 905 K 75 % 1.9 M 906 K 53 % ApiDemos in Scala* ~6 M 542 K ~90 % ~8 M 2.5 M ~70 % * [Stéphane Micheloud, http://lampwww.epfl.ch/~michelou/android/library-code-shrinking.html]
  7. Performance: CaffeineMark Without ProGuard Sieve score = 6833 Loop score

    = 14831 Logic score = 19038 String score = 7694 Float score = 6425 Method score = 4850 Overall score = 8794 With ProGuard Sieve score = 6666 Loop score = 15473 Logic score = 47840 String score = 7717 Float score = 6488 Method score = 5229 Overall score = 10436 Improvement: 18% [Acer Iconia Tab A500, nVidia Tegra 2, 1.0 GHz, Android 3.2.1]
  8. Battery life Extreme example: “5 x better battery life, by

    removing verbose logging code in a background service” (but don't count on it)
  9. Results • Size reduction: – Code (classes.dex): 20 … 90%

    – Application (.apk): 5 ... 70% • Performance improvements: 0 … 20%
  10. How to enable ProGuard? Ant and Eclipse: project.properties → only

    applied when building release versions # To enable ProGuard to shrink and obfuscate your code, uncomment this #proguard.config= ${sdk.dir}/tools/proguard/proguard-android.txt: proguard-project.txt # To enable ProGuard to shrink and obfuscate your code, uncomment this #proguard.config= ${sdk.dir}/tools/proguard/proguard-android.txt: proguard-project.txt Tip
  11. How to enable ProGuard? Gradle: build.gradle → completely flexible android

    { buildTypes { release { runProguard true proguardFile getDefaultProguardFile('proguard-android.txt') } } productFlavors { some_flavor { proguardFile 'proguard-project.txt' } } } android { buildTypes { release { runProguard true proguardFile getDefaultProguardFile('proguard-android.txt') } } productFlavors { some_flavor { proguardFile 'proguard-project.txt' } } } New
  12. Notes and warnings “Closed-world assumption” → if debug build works

    fine, then ok to ignore in proguard-project.txt: Warning: com.dropbox.client2.DropboxAPI: can't find referenced class org.json.simple.JSONArray Warning: com.dropbox.client2.DropboxAPI: can't find referenced class org.json.simple.JSONArray -dontwarn twitter4j.internal.logging.** -dontwarn com.dropbox.client2.** -dontwarn twitter4j.internal.logging.** -dontwarn com.dropbox.client2.** Warning: twitter4j.internal.logging.Log4JLoggerFactory: can't find referenced class org.apache.log4j.Logger Warning: twitter4j.internal.logging.SLF4JLoggerFactory: can't find referenced class org.slf4j.LoggerFactory ... Warning: twitter4j.internal.logging.Log4JLoggerFactory: can't find referenced class org.apache.log4j.Logger Warning: twitter4j.internal.logging.SLF4JLoggerFactory: can't find referenced class org.slf4j.LoggerFactory ... Tip
  13. Notes and warnings Straight to the Troubleshooting page: Warning: there

    were 12 unresolved references to classes or interfaces. You may need to add missing library jars or update their versions. If your code works fine without the missing classes, you can suppress the warnings with '-dontwarn' options. (http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedclass) Warning: there were 12 unresolved references to classes or interfaces. You may need to add missing library jars or update their versions. If your code works fine without the missing classes, you can suppress the warnings with '-dontwarn' options. (http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedclass) New
  14. Entry points 1) Activities, applications, services, fragments,... → provided automatically

    by Android build process -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service … -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service …
  15. Entry points 2) Introspection, e.g. Guice, RoboGuice → must be

    specified in proguard-project.txt: -keepclassmembers class * { @javax.inject.** <fields>; @com.google.inject.** <fields>; @roboguice.** <fields>; @roboguice.event.Observes <methods>; } -keepclassmembers class * { @javax.inject.** <fields>; @com.google.inject.** <fields>; @roboguice.** <fields>; @roboguice.event.Observes <methods>; } Tip
  16. Optimization At the bytecode instruction level: • Dead code elimination

    • Constant propagation • Method inlining • Class merging • Remove logging code • Peephole optimizations • Devirtualization • ...
  17. Optimization example int answer = computeAnswer(1, 2, 3, 7); int

    answer = computeAnswer(1, 2, 3, 7); int computeAnswer(int f1, int f2, int f3, int f4) { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { return computeAnswer(f1 * f2, f3, f4, 1); } } int computeAnswer(int f1, int f2, int f3, int f4) { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { return computeAnswer(f1 * f2, f3, f4, 1); } }
  18. Optimization example int answer = computeAnswer(1, 2, 3, 7); int

    answer = computeAnswer(1, 2, 3, 7); int computeAnswer(int f1, int f2, int f3, int f4) { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { return computeAnswer(f1 * f2, f3, f4, 1); } } int computeAnswer(int f1, int f2, int f3, int f4) { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { return computeAnswer(f1 * f2, f3, f4, 1); } } int computeAnswer(int f1, int f2, int f3, int f4) { do { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { f1 = f1 * f2; f2 = f3, f3 = f4, f4 = 1; } } while (true); } int computeAnswer(int f1, int f2, int f3, int f4) { do { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { f1 = f1 * f2; f2 = f3, f3 = f4, f4 = 1; } } while (true); }
  19. Optimization example int answer = computeAnswer(1, 2, 3, 7); int

    answer = computeAnswer(1, 2, 3, 7); int computeAnswer(int f1, int f2, int f3, int f4) { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { return computeAnswer(f1 * f2, f3, f4, 1); } } int computeAnswer(int f1, int f2, int f3, int f4) { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { return computeAnswer(f1 * f2, f3, f4, 1); } } int computeAnswer(int f1, int f2, int f3, int f4) { do { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { f1 = f1 * f2; f2 = f3, f3 = f4, f4 = 1; } } while (true); } int computeAnswer(int f1, int f2, int f3, int f4) { do { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { f1 = f1 * f2; f2 = f3, f3 = f4, f4 = 1; } } while (true); } 1 2 3 7
  20. Optimization example int answer = computeAnswer(1, 2, 3, 7); int

    answer = computeAnswer(1, 2, 3, 7); int computeAnswer(int f1, int f2, int f3, int f4) { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { return computeAnswer(f1 * f2, f3, f4, 1); } } int computeAnswer(int f1, int f2, int f3, int f4) { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { return computeAnswer(f1 * f2, f3, f4, 1); } } int computeAnswer(int f1, int f2, int f3, int f4) { do { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { f1 = f1 * f2; f2 = f3, f3 = f4, f4 = 1; } } while (true); } int computeAnswer(int f1, int f2, int f3, int f4) { do { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { f1 = f1 * f2; f2 = f3, f3 = f4, f4 = 1; } } while (true); } int computeAnswer() { return 42; } int computeAnswer() { return 42; }
  21. Optimization example int answer = computeAnswer(1, 2, 3, 7); int

    answer = computeAnswer(1, 2, 3, 7); int computeAnswer(int f1, int f2, int f3, int f4) { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { return computeAnswer(f1 * f2, f3, f4, 1); } } int computeAnswer(int f1, int f2, int f3, int f4) { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { return computeAnswer(f1 * f2, f3, f4, 1); } } int computeAnswer(int f1, int f2, int f3, int f4) { do { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { f1 = f1 * f2; f2 = f3, f3 = f4, f4 = 1; } } while (true); } int computeAnswer(int f1, int f2, int f3, int f4) { do { if (f2 == 1 && f3 == 1 && f4 == 1) { return f1; } else { f1 = f1 * f2; f2 = f3, f3 = f4, f4 = 1; } } while (true); } int computeAnswer() { return 42; } int computeAnswer() { return 42; } int answer = 42; int answer = 42;
  22. Optimization: enum int answer = getAnswer(MyEnum.GREEN); int answer = getAnswer(MyEnum.GREEN);

    int getAnswer(MyEnum e) { switch (e) { case RED: return 1; case GREEN: return 42; default: return 0; } } int getAnswer(MyEnum e) { switch (e) { case RED: return 1; case GREEN: return 42; default: return 0; } } enum MyEnum { RED, GREEN, BLUE } enum MyEnum { RED, GREEN, BLUE } New
  23. Optimization: enum int answer = getAnswer(MyEnum.GREEN); int answer = getAnswer(MyEnum.GREEN);

    int getAnswer(MyEnum e) { switch (e) { case RED: return 1; case GREEN: return 42; default: return 0; } } int getAnswer(MyEnum e) { switch (e) { case RED: return 1; case GREEN: return 42; default: return 0; } } enum MyEnum { RED, GREEN, BLUE } enum MyEnum { RED, GREEN, BLUE } int getAnswer(MyEnum e) { switch (Internal.$SwitchMap[e.ordinal()]) { case 1: return 1; case 2: return 42; default: return 0; } } int getAnswer(MyEnum e) { switch (Internal.$SwitchMap[e.ordinal()]) { case 1: return 1; case 2: return 42; default: return 0; } } class MyEnum { static final MyEnum RED = new MyEnum(“RED”, 0); static final MyEnum GREEN = new MyEnum(“GREEN”, 0); static final MyEnum BLUE = new MyEnum(“BLUE”, 0); static final MyEnum[] $VALUES = new MyEnum[] { RED, GREEN, BLUE }; ... } class MyEnum { static final MyEnum RED = new MyEnum(“RED”, 0); static final MyEnum GREEN = new MyEnum(“GREEN”, 0); static final MyEnum BLUE = new MyEnum(“BLUE”, 0); static final MyEnum[] $VALUES = new MyEnum[] { RED, GREEN, BLUE }; ... } New
  24. Optimization: enum int answer = getAnswer(MyEnum.GREEN); int answer = getAnswer(MyEnum.GREEN);

    int getAnswer(MyEnum e) { switch (e) { case RED: return 1; case GREEN: return 42; default: return 0; } } int getAnswer(MyEnum e) { switch (e) { case RED: return 1; case GREEN: return 42; default: return 0; } } enum MyEnum { RED, GREEN, BLUE } enum MyEnum { RED, GREEN, BLUE } int getAnswer(MyEnum e) { switch (Internal.$SwitchMap[e.ordinal()]) { case 1: return 1; case 2: return 42; default: return 0; } } int getAnswer(MyEnum e) { switch (Internal.$SwitchMap[e.ordinal()]) { case 1: return 1; case 2: return 42; default: return 0; } } class MyEnum { static final MyEnum RED = new MyEnum(“RED”, 0); static final MyEnum GREEN = new MyEnum(“GREEN”, 0); static final MyEnum BLUE = new MyEnum(“BLUE”, 0); static final MyEnum[] $VALUES = new MyEnum[] { RED, GREEN, BLUE }; ... } class MyEnum { static final MyEnum RED = new MyEnum(“RED”, 0); static final MyEnum GREEN = new MyEnum(“GREEN”, 0); static final MyEnum BLUE = new MyEnum(“BLUE”, 0); static final MyEnum[] $VALUES = new MyEnum[] { RED, GREEN, BLUE }; ... } New class Internal { static final int[] $SwitchMap = new int[MyEnum.values().length]; static { $SwitchMap[MyEnum.RED.ordinal()] = 1; $SwitchMap[MyEnum.GREEN.ordinal()] = 2; } } class Internal { static final int[] $SwitchMap = new int[MyEnum.values().length]; static { $SwitchMap[MyEnum.RED.ordinal()] = 1; $SwitchMap[MyEnum.GREEN.ordinal()] = 2; } }
  25. Optimization: enum int answer = getAnswer(MyEnum.GREEN); int answer = getAnswer(MyEnum.GREEN);

    int getAnswer(MyEnum e) { switch (e) { case RED: return 1; case GREEN: return 42; default: return 0; } } int getAnswer(MyEnum e) { switch (e) { case RED: return 1; case GREEN: return 42; default: return 0; } } enum MyEnum { RED, GREEN, BLUE } enum MyEnum { RED, GREEN, BLUE } int getAnswer(MyEnum e) { switch (Internal.$SwitchMap[e.ordinal()]) { case 1: return 1; case 2: return 42; default: return 0; } } int getAnswer(MyEnum e) { switch (Internal.$SwitchMap[e.ordinal()]) { case 1: return 1; case 2: return 42; default: return 0; } } class MyEnum { static final MyEnum RED = new MyEnum(“RED”, 0); static final MyEnum GREEN = new MyEnum(“GREEN”, 0); static final MyEnum BLUE = new MyEnum(“BLUE”, 0); static final MyEnum[] $VALUES = new MyEnum[] { RED, GREEN, BLUE }; ... } class MyEnum { static final MyEnum RED = new MyEnum(“RED”, 0); static final MyEnum GREEN = new MyEnum(“GREEN”, 0); static final MyEnum BLUE = new MyEnum(“BLUE”, 0); static final MyEnum[] $VALUES = new MyEnum[] { RED, GREEN, BLUE }; ... } New class Internal { static final int[] $SwitchMap new int[MyEnum.values().length]; static { $SwitchMap[MyEnum.GREEN.ordinal()] = 1; $SwitchMap[MyEnum.RED.ordinal()] = 2; } } class Internal { static final int[] $SwitchMap new int[MyEnum.values().length]; static { $SwitchMap[MyEnum.GREEN.ordinal()] = 1; $SwitchMap[MyEnum.RED.ordinal()] = 2; } } int answer = getAnswer(2); int answer = getAnswer(2); int getAnswer(int e) { switch (Internal.$SwitchMap[e]) { case 1: return 1; case 2: return 42; default: return 0; } } int getAnswer(int e) { switch (Internal.$SwitchMap[e]) { case 1: return 1; case 2: return 42; default: return 0; } } class Internal { static final int[] $SwitchMap = new int[3]; static { $SwitchMap[1] = 1; $SwitchMap[2] = 2; } } class Internal { static final int[] $SwitchMap = new int[3]; static { $SwitchMap[1] = 1; $SwitchMap[2] = 2; } }
  26. Optimization: enum int answer = getAnswer(MyEnum.GREEN); int answer = getAnswer(MyEnum.GREEN);

    int getAnswer(MyEnum e) { switch (e) { case RED: return 1; case GREEN: return 42; default: return 0; } } int getAnswer(MyEnum e) { switch (e) { case RED: return 1; case GREEN: return 42; default: return 0; } } enum MyEnum { RED, GREEN, BLUE } enum MyEnum { RED, GREEN, BLUE } int getAnswer(MyEnum e) { switch (Internal.$SwitchMap[e.ordinal()]) { case 1: return 1; case 2: return 42; default: return 0; } } int getAnswer(MyEnum e) { switch (Internal.$SwitchMap[e.ordinal()]) { case 1: return 1; case 2: return 42; default: return 0; } } class MyEnum { static final MyEnum RED = new MyEnum(“RED”, 0); static final MyEnum GREEN = new MyEnum(“GREEN”, 0); static final MyEnum BLUE = new MyEnum(“BLUE”, 0); static final MyEnum[] $VALUES = new MyEnum[] { RED, GREEN, BLUE }; ... } class MyEnum { static final MyEnum RED = new MyEnum(“RED”, 0); static final MyEnum GREEN = new MyEnum(“GREEN”, 0); static final MyEnum BLUE = new MyEnum(“BLUE”, 0); static final MyEnum[] $VALUES = new MyEnum[] { RED, GREEN, BLUE }; ... } New class Internal { static final int[] $SwitchMap new int[MyEnum.values().length]; static { $SwitchMap[MyEnum.GREEN.ordinal()] = 1; $SwitchMap[MyEnum.RED.ordinal()] = 2; } } class Internal { static final int[] $SwitchMap new int[MyEnum.values().length]; static { $SwitchMap[MyEnum.GREEN.ordinal()] = 1; $SwitchMap[MyEnum.RED.ordinal()] = 2; } } int answer = getAnswer(2); int answer = getAnswer(2); int getAnswer(int e) { switch (Internal.$SwitchMap[e]) { case 1: return 1; case 2: return 42; default: return 0; } } int getAnswer(int e) { switch (Internal.$SwitchMap[e]) { case 1: return 1; case 2: return 42; default: return 0; } } class Internal { static final int[] $SwitchMap = new int[3]; static { $SwitchMap[1] = 1; $SwitchMap[2] = 2; } } class Internal { static final int[] $SwitchMap = new int[3]; static { $SwitchMap[1] = 1; $SwitchMap[2] = 2; } } int getAnswer(int e) { switch (e) { case 1: return 1; case 2: return 42; default: return 0; } } int getAnswer(int e) { switch (e) { case 1: return 1; case 2: return 42; default: return 0; } }
  27. Optimization: enum int answer = getAnswer(MyEnum.GREEN); int answer = getAnswer(MyEnum.GREEN);

    int getAnswer(MyEnum e) { switch (e) { case RED: return 1; case GREEN: return 42; default: return 0; } } int getAnswer(MyEnum e) { switch (e) { case RED: return 1; case GREEN: return 42; default: return 0; } } enum MyEnum { RED, GREEN, BLUE } enum MyEnum { RED, GREEN, BLUE } int getAnswer(MyEnum e) { switch (Internal.$SwitchMap[e.ordinal()]) { case 1: return 1; case 2: return 42; default: return 0; } } int getAnswer(MyEnum e) { switch (Internal.$SwitchMap[e.ordinal()]) { case 1: return 1; case 2: return 42; default: return 0; } } class MyEnum { static final MyEnum RED = new MyEnum(“RED”, 0); static final MyEnum GREEN = new MyEnum(“GREEN”, 0); static final MyEnum BLUE = new MyEnum(“BLUE”, 0); static final MyEnum[] $VALUES = new MyEnum[] { RED, GREEN, BLUE }; ... } class MyEnum { static final MyEnum RED = new MyEnum(“RED”, 0); static final MyEnum GREEN = new MyEnum(“GREEN”, 0); static final MyEnum BLUE = new MyEnum(“BLUE”, 0); static final MyEnum[] $VALUES = new MyEnum[] { RED, GREEN, BLUE }; ... } New class Internal { static final int[] $SwitchMap new int[MyEnum.values().length]; static { $SwitchMap[MyEnum.GREEN.ordinal()] = 1; $SwitchMap[MyEnum.RED.ordinal()] = 2; } } class Internal { static final int[] $SwitchMap new int[MyEnum.values().length]; static { $SwitchMap[MyEnum.GREEN.ordinal()] = 1; $SwitchMap[MyEnum.RED.ordinal()] = 2; } } int answer = getAnswer(2); int answer = getAnswer(2); int getAnswer(int e) { switch (Internal.$SwitchMap[e]) { case 1: return 1; case 2: return 42; default: return 0; } } int getAnswer(int e) { switch (Internal.$SwitchMap[e]) { case 1: return 1; case 2: return 42; default: return 0; } } class Internal { static final int[] $SwitchMap = new int[3]; static { $SwitchMap[1] = 1; $SwitchMap[2] = 2; } } class Internal { static final int[] $SwitchMap = new int[3]; static { $SwitchMap[1] = 1; $SwitchMap[2] = 2; } } int getAnswer(int e) { switch (e) { case 1: return 1; case 2: return 42; default: return 0; } } int getAnswer(int e) { switch (e) { case 1: return 1; case 2: return 42; default: return 0; } } int answer = 42; int answer = 42;
  28. How to enable optimization? Ant and Eclipse: project.properties # To

    enable ProGuard to shrink and obfuscate your code, uncomment this proguard.config= ${sdk.dir}/tools/proguard/proguard-android-optimize.txt: proguard-project.txt # To enable ProGuard to shrink and obfuscate your code, uncomment this proguard.config= ${sdk.dir}/tools/proguard/proguard-android-optimize.txt: proguard-project.txt Tip
  29. How to enable optimization? Gradle: build.gradle android { buildTypes {

    release { runProguard true proguardFile getDefaultProguardFile('proguard-android-optimize.txt') } } productFlavors { some_flavor { proguardFile 'proguard-project.txt' } } } android { buildTypes { release { runProguard true proguardFile getDefaultProguardFile('proguard-android-optimize.txt') } } productFlavors { some_flavor { proguardFile 'proguard-project.txt' } } } New
  30. Remove logging code Specify assumptions in proguard-project.txt: -assumenosideeffects class android.util.Log

    { public static boolean isLoggable(java.lang.String, int); public static int v(...); public static int i(...); public static int w(...); public static int d(...); public static int e(...); public static java.lang.String getStackTraceString (java.lang.Throwable); } -assumenosideeffects class android.util.Log { public static boolean isLoggable(java.lang.String, int); public static int v(...); public static int i(...); public static int w(...); public static int d(...); public static int e(...); public static java.lang.String getStackTraceString (java.lang.Throwable); } Tip
  31. Obfuscation Traditional name obfuscation: • Rename identifiers: class/field/method names •

    Remove debug information: line numbers, local variable names,...
  32. Obfuscation example public class MyComputationClass { private MySettings settings; private

    MyAlgorithm algorithm; private int answer; public int computeAnswer(int input) { … return answer; } } public class MyComputationClass { private MySettings settings; private MyAlgorithm algorithm; private int answer; public int computeAnswer(int input) { … return answer; } }
  33. Obfuscation example public class MyComputationClass { private MySettings settings; private

    MyAlgorithm algorithm; private int answer; public int computeAnswer(int input) { … return answer; } } public class MyComputationClass { private MySettings settings; private MyAlgorithm algorithm; private int answer; public int computeAnswer(int input) { … return answer; } } public class a { private b a; private c b; private int c; public int a(int a) { … return c; } } public class a { private b a; private c b; private int c; public int a(int a) { … return c; } }
  34. What about ART? The new (experimental) Android RunTime Application Java

    bytecode ProGuard Libraries Java bytecode Processed Java bytecode Dex Dalvik bytecode Dex2oat Native code Development Device
  35. More application protection? public class MyVerificationClass { public int checkSignatures()

    { … return activity .getPackageManager() .checkSignatures(“mypackage1”, “mypackage2”); } } public class MyVerificationClass { public int checkSignatures() { … return activity .getPackageManager() .checkSignatures(“mypackage1”, “mypackage2”); } }
  36. More application protection? public class MyVerificationClass { public int checkSignatures()

    { … return activity .getPackageManager() .checkSignatures(“mypackage1”, “mypackage2”); } } public class MyVerificationClass { public int checkSignatures() { … return activity .getPackageManager() .checkSignatures(“mypackage1”, “mypackage2”); } } public class a { public int a() { … return a .getPackageManager() .checkSignatures(“mypackage1”, “mypackage2”); } } public class a { public int a() { … return a .getPackageManager() .checkSignatures(“mypackage1”, “mypackage2”); } }
  37. What to protect? • Application • Algorithms • Communication •

    Assets • Keys Code Resources Assets Signatures
  38. Static analysis • Disassemblers: dexdump, baksmali • Decompilers: dex2jar +

    jad, JD-GUI, JEB,... • Resources: aapt, apktool,...
  39. String encryption Be creative! String KEY = “Secret key”; String

    KEY = “Secret key”; String KEY = new String(Base64.decode(“U2VjcmV0IGtleQo=”)); String KEY = new String(Base64.decode(“U2VjcmV0IGtleQo=”));
  40. Reflection java.lang.reflect System.out.println(“Hello world!”); System.out.println(“Hello world!”); Class clazz = Class.forName(“java.io.PrintStream”);

    Method method = clazz.getMethod(“println”, new Class[] { String.class }); method.invoke(nul, new Object[] { “Hello world!” }); Class clazz = Class.forName(“java.io.PrintStream”); Method method = clazz.getMethod(“println”, new Class[] { String.class }); method.invoke(nul, new Object[] { “Hello world!” });
  41. Data encryption • Standard cryptography APIs • SQLCipher • android.drm

    Cipher cipher = Cipher.getInstance(“AES/CFB/NoPadding”); cipher.init(Cipher.ENCRYPT_MODE, key, initializationVector); byte[] decrypted = cipher.doFinal(encrypted); Cipher cipher = Cipher.getInstance(“AES/CFB/NoPadding”); cipher.init(Cipher.ENCRYPT_MODE, key, initializationVector); byte[] decrypted = cipher.doFinal(encrypted);
  42. White-box cryptography Research: http://www.whiteboxcrypto.com/ Real examples: DES, AES final int

    KEY = 42; int decryptValue(int encryptedValue) { return encryptedValue * KEY; } final int KEY = 42; int decryptValue(int encryptedValue) { return encryptedValue * KEY; } int decryptValue(int encryptedValue) { encryptedValue += 5; encryptedValue *= 21; encryptedValue -= 105; encryptedValue += encryptedValue; return encryptedValue; } int decryptValue(int encryptedValue) { encryptedValue += 5; encryptedValue *= 21; encryptedValue -= 105; encryptedValue += encryptedValue; return encryptedValue; }
  43. Application checks • Check application certificate: • Check debug flag:

    boolean debug = (activity.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; boolean debug = (activity.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; byte[] certificateData = activity .getPackageManager() .getPackageInfo(activity.getPackageName(), PackageManager.GET_SIGNATURES) .signatures[0] .toByteArray(); byte[] certificateData = activity .getPackageManager() .getPackageInfo(activity.getPackageName(), PackageManager.GET_SIGNATURES) .signatures[0] .toByteArray();
  44. Conclusion Nothing is unbreakable, but you can raise the bar:

    • ProGuard • String encryption • Reflection • Dynamic class loading • Native code • Data encryption • Application checks • Environment checks • … DexGuard
  45. DexGuard • Specialized for Android • Code and data •

    Layers of protection • Recommended techniques • Low-level techniques • Transparent Tip Code Resources Assets Signatures
  46. ProGuard - DexGuard Open source Generic Shrinker Optimizer Obfuscator For

    Java bytecode Closed source Specialized Shrinker Optimizer Obfuscator Protector For Android Compatible