1. How it happened

A plain afternoon, I still leisurely leisurely knocking code with tea. Suddenly, a server colleague told me that I was concerned that the interface was being called mechanically, suspecting that someone was using a script to brush the interface (mainly to divert traffic from the platform). What? No, because as far as I know the interface request is encrypted, unless you know the encryption key and encryption method, otherwise it will not be successful, you must feel wrong. However, when a server colleague sent me the interface call log, my luck was completely denied.

  1. The interface call frequency is fixed at **1s* * a
  2. The attention of the **id** Increments one for each call (currently, the generation of user IDS in business is increments according to the time of registration)
  3. Encrypted * *The key** Always use a fixed key (normal is to use a random key at a time in a fixed number of keys)

Based on the above three points, it can be concluded that there must be a brush interface behavior.

2. Event analysis

Since the above behavior of brushing the interface is true, it means that the key and encryption method are known to the other party, for the following reasons:

  1. Insider leakage
  2. Apk be cracked

After confirmation, the first point is basically excluded, so only APK is cracked, but the package released by APK has been strengthened and confused, is the other party unshelled? Anyway, try decompiling yourself first. As a result, we decompile from the latest version one by one, and when we decompile to an earlier version, we find that the source code of the utility class that holds the key and encryption is completely exposed.This version has been released without hardening, and the encryption tool class has not been confused. It is not clear whether the encryption key and algorithm are obtained in this way, but there is no doubt that this is a security hole on the client side.

3. Event handling

Now that the above problems have been identified, we must try to solve them. First of all, regardless of hardening, how to ensure that sensitive data in the client is not leaked to the maximum extent possible? On the other hand, even if the other side wants to crack, also want to think of ways to build obstacles, increase crack difficulty. Thinking of this, I basically determined a idea: use NDK to put sensitive data and encryption methods into native layer, because the so library generated after compilation of C++ code is a binary file, which will undoubtedly increase the difficulty of cracking. With this feature, you can write client-sensitive data in C++ code to enhance application security. Just do it!!


1. First create the encryption tool class:

public class HttpKeyUtil { static { System.loadLibrary("jniSecret"); } public static native String getHttpSecretKey(int index); Public static native String getSecretValue(byte[] bytes); }Copy the code

2. Generate the corresponding header file com_test_util_httpkeyutil.h

#include <jni.h>
#ifndef _Included_com_test_util_HttpKeyUtil
#define _Included_com_test_util_HttpKeyUtil
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL Java_com_esky_common_component_util_HttpKeyUtil_getHttpSecretKey
        (JNIEnv *, jclass, jint);
        
JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getSecretValue
        (JNIEnv *, jclass, jbyteArray);

#ifdef __cplusplus
}
#endif
#endif
Copy the code

3. Prepare relevant CPP files:

To create a JNI directory in the appropriate Module, willcom_test_util_HttpKeyUtil.hCopy it in, and then create itcom_test_util_HttpKeyUtil.cppfile

#include <jni.h> #include <cstring> #include <malloc.h> #include "com_test_util_HttpKeyUtil.h" extern "C" const char *KEY1 = "KEY1 "; Const char *KEY2 = "KEY2 "; Const char *KEY3 = "KEY3 "; const char *UNKNOWN = "unknown"; jstring toMd5(JNIEnv *pEnv, jbyteArray pArray); extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getHttpSecretKey (JNIEnv *env, jclass cls, Jint index) {if (random number condition 1) {return env->NewStringUTF(KEY1); } else if (random number condition 2) {return env->NewStringUTF(KEY2); } else if (random number condition 3) {return env->NewStringUTF(KEY3); } else { return env->NewStringUTF(UNKNOWN); } } extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getSecretValue (JNIEnv *env, jclass cls, JbyteArray jbyteArray1) {return toMd5(env, jbyteArray1); } //md5 jstring toMd5(JNIEnv *env, jbyteArray source) { // MessageDigest jclass classMessageDigest = env->FindClass("java/security/MessageDigest"); // MessageDigest.getInstance() jmethodID midGetInstance = env->GetStaticMethodID(classMessageDigest, "getInstance", "(Ljava/lang/String;) Ljava/security/MessageDigest;" ); // MessageDigest object jobject objMessageDigest = env->CallStaticObjectMethod(classMessageDigest, midGetInstance, env->NewStringUTF("md5")); jmethodID midUpdate = env->GetMethodID(classMessageDigest, "update", "([B)V"); env->CallVoidMethod(objMessageDigest, midUpdate, source); // Digest jmethodID midDigest = env->GetMethodID(classMessageDigest, "digest", "()[B"); jbyteArray objArraySign = (jbyteArray) env->CallObjectMethod(objMessageDigest, midDigest); jsize intArrayLength = env->GetArrayLength(objArraySign); jbyte *byte_array_elements = env->GetByteArrayElements(objArraySign, NULL); size_t length = (size_t) intArrayLength * 2 + 1; char *char_result = (char *) malloc(length); memset(char_result,  0, length); toHexStr((const char *) byte_array_elements, char_result, \0 *(char_result + intArrayLength * 2) = '\0'; jString stringResult = env->NewStringUTF(char_result); // release env->ReleaseByteArrayElements(objArraySign, byte_array_elements, JNI_ABORT); // Pointer free(char_result); return stringResult;} // Convert to hexadecimal string void toHexStr(const char *source, char *dest, int sourceLen) { short i; char highByte, lowByte; for (i = 0; i < sourceLen; i++) { highByte = source[i] >> 4; lowByte = (char) (source[i] & 0x0f); highByte += 0x30; if (highByte > 0x39) { dest[i * 2] = (char) (highByte + 0x07); } else { dest[i * 2] = highByte; } lowByte += 0x30;  if (lowByte > 0x39) { dest[i * 2 + 1] = (char) (lowByte + 0x07); } else { dest[i * 2 + 1] = lowByte; } } }Copy the code

