首页
社区
课程
招聘
[原创]小米学安卓逆向一 - NDK开发(3)
2021-5-14 15:54 6536

[原创]小米学安卓逆向一 - NDK开发(3)

2021-5-14 15:54
6536

这节主要写java反射思维和JNI调用、JNI调用类变量、以及创建对象。

java反射思维和JNI调用

其实是为了类比。将java反射的思维和JNI调用的方式进行类比,加深JNI调用印象。

 

java反射概念:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

 

如果将类比喻为人的话,那么反射的操作者就如同上帝,无论这个人是运动着的还是静止的,都可以从他身上获取所有的器官和属性。(不知道这样比喻会不会有些残忍~)

 

以下是发生在程序世界的反射机制图。
图片描述
而java反射的具体调用,如下所示:

1
2
3
4
5
6
//获取类
testClazz = MainActivity.class.getClassLoader().loadClass("com.example.reflectiontest.Test");
 
//获取类中静态变量
Field publicStaticField_field  testClazz3.getDeclaredField("publicStaticField");
String content = (String) publicStaticField_field.get(null);

JNI调用java的方式和java反射方式非常相似,接下来开始分析JNI是如何调用类以及类成员的。

JavaVM和JNIEnv

JavaVM和JNIEnv在C++代码中,是两个不同的结构体,提供着不同的功能和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
 * C++ version.
 */
struct _JavaVM {
    const struct JNIInvokeInterface* functions;
 
#if defined(__cplusplus)
    jint DestroyJavaVM()
    { return functions->DestroyJavaVM(this); }
    jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThread(this, p_env, thr_args); }
    jint DetachCurrentThread()
    { return functions->DetachCurrentThread(this); }
    jint GetEnv(void** env, jint version)
    { return functions->GetEnv(this, env, version); }
    jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct _JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    const struct JNINativeInterface* functions;
 
#if defined(__cplusplus)
 
    jint GetVersion()
    { return functions->GetVersion(this); }
 
    jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
        jsize bufLen)
    { return functions->DefineClass(this, name, loader, buf, bufLen); }
 
   ......
};

两者关系:

  • JavaVM是java虚拟机在jni中的native层的表现形式,它提供了java运行以及调用的基础,一个进程中共享JavaVM,一个进程中可能有多个线程,它们共有JavaVM。
  • JNIEnv是jni环境,也是java执行环境。拥有JNIEnv才能够调用一系列jni api对java层或其他的调用,JNI是一个线程独有的。一个功能的实现可能有多个线程,JNIEnv对它们来说,并不是共有的。

什么意思呢?举个例子:

JNI_Onload和JNI_OnUnload

JNI_Onload是需要我们自己实现,早于其他JNI函数执行,当so被加载,由art去调用JNI_Onload查询so的导出符号表。调用JNI_Onload有两个API,一个是我们常见的ndk工程下的System.loadLibrary("native-lib")、另一个是System.load("native-lib")。
图片描述

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
JavaVM globalVM = nullptr;
 
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
    globalVM = vm;
    __android_log_print(4, "miniboom->jni", "jni->%s", "JNI_OnLoad is called");
    jint result = 0;
    result = JNI_VERSION_1_6;
 
    JNIEnv* env = nullptr;
    //从JVM中获取JNIEnv
    if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_OK){
    __android_log_print(4, "miniboom->jni", "jni->%s", "GetEnv((void**)&env,JNI_VERSION_1_6) is JNI_OK");
    }
 
    //创建新线程,调用threadtest
     pthread_t thread;
    pthread_create(&thread, nullptr, threadtest, nullptr);
    pthread_join(thread, nullptr);
 
    return result;
}
 
void *threadtest(void *args){
 
    for(int i=0; i<10;i++){
        __android_log_print(4,"miniboom-jni", "jni->%s,%d","i am from thread",i);
    }
 
    JNIEnv* threadenv = nullptr;
 
    //这是在子线程中,使用AttachCurrentThread,依附到主线程中,才能够获取threadenv这个JNIEnv,否则直接使用globalVM->GetEnv会报错。
    if(globalVM->AttachCurrentThread(&threadenv, nullptr) == JNI_OK) {
        __android_log_print(4,"miniboom-jni", "jni->%s,","globalVM->AttachCurrentThread(&threadenv, nullptr) == JNI_OK");
        jstring  jstring1 = threadenv->NewStringUTF("threadtest jstring");
        const char* content = threadenv->GetStringUTFChars(jstring1, nullptr);
        __android_log_print(4,"miniboom-jni", "jni->%s",content);
        threadenv->ReleaseStringUTFChars(jstring1, content);
    } else{
        __android_log_print(4,"miniboom-jni", "jni->%s,","globalVM->AttachCurrentThread(&threadenv, nullptr) == JNI_NOT_OK");
    }
 
    globalVM->DetachCurrentThread();
 
    pthread_exit(0);
}

需要注意的是,JNIEnv和classloader绑定在一起的,在主线程中可以使用findClass方法,但是在子线程往往会报错找不到该类。这是因为JNIEnv绑定的是主线程的classloader,所以即使在子线程中获取到了JNIEnv,不能够直接使用该classloader进行加载。

 

