Android 通过JNI进行本地应用指纹校验(MD5/SHA1)

由于Android系统开源原因使得其底层机制让大家所熟悉,因此应用安全是个挑战,当然开放的环境才能反哺促进Android的发展。我们经常在市面上看到很多应用被篡改后重新打包盗版发行,导致正版应用受到不利的影响,这里介绍一种加大篡改难度的校验方式JNI应用指纹校验

基本常识:每个发行的应用都需要进行秘钥签名,签名后应用会有唯一的指纹(MD5/SHA1),我们可以根据该指纹进行判断是否被篡改。

实现思路

打开应用调用C++编译的os静态库的init方法校验APP是否被篡改

—>打开应用
—>os库中init方法
—>C++层获取应用签名(md5/sha1)
—>和本地正常的签名对比
—>匹配不成功直接在C中退出应用。

1.应用层编写Naive接口

public static native void init();

2.使用Android-C编写校验流程

在jni下面新建一个.c文件,名字随意如test.c,在该文件中要实现获取应用签名,对比,退出应用。
这个地方的init方法可以使用动态注册的方式避免又长又臭的方法名在C文件中(如何动态注册请移步:http://blog.csdn.net/leifengpeng/article/details/52447864)

void init(JNIEnv* env, jobject thiz){
    //获取到Context
    jobject context= getApplication(env);
    jclass  activity = (*env)->GetObjectClass(env,context);
    // 得到 getPackageManager 方法的 ID
    jmethodID methodID_func = (*env)->GetMethodID(env,activity, "getPackageManager", "()Landroid/content/pm/PackageManager;");
    // 获得PackageManager对象
    jobject packageManager = (*env)->CallObjectMethod(env,context,methodID_func);
    jclass packageManagerclass = (*env)->GetObjectClass(env,packageManager);
    //得到 getPackageName 方法的 ID
    jmethodID methodID_pack = (*env)->GetMethodID(env,activity,"getPackageName", "()Ljava/lang/String;");
    //获取包名
    jstring name_str = (jstring)((*env)->CallObjectMethod(env,context, methodID_pack));
    // 得到 getPackageInfo 方法的 ID
    jmethodID methodID_pm = (*env)->GetMethodID(env,packageManagerclass,"getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    // 获得应用包的信息
    jobject package_info = (*env)->CallObjectMethod(env,packageManager, methodID_pm, name_str, 64);
    // 获得 PackageInfo 类
    jclass package_infoclass = (*env)->GetObjectClass(env,package_info);
    // 获得签名数组属性的 ID
    jfieldID fieldID_signatures = (*env)->GetFieldID(env,package_infoclass,"signatures", "[Landroid/content/pm/Signature;");
    // 得到签名数组,待修改
    jobject signatur = (*env)->GetObjectField(env,package_info, fieldID_signatures);
    jobjectArray  signatures = (jobjectArray)(signatur);
    // 得到签名
    jobject signature = (*env)->GetObjectArrayElement(env,signatures, 0);
    // 获得 Signature 类,待修改
    jclass signature_clazz = (*env)->GetObjectClass(env,signature);
    //---获得签名byte数组
    jmethodID tobyte_methodId = (*env)->GetMethodID(env,signature_clazz, "toByteArray", "()[B");
    jbyteArray signature_byte = (jbyteArray) (*env)->CallObjectMethod(env,signature, tobyte_methodId);
    //把byte数组转成流
    jclass byte_array_input_class=(*env)->FindClass(env,"java/io/ByteArrayInputStream");
    jmethodID init_methodId=(*env)->GetMethodID(env,byte_array_input_class,"<init>","([B)V");
    jobject byte_array_input=(*env)->NewObject(env,byte_array_input_class,init_methodId,signature_byte);
    //实例化X.509
    jclass certificate_factory_class=(*env)->FindClass(env,"java/security/cert/CertificateFactory");
    jmethodID certificate_methodId=(*env)->GetStaticMethodID(env,certificate_factory_class,"getInstance","(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;");
    jstring x_509_jstring=(*env)->NewStringUTF(env,"X.509");
    jobject cert_factory=(*env)->CallStaticObjectMethod(env,certificate_factory_class,certificate_methodId,x_509_jstring);
    //certFactory.generateCertificate(byteIn);
    jmethodID certificate_factory_methodId=(*env)->GetMethodID(env,certificate_factory_class,"generateCertificate",("(Ljava/io/InputStream;)Ljava/security/cert/Certificate;"));
    jobject x509_cert=(*env)->CallObjectMethod(env,cert_factory,certificate_factory_methodId,byte_array_input);

    jclass x509_cert_class=(*env)->GetObjectClass(env,x509_cert);
    jmethodID x509_cert_methodId=(*env)->GetMethodID(env,x509_cert_class,"getEncoded","()[B");
    jbyteArray cert_byte=(jbyteArray)(*env)->CallObjectMethod(env,x509_cert,x509_cert_methodId);

    //MessageDigest.getInstance("SHA1")
    jclass message_digest_class=(*env)->FindClass(env,"java/security/MessageDigest");
    jmethodID methodId=(*env)->GetStaticMethodID(env,message_digest_class,"getInstance","(Ljava/lang/String;)Ljava/security/MessageDigest;");
    //如果取SHA1则输入SHA1
    //jstring sha1_jstring=(*env)->NewStringUTF(env,"SHA1");
    jstring sha1_jstring=(*env)->NewStringUTF(env,"MD5");
    jobject sha1_digest=(*env)->CallStaticObjectMethod(env,message_digest_class,methodId,sha1_jstring);
    //sha1.digest (certByte)
    methodId=(*env)->GetMethodID(env,message_digest_class,"digest","([B)[B");
    jbyteArray sha1_byte=(jbyteArray)(*env)->CallObjectMethod(env,sha1_digest,methodId,cert_byte);
    //toHexString
    jsize array_size=(*env)->GetArrayLength(env,sha1_byte);
    jbyte* sha1 =(*env)->GetByteArrayElements(env,sha1_byte,NULL);
    char hex_sha[array_size*2+1];
    int i;
    for (i = 0;i<array_size;++i) {
        hex_sha[2*i]=HexCode[((unsigned char)sha1[i])/16];
        hex_sha[2*i+1]=HexCode[((unsigned char)sha1[i])%16];
    }
    hex_sha[array_size*2]='\0';
    LOGV("sin-sha1:%s",hex_sha);
    const  char *sign=(*env)->GetStringUTFChars(env,signstr,NULL);
    if (strcmp(hex_sha,sha1final)!=0){
        LOGV("验证不通过!");
        exitApplication(env,0);
    }else{
        LOGV("验证通过!");
    }
}

2.1 C环境中获取Context上下文方法:

jobject getApplication(JNIEnv *env) {
    jclass localClass = (*env)->FindClass(env,"android/app/ActivityThread");
    if (localClass!=NULL)
    {
        // LOGI("class have find");
        jmethodID getapplication = (*env)->GetStaticMethodID(env,localClass, "currentApplication", "()Landroid/app/Application;");
        if (getapplication!=NULL)
        {
            jobject application = (*env)->CallStaticObjectMethod(env,localClass, getapplication);
            return application;
        }
        return NULL;
    }
    return NULL;
}

2.2 直接退出应用方法:(直接寻找到java/lang/Systemz这个类)

void exitApplication(JNIEnv *env, jint flag){
    jclass temp_clazz = NULL;
    jmethodID mid_static_method;
    // 1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象
    temp_clazz =(*env)->FindClass(env,"java/lang/System");
    mid_static_method = (*env)->GetStaticMethodID(env,temp_clazz,"exit","(I)V");
    (*env)->CallStaticVoidMethod(env,temp_clazz,mid_static_method,flag);
    (*env)->DeleteLocalRef(env,temp_clazz);
}
上述流程使用到的产量
const char HexCode[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
const char *sha1final="3A0558B19DE14EEC82FFA3721CED4AA9";--本地固定的指纹

以上代码即可实现在应用初始化后直接调用init方法即可触发校验,在一定程度上加大了篡改后的拦截。