项目
- 创建的项目
idea
已经集成 直接选择native c++
项目即可 - 在
main
目录 有一个CMakeLists.txt
和native-lib.cpp
是对应关系 - 其中
cpp
是 C 层实现的算法txt
是指导cpp
文件如何进行编译 find_library
so
依赖 一般存在于系统中- 创建项目的时候 包名字 尽量不要有大写 不知道为什么 出问题卡了一个小时
add_library(
native-lib # 链接库名 生成的 so 名字
SHARED # 生成链接库的 类型 这里是共享库
native-lib.cpp # 编译源文件的 相对路径
)
- 在
cpp
文件中可以看到函数 自带两个参数JNIEnv* env
:Env
环境 相当于java
和c
交互的桥梁 通过Env
可以调用java
层 转换java
数据类型jobject
/jclass
: 两种情况 当native
函数 被调用的时候 谁调用了这个 函数jobject
就是谁. 当native
是静态方法的时候 第二个参数就是jclass
相对的 谁调用了 这个静态方法jclass
就指向谁- 第三个参数开始 就是在
java
层定义的函数参数
- 在 静态注册 开头可以看到
extern "C" JNIEXPORT jstring JNICALL
- 因为静态注册是根据包名拼接进行查找函数 当不指定
extern "C"
就会以C++
的形式进行编译C++
会自动进行符号修饰 导致静态注册找不到函数 JNIEXPORT
表示函数可见性EXPORT
的函数将会出现在so
导出表 也必须出现jstring
表示返回值是java
层的string
类型JNICALL
宏未定义
- 因为静态注册是根据包名拼接进行查找函数 当不指定
- 注意 在
c
和java
之间的数据交互 是需要进行数据转换的 - 配置生成所有版本的
so
文件
defaultConfig {
ndk {
adiFilters "armeabi-v7a", "arm640v8a", "x86", "x86_64"
}
}
在 C 层的 LOG 输出
#include <android/log.h> // 必要头文件
#define TAG "Ass"
#define LogD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
// 参数对应为 日志等级 日志标签 格式化标签 参数 (这里如果是 c++ 参数 需要转换为 c 参数)
__android_log_print(ANDROID_LOG_DEBUG, "Assists", "c str : %s", hello.c_str()); // 函数内使用语句进行输出
LogD("c str : %p", clazz); // 函数内使用语句进行输出
Env
JNI_OnLoad
// 一个最简洁的 JNI_OnLoad 函数 在 so 加载之前会被执行 可以在这进行 函数注册
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv* env = nullptr; // 定义一个空指针 用于 GetEnv 的第一个 二级指针参数
if(vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) // 判断是否成功获取 Env
return JNI_ERR;
return JNI_VERSION_1_6;
}
静态注册
- 当 函数 首次被调用的时候 就会完成
C
和java
层函数的绑定 未找到则报错 - 静态注册的函数 必须在 导出表 内
extern "C" JNIEXPORT jstring JNICALL
// 以下路径 必须 完全对应 java 层目录结构 指定 到函数
Java_com_AndroidUtils_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
动态注册
- 动态注册一般在
JNI_OnLoad
被调用时就已经注册完毕
jstring stringFromJNI_Reg(JNIEnv *env, jobject) {
return env->NewStringUTF("stringFromJNI_Reg");
}
// typedef struct {
// const char* name; // 指定 Java 层函数名
// const char* signature; // smali 形式的 参数 与 返回值
// void* fnPtr; // 一个指针 指向 C层函数
// } JNINativeMethod;
static const JNINativeMethod nativeMethods[] = {
// 内部每个 都是上面的结构体
{
"stringFromJNI_Reg",
"()Ljava/lang/String;",
(jstring *) stringFromJNI_Reg
},
};
// 一个最简洁的 JNI_OnLoad 函数 在 so 加载之前会被执行 可以在这进行 函数注册
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = nullptr; // 定义一个空指针 用于 GetEnv 的第一个 二级指针参数
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) // 判断是否成功获取 Env
return JNI_ERR;
// 当没有报错 成功获取到 Env 则开始动态注册
jclass jClass = env->FindClass("com/utils/MainActivity"); // 查找需要动态绑定的类
// 第一个参数 注册函数 所在的类
// 第二个参数 指定方法结构体 数组 JNINativeMethod
// 第三个参数 每个方法的大小 可以用 sizeof 计算 防止不同 位数 cpu 大小不同
env->RegisterNatives(jClass, nativeMethods, sizeof(nativeMethods) / sizeof(JNINativeMethod));
return JNI_VERSION_1_6;
}
- **注意 : **
C++
版本的动态注册 就是对C
版本的一个封装 隐藏掉了 第一个参数JNIEnv
所以当so
拖入ida
进行反编译的时候 第一个参数 都可以忽略掉 所有函数参数位置都需要后移 一位
Jni 调用 java
JNI
创建 java
层对象
- 通过获取对象的
init
构造方法new
一个对象
jclass cls = env->FindClass("com/utils/Test");
jmethodID methodId = env->GetMethodID(cls, "<init>", "()V");
jobject obj = env->NewObject(cls, methodId);
LogD("C << %p", obj)
- 另外一种方式 创建 对象
jclass cls = env->FindClass("com/utils/Test");
jmethodID methodId = env->GetMethodID(cls,"<init>", "(Ljava/lang/String;)V");
jobject obj = env->AllocObject(cls); // 分配类内存空间 并没有进行实例化
jstring c_str = env->NewStringUTF("c_str"); // 因为上面调用一个需要参数的 构造函数 所以这 new 一个 java 字符串
// 这里调用父类方法 进行实例化 最终实例化的对象就是在上面分配的 obj 空间内
env->CallNonvirtualVoidMethod(obj,cls,methodId,c_str);
LogD("C << %p", obj)
JNI
操作 java
属性
// 首先还是获取类对象
jclass cls = env->FindClass("com/utils/Test");
// 然后获取 Field ID
jfieldID fieldId = env->GetStaticFieldID(cls,"name", "Ljava/lang/String;");
// 获取 java 层字段
jstring j_str = static_cast<jstring>(env->GetStaticObjectField(cls, fieldId));
// 设置 指定字段的值
env->SetStaticObjectField(cls,fieldId,env->NewStringUTF("C value"));
// 强转为 C 层类型
char* c_str = const_cast<char *>(env->GetStringUTFChars(j_str, nullptr));
LogD("C << %s", c_str)
JNI
调用 java
方法
- 大体上调用流程都一样
- 只要分辨返回值 参数
// 调用静态方法 无参
// 首先还是获取类对象
jclass cls = env->FindClass("com/utils/Test");
// 找到方法 ID
jmethodID methodId = env->GetStaticMethodID(cls,"staticMethods","()V");
// 调用指定方法
env->CallStaticVoidMethod(cls,methodId);
// 调用实例方法 传参
// 首先还是获取类对象
jclass cls = env->FindClass("com/utils/Test");
// 找到方法 id
jmethodID methodID = env->GetMethodID(cls, "MethodsArgs", "(Ljava/lang/String;)V");
// 因为是实例方法 所以需要 new 出来一个对象
// 获取 init
jmethodID JInitID = env->GetMethodID(cls, "<init>", "()V");
// 实例化一个对象
jobject obj = env->NewObject(cls, JInitID);
// 调用实例方法
env->CallVoidMethod(obj, methodID, env->NewStringUTF("C value"));
- 可以看到有
CallVoidMethodV
CallVoidMethodA
CallVoidMethod
三种调用方式 CallVoidMethod
内部其实就是CallVoidMethodV
的封装 后面的可变参数 在内部进行了处理 就不要自己操作CallVoidMethodA
同理 不过 最终处理完毕的是一个 指针 再进行调用- **平时 直接 使用
CallVoidMethod
即可 **
JNI
调用 java
层父类方法
- 前面 创建 对象的时候已经使用过了
env->CallNonvirtualVoidMethod
- 所有 带有
Nonvirtual
的env
函数 都是 调用父类方法 参数相同
全局引用
jclass GlobalRefCls;
extern "C" JNIEXPORT jstring JNICALL
Java_com_utils_MainActivity_stringFromJNI(JNIEnv *env, jobject) {
// 首先还是获取类对象 (通过这种方式获取的类 都是局部引用 在函数执行完毕之后 就会被释放)
// 就算你把定义 cls 的代码放在全局也不行
// 如果希望 跨函数使用 需要套一层 并且 定义 放在 全局
jclass cls = env->FindClass("com/utils/Test");
// 将 cls 变为全局引用
// 以下 cls 就可以在其他函数中使用
GlobalRefCls = static_cast<jclass>(env->NewGlobalRef(cls));
// 当需要释放的时候就要调用
env->DeleteGlobalRef(GlobalRefCls);
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
- 这里的释放不只是 全局引用需要释放
- 获取到的 指针类型 都需要释放
nullptr
注意这个类型的释放
init
和 initarray
- 两个函数的执行优先级比
JniOnLoad
高init > initarray > JniOnLoad
- 一般加固都会把
so
解密放在这两个内initarray
可以有多个函数 执行顺序也可以不一样 - 一般执行到
JniOnLoad
的时候so
就已经是解密状态了
// 函数名字可以随便起
// 如果 constructor 后面没有跟函数 那么就是按顺序执行 initArray 函数
// 在内部 跟上数值 ( 推荐 大于 100 的是数) 这个值越小越早执行
// 如果没有指定数字 会在最后执行
// 在 so 逆向的时候 已经排好顺序了
__attribute__ ((constructor(201))) void initArray2(){
LogD("initArray2 call")
}
__attribute__ ((constructor(200))) void initArray1(){
LogD("initArray1 call")
}
// 在编译之后 名字就变成 .init_proc 了
extern "C" void _init(){
LogD("_init call")
}
onCreate
native
化
- 其实就是通过 重新 定义一个
onCreate
增加native
- 然后 动态绑定 在
c
层实现java
层的全部流程 即可 - 相当于前面所有流程的结合使用