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

校验APK安装包信息

获取应用包名

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

1
2
3
4
5
6
7
8
9
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层调用:

1
public static  native String getPackageName(Context context);

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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上的实现。

1
2
3
4
5
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,这是个很好的学习范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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;

实现的对应代码:

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

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
6
7
8
9
10
11
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实现的加密相对来说速度快,效率高

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