This article is just a summary of the recent work for the convenience of future inquiries.
Disable log printing
Disable the printed logs to prevent debugging information in logs from being seen. This is especially true if logging is used in a network framework.
Code confusion
Obfuscating code is the most basic practice, and at the very least it makes it harder to read the source code when the App is decomcompiled.
Of course, even after the obfuscation of the code, as long as it takes a certain amount of time, it is still possible to clarify the logic between the codes.
Confuse the use of dictionaries
If you don’t like the idea of changing class and variable names to A, B, or C in your code, you can customize some characters to replace them. This is where the obfuscation dictionary comes in.
Recommend a lot on the library: https://github.com/ysrc/AndroidObfuseDictionary
Add obtrusion dictionary configuration in proguard-rules.pro
# Confound the dictionary
-obfuscationdictionary dic.txt
-classobfuscationdictionary dic.txt
-packageobfuscationdictionary dic.txt
Copy the code
Native layer calibration
In addition to being obfuscated, apps also need to be protected from being repackaged by others.
Due to the uniqueness of release signature, signature verification can be considered in native layer. If the signature is incorrect, the App will crash.
We override the JNI_OnLoad() function to check here.
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) ! = JNI_OK) {return JNI_ERR;
}
if (verifySign(env) == JNI_OK) {
return JNI_VERSION_1_4;
}
LOGE("Inconsistent signatures!");
return JNI_ERR;
}
Copy the code
The verifySign() function performs the actual validation, comparing the signature string stored in the Native layer with the current App signature.
static int verifySign(JNIEnv *env) {
// Application object
jobject application = getApplication(env);
if (application == NULL) {
return JNI_ERR;
}
// Context(ContextWrapper) class
jclass context_clz = env->GetObjectClass(application);
// getPackageManager()
jmethodID getPackageManager = env->GetMethodID(context_clz, "getPackageManager"."()Landroid/content/pm/PackageManager;");
// android.content.pm.PackageManager object
jobject package_manager = env->CallObjectMethod(application, getPackageManager);
// PackageManager class
jclass package_manager_clz = env->GetObjectClass(package_manager);
// getPackageInfo()
jmethodID getPackageInfo = env->GetMethodID(package_manager_clz, "getPackageInfo"."(Ljava/lang/String; I)Landroid/content/pm/PackageInfo;");
// context.getPackageName()
jmethodID getPackageName = env->GetMethodID(context_clz, "getPackageName"."()Ljava/lang/String;");
// call getPackageName() and cast from jobject to jstring
jstring package_name = (jstring) (env->CallObjectMethod(application, getPackageName));
// PackageInfo object
jobject package_info = env->CallObjectMethod(package_manager, getPackageInfo, package_name, 64);
// class PackageInfo
jclass package_info_clz = env->GetObjectClass(package_info);
// field signatures
jfieldID signatures_field = env->GetFieldID(package_info_clz, "signatures"."[Landroid/content/pm/Signature;");
jobject signatures = env->GetObjectField(package_info, signatures_field);
jobjectArray signatures_array = (jobjectArray) signatures;
jobject signature0 = env->GetObjectArrayElement(signatures_array, 0);
jclass signature_clz = env->GetObjectClass(signature0);
jmethodID toCharsString = env->GetMethodID(signature_clz, "toCharsString"."()Ljava/lang/String;");
// call toCharsString()
jstring signature_str = (jstring) (env->CallObjectMethod(signature0, toCharsString));
// release
env->DeleteLocalRef(application);
env->DeleteLocalRef(context_clz);
env->DeleteLocalRef(package_manager);
env->DeleteLocalRef(package_manager_clz);
env->DeleteLocalRef(package_name);
env->DeleteLocalRef(package_info);
env->DeleteLocalRef(package_info_clz);
env->DeleteLocalRef(signatures);
env->DeleteLocalRef(signature0);
env->DeleteLocalRef(signature_clz);
const char *sign = env->GetStringUTFChars(signature_str, NULL);
if (sign == NULL) {
LOGE("Memory allocation failed");
return JNI_ERR;
}
int result = strcmp(sign, RELEASE_SIGN);
// Release the memory after use
env->ReleaseStringUTFChars(signature_str, sign);
env->DeleteLocalRef(signature_str);
if (result == 0) { // The signature is consistent
return JNI_OK;
}
return JNI_ERR;
}
Copy the code
The JNI_OnLoad() function is called only if JNI is used. To make sure the App can verify the signature as soon as it starts.
I also wrote a method:
jstring Java_io_merculet_core_utils_EncryptUtils_nativeCheck(JNIEnv *env, jclass type) {
return env->NewStringUTF("Security str from native.");
}
Copy the code
It is used in onCreate() of the App’s MainActivity.
EncryptUtils.nativeCheck()
Copy the code
EncryptUtils is a utility class that calls native layer methods.
/ * * *@versionV1.0 < Describes current version features > *@FileName: io.merculet.core.utils.EncryptUtils.java
* @author: Tony Shen
* @date: 2018-05-21 20:53 * /
public class EncryptUtils {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("codec");
}
public static native String nativeCheck(a); . }Copy the code
Do not transmit key passwords in plain text
For example, passwords related to login and payment should be encrypted rather than transmitted in plain text. If doing encryption at the Java layer is easy to decompile, consider using C++.
conclusion
These measures are just the tip of the iceberg, because safety is always a topic. We can also consider using shells, anti-dynamic debugging, and so on.
References:
- http://qbeenslee.com/article/about-wandoujia-proguard-config/
- https://github.com/Qrilee/AndroidObfuseDictionary
- https://www.jianshu.com/p/2576d064baf1
Java and Android technology stack: update and push original technical articles every week, welcome to scan the qr code of the public account below and pay attention to, looking forward to growing and progress with you together.