Android动态注册NDK函数 前言 之前在 https://kuangtant.gitee.io/2021/12/12/android-studio-sheng-cheng-so-wen-jian/ 一文讲过在Android Studio中java与so文件的关系,和简单实现相互的调用,文章里面介绍的native函数调用属于静态调用,原理是Java代码中的native方法与so中的JNI方法一一对应,当Java层调用so层的函数时,如果发现其上有JNIEXPORT和JNICALL两个宏定义声明时,就会将so层函数链接到对应的native方法上。本文将介绍一下安卓NDK动态注册函数。
实现例子 c++文件 创建过程和静态创建过程一致,主要不同在于其中的内容。
写待注册函数 动态函数注册,需要准备被注册的函数,这里被注册的函数就没有静态注册函数那样严格,可以直接写函数主体,不需要其它宏定义,例如,实现加减法的native函数:
1 2 3 4 5 6 7 8 jint addAB (JNIEnv *env, jobject thiz, jint a, jint b) { return a + b; } jint subAB (JNIEnv *env, jobject thiz, jint a, jint b) { return a - b; }
写JNINativeMethod数组 JNINativeMethod是一个结构体,在jni.h文件中有定义:
1 2 3 4 5 typedef struct { const char * name; const char * signature; void * fnPtr; } JNINativeMethod;
动态注册方法需要一个数组将我们想要的东西记录下来,方便动态调用注册。
1 JNINativeMethod methods[] = {}
java函数名和native函数指针就不需要介绍了,主要要注意一下java函数签名,其类型为字符串,由一对小括号和若干签名符号组成,其中括号内写传入参数的签名符号,没有参数则不写,括号外写返回参数的签名符号。
签名符号
C/C++
java
V
void
void
Z
jboolean
boolean
I
jint
int
J
jlong
long
D
jdouble
double
F
jfloat
float
B
jbyte
byte
C
jchar
char
S
jshort
short
[Z
jbooleanArray
boolean[]
[I
jintArray
int[]
[J
jlongArray
long[]
[D
jdoubleArray
double[]
[F
jfloatArray
float[]
[B
jbyteArray
byte[]
[C
jcharArray
char[]
[S
jshortArray
short[]
L+完整包名+类名
jobject
class
例如:Java层函数String getString(int a,int b)的方法签名就是(II)Ljava/lang/String;。
所以,综合以上内容,JNINativeMethod数组,即函数映射表,我们写为:
1 2 3 4 5 6 JNINativeMethod methods[] = { {"add" ,"(II)I" ,(void *)addAB}, {"sub" ,"(II)I" ,(void *)subAB}, {"iWillGet" ,"()Ljava/lang/String;" ,(void *)getString} };
写JNI_OnLoad方法 之所以要写JNI_OnLoad方法,是因为在java层,System.loadLibrary()执行后,将会自动调用JNI_OnLoad方法。
动态注册函数代码,应该
调用FindClass方法,获取java对象
调用RegisterNatives方法,传入java对象、JNINativeMethod数组以及注册数目完成注册
1 2 3 4 5 6 7 8 9 10 11 12 13 jint JNI_OnLoad (JavaVM* vm, void * reserved) { JNIEnv* env = NULL ; if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) return JNI_FALSE; jclass clazz = env->FindClass("com/qqq/test/autimaticso/MainActivity" ); if (clazz == NULL ) return JNI_FALSE; if (env->RegisterNatives(clazz, methods, sizeof (methods)/ sizeof (methods[0 ])) < 0 ) return JNI_FALSE; return JNI_VERSION_1_6; }
jni代码 最终的jni代码如下:
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 #include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL Java_com_qqq_test_autimaticso_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) { std ::string hello = "Hello from C++" ; return env->NewStringUTF(hello.c_str()); } jint addAB (JNIEnv *env, jobject thiz, jint a, jint b) { return a + b; } jint subAB (JNIEnv *env, jobject thiz, jint a, jint b) { return a - b; } jstring getString (JNIEnv *env, jobject thiz) { std ::string hello = "you got it" ; return env->NewStringUTF(hello.c_str()); } JNINativeMethod methods[] = { {"add" ,"(II)I" ,(void *)addAB}, {"sub" ,"(II)I" ,(void *)subAB}, {"iWillGet" ,"()Ljava/lang/String;" ,(void *)getString} }; jint JNI_OnLoad (JavaVM* vm, void * reserved) { JNIEnv* env = NULL ; if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) return JNI_FALSE; jclass clazz = env->FindClass("com/qqq/test/autimaticso/MainActivity" ); if (clazz == NULL ) return JNI_FALSE; if (env->RegisterNatives(clazz, methods, sizeof (methods)/ sizeof (methods[0 ])) < 0 ) return JNI_FALSE; return JNI_VERSION_1_6; }
java文件 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 public class MainActivity extends AppCompatActivity implements View .OnClickListener { static { System.loadLibrary("native-lib" ); } @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = (TextView) findViewById(R.id.sample_text); tv.setText(stringFromJNI()); tv.setOnClickListener(this ); } public native String stringFromJNI () ; public native int add (int a1, int a2) ; public native int sub (int a1, int a2) ; public native String iWillGet () ; @Override public void onClick (View v) { Toast.makeText(this , "add:" +add(1 ,2 ), Toast.LENGTH_SHORT).show(); Toast.makeText(this , "sub:" +sub(2 ,1 ), Toast.LENGTH_SHORT).show(); Toast.makeText(this , iWillGet(), Toast.LENGTH_SHORT).show(); } }
实现的demo
逆向查看 JEB查看java代码
IDA查看so代码 不管在任何架构下,都可以看到Java_com_qqq_test_autimaticso_MainActivity_stringFromJNI函数名,因为这是静态注册的函数,那么动态注册的函数,以Arm64-v8a架构下的so为例,动态注册的函数需要在JNI_OnLoad函数里面观察调试。
里面我们只要找到了函数映射表,即JNINativeMethod数组,就能得到所有动态函数注册的映射关系。这里很显然,在RegisterNatives函数的第三个参数,就是我们需要的函数映射表。
然后,到这里,直接可以发现,第三个参数,第六个参数,第九个参数就是函数的地址。可以双击查看各自伪代码
当然,这是由于demo比较简单,在实际场景中,动态调试分析或hook分析过程中,可以注意一下NDK函数可以静态注册,有些分析方法思路得另辟捷径。
参考链接 本文demo下载:https://pan.baidu.com/s/1m0gxIVOe0LwL2o3jwfXPzw (kata)
Android studio实现动态注册Native方法:https://www.jianshu.com/p/14436d56e6be
Android NDK开发——静态注册和动态注册:https://zhuanlan.zhihu.com/p/357885076