View
222
Download
2
Category
Preview:
Citation preview
ProGuardOptimizer and Obfuscator
in the Android SDK
Eric LafortuneDeveloper of ProGuard
Technical director at Saikoa
Topics
ProGuard
– Techniques– Results
More application protection
– Goals– Attackers– Types of attacks– Protection
ProGuard
Open source
Generic
ShrinkerOptimizer
Obfuscator
For Java bytecode
ProGuard historyJava 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
ProGuard
Applicationcode
Shrink
Libraries
Androidruntime
Optimize Obfuscate Processedcode
Androidruntime
ProGuard in Android builds
ApplicationJava bytecode
ProGuard
LibrariesJava bytecode
ProcessedJava bytecode Dex
Dalvik bytecode
JavacApplicationJava source
LibrariesJava bytecode
XML resources
Assets Assets
CompiledXML resourcesAapt
Why use ProGuard?
● Application size
● Performance
● Remove logging, debugging, testing code
● Protection
Application size
classes.dex size .apk size
WithoutProGuard
WithProGuard
Reduction
ApiDemos 716 K 482 K 33 % 2.6 M 2.5 M 4 %
GoogleIOApp
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]
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]
Battery life
Extreme example:
“5 x better battery life,by removing verbose logging code
in a background service”
(but don't count on it)
Results
● Size reduction:
– Code (classes.dex): 20 … 90%– Application (.apk): 5 ... 70%
● Performance improvements: 0 … 20%
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
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
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.JSONArrayWarning: 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.LoggerWarning: 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.LoggerWarning: twitter4j.internal.logging.SLF4JLoggerFactory: can't find referenced class org.slf4j.LoggerFactory...
Tip
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
Shrinking
Also called treeshaking, minimizing, shrouding
Shrinking
● Classes, fields, methods
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…
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
Optimization
At the bytecode instruction level:
● Dead code elimination
● Constant propagation
● Method inlining
● Class merging
● Remove logging code
● Peephole optimizations
● Devirtualization
● ...
Optimization exampleint 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); }}
Optimization exampleint 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);}
Optimization exampleint 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
Optimization exampleint 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;}
Optimization exampleint 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;
Optimization: enumint 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
Optimization: enumint 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
Optimization: enumint 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; }}
Optimization: enumint 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; }}
Optimization: enumint 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; }}
Optimization: enumint 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;
How to enable optimization?
Ant and Eclipse: project.properties
# To enable ProGuard to shrink and obfuscate your code, uncomment thisproguard.config= ${sdk.dir}/tools/proguard/proguard-android-optimize.txt: proguard-project.txt
# To enable ProGuard to shrink and obfuscate your code, uncomment thisproguard.config= ${sdk.dir}/tools/proguard/proguard-android-optimize.txt: proguard-project.txt
Tip
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
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
Obfuscation
Traditional name obfuscation:
● Rename identifiers:class/field/method names
● Remove debug information: line numbers, local variable names,...
Obfuscation examplepublic 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; }}
Obfuscation examplepublic 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; }}
Complementary steps
Optimization Obfuscation
Irreversibly remove information
What about ART?
The new (experimental) Android RunTime
ApplicationJava bytecode
ProGuard
LibrariesJava bytecode
ProcessedJava bytecode Dex
Dalvik bytecode Dex2oat
Native code
Development
Device
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”); }}
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”); }}
More application protection
● Goals
● Attackers
● Types of attacks
● Protection
What to protect?
● Application
● Algorithms
● Communication
● Assets
● Keys
Code
Resources
Assets
Signatures
Attackers
Attackers
Time
Choice
Static analysis
Static analysis
● Disassemblers: dexdump, baksmali
● Decompilers: dex2jar + jad, JD-GUI, JEB,...
● Resources: aapt, apktool,...
Dynamic analysis
Dynamic analysis
● Debuggers: adb,...
● Subverted runtime
● Cracking tools
Server-side code
Client-sideapplication
Server-sideapplication
?
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=”));
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!” });
Dynamic class loading
http://android-developers.blogspot.be/2011/07/custom-class-loading-in-dalvik.html
classes.dex
extra.dex
classes.dex
libutil.so
libutil.so
Native code
Java Native Interface
classes.dex
libutil.so
classes.dex
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);
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;}
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();
Environment checks
● Emulator detection
● Root detection
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
DexGuard
● Specialized for Android
● Code and data
● Layers of protection
● Recommended techniques
● Low-level techniques
● Transparent
Tip
Code
Resources
Assets
Signatures
ProGuard - DexGuard
Open source
Generic
ShrinkerOptimizer
Obfuscator
For Java bytecode
Closed source
Specialized
ShrinkerOptimizer
ObfuscatorProtector
For Android
Compatible
ProGuard guide – Android SDK
developer.android.comdeveloper.android.com
ProGuard website
proguard.sourceforge.netproguard.sourceforge.net
ProGuard manual
proguard.sourceforge.netproguard.sourceforge.net
Tip
Saikoa website
www.saikoa.comwww.saikoa.com
Questions?
Open source
ShrinkingOptimizationObfuscation
Java bytecode
ProGuard
Saikoa
DexGuard
Dalvik bytecode
Protection
Recommended