今日内容

1 JNI介绍和安装

1.1 JNI介绍

1
2
3
# JNI java native interface 简写,java本地开发接口
# 实现安卓中java和c语言直接的调用

image-20240517164610559

1.2 NDK安装

1
2
# 搭建JNI开发环境---》NDK是JNI的开发工具包
# NDK:Native Develop Kits 本地开发工具包,只需要在 AndroidStudio中下载即可

image-20240517164626385

2 创建JNI项目

1
2
3
4
5
6
# 之前创建普通项目: Empty View Activity  ---》只做java开发
# 现在做JNI开发: Native C++---》java和C一起用
-如果选了这个,项目会多配置内容,带一个c++案例(JNI),在java中调用
-生成一些默认jni配置,不需要我们额外再配置了
-即便咱们创建的是:Empty View Activity 也可以改造成 Native项目

2.1 创建项目

image-20240517164637042

image-20240517164645502

image-20240517164652727

image-20240517164704410

2.2 快速使用

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
49
50
51
52
53
54
55
56
57
# 使用java---》JNI调用---》C代码---》完成功能
java调用c中某个方法,完成 a+b这个功能


# 使用步骤:
### 第一步:在cpp目录下,创建一个c文件,注意文件名结尾是c--》utils.c
### 第二步:编写一个java类(Utils.java),在java类中编写静态方法
package com.justin.s10demo03;
public class Utils {
public static native int v1(int a,int b); # 不用java代码实现,用c实现
}

###第三步:在java类中引入静态文件---》让这个java类跟某个c文件对应好
static {
System.loadLibrary("utils"); # 跟写的c文件对应,不要带后缀名
}

### 第四步:在CMakeLists.txt中注册

# 如果有多个c文件,继续复制它
add_library(
utils # 给c文件起了个别名
SHARED
utils.c)

target_link_libraries(
utils
${log-lib})

### 第五步:utils.c 写代码,具体实现,实现a+b
#include <jni.h>

JNIEXPORT jint

JNICALL Java_com_justin_s10demo03_Utils_v1(JNIEnv *env, jclass clazz, jint a, jint b) {
// jint a, jint b 参数,是Utils.java 中 v1的a和b参数

return a+b;
}



### 第六步: 删除MainActivity.java中的代码
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
TextView tv = findViewById(R.id.sample_text);
tv.setText(String.valueOf(Utils.v1(22,30)));
}

}

image-20240517164718511

image-20240517164725854

image-20240517164733229

image-20240517164739993

3 JNI案例

3.1 类型

1
2
3
4
5
6
7
8
9
# java中的类型和 C的Native中的类型是由对应关系的---》目前只需要做个了解
比如:java中 int---》jint
java中的String---》jstring


# 后期:在c中调用java的代码---》需要做类型签名
java中的int类型---》类型签名是 I
java中double类型---》类型签名是D
java中String类型---》类型签名是:Ljava/lang/String

image-20240517164751562

image-20240517164759181

3.2 Java调用C案例

3.2.1 数字处理

1
2
3
4
5
6
7
8
# 刚刚写的很复杂的---》java调用c案例--》数字处理
# Utils.java中
public static native int v1(int a, int b); // 使用c具体实现
# utils.c中
JNIEXPORT jint
JNICALL Java_com_justin_s10demo03_Utils_v1(JNIEnv *env, jclass clazz, jint a, jint b) {
return a+b;
}

3.2.2 通过指针修改字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
## MainActivity.java
tv.setText(Utils.v2("justin"));

## Utils.java中写
public static native String v2(String s);

## utils.c 中具体实现
JNIEXPORT jstring

JNICALL

Java_com_justin_s10demo03_Utils_v2(JNIEnv *env, jclass clazz, jstring s) {
// s 就是justin这个字符串
// 1 拿到jstring类型的s,把它转成c语言的指针类型---》固定用法
char *info = (*env)->GetStringUTFChars(env, s, 0); // jjjtin
info += 1; //指针往后移动了一位
*info = 'j';
info += 1; //指针往后移动了一位
*info = 'j';
info -= 2;
// 把字符串返回给java--》把 c的指针类型,转陈 jstring类型返回
return (*env)->NewStringUTF(env, info);

}

