- 静态注册
一般我们写的java native
方法在c
层都有对应的方法,例如
java
代码如下
package com.example.administrator.ndkstudydemo.demo;
public class CounterNative {
private native void init();
}
声明了一个init
本地方法,对应的如果静态注册,会生成一个这样的方法在c
代码里面,c
代码如下
extern "C"
JNIEXPORT void JNICALL
Java_com_example_administrator_ndkstudydemo_demo_CounterNative_init(JNIEnv *env,jobject thiz) {}
这就是典型的静态注册,通过Java_包名_方法名
的规则生成
正常情况下它没什么问题,但是它第一相对
不安全,为啥这么说?
相对
不安全
-
相对
不安全是因为首先java代码反编译者(统称hacker
)很容易反编译,如果你在so写了核心逻辑,hacker
就会通过java里面的名字按图索骥的找到c里面的代码,这样就有利于hacker
分析我们的逻辑。 - 另外为什么我们把
相对
引了起来,是因为如果了解JNI
加载逻辑的的都知道,在System.loadLibrary("so名称");
的时候会先调用JNI_OnLoad
方法,我们做动态注册也会在这里进行(后面会讲到),那么其实hacker
只要在找不到静态注册的方法,就可以分析这个函数里的逻辑,同样可以找到方法。
- 动态注册
首先java
代码不变,我们改变c++代码
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
jvm = vm;
if (jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
//动态注册函数
if (registerNatives(env) != JNI_TRUE) {
return -1;
}
return JNI_VERSION_1_6;
}
首先重写JNI_OnLoad
方法,核心逻辑是注释部分代码if (registerNatives(env) != JNI_TRUE) { return -1; }
,接下来我们看一下这个registerNatives
函数实现
//java类全路径
static const char *classPathName = "com/example/administrator/ndkstudydemo/demo/CounterNative";
//方法数组
static JNINativeMethod methods[] = {
{"init", "()V", (void *) Java_com_example_administrator_ndkstudydemo_demo_CounterNative_nativeSetup}
};
static int registerNatives(JNIEnv *env) {
registerNativeMethods(env, classPathName, methods, sizeof(methods) / sizeof(methods[0]));
return JNI_TRUE;
}
static int registerNativeMethods(JNIEnv *env, const char *className,
JNINativeMethod *gMethods, int numMethods) {
jclass clazz = env->FindClass(className);
//调用env注册本地方法的函数,传入需要修改的方法数组,和需要修改的方法数
env->RegisterNatives(clazz, gMethods, numMethods);
return JNI_TRUE;
}
其实就是调用env
的一个注册本地方法的函数env->RegisterNatives(clazz, gMethods, numMethods)
,它需要三个参数,第一个是方法所在的class对象,还有对应的java方法数组,第三个参数就是方法数组数目,这样就完成了动态注册本地方法
- 总结
java
的本地方法名init
只是本地方法nativeSetup
的一个别名,在vm
方法表里存储了他们的对应关系,才使得java方法能正确访问到本地方法
网友评论