为了账号安全,请及时绑定邮箱和手机立即绑定

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

标签:
Android

国内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 JNICALLJNI_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

参考资料

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

原文链接:http://www.apkbus.com/blog-98050-68216.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消