• 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; //Java层native函数名
    const char* signature; //Java函数签名,记录参数类型和个数,以及返回值类型
    void* fnPtr; //Native层对应的函数指针
    } JNINativeMethod;

    动态注册方法需要一个数组将我们想要的东西记录下来,方便动态调用注册。

    1
    JNINativeMethod methods[] = {} //三个参数:java层函数名,java函数签名,native函数指针

    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方法。

    动态注册函数代码,应该

    1. 调用FindClass方法,获取java对象
    2. 调用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;
    //获取java对象
    jclass clazz = env->FindClass("com/qqq/test/autimaticso/MainActivity");
    if (clazz == NULL) return JNI_FALSE;

    //调用RegisterNatives方法,传入java对象、函数映射表以及注册数目完成注册注册
    if (env->RegisterNatives(clazz, methods, sizeof(methods)/ sizeof(methods[0])) < 0) return JNI_FALSE;//注册失败,返回值小于0

    return JNI_VERSION_1_6; //注册成功返回JNI版本
    }

    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);

    // Example of a call to a native method
    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

    demo

    逆向查看

    JEB查看java代码

    image-20220718173657712

    IDA查看so代码

    不管在任何架构下,都可以看到Java_com_qqq_test_autimaticso_MainActivity_stringFromJNI函数名,因为这是静态注册的函数,那么动态注册的函数,以Arm64-v8a架构下的so为例,动态注册的函数需要在JNI_OnLoad函数里面观察调试。

    image-20220718174641356

    里面我们只要找到了函数映射表,即JNINativeMethod数组,就能得到所有动态函数注册的映射关系。这里很显然,在RegisterNatives函数的第三个参数,就是我们需要的函数映射表。

    image-20220718175059869

    然后,到这里,直接可以发现,第三个参数,第六个参数,第九个参数就是函数的地址。可以双击查看各自伪代码

    image-20220718175609917

    当然,这是由于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

    上一篇:
    Hook神器-frida的安装和使用
    下一篇:
    SO加固之section加密
    本文目录
    本文目录