JNI开发
JNI(java native interface),Java本地开发接口,实现JAVA和C语言之间的相互调用。
一、NDK
- NDK,Native Develop Kits,是JNI开发的工具包(在Android Studio中下载即可)
二、项目创建
- 普通项目:模板Empty Activity(Java)
- JNI项目:模板Native C++(Java + C)
- 会多一些默认配置
三、快速实现
-
1、新建java类 + native方法
package com.example.jni; public class Utils { // 加载c文件 static { System.loadLibrary("encrypt"); } // 1、java实现 public static String v0(String data) { return data + "hello world"; } // 2、native方法(声明即可,具体在c语言中实现) public static native int v1(int data1, int data2); }
-
2、创建c文件 + 创建函数【Java_包名_类名_方法名】
- 在【cpp】目录下,右键【new】选择【C/C++ Source File】,记得文件后缀改成【.c】
- 快捷键:直接输入java类中定义的native方法名【v1】,根据提示选择后回车
#include <jni.h> JNIEXPORT jint JNICALL Java_com_example_jni_Utils_v1(JNIEnv *env, jclass clazz, jint data1, jint data2) { return data1 + data2 + 100; }
-
3、c编译配置
-
4、java类中加载c文件
package com.example.jni; public class Utils { static { System.loadLibrary("encrypt"); } // 1、java实现 public static String v0(String data) { return data + "hello world"; } // 2、c实现 public static native int v1(int data1, int data2); }
-
5、逆向和反编译
- jadx反编译并定位到了native方法
- 将apk包中的so文件导出到ida中进一步分析
点击进入后,按F5,并可以导入jni.h头文件等操作
四、类型对照
-
JNIEXPORT jstring JNICALL Java_com_example_jni_Utils_v2(JNIEnv *env, jclass clazz, jstring data) { // jstring转c语言中的字符串 char *newData = (*env)->GetStringUTFChars(env, data, 0); newData[0] = 'a'; newData[3] = 'x'; // c语言中的字符串转jstring return (*env)->NewStringUTF(env, newData); }
- 输入Utils.v2后有提示,可以按回车快速补全代码
六、示例
-
1、Java调用C示例
- 字符串拼接
- java中的方法声明
// 字符串拼接 public static native String v3(String data1, String data2);
- encrypt.c
#include <jni.h> #include <string.h> #include <malloc.h> JNIEXPORT jstring JNICALL Java_com_example_jni_Utils_v3(JNIEnv *env, jclass clazz, jstring data1, jstring data2) { char *res1 = (*env)->GetStringUTFChars(env, data1, 0); char *res2 = (*env)->GetStringUTFChars(env, data2, 0); char *result = malloc(strlen(res1) + strlen(res2) + 1); strcpy(result, res1); strcat(result, res2); return (*env)->NewStringUTF(env, result); }
- java中的方法声明
- 字符串拼接
-
2、C调用Java示例
- 静态方法
- java中的方法声明
package com.example.jni; public class Utils { static { System.loadLibrary("encrypt"); } // c调用java静态方法 public static native String v4(); }
- C语言中需要调用的java方法声明
package com.example.jni; public class SignQuery { public static String getPart1() { return "hello"; } public static String getPart2(int len) { return "world".substring(2); } public static String getPart3(String prev) { return "NB"; } public static int getPart4(String prev, int v1) { return 100; } }
- encrypt.c
#include <jni.h> #include <string.h> #include <malloc.h> JNIEXPORT jstring JNICALL Java_com_example_jni_Utils_v4(JNIEnv *env, jclass clazz) { // 1、找类 (类名路径分隔符"/") jclass cls = (*env)->FindClass(env, "com/example/jni/SignQuery"); // 2、找静态方法 (参数3:方法名, 参数4:JNI签名) jmethodID methodId1 = (*env)->GetStaticMethodID(env, cls, "getPart1", "()Ljava/lang/String;"); jmethodID methodId2 = (*env)->GetStaticMethodID(env, cls, "getPart2", "(I)Ljava/lang/String;"); jmethodID methodId3 = (*env)->GetStaticMethodID(env, cls, "getPart3", "(Ljava/lang/String;)Ljava/lang/String;"); jmethodID methodId4 = (*env)->GetStaticMethodID(env, cls, "getPart4", "(Ljava/lang/String;I)I"); // 3、调用静态方法 (参数3:jmethodID,如果方法有参数,则以位置参数的形式依次写上) // 调用方法时,需要根据返回值类型来进行选择 jstring res1 = (*env)->CallStaticObjectMethod(env, cls, methodId1); jstring res2 = (*env)->CallStaticObjectMethod(env, cls, methodId2, 100); jstring res3 = (*env)->CallStaticObjectMethod(env, cls, methodId3, (*env)->NewStringUTF(env, "come on!")); jint res4 = (*env)->CallStaticIntMethod(env, cls, methodId4, (*env)->NewStringUTF(env, "my god!"), 28); // 4、jstring => c语言字符串 char *result1 = (*env)->GetStringUTFChars(env, res1, 0); char *result2 = (*env)->GetStringUTFChars(env, res2, 0); char *result3 = (*env)->GetStringUTFChars(env, res3, 0); // 5、c语言中字符串相关操作 char *result = malloc(strlen(result1) + strlen(result2) + strlen(result3) + 1); strcpy(result, result1); strcat(result, result2); strcat(result, result3); // 6、C语言字符串 => jstring return (*env)->NewStringUTF(env, result); }
- java中的方法声明
- 实例方法
- java中的方法声明
package com.example.jni; public class Utils { static { System.loadLibrary("encrypt"); } // c调用java实例方法 public static native String v5(); }
- C语言中需要调用的java方法声明
package com.example.jni; public class GetSign { String name; int age; public GetSign(String name, int age){ this.name = name; this.age = age; } public String getPart1() { return this.name; } public String getPart2(int len) { return "world".substring(2); } public String getPart3(String prev) { return "NB"; } public int getPart4(String prev, int v1) { return 100; } }
- encrypt.c
#include <jni.h> #include <string.h> #include <malloc.h> JNIEXPORT jstring JNICALL Java_com_example_jni_Utils_v5(JNIEnv *env, jclass clazz) { // 1、找类 (类名路径分隔符"/") jclass cls = (*env)->FindClass(env, "com/example/jni/GetSign"); // 2、找构造方法 jmethodID init = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;I)V"); // 3、实例化对象 jobject obj = (*env)->NewObject(env, cls, init, (*env)->NewStringUTF(env, "eliwang"), 28); // 4、找实例方法 jmethodID methodId1 = (*env)->GetMethodID(env, cls, "getPart1", "()Ljava/lang/String;"); jmethodID methodId2 = (*env)->GetMethodID(env, cls, "getPart2", "(I)Ljava/lang/String;"); jmethodID methodId3 = (*env)->GetMethodID(env, cls, "getPart3", "(Ljava/lang/String;)Ljava/lang/String;"); jmethodID methodId4 = (*env)->GetMethodID(env, cls, "getPart4", "(Ljava/lang/String;I)I"); // 5、执行实例方法 jstring res1 = (*env)->CallObjectMethod(env, obj, methodId1); jstring res2 = (*env)->CallObjectMethod(env, obj, methodId2, 100); jstring res3 = (*env)->CallObjectMethod(env, obj, methodId3, (*env)->NewStringUTF(env, "come on!")); jint res4 = (*env)->CallIntMethod(env, obj, methodId4, (*env)->NewStringUTF(env, "my god!"), 28); // 6、jstring => c语言字符串 char *result1 = (*env)->GetStringUTFChars(env, res1, 0); char *result2 = (*env)->GetStringUTFChars(env, res2, 0); char *result3 = (*env)->GetStringUTFChars(env, res3, 0); // 7、c语言中字符串相关操作 char *result = malloc(strlen(result1) + strlen(result2) + strlen(result3) + 1); strcpy(result, result1); strcat(result, result2); strcat(result, result3); // 8、C语言字符串 => jstring return (*env)->NewStringUTF(env, result); }
- java中的方法声明
- 静态方法
七、静态和动态注册
-
静态注册
- C语言中函数名和Java中函数名之间的对应关系
Java_包名_类名_函数名
c文件中的函数名
- C语言中函数名和Java中函数名之间的对应关系
-
动态注册
- c语言中函数名特点
1、必须写JNI_Onload函数 2、必须有RegisterNatives方法:第3个参数是映射关系函数,第4个参数是动态注册函数的数量 映射函数中的对应关系:{java函数名,方法签名,c中对应函数}
- DynamicUtils.java
package com.example.jni; public class DynamicUtils { static { System.loadLibrary("dynamic"); } // JNI动态注册 public static native int add(int v1, int v2); }
- dynamic.c
#include <jni.h> jint plus(JNIEnv *env, jobject obj, jint v1, jint v2){ return v1 + v2; } static JNINativeMethod gMethods[] = { {"add", "(II)I", (void *) plus}, }; 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/example/jni/DynamicUtils"); // 将类中的方法注册到JNI中 (RegisterNatives) int res = (*env)->RegisterNatives(env, clazz, gMethods, 1); if (res < 0) { return JNI_ERR; } return JNI_VERSION_1_6; }
dynamic.c需要添加编译配置
- c语言中函数名特点
八、反编译app中c代码流程
- 复制apk文件并解压缩,改后缀名.zip
- 去目录中找到so文件:lib -> 对应平台(比如arm64-v8a) -> libxxx.so(与System.loadLibrary("xxx")中名称一致)
- so文件拖到IDA中,选择exports导出
- 对于32位的so,使用ida.exe,对于64位的so,使用ida64.exe
- 静态注册
- 双击函数名,看到汇编代码,按F5对汇编代码进行反编译
- 动态注册:
- 找JNI_Onload,按F5
- 优化显示几种策略
- 1、右键-Hide casts隐藏转换
- 2、加载头文件:File -> Load File -> Parse C header File -> 选择jni.h
- 3、然后右键函数第一个参数类型,比如int,点击右键 -> Convert to struct *... -> 选择JNIEnv_
- 找RegisterNatives的第3个参数,即对应关系