美文网首页安卓rom源码分析APPAndroid开发之路
Android JNI 防二次打包应用签名检测

Android JNI 防二次打包应用签名检测

作者: Mark_Liu | 来源:发表于2017-07-06 15:02 被阅读685次

    国内android市场的环境比较混乱,大小市场有数百家,应用被修改或加广告病毒后二次打包发布也是常有的事情,对开发者和用户都造成了损失

    经各方资料研究,在C++中做签名检测处理,安全性更高一些,结合网上资料,写下此文

    签名检测逻辑

    • 获取应用签名生成的MD5值写入C++中 (C++中会打印出当前的MD5,后面会说)
    • 获取Application
    • 获取应用的签名信息
    • 将签名信息MD5化
    • 匹配签名MD5是否与写入的相同
    • 签名不同时在JNI_OnLoad 中返回 -1
    • 在App的代码中加载so文件,会自动调用JNI_OnLoad,若返回-1,App Crash掉

    NDK编译

    NDK编译使用的实验性NDK构建插件 Experimental Plugin
    只要是因为该插件支持C语言Debug调试,但是使用该插件需要修改Module的Build.gradle文件,不太建议使用在开发的项目中,可以使用之前的NDK或CMake来编译NDK

    这里不介绍实验性NDK插件了,附上使用手册Experimental Plugin use guide

    Java代码上的逻辑

    /**
     * 获取Application,ActivityThread是一个不被公开的类,java代码是不可直接调用的
     * 对应C++代码中的static jobject getApplication(JNIEnv *env)
     */
    Application application = ActivityThread.currentApplication();
    
    /**
     * 获取Signature数据
     * 对应C++ 的jstring loadSignature(JNIEnv *env, jobject * *context) 方法
     */
            PackageManager manager = application.getPackageManager();
            String packageName = application.getPackageName();
            PackageInfo packageInfo = manager.getPackageInfo(packageName,PackageManager.GET_SIGNATURES);
            Signature signature = packageInfo.signatures[0];
            byte[] chars = signature.toByteArray();
            
    /**
    * 将数据MD5化
    * 对应C++的jstring ToMd5(JNIEnv *env, jbyteArray source)
    */
            MessageDigest digest = MessageDigest.getInstance("md5");
            digest.update(chars);
            byte[] objArraySign = digest.digest(chars);
            
    //MD5化的C++ 代码
            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);
            // 将byte数组转换成16进制字符串,发现这里不用强转,jbyte和unsigned char应该字节数是一样的
            ByteToHexStr((const char *) byte_array_elements, char_result, intArrayLength);
            // 在末尾补\0
        *(char_result + intArrayLength * 2) = '\0';
            jstring stringResult = env->NewStringUTF(char_result);
    
    //检测当前应用的签名MD5与我们设置的MD5是否一致
    
    

    奉上C++ 代码

    
    #include <jni.h>
    #include <string.h>
    #include <android/log.h>
    #include <malloc.h>
    
    #define  LOG_TAG    "native-dev"
    #define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
    
    const char *APP_SIGNATURE = "10645EA8A12BE7A2C04B1F81DF3B4D90";
    
    void ByteToHexStr(const char *source, char *dest, int sourceLen) {
        short i;
        char highByte, lowByte;
    
        for (i = 0; i < sourceLen; i++) {
            highByte = source[i] >> 4;
            lowByte = source[i] & 0x0f;
            highByte += 0x30;
    
            if (highByte > 0x39) {
                dest[i * 2] = highByte + 0x07;
            } else {
                dest[i * 2] = highByte;
            }
    
            lowByte += 0x30;
            if (lowByte > 0x39) {
                dest[i * 2 + 1] = lowByte + 0x07;
            } else {
                dest[i * 2 + 1] = lowByte;
            }
        }
    }
    
    
    // byte数组转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"));
    
        // update方法,这个函数的返回值是void,写V
        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);
    
        // 将byte数组转换成16进制字符串,发现这里不用强转,jbyte和unsigned char应该字节数是一样的
        ByteToHexStr((const char *) byte_array_elements, char_result, intArrayLength);
        // 在末尾补\0
        *(char_result + intArrayLength * 2) = '\0';
    
        jstring stringResult = env->NewStringUTF(char_result);
        // release
        env->ReleaseByteArrayElements(objArraySign, byte_array_elements, JNI_ABORT);
        // 释放指针使用free
        free(char_result);
        env->DeleteLocalRef(classMessageDigest);
        env->DeleteLocalRef(objMessageDigest);
    
        return stringResult;
    }
    
    //获取应用签名
     jstring loadSignature(JNIEnv *env, jobject context) {
        // 获得Context类
        jclass cls = env->GetObjectClass(context);
        // 得到getPackageManager方法的ID
        jmethodID mid = env->GetMethodID(cls, "getPackageManager",
                                         "()Landroid/content/pm/PackageManager;");
    
        // 获得应用包的管理器
        jobject pm = env->CallObjectMethod(context, mid);
    
        // 得到getPackageName方法的ID
        mid = env->GetMethodID(cls, "getPackageName", "()Ljava/lang/String;");
        // 获得当前应用包名
        jstring packageName = (jstring) env->CallObjectMethod(context, mid);
    
        // 获得PackageManager类
        cls = env->GetObjectClass(pm);
        // 得到getPackageInfo方法的ID
        mid = env->GetMethodID(cls, "getPackageInfo",
                               "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
        // 获得应用包的信息
        jobject packageInfo = env->CallObjectMethod(pm, mid, packageName, 0x40); //GET_SIGNATURES = 64;
        // 获得PackageInfo 类
        cls = env->GetObjectClass(packageInfo);
        // 获得签名数组属性的ID
        jfieldID fid = env->GetFieldID(cls, "signatures", "[Landroid/content/pm/Signature;");
        // 得到签名数组
        jobjectArray signatures = (jobjectArray) env->GetObjectField(packageInfo, fid);
        // 得到签名
        jobject signature = env->GetObjectArrayElement(signatures, 0);
    
        // 获得Signature类
        cls = env->GetObjectClass(signature);
        // 得到toCharsString方法的ID
        mid = env->GetMethodID(cls, "toByteArray", "()[B");
        // 返回当前应用签名信息
        jbyteArray signatureByteArray = (jbyteArray) env->CallObjectMethod(signature, mid);
    
        return ToMd5(env, signatureByteArray);
    }
    //检测签名是否匹配
    jboolean checkSignature(
            JNIEnv *env, jobject context) {
    
        jstring appSignature = loadSignature(env,
                                                                               context); // 当前 App 的签名
        jstring releaseSignature = env->NewStringUTF(APP_SIGNATURE); // 发布时候的签名
        const char *charAppSignature = env->GetStringUTFChars(appSignature, NULL);
        const char *charReleaseSignature = env->GetStringUTFChars(releaseSignature, NULL);
    
    //    LOGI("  start cmp  getSignature");
           __android_log_print(ANDROID_LOG_INFO, LOG_TAG, charAppSignature);
    //    LOGI("  start cmp  getReleaseSignature");
        //  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, charAppSignature);
    
        jboolean result = JNI_FALSE;
        // 比较是否相等
        if (charAppSignature != NULL && charReleaseSignature != NULL) {
            if (strcmp(charAppSignature, charReleaseSignature) == 0) {
                result = JNI_TRUE;
            }
        }
    
        env->ReleaseStringUTFChars(appSignature, charAppSignature);
        env->ReleaseStringUTFChars(releaseSignature, charReleaseSignature);
    
        return result;
    }
    
    static jobject getApplication(JNIEnv *env) {
        jobject application = NULL;
        jclass activity_thread_clz = env->FindClass("android/app/ActivityThread");
        if (activity_thread_clz != NULL) {
            jmethodID currentApplication = env->GetStaticMethodID(
                    activity_thread_clz, "currentApplication", "()Landroid/app/Application;");
            if (currentApplication != NULL) {
                application = env->CallStaticObjectMethod(activity_thread_clz, currentApplication);
            } else {
                //           LOGE("Cannot find method: currentApplication() in ActivityThread.");
            }
            env->DeleteLocalRef(activity_thread_clz);
        } else {
    //        LOGE("Cannot find class: android.app.ActivityThread");
        }
    
        return application;
    }
    
    /**
     * 检查加载该so的应用的签名,与预置的签名是否一致
     */
    static jboolean checkSignature(JNIEnv *env) {
    
        // 调用 getContext 方法得到 Context 对象
        jobject appContext = getApplication(env);
    
        if (appContext != NULL) {
            jboolean signatureValid = checkSignature(
                    env, appContext);
            return signatureValid;
        }
    
        return JNI_FALSE;
    }
    
    /**
     * 加载 so 文件的时候,会触发 OnLoad
     * 检测失败,返回 -1,App 就会 Crash
     */
    JNIEXPORT jint JNICALL
    JNI_OnLoad(JavaVM *vm, void *reserved) {
        JNIEnv *env;
    //    LOGI("  JNI_OnLoad  ");
        if (vm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK) {
            return -1;
        }
    //    LOGI("  start checkSignature  ");
        if (checkSignature(env) != JNI_TRUE) {
    //        LOGI("  checkSignature = false ");
            // 检测不通过,返回 -1 就会使 App crash
            return -1;
        }
    
        return JNI_VERSION_1_6;
    }
    
    

    在C++的 checkSignature方法中,
    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, charAppSignature);
    此代码会打印出当前应用的签名MD5码,也是我们需要写在C++中用于检测的MD5码,获取到之后写C++中并注释掉这行代码

    加载jni

    static{
        System.loadLibrary("SignatureLib");
    }
    

    完整代码的GitHub地址
    https://github.com/junzLiu/JniTest

    参考资料

    如有问题 欢迎指正 与君共勉

    相关文章

      网友评论

      • HelloCsl:似乎在 x86 下 MD5 的计算有问题
        Mob_Developer:@HelloCsl 确实有这个问题,这个链接的正确。
        HelloCsl:我再用 https://blog.csdn.net/leifengpeng/article/details/52681196 这里的方法来计算 MD5 能得到正确的值,在 x86 的虚拟机上
      • _孑孓_:签名不写在本地,server端维护,应该更好一些,请问还有什么更安全的防护么
        Mark_Liu:你的安装包都是有签名的,server端维护什么?
      • _杭:如果反编译后,把加载 jni 的代码干掉,还会有效吗?
        Mark_Liu:会失效,只是防一些批量恶意注入代码重新签名的,如果别人特意破你App,你还需要更多的安全方案,安全之路无止境
      • difcareer:麻烦再投到这个专题下,谢谢
        http://www.jianshu.com/c/f0d5c9062134
        Mark_Liu:@difcareer 已经达到投稿上限
      • sendtion:很强势

      本文标题:Android JNI 防二次打包应用签名检测

      本文链接:https://www.haomeiwen.com/subject/umvhhxtx.html