国内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
参考资料
前人方案代码 https://github.com/ksxkq/CheckSignatureInNativeSample
Experimental Plugin User Guide http://tools.android.com/tech-docs/new-build-system/gradle-experimental
如有问题 欢迎指正 与君共勉
共同学习,写下你的评论
评论加载中...
作者其他优质文章