day12-JNI案例

发布时间 2023-10-21 14:49:56作者: hanfe1

1 JNI类型签名


# 我们开发安卓--》写java代码---》通过JNI---》调用C代码(JNI的c代码)
				java中变量---》通过JNI---》转成c变量

image-20231021144045690

image-20231021144101246

2 JNI中java调用c案例

2.1 数字处理

Utils.java

package com.justin.s9day11;
public class Utils {
    static {
        System.loadLibrary("utils");
    }

    // 1.1 数字类型处理
    // 在c语言中要对象 v进行具体实现---》jni代码---》c的函数名必须叫    Java_包名_类名_方法名(JNIEnv *env, jclass clazz, 后续的参数)
    public static native int v1(int a1, int a2);
}

demo.c

#include <jni.h>


//1.1  JNI编写 Utils.java中的v1方法
JNIEXPORT jint JNICALL
Java_com_justin_s9day11_Utils_v1(JNIEnv *env, jclass clazz, jint a1, jint a2) {
    return a1 + a2;
}

MainActivity.java

tv.setText(String.valueOf(Utils.v1(1,2)));

2.2 通过指针修改字符串

Utils.java

package com.justin.s9day11;
public class Utils {
    static {
        System.loadLibrary("utils");
    }
    
    // 1.2 修改字符串   传入 justin 字符串,在c中把字符串改变,返回回来
    public static native String v2(String s);
}

demo.c

#include <jni.h>
// 1.2  JNI编写 Utils.java中的v2方法,对字符串进行修改,然后返回
JNIEXPORT jstring JNICALL
Java_com_justin_s9day11_Utils_v2(JNIEnv *env, jclass clazz, jstring s) {
    // 传入justin 字符串,把 justin 字符串的第1个位置和第4个位置,都变成 j

    // 1 把jstring类型的s,转成c语言 的 字符指针--》才能操作这个字符串
    // char info[]  = {'j','u','s','t','i','n'};
    char *info = (*env)->GetStringUTFChars(env, s, 0);  // 固定写法,指针指向s这个字符串
    info += 1;
    *info = 'j';  // 把字符串第1个位置的字符变成了 j
    info += 3;
    *info = 'j';  // 把字符串第4个位置的字符变成了 j
    info -= 4;    //让指针回到了 字符数组的第0个位置

    // 把字符数组 指针转成字符串类型返回--》固定用户
    return (*env)->NewStringUTF(env, info);

}

MainActivity.java

tv.setText(Utils.v2("aaaaaaaa"));

2.3 通过数组修改字符串

Utils.java

package com.justin.s9day11;
public class Utils {
    static {
        System.loadLibrary("utils");
    }
    
    // 1.3 修改字符串,传入 justin 字符串,在c中把字符串改变,返回回来,内部通过数组修改
    public static native String v3(String s);
}

demo.c

#include <jni.h>
// 1.3  JNI编写 Utils.java中的v2方法,对字符串进行修改,然后返回-->使用数组修改
JNIEXPORT jstring JNICALL
Java_com_justin_s9day11_Utils_v3(JNIEnv *env, jclass clazz, jstring s) {
    // 指针,指向了字符数组
    char *info = (*env)->GetStringUTFChars(env, s, 0);  // 固定写法,指针指向s这个字符串
    // c语言指针的用法
    info[1] = 't';
    info[4] = 't';
    // 把字符数组 指针转成字符串类型返回--》固定用法
    return (*env)->NewStringUTF(env, info);


}


MainActivity.java

tv.setText(Utils.v3("aaaaaaaa"));

2.4 字符串拼接

Utils.java

package com.justin.s9day11;
public class Utils {
    static {
        System.loadLibrary("utils");
    }
    
     // 1.4 字符串拼接
    public static native String v4(String name,String role);
}

demo.c

#include <jni.h>
#include <malloc.h>
#include <string.h>
// 1.4 字符串串拼接
JNIEXPORT jstring JNICALL
Java_com_justin_s9day11_Utils_v4(JNIEnv *env, jclass clazz, jstring name, jstring role) {
    // 1 把传入的name和role转成 字符指针
    char *nameString = (*env)->GetStringUTFChars(env, name, 0);
    char *roleString = (*env)->GetStringUTFChars(env, role, 0);
    char *s = malloc(strlen(nameString) + strlen(roleString) + 1);  // 定义一个指针,指定长度
    // 把name复制到s中去
    strcpy(s, nameString);
    // 把role拼接到后面
    strcat(s, roleString);
    //sprintf(s, "%s%s", name, role);
    return (*env)->NewStringUTF(env, s);
}


MainActivity.java

tv.setText(Utils.v4("justin","teacher"));

2.5 字符处理-把字符串转成16进制

Utils.java

package com.justin.s9day11;
public class Utils {
    static {
        System.loadLibrary("utils");
    }
    
        // 1.5 字符处理
    public static native String v5(String data);
}

demo.c

