前言
上一章我们对JNI静态注册进行了讨论,现在我们就基于Android Studio和C++来讨论一下JNI的动态注册。因为要熟悉不同平台的开发环境所以JNI的这一系列我都换了不同的环境。其实我们依然可以使用上一章手动的方法编写动态注册的so,但是Android Studio提供了CMake,方便了我们的开发流程
环境
Android Studio中需安装CMake
image.png
创建项目
image.png
这里不同的Android Studio版本中会有所出入,不过你在创建的时候找找应该都能找到
image.png
image.png
完成
image.png
IDE已经帮我们写了一个简单的JIN例子,我们只要照着他修改即可
这里我们关注三个文件
- MainActivity.java 这个是我们Android的主界面,我也将native写进了这里
- CMakeLists.txt 这个是CMake的配置文件(它取代了我们以前Application.mk和Android.mk)
- native-lib.cpp 我们so的源码文件(这里我使用c++编写)
MainActivity.java
package com.example.jnitest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
native-lib.cpp
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_jnitest_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
CMakeLists.txt
# 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.
# 指定CMark构建本地库时所需的最小版本
cmake_minimum_required(VERSION 3.4.1)
# 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.
# 创建并命名库 native-lib 动态库的名称,“SHARED ”表示加载的是动态库。 native-lib.cpp源码路径
# set(DIR ${CMAKE_SOURCE_DIR}/libs) ${CMAKE_SOURCE_DIR}表示的是CMakeLists.txt所在的目录 其实应该有一段路径设置的 这里cmake应该默认 这里我们不深究
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp)
# 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.
# 引用android系统的系统库,如log库,是打印日志用的库 我们也可以自己实现
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)
# 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.
# 与第三库链接生成目标native-lib库
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib})
MainActivity.java与native-lib.cpp的内容不再赘述(前两章都有说明),CMakeLists.txt注释中简单进行了说明,其实英文注释已经说得很清楚了。
运行
image.png
编写我们自己的c++文件
我们只需要在原来的基础上拷贝修改一下即可
image.png
修改三个文件如下:
MainActivity.java
package com.example.ancpp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
System.loadLibrary("Mylib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(dynamicJavaMethod2());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native void dynamicJavaMethod1(int i);
public native String dynamicJavaMethod2();
}
CMakeLists.txt
# 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.
# 指定CMark构建本地库时所需的最小版本
cmake_minimum_required(VERSION 3.4.1)
# 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.
# 创建并命名库 native-lib 动态库的名称,“SHARED ”表示加载的是动态库。 native-lib.cpp源码路径
# set(DIR ${CMAKE_SOURCE_DIR}/libs) ${CMAKE_SOURCE_DIR}表示的是CMakeLists.txt所在的目录 其实应该有一段路径设置的 这里cmake应该默认 这里我们不深究
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp)
add_library( # Sets the name of the library.
Mylib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
Mylib.cpp)
# 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.
# 引用android系统的系统库,如log库,是打印日志用的库 我们也可以自己实现
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)
# 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.
# 与第三库链接生成目标native-lib库
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib})
target_link_libraries( # Specifies the target library.
Mylib
# Links the target library to the log library
# included in the NDK.
${log-lib})
Mylib.cpp(重点是Mylib.cpp的编写,因为IDE给的例子是静态注册,我们这是动态注册)
#include<jni.h>
#include<android/log.h>
JavaVM *_vm;
//动态注册的第一个方法
void dynamicJNIMethod1(JNIEnv *env, jobject jobj, jint ji) {
__android_log_print(ANDROID_LOG_ERROR, "JNI", "第一个动态注册方法传递的参数:%d", ji);
}
//动态注册的第二个方法
jstring dynamicJNIMethod2(JNIEnv *env, jobject jobj) {
jstring returnStr = env->NewStringUTF("第二个动态注册方法返回的字符串");
return returnStr;
}
//需要动态注册的方法组
static JNINativeMethod gMethods[] = {
{"dynamicJavaMethod1", "(I)V",(void *) dynamicJNIMethod1},
//()Ljava/lang/String; 函数签名不要用.
{"dynamicJavaMethod2", "()Ljava/lang/String;", (void *) dynamicJNIMethod2}
};
//需要动态注册的类全名
static const char *mClassName = "com/example/ancpp/MainActivity";
//此函数通过调用RegisterNatives方法来注册我们的函数
static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *getMethods,
int methodsNum) {
jclass clazz;
//找到声明native方法的类
clazz = env->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
jint result = 0;
result = env->RegisterNatives(clazz, gMethods, methodsNum);
//注册函数 参数:java类 所要注册的函数数组 注册函数的个数
if ( result< 0) {
__android_log_print(ANDROID_LOG_ERROR, "JNI", "RegisterNatives:%d", result);
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv *env) {
const char *className = "com/example/ancpp/MainActivity";
return registerNativeMethods(env, className, gMethods, sizeof(gMethods) / sizeof(gMethods[0]));
}
//动态注册时都会执行到这个方法中
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;//定义JNI Env
jint result = -1;
/*JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint);
*/
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
return result;
}
/*开始注册
* 传入参数是JNI env
* 以registerNatives(env)为例说明
*/
if (!registerNatives(env)) {
return -1;
}
_vm = vm;
result = JNI_VERSION_1_6;
return result;
}
运行
image.png
代码注释已经写得很清楚了,我们只讨论一下细节和要注意的地方
-
方法签名
我们要注册类的native方法必须在JNINativeMethod数组中指定方法签名,我们如何得到这个签名呢?你可以手写,单java提供了获取方法签名的工具
进入到你项目的classes文件夹
1、javap -s packagename.classname
2、javap -s -p packagename.classname
-s表示打印签名信息
-p表示打印所有函数和成员的签名信息,默认只打印public的签名信息。
例如:javap -s -p com.example.ancpp.MainActivity 得到以下信息(descriptor后面就是签名信息):
public class com.example.ancpp.MainActivity extends androidx.appcompat.app.AppCompatActivity {
public com.example.ancpp.MainActivity();
descriptor: ()V
protected void onCreate(android.os.Bundle);
descriptor: (Landroid/os/Bundle;)V
public native java.lang.String stringFromJNI();
descriptor: ()Ljava/lang/String;
public native void dynamicJavaMethod1(int);
descriptor: (I)V
public native java.lang.String dynamicJavaMethod2();
descriptor: ()Ljava/lang/String;
static {};
descriptor: ()V
- JNIEnv 与 JavaVM
JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局仅仅有一个;
JNIEnv : JavaVM 在线程中的代表, 每一个线程都有一个, JNI 中可能有非常多个 JNIEnv;
详细介绍可以看一下这篇文章:https://www.cnblogs.com/mfmdaoyou/p/7252031.html
总结
在java代码中无论是静态注册还是动态注册其实都没什么变化,主要体现在c和c++代码中。
-
静态注册
需要遵守jni的命名规则,并且将写的函数导出,这样jni就能一一对应c函数和java方法的关系 -
动态注册
动态注册在java层loadLibrary的时候回去执行so中的JNI_OnLoad函数,所以这个函数必须导出,在这里面我们使用env->RegisterNatives的函数去依照JNINativeMethod数组给的信息去指定的类中建立方法调用对应关系。
在逆向分析so的时候使用动态注册可以有更好的隐秘性,因为我们只导出JNI_OnLoad,很多时候脱壳的操作就会在这里。











网友评论