3.2.3 通过数组修改字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## MainActivity.java
tv.setText(Utils.v3("justin"));

## Utils.java中写
public static native String v3(String s); // justin---》jjjtin

## utils.c 中具体实现
JNIEXPORT jstring

JNICALL
Java_com_justin_s10demo03_Utils_v3(JNIEnv *env, jclass clazz, jstring s) {
char *info = (*env)->GetStringUTFChars(env, s, 0); // justin--->jjjtin
info[1] = 'o';
info[2] = 'j';
return (*env)->NewStringUTF(env, info);

}

3.2.4 字符串拼接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## MainActivity.java
tv.setText(Utils.v4("justin","teacher"));
## Utils.java中写
public static native String v4(String name,String role); // justin teacher
## utils.c 中具体实现
JNIEXPORT jstring

JNICALL
Java_com_justin_s10demo03_Utils_v4(JNIEnv *env, jclass clazz, jstring name, jstring role) {
char *nameString = (*env)->GetStringUTFChars(env, name, 0);
char *roleString = (*env)->GetStringUTFChars(env, role, 0);
// 分配内存
char *result = malloc(strlen(nameString) + strlen(roleString) + 1);
strcpy(result, nameString); // 字符串复制
strcat(result, roleString); // 字符串拼接
return (*env)->NewStringUTF(env, result);
}

3.2.5 字符处理

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
# 传入的字符串,转成16进制返回
## MainActivity.java
tv.setText(Utils.v5("name=justin&age=19"));
## Utils.java中写
public static native String v5(String s); // name=justin&age=19
## utils.c 中具体实现

JNIEXPORT jstring

JNICALL
Java_com_justin_s10demo03_Utils_v5(JNIEnv *env, jclass clazz, jstring s) {
// s name=justin&age=19
char *urlParams = (*env)->GetStringUTFChars(env, s, 0);
int size=strlen(urlParams); // 拿到字符串长度
char v34[size*2]; //数组大小是字符串长度两倍---》字符串 转成16进制放到v34中,每位不足两位用0补齐
char *v28 =&v34;
for (int i = 0; urlParams[i] != '\0'; i++) { // \0是字符串结尾,如果不是字符串结尾,就一直循环
sprintf(v28, "%02x", urlParams[i]);
v28 += 2;
}
return (*env)->NewStringUTF(env, v34);
}

### sprintf
%% 印出百分比符号,不转换。
%c 整数转成对应的 ASCII 字元。
%d 整数转成十进位。
%f 倍精确度数字转成浮点数。
%o 整数转成八进位。
%s 整数转成字符串。
%x 整数转成小写十六进位。
%X 整数转成大写十六进位

3.2.6 字节处理1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 传入的字符数组,转成16进制返回
## MainActivity.java
tv.setText(Utils.v6("name=justin".getBytes()));
## Utils.java中写
public static native String v6(byte[] data);
## utils.c 中具体实现

JNIEXPORT jstring

JNICALL
Java_com_justin_s10demo03_Utils_v6(JNIEnv *env, jclass clazz, jbyteArray data) {
char *byteArray = (*env)->GetByteArrayElements(env, data, 0);
int size = (*env)->GetArrayLength(env, data); // 获取byte数组的长度
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);
}

3.2.7 字节处理2(do-while循环)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 传入的字符数组,转成16进制返回
## MainActivity.java
tv.setText(Utils.v7("name=justin".getBytes()));
## Utils.java中写
public static native String v7(byte[] data);

## utils.c 中具体实现
JNIEXPORT jstring

JNICALL
Java_com_justin_s10demo03_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);
}

3.3 C调用java案例

image-20240517164819172

3.3.1 静态方法

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
# java中  static 修饰的静态方法---》类直接来调用

## MainActivity.java
tv.setText(Utils.v8());
## Utils.java中写
public static native String v8();

## Foo.java
package com.justin.s10demo03;
public class Foo {

//静态方法,让c来调用的
public static String getSign(){
return "justin";
}
}

## utils.c 中具体实现
JNIEXPORT jstring

