day12-JNI案例
1 JNI类型签名
# 我们开发安卓--》写java代码---》通过JNI---》调用C代码(JNI的c代码)
java中变量---》通过JNI---》转成c变量
返回值V,代表void 没有返回值
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,查看源代码