Проверьте .apk-подпись в C / native Code

Я разработал приложение для Android, которое также содержит нативную часть, написанную на C (которая не зависит от приложения).

Само приложение бесполезно, если разделяемая библиотека не выполняет свою работу.

Я бы хотел, чтобы родная часть (общая библиотека) выполняла свою работу, если существует немодифицированная версия приложения (.apk), с которой она была отправлена.

Лучший способ для меня был бы таким:

  1. Приложение установлено
  2. Общая библиотека проверяет подпись / hash приложения / .apk
  3. Выполняет свою работу только тогда, когда подпись соответствует известному

Таким образом, я хотел бы защитить свое приложение от модификации и пиратства.

Есть ли советы для этого? Я только что нашел сообщения с проверкой собственной подписи в java, но это безделушка, если можно отменить и перекомпилировать приложение.

    Поскольку мне было предложено опубликовать некоторый код, как я теперь проверяю CRC-код моего Java-приложения из C, вот некоторые fragmentы.

    Я не могу опубликовать полное рабочее решение, поскольку оно распространяется по нескольким линиям из-за причин производительности, но я надеюсь, что это наиболее полно и работает:

    В 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 

    Внутри вашего C-кода:

     #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, "", "(Ljava/lang/String;)V"); if (!ZipFile_ConstructorMethodId) { LOGEH("Unable to find the constructor method"); return false; } CheckedInputStream_ConstructorMethodId = (*env)->GetMethodID(env, CheckedInputStream, "", "(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, "", "()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; } 

    Не забудьте добавить методы MyApplication в настройки Proguard, чтобы предотвратить их удаление!

    Использование:

    1. Создайте APK с помощью -DDEBUG_MODE = 1
    2. запустите APK на своем Android-устройстве
    3. прочитайте значение внутри «CRC-check failed: xxx»
    4. возьмите это значение и вставьте его в class CLASSES_CRC = xxxx
    5. переключиться на -DDEBUG_MODE = 0
    6. перестроить APK
    7. проверьте, работает ли ваше приложение
    8. публиковать

    Этот метод немного сложнее, так как для получения правильного APK требуется 2 сборки. Но поскольку CRC проверяется внутри C, а не только фиктивная подпись APK-файла, эта проверка почти пуленепробиваемая.

    В моей ситуации я за экзамен даже не выполняю настройку лицензионных механизмов в случае, если подпись недопустима.

    Кроме того, поскольку этот метод использует только числа, а не символы, он полностью компилируется.

    Надеюсь, это поможет кому-то!