Check .apk-signature in C/native Code

后端 未结 1 1705
抹茶落季
抹茶落季 2020-12-14 05:01

I have developed an Android application which also contains a native part written in C (which does not depend on the app).

The application itself is useless if the s

相关标签:
1条回答
  • 2020-12-14 05:56

    As I have been asked to publish some code how I'm now checking the CRC-code of my Java-application from within C, here are some snippets.

    I cannot post a complete working solution as it's spread over multiple lines due to performance-reasons, but I hope this is most complete and working:

    In your MyApplication.java:

    public class MyApplication extends Application {
        private static Context context;
    
        public static Context getAppContext() {
            return MyApplication.context;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            MyApplication.context = getApplicationContext();
        }
    }
    

    Android.mk:

    LOCAL_CFLAGS += -O3 -DDEBUG_MODE=0 -DCLASSES_CRC=2331492378
    

    Inside your C-code:

    #define LOG_TAG "Your Log Tag"
    
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
    
    #if DEBUG_MODE
    #define LOGDH(...) LOGD(__VA_ARGS__)
    #define LOGIH(...) LOGI(__VA_ARGS__)
    #define LOGEH(...) LOGE(__VA_ARGS__)
    #else
    #define LOGDH(...) //
    #define LOGIH(...) //
    #define LOGEH(...) //
    #endif
    
    int isSecure = -1;
    
    jclass MyApplication;
    jclass Context;
    jclass ApplicationInfo;
    
    
    jclass ZipFile;
    jclass ZipEntry;
    jclass CheckedInputStream;
    jclass Adler32;
    jclass Checksum;
    
    jmethodID MyApplication_getAppContextMethodId;
    jmethodID Context_getApplicationInfoMethodId;
    
    jmethodID ZipFile_ConstructorMethodId;
    jmethodID CheckedInputStream_ConstructorMethodId;
    jmethodID Adler32_ConstructorMethodId;
    
    jmethodID ZipFile_getEntryMethodId;
    jmethodID ZipFile_getInputStreamMethodId;
    jmethodID CheckedInputStream_readMethodId;
    jmethodID CheckedInputStream_getChecksumMethodId;
    jmethodID Checksum_getValueMethodId;
    
    jfieldID ApplicationInfo_flagsFieldId;
    jfieldID ApplicationInfo_FLAG_DEBUGGABLEFieldId;
    jfieldID ApplicationInfo_sourceDirFieldId;
    
    
    static long getClassesCRC(JNIEnv *env) {
        jobject appContextInstance = (*env)->CallStaticObjectMethod(env,
                MyApplication, MyApplication_getAppContextMethodId);
        if (!appContextInstance) {
            LOGEH("Unable to get instance of AppContext");
            return false;
        }
    
        jobject applicationInfoInstance = (*env)->CallObjectMethod(env,
                appContextInstance, Context_getApplicationInfoMethodId);
        if (!appContextInstance) {
            LOGEH("Unable to get instance of ApplicationInfo");
            return false;
        }
    
        jobject zipFileInstance = (*env)->NewObject(env, ZipFile,
                ZipFile_ConstructorMethodId,
                (*env)->GetObjectField(env, applicationInfoInstance,
                        ApplicationInfo_sourceDirFieldId));
        if (!zipFileInstance) {
            LOGEH("Unable to get instance of ZipFile");
            return -1;
        }
    
        jstring classesDexString = (*env)->NewStringUTF(env, "classes.dex");
        jobject zipEntryInstance = (*env)->CallObjectMethod(env, zipFileInstance,
                ZipFile_getEntryMethodId, classesDexString);
        if (!zipFileInstance) {
            LOGEH("Unable to get instance of ZipEntry");
            return -1;
        }
        (*env)->DeleteLocalRef(env, classesDexString);
    
        jobject adler32Instance = (*env)->NewObject(env, Adler32,
                Adler32_ConstructorMethodId);
        if (!adler32Instance) {
            LOGEH("Unable to get instance of Adler32");
            return -1;
        }
    
        jobject inputStreamInstance = (*env)->CallObjectMethod(env, zipFileInstance,
                ZipFile_getInputStreamMethodId, zipEntryInstance);
        if (!inputStreamInstance) {
            LOGEH("Unable to get instance of InputStream");
            return -1;
        }
    
        jobject checkedInputStreamInstance = (*env)->NewObject(env,
                CheckedInputStream, CheckedInputStream_ConstructorMethodId,
                inputStreamInstance, adler32Instance);
        if (!checkedInputStreamInstance) {
            LOGEH("Unable to get instance of CheckedInputStream");
            return -1;
        }
    
        int bufferSize = 128;
        jbyteArray bufferBytes = (*env)->NewByteArray(env, bufferSize);
    
        while ((*env)->CallIntMethod(env, checkedInputStreamInstance,
                CheckedInputStream_readMethodId, bufferBytes) > 0) {
        }
        (*env)->DeleteLocalRef(env, bufferBytes);
    
        jobject checksumInstance = (*env)->CallObjectMethod(env,
                checkedInputStreamInstance, CheckedInputStream_getChecksumMethodId);
        if (!checksumInstance) {
            LOGEH("Unable to get instance of CheckSum");
            return -1;
        }
    
        return (*env)->CallLongMethod(env, checksumInstance,
                Checksum_getValueMethodId);
    }
    
    static bool isDebuggable(JNIEnv *env) {
        jobject appContextInstance = (*env)->CallStaticObjectMethod(env,
                MyApplication, Application_getAppContextMethodId);
        if (!appContextInstance) {
            LOGEH("Unable to get instance of AppContext");
            return false;
        }
    
        jobject applicationInfoInstance = (*env)->CallObjectMethod(env,
                appContextInstance, Context_getApplicationInfoMethodId);
        if (!appContextInstance) {
            LOGEH("Unable to get instance of ApplicationInfo");
            return false;
        }
    
        int FLAG_DEBUGGABLE = (*env)->GetStaticIntField(env, ApplicationInfo,
                ApplicationInfo_FLAG_DEBUGGABLEFieldId);
        int flags = (*env)->GetIntField(env, applicationInfoInstance,
                ApplicationInfo_flagsFieldId);
    
        return (0 != (flags &= FLAG_DEBUGGABLE));
    }
    
    static bool isSecureEnvironment(JNIEnv *env) {
    
        //isSecure = true; // TODO remove this
    
        if (isSecure == -1) {
            isSecure = true;
    
            if (isDebuggable(env)) {
                // someone used the app in debug-mode
    #if DEBUG_MODE != 1
                // TODO report
    
    #endif
                LOGEH("App IS DEBUGGABLE!");
                isSecure = false;
            } else {
    
                // check CRC
                long classesCRC = getClassesCRC(env);
                if (classesCRC != (long) CLASSES_CRC) {
    #if DEBUG_MODE != 1
                    // TODO report
    #endif
                    LOGEH("CRC-CHECK FAILED: %lu", classesCRC);
    
                    isSecure = false;
                }
            }
        }
        return isSecure;
    }
    
    
    static bool initJavaClasses(JNIEnv * env) {
        jclass local = (*env)->FindClass(env, "eu/my/MyApplication");
        MyApplication = (*env)->NewGlobalRef(env, local);
        if (!MyApplication) {
        LOGEH("Unable to find the MyApplication class");
        return false;
        }
        local = (*env)->FindClass(env, "android/content/Context");
        Context = (*env)->NewGlobalRef(env, local);
        (*env)->DeleteLocalRef(env, local);
        if (!Context) {
        LOGEH("Unable to find the Context class");
        return false;
        }
        local = (*env)->FindClass(env, "android/content/pm/ApplicationInfo");
        ApplicationInfo = (*env)->NewGlobalRef(env, local);
        (*env)->DeleteLocalRef(env, local);
        if (!ApplicationInfo) {
        LOGEH("Unable to find the ApplicationInfo class");
        return false;
        }
    
        local = (*env)->FindClass(env, "java/util/zip/ZipFile");
        ZipFile = (*env)->NewGlobalRef(env, local);
        (*env)->DeleteLocalRef(env, local);
        if (!ZipFile) {
        LOGEH("Unable to find the ZipFile class");
        return false;
        }
    
        local = (*env)->FindClass(env, "java/util/zip/ZipEntry");
        ZipEntry = (*env)->NewGlobalRef(env, local);
        (*env)->DeleteLocalRef(env, local);
        if (!ZipEntry) {
        LOGEH("Unable to find the ZipEntry class");
        return false;
        }
    
        local = (*env)->FindClass(env, "java/util/zip/CheckedInputStream");
        CheckedInputStream = (*env)->NewGlobalRef(env, local);
        (*env)->DeleteLocalRef(env, local);
        if (!CheckedInputStream) {
        LOGEH("Unable to find the CheckedInputStream class");
        return false;
        }
    
        local = (*env)->FindClass(env, "java/util/zip/Adler32");
        Adler32 = (*env)->NewGlobalRef(env, local);
        (*env)->DeleteLocalRef(env, local);
        if (!Adler32) {
        LOGEH("Unable to find the Adler32 class");
        return false;
        }
    
        local = (*env)->FindClass(env, "java/util/zip/Checksum");
        Checksum = (*env)->NewGlobalRef(env, local);
        (*env)->DeleteLocalRef(env, local);
        if (!Checksum) {
        LOGEH("Unable to find the Checksum class");
        return false;
        }
    
        return true;
    }
    
    static bool initJavaMethods(JNIEnv * env) {
        MyApplication_getAppContextMethodId = (*env)->GetStaticMethodID(env,
        MyApplication, "getAppContext", "()Landroid/content/Context;");
        if (!MyApplication_getAppContextMethodId) {
        LOGEH("Unable to find the getAppContext method");
        return false;
        }
        Context_getApplicationInfoMethodId = (*env)->GetMethodID(env, Context,
        "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
        if (!Context_getApplicationInfoMethodId) {
        LOGEH("Unable to find the getApplicationInfo method");
        return false;
        }
    
        ZipFile_ConstructorMethodId = (*env)->GetMethodID(env, ZipFile, "<init>",
        "(Ljava/lang/String;)V");
        if (!ZipFile_ConstructorMethodId) {
        LOGEH("Unable to find the constructor method");
        return false;
        }
    
        CheckedInputStream_ConstructorMethodId = (*env)->GetMethodID(env,
        CheckedInputStream, "<init>",
        "(Ljava/io/InputStream;Ljava/util/zip/Checksum;)V");
        if (!CheckedInputStream_ConstructorMethodId) {
        LOGEH("Unable to find the constructor method");
        return false;
        }
    
        Adler32_ConstructorMethodId = (*env)->GetMethodID(env, Adler32, "<init>",
        "()V");
        if (!Adler32_ConstructorMethodId) {
        LOGEH("Unable to find the constructor method");
        return false;
        }
    
        ZipFile_getEntryMethodId = (*env)->GetMethodID(env, ZipFile, "getEntry",
        "(Ljava/lang/String;)Ljava/util/zip/ZipEntry;");
        if (!ZipFile_getEntryMethodId) {
        LOGEH("Unable to find the getEntry method");
        return false;
        }
    
        ZipFile_getInputStreamMethodId = (*env)->GetMethodID(env, ZipFile,
        "getInputStream", "(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;");
        if (!ZipFile_getInputStreamMethodId) {
        LOGEH("Unable to find the getInputStream method");
        return false;
        }
    
        CheckedInputStream_readMethodId = (*env)->GetMethodID(env, CheckedInputStream,
        "read", "([B)I");
        if (!CheckedInputStream_readMethodId) {
        LOGEH("Unable to find the read method");
        return false;
        }
    
        CheckedInputStream_getChecksumMethodId = (*env)->GetMethodID(env,
        CheckedInputStream, "getChecksum", "()Ljava/util/zip/Checksum;");
        if (!CheckedInputStream_getChecksumMethodId) {
        LOGEH("Unable to find the getChecksum method");
        return false;
        }
    
        Checksum_getValueMethodId = (*env)->GetMethodID(env, Checksum, "getValue",
        "()J");
        if (!Checksum_getValueMethodId) {
        LOGEH("Unable to find the getValue method");
        return false;
        }
    
        return true;
    }
    
    static bool initJavaFields(JNIEnv * env) {
        ApplicationInfo_flagsFieldId = (*env)->GetFieldID(env, ApplicationInfo, "flags",
        "I");
    
        if (!ApplicationInfo_flagsFieldId) {
        LOGEH("Unable to find the flags field");
        return false;
        }
    
        ApplicationInfo_FLAG_DEBUGGABLEFieldId = (*env)->GetStaticFieldID(env,
        ApplicationInfo, "FLAG_DEBUGGABLE", "I");
        if (!ApplicationInfo_FLAG_DEBUGGABLEFieldId) {
        LOGEH("Unable to get static field FLAG_DEBUGGABLE");
        return false;
        }
    
        ApplicationInfo_sourceDirFieldId = (*env)->GetFieldID(env, ApplicationInfo,
        "sourceDir", "Ljava/lang/String;");
        if (!ApplicationInfo_sourceDirFieldId) {
        LOGEH("Unable to get static field sourceDir");
        return false;
        }
    
        return true;
    }
    
    
    jint JNI_OnLoad(JavaVM* vm, void* reserved) {
        (void) reserved; // Suppress the warning.
        JNIEnv * env;
    
        if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
        }
    
        if (!initJavaClasses(env)) {
        return -1;
        }
    
        if (!initJavaMethods(env)) {
        return -1;
        }
    
        if (!initJavaFields(env)) {
        return -1;
        }
    
        return JNI_VERSION_1_6;
    }
    

    Don't forget to add the methods of the MyApplication into the Proguard-settings to prevent their remove!

    Usage:

    1. Create your APK with -DDEBUG_MODE=1
    2. start the APK on your Android
    3. read the value inside "CRC-check failed:xxx"
    4. take that value and insert it into CLASSES_CRC=xxxx
    5. switch to -DDEBUG_MODE=0
    6. rebuild your APK
    7. check if your app is running well
    8. publish

    This method is a little more complicated as it requires 2 builds to get a valid APK. But as the CRC is checked inside C and not just the dummy-signature of the APK-file is taken, this check is nearly bullet-proof.

    In my situation, I'm for exampling not even doing the setup of the license-mechanisms in case the signature is invalid.

    Additionally, as this method only uses numbers and not chars, it's fully compiled.

    Hope this helps someone!

    0 讨论(0)
提交回复
热议问题