day11 11.2 JNI案例

发布时间 2023-08-02 11:39:31作者: Chimengmeng

【一】创建项目

【1】新建utils.c

  • 在cpp文件夹下新建utils.c 的源文件,后缀名是c,因为怎么用c写(.cpp 是c++,不要用这个)

【2】新建java类

  • 新建一个java类,类中写函数
package com.dream.demo11;

public class Utils {
    // 定义一个方法,实现C中的某个方法相对应
    public static native int v1(int a,int b);
}

【3】引入静态文件

在java类中,引入静态文件

// Used to load the 'demo11' library on application startup.
static {
    // 对应c文件的文件名
    System.loadLibrary("utils");
}

【4】在CMakeLists.txt中注册

  • 在CMakeLists.txt中加入编写的c文件
# 注册自己写的c文件
add_library( # Sets the name of the library.
        utils  # 最终这个utils.c会编译成so文件,编译成的名字叫 lib + utils

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        utils.c) # 指定需要编译的c文件


target_link_libraries( # Specifies the target library.
        demo11 utils  # 注册C文件,用空格分隔

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

【5】在utils中写代码

#include <jni.h>

//
// Created by Administrator on 2023/8/2.
//

JNIEXPORT jint // 函数返回的类型是int类型 ---> jint类型

JNICALL
// 函数名必须叫 Java_包名(com_dream_demo11)_类名_方法名
// JNIEnv *env, jclass 参数固定(结构体)
// jint a, jint b 这两个参数和Java中Utils下的v1方法对应,接收两个 int 类型的参数
Java_com_dream_demo11_Utils_v1(JNIEnv *env, jclass clazz, jint a, jint b) {
    // TODO: implement v1()

    return a + b;
}

【6】在java代码中调用

package com.dream.demo11;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.dream.demo11.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'demo11' library on application startup.
    static {
        System.loadLibrary("demo11");
    }

    private ActivityMainBinding binding;

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI()); // 函数是调用的C代码执行的结果
        tv.setText(String.valueOf(Utils.v1(3, 5))); // 调用C文件中的代码
    }

    /**
     * A native method that is implemented by the 'demo11' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

【二】逆向和反编译

【1】反编译 apk

  • 把咱们自己写的apk,拖动到jadx中即可

  • 反编译成java代码 ----> jadx

    • 已经看不到具体的操作了 ---> 反编译c代码
  • 反编译它的so文件的c代码 ---> IDA

    • 拿到apk---》后缀名改成zip----》使用压缩软件解压
    • 拿到咱们写的so文件,使用IDA反编译
    • 把so文件拖动到ida软件中
    • 点中exports

【2】反编译so文件

  • 使用压缩工具把 apk解压

  • 进入lib的arm64-v8a目录,看到so文件

  • 把so文件拖动到IDA中

  • 选择exports导出

  • 双击函数名,看到汇编

  • 按F5,把混编进行反编译

  • 找到F:\桌面\app-debug - 副本\lib\arm64-v8a\libutils.so

    • C文件前面会自动加 lib

  • 用IDA反编译
    • ida.exe 解32位so文件
    • ida64.exe 解64位so文件

  • 编译文件

  • 点击Exports

  • 双击会显示汇编页面
    • 但是我们没有学过汇编,所以还要再编译成c
    • 按F5

  • 反编译出来和原来的代码相差不大

【三】优化代码运行程序

  • 当我们不使用自带的C++文件时,可以将文件删除

    • 删除native-lib.cpp文件

    • 删除注册

      # For more information about using CMake with Android Studio, read the
      # documentation: https://d.android.com/studio/projects/add-native-code.html
      
      # Sets the minimum version of CMake required to build the native library.
      
      cmake_minimum_required(VERSION 3.22.1)
      
      # Declares and names the project.
      
      project("demo11")
      
      # Creates and names a library, sets it as either STATIC
      # or SHARED, and provides the relative paths to its source code.
      # You can define multiple libraries, and CMake builds them for you.
      # Gradle automatically packages shared libraries with your APK.
      
      # Searches for a specified prebuilt library and stores the path as a
      # variable. Because CMake includes system libraries in the search path by
      # default, you only need to specify the name of the public NDK library
      # you want to add. CMake verifies that the library exists before
      # completing its build.
      
      find_library( # Sets the name of the path variable.
              log-lib
      
              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log)
      
      # 注册自己写的c文件
      add_library( # Sets the name of the library.
              utils  # 最终这个utils.c会编译成so文件,编译成的名字叫 lib + utils
      
              # Sets the library as a shared library.
              SHARED
      
              # Provides a relative path to your source file(s).
              utils.c) # 指定需要编译的c文件
      
      
      # Specifies libraries CMake should link to your target library. You
      # can link multiple libraries, such as libraries you define in this
      # build script, prebuilt third-party libraries, or system libraries.
      
      target_link_libraries( # Specifies the target library.
              utils  # 注册C文件,用空格分隔
      
              # Links the target library to the log library
              # included in the NDK.
              ${log-lib})
      
    • 主文件删除相关信息

      package com.dream.demo11;
      
      import androidx.appcompat.app.AppCompatActivity;
      
      import android.os.Bundle;
      import android.widget.TextView;
      
      import com.dream.demo11.databinding.ActivityMainBinding;
      
      public class MainActivity extends AppCompatActivity {
      
      
          private ActivityMainBinding binding;
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
      
              binding = ActivityMainBinding.inflate(getLayoutInflater());
              setContentView(binding.getRoot());
      
              // Example of a call to a native method
              TextView tv = binding.sampleText;
              // tv.setText(stringFromJNI()); // 函数是调用的C代码执行的结果
              tv.setText(String.valueOf(Utils.v1(3, 5))); // 调用C文件中的代码
          }
      
      }
      

【四】Java调用C案例之指针修改字符串

【1】在Utils.java中写方法

package com.dream.demo11;

public class Utils {

    // Used to load the 'demo11' library on application startup.
    static {
        // 对应c文件的文件名
        System.loadLibrary("utils");
    }

    // 定义一个方法,实现C中的某个方法相对应

    public static native String v2(String s);
}

【2】在 utils.c 中注册

#include <jni.h>
#include <syslog.h>

//
// Created by Administrator on 2023/8/2.
//

JNIEXPORT jstring JNICALL // jstring : JNI的签名
Java_com_dream_demo11_Utils_v2(JNIEnv *env, jclass clazz, jstring s) {
    // TODO: implement v2()
    // 传入字符串 dream 将 字符串第一个位置和第三个位置的字符串都改为 p,然后返回
    // char info[]  = {'d','r','e','a','m'};
    // 通过env指针,取到GetStringUTFChars结构体,初始化得到char指针
    char *info = (*env)->GetStringUTFChars(env, s, 0);
    syslog(LOG_ERR, "%s", info);

    // C语言,通过字符数据指针修改字符串
    info += 1;
    *info = 'p'; // 修改第一个位置字符为p

    info += 3;
    *info = 'p';// 修改第三个位置字符为p

    info -= 4; //指针回退

    syslog(LOG_ERR, "%s", info);

    return (*env)->NewStringUTF(env, info); // 把char类型指针,再转成字符串类型返回

}

【3】在主文件中调用该方法

package com.dream.demo11;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.dream.demo11.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {


    private ActivityMainBinding binding;

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(Utils.v2("dream"));

    }

}
  • 运行APP,发现字符被替换成功

【五】Java调用C案例之数组修改字符串

【1】在Utils.java中写方法

package com.dream.demo11;

public class Utils {

    // Used to load the 'demo11' library on application startup.
    static {
        // 对应c文件的文件名
        System.loadLibrary("utils");
    }

    // 定义一个方法,实现C中的某个方法相对应

    public static native String v3(String s);
}

【2】在utils.c中注册

#include <jni.h>
#include <syslog.h>

//
// Created by Administrator on 2023/8/2.
//


JNIEXPORT jstring JNICALL
Java_com_dream_demo11_Utils_v3(JNIEnv *env, jclass clazz, jstring s) {
    // TODO: implement v3()
    // 字符串数组
    char *info = (*env)->GetStringUTFChars(env, s, 0);
    info[0] = 'p';
    info[1] = 'p';

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

【3】在主文件中调用

package com.dream.demo11;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.dream.demo11.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {


    private ActivityMainBinding binding;

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(Utils.v3("dream"));

    }

}
  • 运行APP
    • 发现字符串被修改成功

【六】Java调用C案例之字符串拼接

【1】在Utils.java中写方法

package com.dream.demo11;

public class Utils {

    // Used to load the 'demo11' library on application startup.
    static {
        // 对应c文件的文件名
        System.loadLibrary("utils");
    }

    // 定义一个方法,实现C中的某个方法相对应

    public static native String v4(String name, String role);


}

【2】在utils.c中注册

#include <jni.h>
#include <syslog.h>
#include <malloc.h>

//
// Created by Administrator on 2023/8/2.
//

JNIEXPORT jstring JNICALL
Java_com_dream_demo11_Utils_v4(JNIEnv *env, jclass clazz, jstring name, jstring role) {
    // TODO: implement v4()
    // 拿到字符串
    char *nameString = (*env)->GetStringUTFChars(env, name, 0);
    char *roleString = (*env)->GetStringUTFChars(env, role, 0);

    // 对字符串进行拼接
    // 定义指针,分配内容
    // char *result = malloc(GetStringLen(nameString) + GetStringLen(roleString) + 1);
    char *result = malloc(strlen(nameString) + strlen(roleString) + 1);
    // 把名字copy到新定义的指针中
    strcpy(result, nameString);
    // 把角色拼接到后面
    strcat(result, roleString);

    syslog(LOG_ERR, "%s", result);

    // 返回字符串
    return (*env)->NewStringUTF(env, result);

}

【3】在主文件中调用

package com.dream.demo11;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.dream.demo11.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {


    private ActivityMainBinding binding;

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(Utils.v4("dream","admin"));

    }

}
  • 运行APP
    • 可以看到字符串被拼接成功

【七】Java调用C案例之字符串处理

【1】在Utils.java中写方法

package com.dream.demo11;

public class Utils {

    // Used to load the 'demo11' library on application startup.
    static {
        // 对应c文件的文件名
        System.loadLibrary("utils");
    }

    // 定义一个方法,实现C中的某个方法相对应
    public static native String v5(String data);


}

【2】在utils.c中注册

#include <jni.h>
#include <syslog.h>
#include <malloc.h>

//
// Created by Administrator on 2023/8/2.
//

// 定义下面函数调用的方法 --- 统计字符串的长度
int GetStringLen(char *dataString) {
    int count = 0;
    for (int i = 0; dataString[i] != '\0'; i++) {
        count += 1;
    }
    return count;
}

JNIEXPORT jstring JNICALL
Java_com_dream_demo11_Utils_v5(JNIEnv *env, jclass clazz, jstring data) {
    // TODO: implement v5()
    // "name=dream&age=19"
    char *urlParams = (*env)->GetStringUTFChars(env, data, 0);
    int size = GetStringLen(urlParams);
    // 统计字符串长度也可以使用
    // int size = strlen(urlParams);

    // v34 = {1,2,,,,,,,,,,,}
    char v34[size * 2]; // 定义字符数组,长度是传进来的字符串的两倍

    // 指向指针
    char *v28 = v34;

    for (int i = 0; urlParams[i] != '\0'; i++) {
        //syslog(LOG_ERR, "%02x", urlParams[i]);
        // 转成 16 进制 返回数据
        sprintf(v28, "%02x", urlParams[i]);
        v28 += 2;
    }

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

}

【3】在主文件中调用

package com.dream.demo11;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.dream.demo11.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {


    private ActivityMainBinding binding;

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(Utils.v5("name=dream&age=19")); // 函数是调用的C代码执行的结果

    }

}
  • 运行APP
    • 可以看到返回了一串二进制字符

【八】Java调用C案例之字节处理一

【1】在Utils.java中写方法

package com.dream.demo11;

public class Utils {

    // Used to load the 'demo11' library on application startup.
    static {
        // 对应c文件的文件名
        System.loadLibrary("utils");
    }

    // 定义一个方法,实现C中的某个方法相对应
    public static native String v6(byte[] data);

}

【2】在utils.c中注册

#include <jni.h>
#include <syslog.h>
#include <malloc.h>

//
// Created by Administrator on 2023/8/2.
//

JNIEXPORT jstring JNICALL
Java_com_dream_demo11_Utils_v6(JNIEnv *env, jclass clazz, jbyteArray data) {
    // TODO: implement v6()
    //    jbyte *byteArray = (*env)->GetByteArrayElements(env, data, 0);
    char *byteArray = (*env)->GetByteArrayElements(env, data, 0);
    int size = (*env)->GetArrayLength(env, data);

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

    for (int i = 0; byteArray[i] != '\0'; i++) {
        syslog(LOG_ERR, "%02x", byteArray[i]);
        sprintf(v28, "%02x", byteArray[i]);
        v28 += 2;
    }

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

【3】在主文件中调用

package com.dream.demo11;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.dream.demo11.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {


    private ActivityMainBinding binding;

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(String.valueOf(Utils.v6("name=dream&age=19".getBytes()))); // 函数是调用的C代码执行的结果

    }

}
  • 运行APP
    • 可以看到屏幕上返回了一串二进制数字

【九】Java调用C案例之字节处理二

【1】在Utils.java中写方法

package com.dream.demo11;

public class Utils {

    // Used to load the 'demo11' library on application startup.
    static {
        // 对应c文件的文件名
        System.loadLibrary("utils");
    }

    // 定义一个方法,实现C中的某个方法相对应

    public static native String v7(byte[] data);
}

【2】在utils.c中注册

#include <jni.h>
#include <syslog.h>
#include <malloc.h>

//
// Created by Administrator on 2023/8/2.
//

JNIEXPORT jstring JNICALL
Java_com_dream_demo11_Utils_v7(JNIEnv *env, jclass clazz, jbyteArray data) {
    // TODO: implement v7()

    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】在主文件中调用

package com.dream.demo11;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.dream.demo11.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {


    private ActivityMainBinding binding;

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(String.valueOf(Utils.v7("name=dream&age=19".getBytes()))); // 函数是调用的C代码执行的结果

    }

}
  • 运行APP
    • 可以看到在屏幕上显示了一串二进制数字

【十】C调用Java案例

  • Java调用C语言

    • 某些APP,加密函数如果使用Java开发,被反编译了后能直接被看到
    • 高级一些的操作,加密函数,不使用Java实现,而是JN开发,C实现
  • 某些公司可能

    • Java调用C
    • C写加密麻烦,有些人不愿意写
    • 于是C中又调用了Java的假面,完成加密,返回数据

【十一】C调用Java案例之使用static修饰

【1】静态方法创建

  • 在包下创建新文件
    • demo11\app\src\main\java\com\dream\demo11\Func.java
package com.dream.demo11;

public class Func {
    // 定义一个静态方法
    public static String getSign() {
        return "Sign001";
    }
}

【2】在Utils.java中写方法

package com.dream.demo11;

public class Utils {

    // Used to load the 'demo11' library on application startup.
    static {
        // 对应c文件的文件名
        System.loadLibrary("utils");
    }

    // ****** C 调用 Java 案例
    // 触发C代码中的代码 Java_com_dream_demo11_Utils_v8 方法执行 --->  内部再调用 Func的 getSign 静态方法
    public static native String v8();

}

【3】在utils.c中注册

#include <jni.h>
#include <syslog.h>
#include <malloc.h>


// v8 方法 ----> 内部调用Java的Func类下的 getSign 静态方法
JNIEXPORT jstring JNICALL
Java_com_dream_demo11_Utils_v8(JNIEnv *env, jclass clazz) {
    // TODO: implement v8()
    // (1) 找到类
    jclass cls = (*env)->FindClass(env,"com/dream/demo11/Func");
    // (2)找到类中的静态方法
    // "()Ljava/lang/String;" :JNI签名
    // () 表示没参数
    // Ljava/lang/String; 表示返回的类型是字符串类型
    jmethodID method1 = (*env)->GetStaticMethodID(env, cls, "getSign", "()Ljava/lang/String;");
    //(3)执行静态方法 getSign
    jstring res1 = (*env)->CallStaticObjectMethod(env, cls, method1);

    // 由于是 jstring 的字符串 ,直接返回即可
    return res1;
    // return (*env)->NewStringUTF(env, result);

}

【4】在主文件中调用

package com.dream.demo11;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.dream.demo11.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {


    private ActivityMainBinding binding;

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(Utils.v8()); // 函数是调用的C代码执行的结果
    }

}

【十二】C调用Java案例之静态方法

【1】静态方法创建

  • 在包下创建新文件
    • demo11\app\src\main\java\com\dream\demo11\Func.java
package com.dream.demo11;

public class Func {
    // 定义一个静态方法
    public static String getSign() {
        return "Sign001";
    }

    // 重载 : 函数名相同 参数不同
    public static String getSign(int a, int b) {
        return "sign002";
    }

    public static String getSign(String s) {
        return "sign003";
    }

    public static int getSign(String prev, int v1) {
        return 100;
    }
}

【2】在Utils.java中写方法

package com.dream.demo11;

public class Utils {

    // Used to load the 'demo11' library on application startup.
    static {
        // 对应c文件的文件名
        System.loadLibrary("utils");
    }

    // 定义一个方法,实现C中的某个方法相对应

    // ****** C 调用 Java 案例
    // 触发C代码中的代码 Java_com_dream_demo11_Utils_v8 方法执行 --->  内部再调用 Func的 getSign 静态方法
    public static native String v8();

}

【3】在utils.c中注册

#include <jni.h>
#include <syslog.h>
#include <malloc.h>

//
// Created by Administrator on 2023/8/2.
//

// v8 方法 ----> 内部调用Java的Func类下的 getSign 静态方法
JNIEXPORT jstring JNICALL
Java_com_dream_demo11_Utils_v8(JNIEnv *env, jclass clazz) {
    // TODO: implement v8()
    // (1) 找到类
    jclass cls = (*env)->FindClass(env, "com/dream/demo11/Func");

    // ******第一组开始*******
    // (2)找到类中的静态方法
    // "()Ljava/lang/String;" :JNI签名
    // () 表示没参数
    // Ljava/lang/String; 表示返回的类型是字符串类型
    // jmethodID method1 = (*env)->GetStaticMethodID(env, cls, "getSign", "()Ljava/lang/String;");
    //(3)执行静态方法 getSign
    // jstring res1 = (*env)->CallStaticObjectMethod(env, cls, method1);
    // ******第一组结束*******


    // ******第二组开始*******
    // (2.1) 找到类中的静态方法
    //jmethodID method2 = (*env)->GetStaticMethodID(env, cls, "getSign", "(II)Ljava/lang/String;");
    // jstring res1 = (*env)->CallStaticObjectMethod(env, cls, method2, 88, 99);
    // ******第二组结束*******

    // ******第三组开始*******
    jmethodID method3 = (*env)->GetStaticMethodID(env, cls, "getSign","(Ljava/lang/String;)Ljava/lang/String;");
    jstring res1 = (*env)->CallStaticObjectMethod(env, cls, method3, (*env)->NewStringUTF(env,"dream"));
    // ******第三组结束*******


    // ******第四组开始*******
    // 因为 这个res1是数字类型,但是 return 回去的是字符串类型,所以直接运行会报错,这里只是演示代码
    //jmethodID method4 = (*env)->GetStaticMethodID(env, cls, "getSign", "(Ljava/lang/String;I)I");
    //jint res1 = (*env)->CallStaticIntMethod(env, cls, method4,(*env)->NewStringUTF(env, "dream"), 999);
    // 可以拿到数字对数字再进行转字符串操作然后返回
    // ******第四组结束*******

    // 可以继续对字符串/数字进行操作

    // 由于是 jstring 的字符串 ,直接返回即可
    return res1;
    // return (*env)->NewStringUTF(env, result);

}

【4】在主文件中调用

package com.dream.demo11;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.dream.demo11.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {


    private ActivityMainBinding binding;

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(String.valueOf(Utils.v8())); // 函数是调用的C代码执行的结果
    }
}

【十三】C调用Java案例之成员方法

【1】静态方法创建

  • 在包下创建新文件
    • demo11\app\src\main\java\com\dream\demo11\Func.java
package com.dream.demo11;

public class Func {

    // 定义一个变量 --- 属性
    public String name;

    // 构造函数
    // 初始化赋值
    public Func(String name) {
        this.name = name;
    }
    // 成员方法 --- 调用属性
    public String ShowName() {
        return this.name;
    }

}

【2】在Utils.java中写方法

package com.dream.demo11;

public class Utils {

    // Used to load the 'demo11' library on application startup.
    static {
        // 对应c文件的文件名
        System.loadLibrary("utils");
    }

    // ****** C 调用 Java 案例
    public static native String v9();
}

【3】在utils.c中注册

#include <jni.h>
#include <syslog.h>
#include <malloc.h>


JNIEXPORT jstring JNICALL
Java_com_dream_demo11_Utils_v9(JNIEnv *env, jclass clazz) {
    // TODO: implement v9()
    // 调用 Func的成员方法
    // (1) 找到类
    jclass cls = (*env)->FindClass(env, "com/dream/demo11/Func");

    // (2)找到构造方法
    jmethodID init = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");

    // (3)执行构造方法得到对象
    jobject cls_obj = (*env)->NewObject(env, cls, init, (*env)->NewStringUTF(env, "dream"));

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

    // (5)使用对象调用成员方法
    jstring res1 = (*env)->CallObjectMethod(env, cls_obj, method1);

    return res1;
}

【4】在主文件中调用

package com.dream.demo11;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.dream.demo11.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {


    private ActivityMainBinding binding;

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(String.valueOf(Utils.v9())); // 函数是调用的C代码执行的结果

    }

}

【补充】静态注册和动态注册

【1】静态注册

  • 上述编写的C语言的函数和Java的对应关系,在函数名上就可以体现,例如:
Java_com_dream_demo11_Utils_v9
Java_com_dream_demo11_Utils_v8
  • 这种称为静态注册,如果是静态注册,那么在逆向时,是比较方便的,直接可以找到函数在C中的实现。例如:车智赢。

【2】动态注册

  • 有的APP为了增加逆向难度,会使用动态注册
    • 看到Java中的方法后,不知道它对应的C是谁

(1)文件创建

  • demo11\app\src\main\cpp\dynamic.c

  • demo11\app\src\main\java\com\dream\demo11\Dynamic.java

package com.dream.demo11;

public class Dynamic {
    static {
        System.loadLibrary("dynamic");
    }

    public static native int vv1(int a1, int a2);

    public static native int vv2(String s);
}

(2)注册app

  • CMakeLists.txt
# 注册自己写的c文件
add_library( # Sets the name of the library.
        dynamic  # 最终这个dynamic.c会编译成so文件,编译成的名字叫 lib + dynamic

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        dynamic.c) # 指定需要编译的c文件
    
    
target_link_libraries( # Specifies the target library.
        utils dynamic # 注册C文件,用空格分隔

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

(3)在 dynamic.c 中注册

#include <jni.h>

//
// Created by Administrator on 2023/8/2.
//

// 静态注册 有 对应关系
//JNIEXPORT jint JNICALL
//Java_com_dream_demo11_Dynamic_vv1(JNIEnv *env, jclass clazz, jint a1, jint a2) {
//    // TODO: implement vv1()
//
//}

// 动态注册 --- 固定写一个方法 --- JNI_OnLoad
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},
};

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/dream/demo11/Dynamic");
    // 将类中的方法注册到JNI中 (RegisterNatives)
    int res = (*env)->RegisterNatives(env, clazz, gMethods, 1);

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

    return JNI_VERSION_1_6;
}

(4)主文件中调用

package com.dream.demo11;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.dream.demo11.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {


    private ActivityMainBinding binding;

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;

        tv.setText(String.valueOf(Dynamic.vv1(11, 22))); // 函数是调用的C代码执行的结果 ---- 动态注册

    }

}
  • 运行app
    • 页面显示 33

【十四】静态注册和动态注册逆向

【1】静态注册逆向

  • 使用IDA打开文件后,如果是静态注册,函数跟明显

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

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

  • jadx反编译

  • 逆向so文件

  • 搜索关键字 java

  • 找到位置

【2】动态注册逆向

  • 使用IDA打开文件后,如果是动态注册,需要去找jni_onload

  • 这是我们写的函数

  • 找到我们写的c加密

【补充】JNI签名对应的关系

【1】JNI和JNI签名是什么

  • JNI (Java Native Interface) 是一种编程接口,它允许Java应用程序与本地代码(如C、C++)进行交互。
  • JNI签名指的是用于表示JNI方法的描述符,它告知Java虚拟机如何在本地库中找到对应的函数。

【2】JNI签名由以下几部分组成:

  • 返回类型:
    • 表示JNI方法的返回类型
    • 例如"V"表示void,"I"表示int,"Ljava/lang/String;"表示返回String类型等。
  • 参数类型列表:
    • 表示JNI方法的参数类型,按照参数顺序排列。
    • 参数类型使用单个字符来表示,例如"Ljava/lang/String;"表示参数为String类型。
  • 方法名称:
    • 表示该JNI方法的名称。

【3】简写的字符来表示常见的参数类型

  • "Z"表示boolean类型
  • "B"表示byte类型
  • "C"表示char类型
  • "S"表示short类型
  • "I"表示int类型
  • "J"表示long类型
  • "F"表示float类型
  • "D"表示double类型
  • "Ljava/lang/Object;"表示Object类型
  • "[I"表示int数组类型

【4】示例JNI签名及其对应的Java方法:

  • Java方法:
    • public static native void doSomething();
    • JNI签名:()V
  • Java方法:
    • public static native int add(int a, int b);
    • JNI签名:(II)I
  • Java方法:
    • public static native String getString(int[] array);
    • JNI签名:([I)Ljava/lang/String;