SDK、NDK 和 JNI、SO、ELF 的关系
NDK是安卓原生开发工具包,作用:把 C/C++ 代码 → 编译成 .so(ELF 文件)
SDK是安卓Java/Kotlin 开发工具包 ,作用:写 APP 界面、逻辑、四大组件。
JNI是Java ↔ C/C++ 的调用桥梁
| 概念 | 作用 |
|---|---|
| NDK | 安卓原生开发工具包,作用:把 C/C++ 代码 → 编译成 .so(ELF 文件) |
| SDK | 安卓Java/Kotlin 开发工具包 ,作用:提供 API、编译器、模拟器 |
| JNI | Java ↔ C/C++ 的调用桥梁 |
关系链
1 | Java 代码 (SDK 编写) |
JNI 基础开发流程
完整流程图
1 | Kotlin/Java 声明 native 方法 |
第一步:Kotlin声明 native 方法
1 | class MainActivity : AppCompatActivity() { |
- 这是安卓的 页面(Activity)
- 所有页面都继承自
AppCompatActivity
`System.loadLibrary(“mylib”);
- 加载 C/C++ 编译好的 libmylib.so 动态库文件
Log.d(标签, 内容)
- 把返回的字符串打印到 Logcat 日志,你就能看到 C++ 传回来的内容
第二步:编写 C/C++ 实现
1 |
|
第一个方法:stringFromJNI
extern "C"
- 告诉编译器:按 C 语言格式编译
- 不加的话,函数名会被打乱,Kotlin 找不到方法
JNIEXPORT - JNI 标记:这个函数可以被外部调用
jstring - 返回值类型= Java/Kotlin 里的 String
JNICALL - JNI 调用约定(固定写法,不用管)
参数
1 | JNIEnv* env |
- JNI 环境指针
- 用来创建字符串、操作数组等
1 | jobject |
- 相当于 Kotlin 里的
this - 指向调用该方法的 Activity 对象
1 | return env->NewStringUTF("Hello from C++!"); |
- 创建一个 C++ 字符串
- 转成 JNI 字符串(jstring)
- 返回给 Kotlin
第二个方法:add
就是实现了一个a + b
第三步:CMakeLists.txt
编译 C/C++ 代码成 .so 库的配置文件
1 | cmake_minimum_required(VERSION 3.22.1)//指定 CMake 最低版本 |
第四步:build.gradle 关联
1 | android { |
JNI 数据类型对照表
| 签名符号 | C/C++ | Java/Ketlin |
|---|---|---|
| V | void | void |
| Z | jboolean | boolean |
| I | jint | int |
| J | jlong | long |
| D | jdouble | double |
| F | jfloat | float |
| B | jbyte | byte |
| C | jchar | char |
| S | jshort | short |
| [Z | jbooleanArray | boolean[] |
| [I | jintArray | int[] |
| [J | jlongArray | long[] |
| [D | jdoubleArray | double[] |
| [F | jfloatArray | float[] |
| [B | jbyteArray | byte[] |
| [C | jcharArray | char[] |
| [S | jshortArray | short[] |
| Ljava/lang/String; | jstring | String |
| L 完整包名加类名; | jobject | class |
静态注册与动态注册
JVM查找Native方法有两种方式:
1、按照JNI规范的命名规则进行查找,这种方式叫静态注册。
2、调用JNI提供的RegisterNatives函数,将本地函数注册到JVM中,这种方式叫动态注册
这两种方法就是JNI将Java方法与Native的方法对应联系起来的
静态注册
静态注册指的是在编译时或程序启动时,就已经确定了注册的内容。也就是说,静态注册发生在编译或程序启动时,且通常是在源代码中显式声明的。
C语言的函数和Java层native方法的对应关系,在函数名上就可以体现,逆向很方便,直接在so文件的导出函数里面以Java*包名*类名_方法名就可以找到对应java层中声明的native方法
静态注册流程
1 | System.loadLibrary("mylib") |
例题:huhstsec平台的Native层反编译(静态注册)
首先将下载好的APK附件拖入jadx中进行查看,对主要函数进行分析

由此我们可以知道summertrain就是所加载的so文件
对APK文件进行解压,我们可以直接在lib里面看到几个文件夹

这些文件夹分别代表不同的设备类型
关于Android系统整体架构,请看:Android系统整体架构
我们点进V8a,将唯一的一个so文件拖入IDA中进行分析,按照刚刚所述,那么这个题目我们要找的函数名为:Java_com_swdd_summertrain_MainActivity_Check
找到对应函数即可直接获得flag
动态注册
在 SO 加载时,通过 JNI_OnLoad 函数手动建立 Java 方法与 C 函数的映射关系
动态注册流程
1 | System.loadLibrary("mylib") |
动态注册的优点是可以自由命名 Native 方法,缺点是如果 Native 方法过多,操作比较麻烦。
例题:huhstsec平台的动态注册
很明显让我们到so文件中去找
丢到IDA中反编译没有查到Java+包名+类名+Native方法名类型的函数
查找JNI_OnLoad函数

点开off_37A90函数

点开sub_172E0函数去看

这里_mm_xor_si128 是 Intel 提供的 SIMD指令集
在函数sub_172E0 中,使用 mm_xor_si128 函数时传入了 -1LL 作为其中一个参数。在计算机中,整数 -1 在二进制补码表示下是全 1。所以这里异或就相当于取反操作
整个函数讲的就是:获取一个字符串,对其进行处理(按字节取反),与特定内存区域比较,创建一个 Java 的 Boolean 对象,最后清理资源并返回该对象的引用。不过,memcmp 的结果未被使用。
找到密文后进行取反操作就可以得到明文
二者之间的差异