JNICALL
Java_com_justin_s10demo03_Utils_v8(JNIEnv *env, jclass clazz) {
// 调用java代码
// 1 找到java的类
jclass cls = (*env)->FindClass(env, "com/justin/s10demo03/Foo");
//2 找到java类中的static方法,静态方法
// env:固定的 cls:哪个类,上面取出来的 getSign:找哪个静态方法 ()Ljava/lang/String; 参数和返回值签名,没有参数,括号是空的,有String类型返回值
jmethodID method1 = (*env)->GetStaticMethodID(env, cls, "getSign", "()Ljava/lang/String;");
// 3 执行静态方法 类 找到的静态方法
jstring res1 = (*env)->CallStaticObjectMethod(env, cls, method1);
// 4 返回给java
char *res2 = (*env)->GetStringUTFChars(env, res1, 0);
return (*env)->NewStringUTF(env, res2);
}
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
# java中  static 修饰的静态方法---》类直接来调用

## MainActivity.java
tv.setText(Utils.v9(22,33));
## Utils.java中写
public static native String v9(int a, int b);

## Foo.java
package com.justin.s10demo03;
public class Foo {

//静态方法,让c来调用的
public static String getSign(){
return "justin";
}
public static String getSign(int a, int b) {
return "justin" + a + b;
}
}

## utils.c 中具体实现

JNIEXPORT jstring

JNICALL
Java_com_justin_s10demo03_Utils_v9(JNIEnv *env, jclass clazz, jint a, jint b) {
// 1 找到java的类
jclass cls = (*env)->FindClass(env, "com/justin/s10demo03/Foo");
//2 找到java类中的static方法,静态方法
// env:固定的 cls:哪个类,上面取出来的 getSign:找哪个静态方法 ()Ljava/lang/String; 参数和返回值签名,没有参数,括号是空的,有String类型返回值
// 找重载的方法,通过签名区分
jmethodID method1 = (*env)->GetStaticMethodID(env, cls, "getSign", "(II)Ljava/lang/String;");
// 3 执行静态方法 类 找到的静态方法 传参数进来
jstring res1 = (*env)->CallStaticObjectMethod(env, cls, method1,a,b);
// 4 返回给java
char *res2 = (*env)->GetStringUTFChars(env, res1, 0);
return (*env)->NewStringUTF(env, res2);
}
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
# java中  static 修饰的静态方法---》类直接来调用

## MainActivity.java
tv.setText(Utils.v10("nb",33));
## Utils.java中写
public static native String v10(String s, int b);

## Foo.java
package com.justin.s10demo03;
public class Foo {

//静态方法,让c来调用的
public static String getSign(){
return "justin";
}
public static String getSign(int a, int b) {
return "justin" + a + b;
}
public static String getSign(String s, int b) {
return "justin" + s + b;
}
}

## utils.c 中具体实现
JNIEXPORT jstring

JNICALL
Java_com_justin_s10demo03_Utils_v10(JNIEnv *env, jclass clazz, jstring s, jint b) {
// 1 找到java的类
jclass cls = (*env)->FindClass(env, "com/justin/s10demo03/Foo");
//2 找到java类中的static方法,静态方法
// env:固定的 cls:哪个类,上面取出来的 getSign:找哪个静态方法 ()Ljava/lang/String; 参数和返回值签名,没有参数,括号是空的,有String类型返回值
// 找重载的方法,通过签名区分
jmethodID method1 = (*env)->GetStaticMethodID(env, cls, "getSign",
"(Ljava/lang/String;I)Ljava/lang/String;");
// 3 执行静态方法 类 找到的静态方法 传参数进来
jstring res1 = (*env)->CallStaticObjectMethod(env, cls, method1,(*env)->NewStringUTF(env, "xxx"), b); // 把xxx转成jstring类型
// jstring res1 = (*env)->CallStaticObjectMethod(env, cls, method1,s, b); // s就是jstring不需要转了
// 4 返回给java
char *res2 = (*env)->GetStringUTFChars(env, res1, 0);
return (*env)->NewStringUTF(env, res2);
}

3.3.2 成员方法

1
# 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# java中  成员方法

## MainActivity.java
tv.setText(Utils.v11());
## Utils.java中写
public static native String v11();