总结:主线程对应一个JNIEnv,这个JNIEnv的Classloader又和该主线程绑定的,如果子线程通过附加进行拿到了JNIEnv是不能够直接使用主线程的classloader,来获取或者加载java类信息的。

 

如果子线程也要使用该classloader,有两种办法:

  • 定义全局的classloader对象,当子线程需要使用时,通过该全局变量来使用
    • jobject appClassloader = nullptr,然后在主线程中赋值。
  • 在调用、生成子线程的时候,可以在主线程中把findClass的对象给取出来,然后作为参数传给子线程使用。

JNI调用java

静态变量、非静态变量,这些内容需要用到哪些API来进行获取呢?

静态变量的获取和修改

共有、私有方式一样,以下为public静态变量为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_reflectiontest_MainActivity_getStaticField(
        JNIEnv *env,
        jobject /* this */) {
    jclass TestJclass = env->FindClass("com/xxx/xxx");
    //获取fieldID
    jfieldID publicStaticField_fid = env->GetStaticFieldID(TestJclass, "publicStaticField",
"Ljava/lang/String");
    //修改
    jstring setjstring = env->NewStringUTF("modified by jni");
    env->SetStaticObjectField(TestJclass, publicStaticField_fid, setjstring);
 
    //获取值(jstring)
    jstring publicStaticField_obj = static_cast<jsting>(env->GetStaticObjectField(TestJclass, publicStaticField_fid));
    //转成C/C++可用的字符类型
    const char* publicStaticField_content = env->GetStringUTFChars(publicStaticField_obj, nullptr);
}

过程:

  • 使用JNIEnv->GetStaticFieldID获取fieldID
  • 使用获取JNIEnv->GetStaticObjectFieldjstring获取类型(其他类型亦可)变量值

非静态变量

共有、私有方式一样,以下为private静态变量为例:

1
2
3
4
5
6
7
8
9
10
11
12
Java_com_kanxue_reflectiontest_MainActivity_getNonStaticField(
        JNIEnv *env,
        jobject obj, jobject testobj){
    jclass TestJclass = env->FindClass("com/xxx/xxx");
    //使用JNIEnv->GetFieldID获取fieldID
    jfieldID privateField_fid = env->GetFieldID(TestJclass, "privateField", "LJava/lang/String;");
 
    //使用JNIEnv->GetObjectField获取jstring值
    jstring privateField_obj = static_cast<jsting>(env->GetObjectField(testobj, privateField_fid));
 
    const char* privateField_content = env->GetStringUTFChars(privateField_obj, nullptr);
}

创建java对象

两种方式:

  • JNIEnv->NewObject
  • JNIEnv->AllocObject配合JNIEnv->CallNonvirtualVoidMethod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Java_com_kanxue_reflectiontest_MainActivity_newObject(
        JNIEnv *env,
        jobject /* this */){
    jclass TestJclass = env->FindClass("com/xxx/xxx");
    //获取methodID
    jmethodID con_mid = env->GetMethodID(TestJclass, "<init>", "(Ljava/lang/String;)V");
 
    //组造传参
    jstring arg = env->NewStringUTF("I am from Jni");
    jboject testobj = env->NewObject(TestJclass, con_mid, arg);
    if (testobj != nullptr) {
        __android_log_print(4, "kanxue->jni", "jni->%s", "NewObject success!");
    }
 
    //AllocObject创建,但并未对对象进行初始化
    jobject testobj2 = env->AllocObject(TestJclass);
    jstring arg1 = env->NewStringUTF("I am From Jni->AllocObject");
    env->CallNonvirtualVoidMethod(testobj2, TestJclass, con_mid, arg1);
    if (testobj2 != nullptr) {
 
        __android_log_print(4, "kanxue->jni", "jni->%s", "AllocObject success!");
    }
}

附件:
链接: https://pan.baidu.com/s/1nO_Tjfcb4uJN6bMsp-mnVg 提取码: 3a8e 复制这段内容后打开百度网盘手机App,操作更方便哦


阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

收藏
点赞0
打赏
分享
最新回复 (7)
雪    币: 62
活跃值: (572)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
万里星河 2021-5-20 04:52
2
0
支持一下
雪    币: 453
活跃值: (129)
能力值: (RANK:0 )
在线值:
发帖
回帖
粉丝
同志们好啊 2021-5-21 16:27
3
0
我实在不知道,为啥大厂不选择C++作为安卓的基础开发语言,而选择java,只为了显示自己的白chi吗?
雪    币: 239
活跃值: (650)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
只是来打酱油 2021-5-27 11:24
4
1
我实在不知道,为啥大厂不选择汇编语言作为安卓的基础开发语言,而选择java,只为了显示自己的白chi吗?
雪    币: 287
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
cheny76 2021-5-27 12:02
5
1
我实在不知道,为啥大厂不选择汇编语言作为安卓的基础开发语言,而选择java,只为了显示自己的白chi吗?
雪    币: 2651
活跃值: (7179)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
至尊小仙侠 2021-8-6 19:58
6
0
这个禁止头衔是啥东西 
雪    币: 233
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
鹿之川 2021-8-8 23:01
7
0
5楼的想法很不一样,汇编是多麻烦,java可以跨平台
雪    币: 980
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_gfeonlex 2022-1-12 16:07
8
0
大佬,招聘安卓逆向,有兴趣私戳我~
游客
登录 | 注册 方可回帖
返回