【问题标题】:Check .apk-signature in C/native Code在 C/native 代码中检查 .apk-signature
【发布时间】:2013-02-22 13:25:43
【问题描述】:

我开发了一个 Android 应用程序,其中还包含一个用 C 编写的本机部分(不依赖于应用程序)。

如果共享库不工作,应用程序本身就毫无用处。

我想让 native-part(一个共享库)只做它的工作,如果存在一个未修改版本的应用程序 (.apk) 已随附。

对我来说最好的方法是这样:

  1. 安装应用程序
  2. 共享库检查应用程序/.apk 的签名/哈希
  3. 仅当签名与已知签名匹配时才起作用

这样,我想保护我的应用程序免受修改和盗版。

有什么建议吗?我刚刚发现在 Java 中检查自己签名的帖子,但如果有人可以反编译应用程序,那就太可笑了。

【问题讨论】:

  • 也可以修改共享库。
  • 好吧,我可以尝试防止简单的反编译,我认为反编译 C 会比 Java 困难得多(我自己已经这样做了)。
  • 哦,这绝对是真的。不过,这并非不可能。
  • 我曾经在应用程序中添加签名检查,它阻止了应用程序在几个月内被分发破解,但一切都在 Java 和应用程序内。拥有一个 C 库(或者更好的是一个简单的 C 二进制文件),应用程序可以检查其校验和,然后检查应用程序签名肯定会有很大帮助。
  • 这就是我同时做的,检查 JNI 库中 classes.dex 的签名。但是我计算了签名并且不使用 JAVA 库提供的签名。在每次部署时,我都会使用 DEBUG=1 创建 APK,然后打印出新的哈希值,然后切换 DEBUG 并将签名编译到 C 代码中。在我的情况下,防弹,同时未破解 6 个月..

标签: android c signature verification


【解决方案1】:

当我被要求发布一些代码时,我现在如何从 C 中检查我的 Java 应用程序的 CRC 代码,这里有一些 sn-ps。

由于性能原因,我无法发布完整的工作解决方案,因为它分布在多行,但我希望这是最完整和最有效的:

在您的 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, "<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;
}

不要忘记将 MyApplication 的方法添加到 Proguard-settings 中以防止它们被删除!

用法:

  1. 使用 -DDEBUG_MODE=1 创建您的 APK
  2. 在您的 Android 上启动 APK
  3. 读取“CRC-check failed:xxx”中的值
  4. 获取该值并将其插入到 CLASSES_CRC=xxxx
  5. 切换到 -DDEBUG_MODE=0
  6. 重建您的 APK
  7. 检查您的应用是否运行良好
  8. 发布

此方法稍微复杂一些,因为它需要 2 次构建才能获得有效的 APK。 但由于在 C 中检查 CRC,而不仅仅是获取 APK 文件的虚拟签名,因此这种检查几乎是万无一失的。

在我的情况下,如果签名无效,我什至不进行许可机制的设置。

此外,由于此方法仅使用数字而不使用字符,因此已完全编译。

希望这对某人有所帮助!

【讨论】:

  • 这只是在不寻常的意义上是安全的,因此如果它保持唯一性则需要特别注意 - 在运行时按名称调用 jni 方法的所有这些调用都是颠覆的准备场所。
  • 当然,你永远无法解决这个问题..在我看来,在 C 中比在 Java 中更好的方法;)
  • 出于所有实际目的,您仍然在 Java 中完成它的关键部分,但以一种实际上更可颠覆的方式调用,而不是更少。
  • 你说得对,我应该重新检查一下网络如何在 Android 上的纯 C 中执行 CRC 检查。
  • 但是,从上面的示例开始,您可以获得 apk 签名(我的意思是通过 getPackageManager().getPackageInfo(...))。这就是我所做的并且完美地工作。
猜你喜欢
  • 2017-08-07
  • 2018-01-04
  • 2018-10-12
  • 2019-04-22
  • 1970-01-01
  • 2023-02-24
  • 2011-06-29
  • 2010-12-21
  • 1970-01-01
相关资源
最近更新 更多