## Foo.java
package com.justin.s10demo03;
public class Foo {

//静态方法,让c来调用的
public static String getSign(){
return "justin";
}
public static String getSign(int a, int b) {
return "justin" + a + b;
}
public static String getSign(String s, int b) {
return "justin" + s + b;
}
public Foo(String name){
this.name=name;

}
public String ShowName(){
return this.name+"nb";
}
}

## utils.c 中具体实现
JNIEXPORT jstring

JNICALL
Java_com_justin_s10demo03_Utils_v11(JNIEnv *env, jclass clazz) {
// 调用java成员方法
// 1 找到类
jclass cls = (*env)->FindClass(env, "com/justin/s10demo03/Foo");
// 2 找到构造方法
jmethodID init = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
// 3 使用构造方法,实例化得到对象
jobject cls_obj = (*env)->NewObject(env, cls, init, (*env)->NewStringUTF(env, "justin"));
// 4 找到成员方法
jmethodID method1 = (*env)->GetMethodID(env, cls, "ShowName", "()Ljava/lang/String;");
// 5 调用成员方法
jstring res1 = (*env)->CallObjectMethod(env, cls_obj, method1);
// 6 返回
return res1;
}

3.4 静态注册和动态注册

1
2
3
# java中的方法  和  c中的方法 有对应关系的
# java中写的 v1 ----》对应C的:Java_com_justin_s10demo03_Utils_v1
# 固定的 Java_包名_类名_方法名---》这种方式叫静态注册

3.4.1 静态注册

1
2
之前咱们写的,全是静态注册
静态注册--》反编译c代码时,可以直接找到java对应的c代码,直接读,简单,对应明确

3.4.2 动态注册

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# 动态注册--》区别于静态注册---》
java中的方法---》跟c中的方法,没有直接对应关系,比较难找

# 以案例讲解如何做动态注册
### 步骤:
# 1 新建 dynamic.c
# 2 新建 Dynamic.java
package com.justin.s10demo03;
public class Dynamic {
static {
System.loadLibrary("dynamic");
}
public static native int vv1(int a, int b);
public static native int vv2(String s);
}
# 3 在CMakeLists.txt注册
add_library(
dynamic # 给c文件起了个别名
SHARED
dynamic.c)
target_link_libraries( # Specifies the target library.
utils
dynamic
${log-lib})
# 4 在 dynamic.c中写代码:必须写JNI_OnLoad
#include <jni.h>
// 3 实现 plus1
jint plus1(JNIEnv *env, jobject obj, jint v1, jint v2) {
return v1 + v2 + 100;
}

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

// 2 写 gMethods
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;
// 在java虚拟机中获取env
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
// ----上面都是固定的----


// 找到Java中的类
jclass clazz = (*env)->FindClass(env, "com/justin/s10demo03/Dynamic");
// 将类中的方法注册到JNI中 (RegisterNatives),通过它完成动态注册
// gMethods 是c语言方法---》 2 表示有两个方法要对应
int res = (*env)->RegisterNatives(env, clazz, gMethods, 2);


// ----下面都是固定的----
if (res < 0) {
return JNI_ERR;
}

return JNI_VERSION_1_6;
}

# 5 MainActivity.java
tv.setText(String.valueOf(Dynamic.vv1(3,3)));
tv.setText(String.valueOf(Dynamic.vv2("xxx")));

4 反编译自己的app

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
# 反编译自己app	
-反编译java---》jadx
-反编译so---》IDA

# 把自己app打包

# 使用jadx反编译 java代码

# 使用ida反编译 so代码
-查看v6如何实现,要去libutils.so中找


# 如何反编译so
-使用压缩工具把 apk解压
-进入lib的arm64-v8a目录,看到so文件
-把so文件拖动到IDA中
-选择exports导出
-双击函数名,看到汇编
-按F5,把混编进行反编译


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


# 反编译动态注册
-把so拖到 IDA后,看JNI_OnLoad--》找到gMethods【java和c的对应关系】---》

image-20240517164850554

image-20240517164904315

image-20240517164912601

image-20240517164920219

image-20240517164927684

image-20240517165012593

image-20240517164935595

image-20240517164945232

4.1 按如下步骤操作

image-20230720174022712

image-20230720174311141

image-20230720174357174

image-20230720175152179

image-20230720175227702

image-20230720175314859

image-20230720175338540

image-20230922224532443

image-20230922224642735

__END__