📜 ⬆️ ⬇️

Protection of Android applications from hacking

In this article, we will briefly describe how you can protect your program from hacking without integrating a standard solution from Google and provide an example of a working code. Interesting? We ask under the cut!



What is the weakness of Google Application Licensing?


')
The fact is that this mechanism is well known to developers and its hacking is not difficult. All you need is to download apktool, find the class LicenseChecker and slightly correct the checkAccess method.

How to implement your own protection?



Obviously, any protection can be broken. This method is not a silver bullet, but it has the right to life. To verify the uniqueness of the application, it makes sense to check the certificate with which this application was signed. Information about the certificate can be read from PackageInfo:

PackageInfo info =getPackageManager().getPackageInfo(getPackageName(), 0); Signature[] signatures = info.signatures; 


The zero element of the array will contain the necessary information about the key with which the application was signed. Save the key itself, you will need it very soon.
It is logical that doing such a check in Java does not make sense, since a similar technique using apktool will bury your protection in a few minutes. Therefore, this test should be moved to the netiv level.

 const char* rsa = "PUT_YOUR_RSA_KEY_HERE"; jint verifyCertificate(JNIEnv *env, jobject obj, jobject cnt) { jclass cls = env->GetObjectClass(cnt); jmethodID mid = env->GetMethodID(cls, "getPackageManager", "()Landroid/content/pm/PackageManager;"); jmethodID pnid = env->GetMethodID(cls, "getPackageName", "()Ljava/lang/String;"); if (mid == 0 || pnid == 0) { return ERROR; } jobject pacMan_o = env->CallObjectMethod(cnt, mid); jclass pacMan = env->GetObjectClass(pacMan_o); jstring packName = (jstring) env->CallObjectMethod(cnt, pnid); /*flags = PackageManager.GET_SIGNATURES*/ int flags = 0x40; mid = env->GetMethodID(pacMan, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"); if (mid == 0) { return ERROR; } jobject pack_inf_o = (jobject) env->CallObjectMethod(pacMan_o, mid, packName, flags); jclass packinf = env->GetObjectClass(pack_inf_o); jfieldID fid; fid = env->GetFieldID(packinf, "signatures", "[Landroid/content/pm/Signature;"); jobjectArray signatures = (jobjectArray) env->GetObjectField(pack_inf_o, fid); jobject signature0 = env->GetObjectArrayElement(signatures, 0); mid = env->GetMethodID(env->GetObjectClass(signature0), "toByteArray", "()[B"); jbyteArray cert = (jbyteArray) env->CallObjectMethod(signature0, mid); if (cert == 0) { return ERROR; } jclass BAIS = env->FindClass("java/io/ByteArrayInputStream"); if (BAIS == 0) { return ERROR; } mid = env->GetMethodID(BAIS, "<init>", "([B)V"); if (mid == 0) { return ERROR; } jobject input = env->NewObject(BAIS, mid, cert); jclass CF = env->FindClass("java/security/cert/CertificateFactory"); mid = env->GetStaticMethodID(CF, "getInstance", "(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;"); jstring X509 = env->NewStringUTF("X509"); jobject cf = env->CallStaticObjectMethod(CF, mid, X509); if (cf == 0) { return ERROR; } //"java/security/cert/X509Certificate" mid = env->GetMethodID(CF, "generateCertificate", "(Ljava/io/InputStream;)Ljava/security/cert/Certificate;"); if (mid == 0) { return ERROR; } jobject c = env->CallObjectMethod(cf, mid, input); if (c == 0) { return ERROR; } jclass X509Cert = env->FindClass("java/security/cert/X509Certificate"); mid = env->GetMethodID(X509Cert, "getPublicKey", "()Ljava/security/PublicKey;"); jobject pk = env->CallObjectMethod(c, mid); if (pk == 0) { return ERROR; } mid = env->GetMethodID(env->GetObjectClass(pk), "toString", "()Ljava/lang/String;"); if (mid == 0) { return ERROR; } jstring all = (jstring) env->CallObjectMethod(pk, mid); const char * all_char = env->GetStringUTFChars(all, NULL); char * out = NULL; if (all_char != NULL) { char * startString = strstr(all_char, "modulus:"); char * end = strstr(all_char, "public exponent"); bool isJB = false; if (startString == NULL) { //4.1.x startString = strstr(all_char, "modulus="); end = strstr(all_char, ",publicExponent"); isJB = true; } if (startString != NULL && end != NULL) { int len; if (isJB) { startString += strlen("modulus="); len = end - startString; } else { startString += strlen("modulus:"); len = end - startString - 5; /* -5 for new lines*/ } out = new char[len + 2]; strncpy(out, startString, len); out[len] = '\0'; } } env->ReleaseStringUTFChars(all, all_char); char * is_found = strstr(out, rsa); //      if (IS_DEBUG) { return is_found != NULL ? 0 : 1; } else { return is_found != NULL ? 1 :0; } } 


What to do next?



It is necessary to move some of its functionality to the same library and before returning the value to check the certificate for authenticity. And do not forget about the fantasy that is needed in order to confuse a potential hacker. Suppose you determine that this copy of the application is not genuine. It makes no sense to clearly talk about it, it’s much more fun to add a random element to the program’s actions. For example, if you have a game, you can add strength to your opponents or make the player more vulnerable. You can also add random drops, for example, in 10% of cases (a fair comment from the habrauser zagayevskiy : random drops will ruin the karma of your program.). It all depends on you.

Here is a simple example of a possible implementation:

First, we write a class that makes a library call to get some data.

 public class YourClass { static { System.loadLibrary("name_of_library"); } public native int getSomeValue(); public native void init(Context ctx); } 


The init method is needed in order not to trigger certificate authentication every time.

Implementing nouveau methods:

 jint isCertCorrect = 0; JNIEXPORT void JNICALL Java_com_your_package_YourClass_init(JNIEnv *env, jobject obj, jobject ctx) { isCertCorrect = verifyCertificate(env, obj, ctx); } JNIEXPORT jint JNICALL Java_com_your_package_YourClass_getSomeValue(JNIEnv *env, jobject obj) { if (isCertCorrect ) { //    } else { //    } 


This protection option can also be hacked by disassembling, but this requires a completely different level of knowledge and much more time than in the case of the implementation of protection at the Java level. In the presence of certain means, I washed the purchase of an obfuscator for the C code, in which case hacking would be a far from trivial task.

Protection of the application in the presence of the server side.



If part of the application logic is implemented on the server, it makes sense to put the certificate authentication on the server. Alternatively, you can use the following algorithm:

  1. The server generates private and public RSA keys;
  2. The client sends a request to the server and receives the public key;
  3. On the client, we implement a native library with a function of the form String getInfo (String publicKey); the function must read the certificate, add some random value to it, then encrypt the resulting string using the public RSA key;
  4. The client makes a new request to the server, sending the resulting string. The server performs decoding, separating the certificate from a random variable and checking it for authenticity;
  5. Depending on the results of the verification, the server responds to all of the following client requests.


We hope this mechanism will help Habr's readers to increase earnings from their Android applications.

Source: https://habr.com/ru/post/179487/


All Articles