β

JNI开发笔记(2) -- Native要校验APP安全

Cyning的博客 42 阅读

由于使用NDK开发,可以中间的数据放入到so中,这样是的关键数据更安全。因为破解原生代码相对来说太容易,而so文件相对来说门槛较高。
我们本篇就是从安全角度来使用NDK开发,将重要的数据放入NDK中,同时将重要的加密也放入到NDK开发,这样在一定程度上可以保证APP应用的安全。

校验APK安装包信息

获取应用包名

上篇文章 中,我们可以通过NDK的方法来获取APK的包名。

extern "C" JNIEXPORT jstring
JNICALL
Java_me_cyning_helloworld_NativeUtils_getPackageName(
        JNIEnv *env, jclass clazz, jobject instance) {
    jclass nativeClass = env->GetObjectClass(instance);
    jmethodID jmethodID1 = env->GetMethodID(nativeClass, "getPackageName", "()Ljava/lang/String;");
    jstring packageName = static_cast<jstring>(env->CallObjectMethod(instance, jmethodID1));
    return  packageName;
}

Java层调用:

public static  native String getPackageName(Context context);

但是在实际的应用中,能不能不传Context来获取到全文的Context呢?答案是可以的!

Class<?> activityThreadClzz = Class.forName("android.app.ActivityThread");
Method currentApplication =  activityThreadClzz.getMethod("currentApplication");
Application application = (Application) currentApplication.invoke(null);

我们可以参考: ActivityThread 。由于 currentApplication 是个静态方法,可以通过反射获取到一个Application对象。

结合之前的代码我们可以合并如下代码:

static jobject getApplication(JNIEnv *env) {
    jobject application = NULL;
    jclass activity_thread_clz = env->FindClass("android/app/ActivityThread");
    if (activity_thread_clz != NULL) {
        // 得到ActivityThread的currentApplication的静态方法id
        jmethodID currentApplication = env->GetStaticMethodID(
                activity_thread_clz, "currentApplication", "()Landroid/app/Application;");

        if (currentApplication != NULL) {
            // 调用ActivityThread的静态方法,返回结果application
            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;
}

static jstring  getPackageName( JNIEnv *env,jobject instance ) {
    jclass nativeClass = env->GetObjectClass(instance);
    jmethodID jmethodID1 = env->GetMethodID(nativeClass, "getPackageName", "()Ljava/lang/String;");
    jstring packageName = static_cast<jstring>(env->CallObjectMethod(instance, jmethodID1));
    return  packageName;
}


//根据context来获取包名
extern "C" JNIEXPORT jstring
JNICALL
Java_me_cyning_helloworld_NativeUtils_getPackageName(
        JNIEnv *env, jclass clazz) {
    return  getPackageName(env, getApplication(env));
}

但是得到包名就安全了么,NO!有些”居心不良”的人可能会改我的包签名,好了那怎么就校验下包签名,不合法的APP让它直接GG。
怎么获取包签名信息呢?

获取应用的签名信息

怎么获取一个APP的签名信息呢?还是和获取包名一样,我们需要拿到在java上的实现。

PackageManager pm = context.getPackageManager();
           PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
           Signature[] signatures = pi.signatures;
           Signature signature0 = signatures[0];
           String string = signature0.toCharsString();

用NDK怎么实现呢?
若是你不知道C怎么调用java,这是个很好的学习范例:

extern "C" JNIEXPORT jint
JNICALL
        Java_me_cyning_helloworld_NativeUtils_getSignature(JNIEnv *env, jclass clazz) {


    // 获取context
    jobject  context_obj = getApplication(env);
    // 获取到context的类
    jclass  context_clazz  = env->GetObjectClass(context_obj);
    jstring  packageName = getPackageName(env, context_obj);

    //获取到context中的getPackageManager的方法id
    jmethodID  getPackageManagerMethod = env->GetMethodID(context_clazz, "getPackageManager", "()Landroid/content/pm/PackageManager;");
    // 调用getPackageManager方法,获取到PackageManager对象
    jobject pm_obj = env->CallObjectMethod(context_obj, getPackageManagerMethod);

    jclass  pm_clazz  = env->GetObjectClass(pm_obj);
    jmethodID  getPackageInfoMethod = env->GetMethodID(pm_clazz, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    jobject  packageInfo_obj = env->CallObjectMethod(pm_obj, getPackageInfoMethod, packageName, 64);

    jclass  pi_clazz  = env->GetObjectClass(packageInfo_obj);
    //  Signature[] signatures = pi.signatures; 返回值是数组[]
    jfieldID signatures_field = env->GetFieldID(pi_clazz, "signatures", "[Landroid/content/pm/Signature;");

    jobject signatures = env->GetObjectField(packageInfo_obj, signatures_field);
    jobjectArray signatures_array = (jobjectArray) signatures;

    jobject signature0 = env->GetObjectArrayElement(signatures_array, 0);


    jclass  signature_clazz  = env->GetObjectClass(signature0);
    jmethodID  toCharsStringMethod = env->GetMethodID(signature_clazz, "toCharsString", "()Ljava/lang/String;");
    jstring  signature_string = (jstring)env->CallObjectMethod(signature0, toCharsStringMethod);

    // 释放内存
    env->DeleteLocalRef(context_obj);
    env->DeleteLocalRef(context_clazz);
    env->DeleteLocalRef(packageName);
    env->DeleteLocalRef(pm_obj);
    env->DeleteLocalRef(pm_clazz);
    env->DeleteLocalRef(packageInfo_obj);
    env->DeleteLocalRef(pi_clazz);
    env->DeleteLocalRef(signatures);
    env->DeleteLocalRef(signature0);
    env->DeleteLocalRef(signatures_array);
    env->DeleteLocalRef(signature_clazz);

    return signature_string;

实现的对应代码:

这样就可以将获取的签名和本地的签名比较:

const char *sign = env->GetStringUTFChars(signature_string, NULL);
   int result = strcmp(sign,
                       "app的签名");
   env->ReleaseStringUTFChars(signature_string, sign);
   env->DeleteLocalRef(signature_string);
   if (result == 0) { // 签名一致
       return JNI_OK;
   }
   return JNI_ERR;

重写 JNI_OnLoad :

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        return JNI_ERR;
    }
    if (verifySign(env) == JNI_OK) {
        return JNI_VERSION_1_4;
    }
    LOGE("签名不一致!");
    return JNI_ERR;
}

这样就会在 System.loadLibrary("native-lib"); 时,就会调用 JNI_OnLoad ,当签名不一致时就会直接导致崩溃。

加密

当然了,我们也可以在NDK层实现加密,如网络请求时,将参数加密等,这样相对来说:

  1. 安全 底层实现的加密不易破解
  2. 高效 c实现的加密相对来说速度快,效率高

    具体的代码可以参考这篇 文章 ,不过可以根据需求来自己定义加密算法。

由于使用NDK开发,可以中间的数据放入到so中,这样是的关键数据更安全。因为破解原生代码相对来说太容易,而so文件相对来说门槛较高。
我们本篇就是从安全角度来使用NDK开发,将重要的数据放入NDK中,同时将重要的加密也放入到NDK开发,这样在一定程度上可以保证APP应用的安全。

作者:Cyning的博客
Follow your Heart
原文地址:JNI开发笔记(2) -- Native要校验APP安全, 感谢原作者分享。

发表评论