4) Is this the end of the incident?

And that’s the end of it? Too yuang too simple!! Although the key and encryption algorithm are written in c++, it seems to be more secure. But what if someone gets the so library generated by c++ code after decomcompiling, and then directly calls the method in the so library to get the key and call the encryption method? It seems that we still need to add a step of identity verification: that is, to authenticate the package name and signature of the application in the native layer, the correct result will be returned only after the verification passes. Here is the code to get the APK package name and signature verification:

Const char *PACKAGE_NAME = "Your ApplicationId"; // const char *SIGN_MD5 = "The MD5 value of your app signature is in uppercase "; // Const char *SIGN_MD5 =" The MD5 value of your app signature is in uppercase "; Jobject getApplication(JNIEnv *env) {jobject Application = NULL; // This is the classpath of your Application. Be sure not to confuse this class with the method that gets an instance of it, such as getInstance jClass baseApplication_clz = env->FindClass("com/test/component/BaseApplication"); if (baseapplication_clz ! = NULL) { jmethodID currentApplication = env->GetStaticMethodID( baseapplication_clz, "getInstance", "()Lcom/test/component/BaseApplication;" ); if (currentApplication ! = NULL) { application = env->CallStaticObjectMethod(baseapplication_clz, currentApplication); } env->DeleteLocalRef(baseapplication_clz); } return application; } bool isRight = false; Jboolean getSignature(JNIEnv *env) {LOGD("getSignature isRight: %d", isRight? 1:0); if (! Jobject context = getApplication(env); Jclass CLS = env->FindClass("android/content/Context"); JmethodID mid = env->GetMethodID(CLS, "getPackageManager", "()Landroid/content/pm/PackageManager;" ); Jobject PM = env->CallObjectMethod(context, mid); GetMethodID(CLS, "getPackageName", "()Ljava/lang/String; ); Jstring packageName = (jString) env->CallObjectMethod(context, mid); const char *c_pack_name = env->GetStringUTFChars(packageName, NULL); If (STRCMP (c_pack_name, PACKAGE_NAME)! = 0) { return false; } // Get PackageManager CLS = env->GetObjectClass(PM); Env ->GetMethodID(CLS, "getPackageInfo", "Ljava/lang/String; I)Landroid/content/pm/PackageInfo;" ); Jobject packageInfo = env->CallObjectMethod(PM, mid, packageName, 0x40); //GET_SIGNATURES = 64; CLS = env->GetObjectClass(PackageInfo); JfieldID fid = env->GetFieldID(CLS, "signatures", "[Landroid/content/PM/Signature;"); / / to get Signature array jobjectArray signatures = (jobjectArray) env - > GetObjectField (packageInfo, Jobject signature = env->GetObjectArrayElement(signatures, CLS = env->GetMethodID(CLS, "toByteArray",); JbyteArray signatureByteArray = (jbyteArray) env->CallObjectMethod(signature, Char *c_msg = (char *) env->GetStringUTFChars(STR, char *) 0); LOGD("getSignature release sign md5: %s", c_msg); isRight = STRCMP (c_msg, SIGN_MD5) == 0; return isRight; Obtain key and encryption method for modification, Extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getHttpSecretKey (JNIEnv *env, jclass CLS, Jint index) {if (getSignature(env)){// Check if (random number condition 1) {return env->NewStringUTF(KEY1);} else if (random number condition 2) {return Env ->NewStringUTF(KEY2);} else if (random number condition 3) {return env->NewStringUTF(KEY3);} else {return env->NewStringUTF(UNKNOWN); } }else { return env->NewStringUTF(UNKNOWN); } } extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getSecretValue (JNIEnv *env, jclass CLS, jbyteArray jbyteArray1) { If (getSignature(env)){return toMd5(env, jbyteArray1); }else { return env->NewStringUTF(UNKNOWN); } }Copy the code

5. To summarize

The above is the relevant code of the native of this event. As for how to generate so library, you can baidu by yourself. A few points to reflect on from this incident are:

  • Safety awareness, safety is no small matter
  • The released packages must go through the hardening process. To avoid omissions, do not manually package the packages and implement the hardening through scripts
  • The server adds an alarm mechanism for related risks