#mceconf #mce-proguard 11 Jan 2014
ProGuard
Optimizer and Obfuscator
in the Android SDK
Eric Lafortune
Developer of ProGuard
Technical director at Saikoa
#mceconf #mce-proguard 11 Jan 2014
ProGuard
Generic
Shrinker
Optimizer
Obfuscator
For Java bytecode
Open source
#mceconf #mce-proguard 11 Jan 2014
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
#mceconf #mce-proguard 11 Jan 2014
ProGuard
Application
code
Libraries
Runtime
libraries
Shrink Optimize Obfuscate
Processed
code
Runtime
libraries
#mceconf #mce-proguard 11 Jan 2014
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 resourcesAapt
#mceconf #mce-proguard 11 Jan 2014
Why use ProGuard?
●
Application size
●
Performance
●
Remove logging, debugging, testing code
●
Protection
#mceconf #mce-proguard 11 Jan 2014
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]
#mceconf #mce-proguard 11 Jan 2014
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]
#mceconf #mce-proguard 11 Jan 2014
Results
●
Size reduction:
– Code (classes.dex): 20 … 90%
– Application (.apk): 5 ... 70%
●
Performance improvements: 0 … 20%
#mceconf #mce-proguard 11 Jan 2014
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
#mceconf #mce-proguard 11 Jan 2014
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
#mceconf #mce-proguard 11 Jan 2014
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
#mceconf #mce-proguard 11 Jan 2014
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
#mceconf #mce-proguard 11 Jan 2014
Shrinking
Also called treeshaking, minimizing, shrouding
#mceconf #mce-proguard 11 Jan 2014
Shrinking
●
Classes, fields, methods
#mceconf #mce-proguard 11 Jan 2014
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
…
#mceconf #mce-proguard 11 Jan 2014
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
#mceconf #mce-proguard 11 Jan 2014
Optimization
At the bytecode instruction level:
●
Dead code elimination
●
Constant propagation
●
Method inlining
●
Class merging
●
Remove logging code
●
Peephole optimizations
●
Devirtualization
●
...
#mceconf #mce-proguard 11 Jan 2014
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);
}
}
#mceconf #mce-proguard 11 Jan 2014
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);
}
#mceconf #mce-proguard 11 Jan 2014
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);
}
#mceconf #mce-proguard 11 Jan 2014
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;
}
#mceconf #mce-proguard 11 Jan 2014
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;
#mceconf #mce-proguard 11 Jan 2014
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
#mceconf #mce-proguard 11 Jan 2014
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
}
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
#mceconf #mce-proguard 11 Jan 2014
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 };
...
}
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;
}
}
New
#mceconf #mce-proguard 11 Jan 2014
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 };
...
}
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;
}
}
New
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;
}
}
#mceconf #mce-proguard 11 Jan 2014
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 };
...
}
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;
}
}
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;
}
}
New
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;
}
}
#mceconf #mce-proguard 11 Jan 2014
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 };
...
}
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;
}
}
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;
New
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;
}
}
#mceconf #mce-proguard 11 Jan 2014
How to enable optimization?
Ant and Eclipse: project.properties
Tip
# 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
#mceconf #mce-proguard 11 Jan 2014
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
#mceconf #mce-proguard 11 Jan 2014
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
#mceconf #mce-proguard 11 Jan 2014
Obfuscation
Traditional name obfuscation:
●
Rename identifiers:
class/field/method names
●
Remove debug information:
line numbers, local variable names,...
#mceconf #mce-proguard 11 Jan 2014
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;
}
}
#mceconf #mce-proguard 11 Jan 2014
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;
}
}
#mceconf #mce-proguard 11 Jan 2014
Complementary steps
Optimization Obfuscation
Irreversibly remove information
#mceconf #mce-proguard 11 Jan 2014
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
#mceconf #mce-proguard 11 Jan 2014
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”);
}
}
#mceconf #mce-proguard 11 Jan 2014
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”);
}
}
#mceconf #mce-proguard 11 Jan 2014
More application protection
Nothing is unbreakable, but you can raise the bar:
●
ProGuard
●
String encryption
●
Reflection
●
Dynamic class loading
●
Native code
●
Data encryption
●
… DexGuard
#mceconf #mce-proguard 11 Jan 2014
ProGuard - DexGuard
Open source
Generic
Shrinker
Optimizer
Obfuscator
For Java bytecode
Closed source
Specialized
Shrinker
Optimizer
Obfuscator
Protector
For Android
Compatible
#mceconf #mce-proguard 11 Jan 2014
ProGuard guide – Android SDK
developer.android.comdeveloper.android.com
#mceconf #mce-proguard 11 Jan 2014
ProGuard website
proguard.sourceforge.netproguard.sourceforge.net
#mceconf #mce-proguard 11 Jan 2014
ProGuard manualTip
proguard.sourceforge.netproguard.sourceforge.net
#mceconf #mce-proguard 11 Jan 2014
Saikoa website
www.saikoa.comwww.saikoa.com
#mceconf #mce-proguard 11 Jan 2014
Questions?
Open source
Shrinking
Optimization
Obfuscation
Java bytecode
ProGuard
Saikoa
DexGuard
Dalvik bytecode
Protection

