何为NDK 安卓开发分SDK和NDK,SDK运行在ART/Dalvik虚拟机中,NDK运行在原生系统中,虚拟机执行效率不如原生效率高,要谈两者的区别,还是直接放一个SDK调用NDK的图,通过实际的调用流程来了解一下吧。
Zygote 是安卓系统启动时就会创建这么一个进程,它是所有安卓应用的父进程。Zygote在启动时,会加载系统核心框架库(如framework.jar,包含Activity、Service等核心类的实现),每启动一个应用,Zygote就会通过Fork自身来生成新进程,前面那些预加载的资源就会被所有子进程共享,避免重复加载。
所以Zygote是创建ART/Dalvik虚拟机的进程,它会完成虚拟机的初始化,确保fork出来的应用进程都继承一个准备就绪的、标准化的虚拟机环境。应用启动后,就会在这个虚拟机中加载APK中的Dex码,进行初始化Application,然后就是四大组件的初始化。
那图中的JNI又是个啥呢,在安卓中,JNI 是指 Java 原生接口,是一个规范。它定义了 Android 从受管理代码(使用 Java 或 Kotlin 编程语言编写)编译的字节码与原生代码(使用 C/C++ 编写)互动的方式。SDK调用NDK,是通过JNI与NDK进行调用映射和数据类型转换等操作,完成JAVA层到NATIVE层的通信。
话说回来,NDK就是为了高运行效率、跨平台复用、底层逻辑操作,按照JNI规范在原生空间中运行编译C\C++代码的Android原生开发工具集。
NDK开发初试 扔一点命令
shell 1 2 3 4 5 6 7 8 //查看当前界面Activity adb shell dumpsys window | grep mCurrentFocus //查看应用完整包名 adb shell pm list package //列出所有应用包,并对应完整路径 adb shell pm list package -f //显示系统详细信息 adb shell uname -a
通过Android Studio创建一个Native开发项目,在java层中写进行声明
1 2 3 4 5 public static native String mdString (String string) ;
对应的,native层的定义(C版)
1 2 3 4 5 6 7 8 JNIEXPORT jstring JNICALL Java_com_example_myndk_MainActivity_stringFromJNI ( JNIEnv* env, jobject thiz) { const char * hello = "Hello from C Native Code!" ; return (*env)->NewStringUTF (env, hello); }
其中,如果是已CPP进行native层的编写,就需要在最开始进行一个name mangling,因为CPP可能会进行重载,抽空去研究一下编译区别(咕咕咕)。
1 2 3 4 5 6 7 8 extern "C" JNIEXPORT jstring JNICALLJava_com_example_myndk_MainActivity_stringFromJNI ( JNIEnv* env, jobject thiz) { std ::string hello = "Hello from C++ Native Code!" ; return env->NewStringUTF(hello.c_str()); }
1. JNI 基本类型对照表 (Java Primitive Types)
Java 基本类型
JNI 对应类型
C/C++ 等效类型
占用字节
说明
boolean
jboolean
unsigned char
1
0=false,非0=true
byte
jbyte
signed char
1
8位有符号整数
char
jchar
unsigned short
2
UTF-16编码的字符
short
jshort
short
2
16位有符号整数
int
jint
int
4
32位有符号整数
long
jlong
long long / int64_t
8
64位有符号整数
float
jfloat
float
4
32位浮点数
double
jdouble
double
8
64位浮点数
2. JNI 引用类型对照表 (Java Reference Types)
Java 引用类型
JNI 对应类型
说明/使用场景
Object
jobject
所有引用类型的父类
Class
jclass
对应 Java 的 Class 对象 (用于静态方法/反射)
String
jstring
对应 Java 的 String
Throwable
jthrowable
对应 Java 的异常类
Object[]
jobjectArray
对象数组 (如 String[], User[])
boolean[]
jbooleanArray
布尔数组
byte[]
jbyteArray
字节数组 (常用作二进制数据传输)
char[]
jcharArray
字符数组
short[]
jshortArray
短整型数组
int[]
jintArray
整型数组
long[]
jlongArray
长整型数组
float[]
jfloatArray
浮点数组
double[]
jdoubleArray
双精度浮点数组
自定义类/接口
jobject
统统映射为 jobject
3. JNI 环境与 ID 类型 (JNI Special Types)
用途
JNI 类型
说明
JNI 环境指针
JNIEnv*
核心接口指针 (C使用 (*env)->, C++使用 env->)
虚拟机指针
JavaVM*
跨线程获取 JNIEnv 时使用
字段 ID
jfieldID
访问 Java 属性的凭据
方法 ID
jmethodID
调用 Java 方法的凭据
直接抄仨对照表扔这,用到的时候再看。
写完对应的代码,再扔一个命令行编译的命令
1 2 3 ./gradlew assembleDebug # 编译好后,通过 find . -name "*.apk" 找到对应的debug文件
JNI_HOOK 这里使用frida进行native层的被动调用,需要找到要hook的libso名称,还有要hook的函数名称,以及入参和出参类型,使用以下命令进行查找。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 Using USB device `KB2000` Agent injected and responds ok! _ _ _ _ ___| |_|_|___ ___| |_|_|___ ___ | . | . | | -_| _| _| | . | | |___|___| |___|___|_| |_|___|_|_| |___|(object)inject(ion) v1.11.0 Runtime Mobile Exploration by: @leonjza from @sensepost [tab] for command suggestions com.example.ezmd5 on (OnePlus: 11) [usb] # memory list modules libezmd5.so 0x718b26b000 45056 (44.0 KiB) /data/app/~~fmgadrx_ut4G29FifjXEmw==/com.example.ezmd5-vLBLnfw0npqVhTLBZNLi... //查找加载的libso名称 com.example.ezmd5 on (OnePlus: 11) [usb] # memory list exports libezmd5.so Save the output by adding `--json exports.json` to this command Type Name Address -------- -------------------------------------------- ------------ function Java_com_example_ezmd5_MainActivity_mdString 0x71da5e9a10 function _ZN7_JNIEnv17GetStringUTFCharsEP8_jstringPh 0x71da5e9b30 function _Z7MD5InitP7MD5_CTX 0x71da5e9b6c function _Z9MD5UpdateP7MD5_CTXPhj 0x71da5e9bcc function _Z8MD5FinalP7MD5_CTX 0x71da5e9d8c function _ZN7_JNIEnv12NewStringUTFEPKc 0x71da5ea0bc //查看libso文件的导出表,找到导出的函数名 //使用c++filt工具,查看函数传参类型 ➜ frida-scripts # c++filt -n _ZN7_JNIEnv17GetStringUTFCharsEP8_jstringPh _JNIEnv::GetStringUTFChars(_jstring*, unsigned char*)
找到我们想hook的JNI,通过ida或者c++filt工具查看传参
由于该jni是在启动时后就进行直接调用的,这里改用了lasting_yang师傅写的 frida_dump去hookdlopen,非常给力的fridahook项目,抽空研究研究(咕咕咕~)
https://github.com/lasting-yang/frida_dump
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 var can_hook_libart = false ;function hook_dlopen ( ) { Interceptor .attach (Module .findExportByName (null , "dlopen" ), { onEnter : function (args ) { var pathptr = args[0 ]; this .targeet = false ; if (pathptr !== undefined && pathptr != null ) { var path = ptr (pathptr).readCString (); if (path.indexOf ("libezmd5.so" ) >= 0 ) { this .target = true ; console .log ("[dlopen:]" , path); } } }, onLeave : function (retval ) { if (this .target ){ console .log ("find Exports => libezmd5.so" , JSON .stringify (Module .enumerateSymbols ())); } } }) Interceptor .attach (Module .findExportByName (null , "android_dlopen_ext" ), { onEnter : function (args ) { var pathptr = args[0 ]; if (pathptr !== undefined && pathptr != null ) { var path = ptr (pathptr).readCString (); console .log ("android_dlopen_ext:" , path); if (path.indexOf ("libezmd5.so" ) >= 0 ){ this .target = true ; } } }, onLeave : function (retval ) { if (this .target ){ console .log ("find Exports => " , JSON .stringify (Module .enumerateExports ("libezmd5.so" ))); const matched_export = Module .enumerateExports ("libezmd5.so" ).filter (function (exported ) { return exported.name .indexOf ("Java_com_example_ezmd5_MainActivity_mdString" ) >= 0 ; }); const matched_addr = matched_export[0 ]["address" ]; console .log ("find Export addr => " , matched_addr); var md5String = new NativeFunction (matched_addr, 'pointer' , ['pointer' , 'pointer' , 'pointer' ]); Interceptor .replace (matched_addr, new NativeCallback ((env, jclass, jstring )=> { console .log ("111>" ) console .log ("jstring =>" , Java .vm .getEnv ().getStringUtfChars (jstring, null ).readCString ()) var string1 = "ghbbhc" ; var jstring1 = Java .vm .getEnv ().newStringUtf (string1); var retval = md5String (env, jclass, jstring1); console .log ("new md5 => " , Java .vm .getEnv ().getStringUtfChars (retval,null ).readCString ()); return retval; }, 'pointer' , ['pointer' , 'pointer' , 'pointer' ])) } } }); } setImmediate (hook_dlopen);
JNI主动调用 1 2 3 4 5 6 7 8 9 10 11 setTimeout (()=> { Java .perform (()=> { var native = Module .findExportByName ("libezmd5.so" , "Java_com_example_ezmd5_MainActivity_mdString" ); console .log ("find native address =>" , native); var md5String = new NativeFunction (native, 'pointer' , ['pointer' , 'pointer' , 'pointer' ]); console .log ("============= 主动调用 =============" ); console .log ("String =>" , "123456" ); var result = md5String (Java .vm .tryGetEnv (), Java .vm .tryGetEnv (), Java .vm .getEnv ().newStringUtf ("123456" )); console .log ("MD5 =>" , Java .vm .getEnv ().getStringUtfChars (result, null ).readCString ()); }) }, 1000 )