// 1.5 把传入的字符串,每一位,转成16进制返回(不足两位,用0补齐)
JNIEXPORT jstring JNICALL
Java_com_justin_s9day11_Utils_v5(JNIEnv *env, jclass clazz, jstring data) {
    // data=     name=justin&age=19
    char *urlParams = (*env)->GetStringUTFChars(env, data, 0);
    //int size= strlen(urlParams);
    int size = GetStringLen(urlParams);

    char v34[size * 2]; // 定义一个char数组,长度是 size的两倍

    char *v28 = v34; // 取到指针,赋值给了v28指针类型

    for (int i = 0; urlParams[i] != '\0'; i++) {
        sprintf(v28, "%02x", urlParams[i]);  // 一位一位的取出 传入的字符串,把每一位转成16进制,不足2位,用0补齐
        v28 += 2;
    }

    return (*env)->NewStringUTF(env, v34);
}


MainActivity.java

tv.setText(Utils.v5("name=justin&age=19"));

2.6 字节处理-->java传进去的是字节类型

Utils.java

package com.justin.s9day11;
public class Utils {
    static {
        System.loadLibrary("utils");
    }
        // 1.6 字节处理
    public static native String v6(byte[] data);
    public static native String v7(byte[] data);

}

demo.c

// 1.6 字节处理---》把byte类型数组传入,转16进制后返回
JNIEXPORT jstring JNICALL
Java_com_justin_s9day11_Utils_v6(JNIEnv *env, jclass clazz, jbyteArray data) {
    // 1 把 jbyteArray 类型,转成  c 字符数组
    char *byteArray = (*env)->GetByteArrayElements(env, data, 0);
    // 2 拿到传入的字节数组的长度
    int size = (*env)->GetArrayLength(env, data);
    //int size = GetStringLen(byteArray);

    char v34[size * 2];
    char *v28 = v34;

    for (int i = 0; byteArray[i] != '\0'; i++) {
        sprintf(v28, "%02x", byteArray[i]);
        v28 += 2;
    }
    return (*env)->NewStringUTF(env, v34);

}

// 1.7 do while 循环完成1.6的操作
JNIEXPORT jstring JNICALL
Java_com_justin_s9day11_Utils_v7(JNIEnv *env, jclass clazz, jbyteArray data) {
    char *byteArray = (*env)->GetByteArrayElements(env, data, 0);
    int size = (*env)->GetArrayLength(env, data);
    char v34[size * 2];
    char *v28 = v34;
    int v29 = 0;
    do {
        sprintf(v28, "%02x", byteArray[v29++]);
        v28 += 2;
    } while (v29 !=  size);

    return (*env)->NewStringUTF(env, v34);
}

MainActivity.java

//tv.setText(Utils.v6("name=justin&age=19".getBytes()));
tv.setText(Utils.v7("name=justin&age=19".getBytes()));

3 JNI中c调用java案例

#  写android---》java写---》很容易被反编译---》看到我们的算法,可以复现算法---》安全性低--》大厂都会使用c做加密---》这个过程是java---》调用c---》上面讲的----》后面会有反编译的案例看到


# java---》调用c的加密---》在c 不用c写---》又调用java中的某个加密方法---》得到结果后--》再返回



# c调用java:
	静态方法:static修饰的,类来调用,不需要对象
    成员方法:需要对象来调用,需要在c中实例化得到对象再调用

3.1 调用java的静态方法

Utils.java

package com.justin.s9day11;
public class Utils {
    static {
        System.loadLibrary("utils");
    }

	public static native String v8();
}

demo.c

// 2.1 c调用java案例
JNIEXPORT jstring JNICALL
Java_com_justin_s9day11_Utils_v8(JNIEnv *env, jclass clazz) {
    // 调用java代码

    // 1 先找到要调用的类:
    jclass cls = (*env)->FindClass(env, "com/justin/s9day11/Foo");
    // 2 找到静态方法 getSign       ()Ljava/lang/String;最后一个参数是 getSign这个方法的参数和返回值签名
    // (参数签名)返回值签名
    //    jmethodID method1 = (*env)->GetStaticMethodID(env, cls, "getSign", "()Ljava/lang/String;");
    //    jmethodID method2 = (*env)->GetStaticMethodID(env, cls, "getSign","(Ljava/lang/String;)Ljava/lang/String;");
    jmethodID method3 = (*env)->GetStaticMethodID(env, cls, "getSign", "(Ljava/lang/String;I)Ljava/lang/String;");



    //3 z执行方法
    //jstring res1 = (*env)->CallStaticObjectMethod(env, cls, method1);
    //jstring res1 = (*env)->CallStaticObjectMethod(env, cls, method2,(*env)->NewStringUTF(env, "lqz"));
    jstring res1 = (*env)->CallStaticObjectMethod(env, cls, method3,(*env)->NewStringUTF(env, "lqz"),99);


    return res1;
    //4 如果想再继续用c操作,可以这么写
    //    char *p1 = (*env)->GetStringUTFChars(env, res1, 0);
    //    p1[0] = 'o';
    //
    //    return (*env)->NewStringUTF(env, p1);

}

MainActivity.java

tv.setText(Utils.v8());

Foo.java

