The server contains a chain of certificates with public keys and certified names. You can obtain a certificate from a Certificate Authority (CA). With the standard Android API, the Android runtime checks such a chain with its database of root certificates. Alternatively, you can create a self-signed certificate. You then need to check the certificate yourself in your application.
Occasionally, a Certificate Authority is compromised (as happened with Comodo and DigiNotar). New releases of Android then typically update the database of root certificates. However, if your application always communicates with the same server, it should pin the certificate or its public key, i.e. only accept a minimal, fixed list of trusted certificates or keys. This technique ensures that the application is always talking to the right server and blocks man-in-the-middle attacks. You should be aware that some large websites update their certificates and keys regularly.
samples/SSLPinning.
samples/SSLPinning-keep options.
bin/proguard/mapping.txt (with Ant),
proguard/mapping.txt (with Eclipse), or
build/outputs/proguard/release/mapping.txt (with Gradle).
Such a mapping file may look like this:
com.example.HelloWorldActivity -> com.example.HelloWorldActivity:
39:42:void onCreate(android.os.Bundle) -> onCreate
com.example.Util -> o.・:
byte[] values -> ・
int doSomething(android.content.Context) -> ・
java.lang.String compute(android.content.Context) -> ˊ
Note that names from the Android runtime are not obfuscated, since obfuscating
them would break the application.
You can also see the obfuscated names if you disassemble the code with a tool
like dexdump (build-tools/20.0.0/dexdump in the Android SDK), or
baksmali (free and open-source:
code.google.com/p/smali/).
In the debug version of the application, the names are still readable:
Class descriptor : 'Lcom/example/Util;
...
#0 : (in Lcom/example/Util;)
name : 'values'
...
In the hardened release version of the application, the names are obfuscated:
Class descriptor : 'Lo/・;'
...
#0 : (in Lo/・;)
name : '・'
...
-encryptstrings. It
offers a number of ways to specify the strings to be encrypted. The most
common ones:
-encryptstrings class class com.example.MyConstants {
public static final java.lang.String SECRET_KEY;
}
Be aware that the Java compiler already inlines final string constants
whereever they are used in the code, which may be in other classes. They may
have spread throughout the code before DexGuard processes it. With the above
configuration, DexGuard encrypts these constants throughout the code, to
provide the expected results.
-encryptstrings class class com.example.MySecretClassThis configuration is convenient if you can identify one or more sensitive classes.
... const-string v0, "Hello world!"In the release version of the application, encrypted strings should no longer be visible.
-encryptstringssamples/StringEncryptionYou can let DexGuard replace invocations by reflection and then encrypt the resulting strings. They then become difficult to find with static analysis.
SecureRandom with
the option
-accessthroughreflection:
-accessthroughreflection class java.security.SecureRandom {
<init>();
int nextInt();
}
You should combine the reflection with string encryption. More specifically,
you can enumerate all strings that are created for the reflection:
-encryptstrings "java.security.SecureRandom", "nextInt"An easier approach may be to encrypt all strings in the class that invokes the cryptographic classes:
-encryptstrings class com.example.MySecretClass
... new-instance v0, Ljava/security/SecureRandom;
... invoke-direct {v0}, Ljava/security/SecureRandom;.:()V
...
... invoke-virtual {v0}, Ljava/security/SecureRandom;.nextInt:()I
In the hardened release version of the application., you can check that any
invocations that you have specified are no longer visible
-accessthroughreflectionsamples/ReflectionBuild.DEBUG, you can let DexGuard remove logging calls for you.
-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);
}
Remove all printing of stack traces:
-assumenosideeffects class java.lang.Exception {
public void printStackTrace();
}
... invoke-static {v0, v1}, Landroid/util/Log;.d:(Ljava/lang/String;Ljava/lang/String;)I
In the release version of the application, all of these invocation should be
gone.
-assumenosideeffectssamples/LoggingRemovalYou can fight this type of dynamic analysis by adding tamper detection to your application. Tamper detection actively checks the integrity of the application at runtime and acts suitably if the application has been modified.
samples/TamperDetection/libs/dexguard_util.jar to your
application. You can then invoke the tamper detection methods in the
application and act based on their return values.
int check = dexguard.util.TamperDetector.checkApk(context);
if (check != 0) {
// Tampering detected.
...
}
int check = dexguard.util.CertificateChecker.checkCertificate(context);
if (check != 0) {
// Different certificate detected.
...
}
You should weave the tamper detection code into your regular code, making it more difficult to simply disable it. For instance, you could use the return value to affect the computation of a key, so the computed key is wrong if the application has been tampered with. A simplistic example:
int checkedKey = dexguard.util.TamperDetector.checkApk(context, key);
If the code detects tampering, the application should fail quietly, possibly with a delay, without giving away any further information.
Once you have added your tamper detection code, you should further harden it
by encrypting its class, together with the tamper detection library
(dexguard.util.**).
zipalign -f 4 myapplication.apkThe tool repackages the application without fundamentally changing it. This will trigger the tamper detection at runtime.
You can check the certificate verification by re-signing the application with a different certificate:
zip -d MyApp.apk META-INF/*
jarsigner -keystore debug.keystore -storepass android -keypass android -signedjar \
MyApp_signed.apk MyApp.apk AndroidDebugKey
This will trigger the certificate check at runtime.
samples/TamperDetectionYou may therefore want to check the environment in which the application is running. This is tricky, since the subverted environment may be constructed to look as ordinary and inconspicuous as possible. Nevertheless, you can perform some basic sanity checks:
samples/EnvironmentCheck/libs/dexguard_util.jar to your
application. You can then invoke the environment checking methods in the
application and act based on their return values.
int check = dexguard.util.DebugDetector.isDebuggable(context);
if (check != 0) {
// Application is debuggable.
...
}
int check = dexguard.util.DebugDetector.isDebuggerConnected();
if (check != 0) {
// Application is being debugged.
...
}
int check = dexguard.util.DebugDetector.isSignedWithDebugKey(context);
if (check != 0) {
// Application has been signed with a debug key.
...
}
int check = dexguard.util.EmulatorDetector.isRunningInEmulator(context);
if (check != 0) {
// Application is running in an emulator.
...
}
int check = dexguard.utilRootDetector.isDeviceRooted();
if (check != 0) {
// Application is running on a rooted device.
...
}
You should again weave the tamper detection code into your regular code and
further harden it by encrypting the class together with the library
(dexguard.util.**).
samples/EnvironmentCheckEncrypting all classes is not technically possible and would add an excessive overhead. You should identify a number of sensitive classes and encrypt those. Typically, these are the classes that you have already hardened with other techniques. For instance, you should include your tamper detection code and the associated library classes.
-encryptclasses:
-encryptclasses com.example.MySecretClass,
com.example.MySecretClass$*
The second name in the list matches all inner classes of
MySecretClass, since inner classes often also contain sensitive
code.
After having added class encryption for the class that contains such code, it will no longer be visible or modifiable.
-encryptclassessamples/ClassEncryption,
samples/TamperDetection,
samples/EnvironmentCheck