Eric Lafortune - ProGuard: Optimizer and obfuscator in the Android SDK

  • 1.
    #mceconf #mce-proguard 11Jan 2014 ProGuard Optimizer and Obfuscator in the Android SDK Eric Lafortune Developer of ProGuard Technical director at Saikoa
  • 2.
    #mceconf #mce-proguard 11Jan 2014 ProGuard Generic Shrinker Optimizer Obfuscator For Java bytecode Open source
  • 3.
    #mceconf #mce-proguard 11Jan 2014 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.
    #mceconf #mce-proguard 11Jan 2014 ProGuard Application code Libraries Runtime libraries Shrink Optimize Obfuscate Processed code Runtime libraries
  • 5.
    #mceconf #mce-proguard 11Jan 2014 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 resourcesAapt
  • 6.
    #mceconf #mce-proguard 11Jan 2014 Why use ProGuard? ● Application size ● Performance ● Remove logging, debugging, testing code ● Protection
  • 7.
    #mceconf #mce-proguard 11Jan 2014 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]
  • 8.
    #mceconf #mce-proguard 11Jan 2014 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]
  • 9.
    #mceconf #mce-proguard 11Jan 2014 Results ● Size reduction: – Code (classes.dex): 20 … 90% – Application (.apk): 5 ... 70% ● Performance improvements: 0 … 20%
  • 10.
    #mceconf #mce-proguard 11Jan 2014 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.
    #mceconf #mce-proguard 11Jan 2014 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.
    #mceconf #mce-proguard 11Jan 2014 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.
    #mceconf #mce-proguard 11Jan 2014 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.
    #mceconf #mce-proguard 11Jan 2014 Shrinking Also called treeshaking, minimizing, shrouding
  • 15.
    #mceconf #mce-proguard 11Jan 2014 Shrinking ● Classes, fields, methods
  • 16.
    #mceconf #mce-proguard 11Jan 2014 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 …
  • 17.
    #mceconf #mce-proguard 11Jan 2014 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
  • 18.
    #mceconf #mce-proguard 11Jan 2014 Optimization At the bytecode instruction level: ● Dead code elimination ● Constant propagation ● Method inlining ● Class merging ● Remove logging code ● Peephole optimizations ● Devirtualization ● ...
  • 19.
    #mceconf #mce-proguard 11Jan 2014 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); } }
  • 20.
    #mceconf #mce-proguard 11Jan 2014 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); }
  • 21.
    #mceconf #mce-proguard 11Jan 2014 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); }
  • 22.
    #mceconf #mce-proguard 11Jan 2014 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; }
  • 23.
    #mceconf #mce-proguard 11Jan 2014 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;
  • 24.
    #mceconf #mce-proguard 11Jan 2014 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
  • 25.
    #mceconf #mce-proguard 11Jan 2014 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 } 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
  • 26.
    #mceconf #mce-proguard 11Jan 2014 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 }; ... } 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; } } New
  • 27.
    #mceconf #mce-proguard 11Jan 2014 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 }; ... } 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; } } New 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; } }
  • 28.
    #mceconf #mce-proguard 11Jan 2014 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 }; ... } 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; } } 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; } } New 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; } }
  • 29.
    #mceconf #mce-proguard 11Jan 2014 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 }; ... } 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; } } 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; New 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; } }
  • 30.
    #mceconf #mce-proguard 11Jan 2014 How to enable optimization? Ant and Eclipse: project.properties Tip # 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
  • 31.
    #mceconf #mce-proguard 11Jan 2014 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
  • 32.
    #mceconf #mce-proguard 11Jan 2014 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
  • 33.
    #mceconf #mce-proguard 11Jan 2014 Obfuscation Traditional name obfuscation: ● Rename identifiers: class/field/method names ● Remove debug information: line numbers, local variable names,...
  • 34.
    #mceconf #mce-proguard 11Jan 2014 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; } }
  • 35.
    #mceconf #mce-proguard 11Jan 2014 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; } }
  • 36.
    #mceconf #mce-proguard 11Jan 2014 Complementary steps Optimization Obfuscation Irreversibly remove information
  • 37.
    #mceconf #mce-proguard 11Jan 2014 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
  • 38.
    #mceconf #mce-proguard 11Jan 2014 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”); } }
  • 39.
    #mceconf #mce-proguard 11Jan 2014 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”); } }
  • 40.
    #mceconf #mce-proguard 11Jan 2014 More application protection Nothing is unbreakable, but you can raise the bar: ● ProGuard ● String encryption ● Reflection ● Dynamic class loading ● Native code ● Data encryption ● … DexGuard
  • 41.
    #mceconf #mce-proguard 11Jan 2014 ProGuard - DexGuard Open source Generic Shrinker Optimizer Obfuscator For Java bytecode Closed source Specialized Shrinker Optimizer Obfuscator Protector For Android Compatible
  • 42.
    #mceconf #mce-proguard 11Jan 2014 ProGuard guide – Android SDK developer.android.comdeveloper.android.com
  • 43.
    #mceconf #mce-proguard 11Jan 2014 ProGuard website proguard.sourceforge.netproguard.sourceforge.net
  • 44.
    #mceconf #mce-proguard 11Jan 2014 ProGuard manualTip proguard.sourceforge.netproguard.sourceforge.net
  • 45.
    #mceconf #mce-proguard 11Jan 2014 Saikoa website www.saikoa.comwww.saikoa.com
  • 46.
    #mceconf #mce-proguard 11Jan 2014 Questions? Open source Shrinking Optimization Obfuscation Java bytecode ProGuard Saikoa DexGuard Dalvik bytecode Protection