package com.justin.s9day11;

public class Foo {

    public static String getSign() {
        return "justin123";
    }

    // 重载
    public static String getSign(String name) {
        return name + "_NB";
    }

    public static String getSign(String name, int age) {
        return name + String.valueOf(age);
    }
}

3.2 调用java的成员方法

Utils.java

package com.justin.s9day11;
public class Utils {
    static {
        System.loadLibrary("utils");
    }
 public static native String v9();

}

demo.c

// 2.2 c调用java的成员方法
JNIEXPORT jstring JNICALL
Java_com_justin_s9day11_Utils_v9(JNIEnv *env, jclass clazz) {
    // 调用Foo类中的ShowName这个成员方法

    // 1 找到类
    jclass cls = (*env)->FindClass(env, "com/justin/s9day11/Foo");
    // 2 找到构造方法
    jmethodID init = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
    //jmethodID init = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;Ljava/lang/String;)V");
    //jmethodID init = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;I)V");

    // 3 通过构造方法,实例化得到对象
    jobject cls_obj = (*env)->NewObject(env, cls, init, (*env)->NewStringUTF(env, "lqz"));

    //4 通过类,先找到方法
    jmethodID method1 = (*env)->GetMethodID(env, cls, "ShowName", "()Ljava/lang/String;");

    // 5 通过对象执行方法
    jstring res1 = (*env)->CallObjectMethod(env, cls_obj, method1);

    return res1;


}

MainActivity.java

tv.setText(Utils.v9());

Foo.java

package com.justin.s9day11;

public class Foo {
    public String name;



    // 构造方法
    public Foo(String name) {
        this.name = name+"_NB";

    }
    public Foo(String name,int age) {
        this.name = name;

    }

    // 成员方法
    public String ShowName() {
        return this.name;
    }
}

//Foo f=new Foo("lqz");
//f.ShowName()

4 JNI注册中的动态注册和静态注册

4.1 静态注册

上述编写的C语言的函数和Java的对应关系,在函数名上就可以体现,例如:

```

Java_com_justin_s8day12_Utils_v9
Java_com_justin_s8day12_Utils_v8

```
这种称为静态注册,如果是静态注册,那么在逆向时,是比较方便的,直接可以找到函数在C中的实现。

4.2 动态注册

# 动态注册步骤:
	1 新建c文件:dynamic.c
    
    2 在CMakeLists.txt注册
    add_library(
        dynamic
        SHARED
        dynamic.c)
    3 在CMakeLists.txt注册
    target_link_libraries(
        s9day11
        utils dynamic
        ${log-lib})
    4 新建Dynamic.java
    package com.justin.s9day11;
    public class Dynamic {
        static {
            System.loadLibrary("dynamic");
        }

        public static native int vv1(int a1,int a2);
        public static native int vv2(String s);


    }

dynamic.c

#include <jni.h>


// 静态注册---》通过vv1---》按固定模板,就能找到c的方法,很简单---》反编译也是这么找
//JNIEXPORT jint JNICALL
//Java_com_justin_s9day11_Dynamic_vv2(JNIEnv *env, jclass clazz, jstring s) {
//    // TODO: implement vv2()
//}
//
//JNIEXPORT jint JNICALL
//Java_com_justin_s9day11_Dynamic_vv1(JNIEnv *env, jclass clazz, jint a1, jint a2) {
//    // TODO: implement vv1()
//}



// 动态注册  通过 java中的方法名,无法确定,c中跟那个方法对应
jint plus1(JNIEnv *env, jobject obj, jint v1, jint v2) {
    return v1 + v2;
}

jint plus2(JNIEnv *env, jobject obj, jstring s1) {
    return 100;
}

static JNINativeMethod gMethods[] = {
        {"vv1", "(II)I",                 (void *) plus1},
        {"vv2", "(Ljava/lang/String;)I", (void *) plus2},
};



// 1 只要是动态注册,都必须写一个方法---》JNI_OnLoad
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    // ----固定的开始----
    JNIEnv *env = NULL;
    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    // ----固定的结束----


    // 找到Java中的类
    jclass clazz = (*env)->FindClass(env, "com/justin/s9day11/Dynamic");
    // 将类中的方法注册到JNI中 (RegisterNatives)
    int res = (*env)->RegisterNatives(env, clazz, gMethods,
                                      2);  //  最后的数字2 表示有两个对应关系:Dynamic.java中有两个方法跟我dynamic.c中有两个方法对应,如何对应的是gMethods决定的



    // ----固定的开始----
    if (res < 0) {
        return JNI_ERR;
    }

    return JNI_VERSION_1_6;
    // ----固定的结束----
}

5 反编译自己的app

# 1:静态注册---》 反编译自己app----》找到了jni调用位置---》通过System.loadLibrary("utils")---》确定是哪个so文件---》去so文件中,通过 静态注册方案找  v9 对应的c中的函数---》很容易找到

# 2 动态注册
	-反编译so后,找JNI_OnLoad
    -找到对应关系,双击进入,按F5,查看源代码

image-20231021144322382

image-20231021144329696