Android NDK 入门
Android NDK 安装
Android NDK 实例-静态方式函数
Android NDK 实例-动态方式函数
Android so 文件格式-ELF 格式
so 文件加载执行流程
Android so 文件动态调试
Android NDK 入门
NDK 就是 Android 平台下的 C++软件开发包。
NDK Native Delvpment Kit 原生开发包,这里包括常用库、编译工具、构建工具
NDK 中逆向分析中最需要了解和熟悉的是 JNI 接口,JNI - Java Native Interface (Java
原生接口)。JNI 是 Java 语言提供的 Java 与 C++交互的接口。
NDK 中使用编译器有 gcc,clang(基于 LLVM)
Android NDK 安装
![](https://img.haomeiwen.com/i14168438/b8ad2e115c67d84a.png)
Android NDK 实例-静态方式函数
Android 中 NDK 应用其实就是 C++的动态库,所以只要我们想要调用 C++的动态库,
就需要在 Java 代码中添加以下代码:
System.loadLibrary("native-lib");
一般来说,我们都会定义一个静态域,让其运行时自动加载。
static {
System.loadLibrary("native-lib");
}
在 Java 中我们可以声明原生函数,无需实现,使用关键字 native
public native String stringFromJNI();
同时在 C++中实现,实现的函数名称需要遵循一定的规则
- 需要 extern "C"
- 函数的名称:Java+包名+类名+函数名,用下划线连接
Java_com_类名_hello13ndk_MainActivity_stringFromJNI - 函数的参数:有两个固定的
第一个参数是 JNI 环境指针
第二个参数是对象的 this 指针(成员函数)或是对象的类型(静态函数)
从第三个参数是 Java 中声明函数时定义的参数
extern "C"
jstring
Java_com_类名_hello13ndk_MainActivity_stringFromJNI(
JNIEnv env,
jobject / this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
![](https://img.haomeiwen.com/i14168438/d2d38015bd4fa620.png)
NDK 中常用基本类型
typedef unsigned char jboolean; /* unsigned 8 bits /
typedef signed char jbyte; / signed 8 bits /
typedef unsigned short jchar; / unsigned 16 bits /
typedef short jshort; / signed 16 bits /
typedef int jint; / signed 32 bits /
typedef long long jlong; / signed 64 bits /
typedef float jfloat; / 32-bit IEEE 754 /
typedef double jdouble; / 64-bit IEEE 754 */
对象类型
typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
注意写代码时,别忘记 extern “C”,如果不加,导出函数会将参数之类一并导出,名称粉碎
![](https://img.haomeiwen.com/i14168438/455f7ce0385c58ed.png)
有 extern “C”的导出
![](https://img.haomeiwen.com/i14168438/fdb9fe7087978ae5.png)
Android NDK 实例-动态方式函数
NDK 中 C++通常生成的是动态库,其实动态有一个入口函数 JNI_Onload,可以在入口
函数进行动态注册对应的类的导出函数。
// 需要动态注册的函数
extern "C"
JNIEXPORT jstring JNICALL
stringFromJNI1(JNIEnv *env, jobject instance,
jint i) {
// TODO
return env->NewStringUTF("hello 15PB!");
}
// 注册的结构体
const JNINativeMethod method = {
"stringFromJNI1",
"(I)Ljava/lang/String;", // 函数的签名
(void*)stringFromJNI1
};
extern "C"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
// 获取 JNI 环境指针
JNIEnv* env = NULL;
jint jRet = vm->GetEnv((void**)&env, JNI_VERSION_1_6);
if (jRet != JNI_OK){
__android_log_print(ANDROID_LOG_DEBUG,
"native-lib",
"获取环境指针 发生错误"
);
return JNI_ERR;
}
// 通过 JNI 接口调用注册原生函数
// 获取类的类型 FindClass 参数是 类描述符,间隔符应该是/ 而不是.
jclass jclass1 = env->FindClass("com/bluelesson/hellondk/MainActivity");
// 动态注册函数
jRet = env->RegisterNatives(jclass1, // 类的类型
&method, // 数组基址
1); // 数组元素个数
if (jRet != JNI_OK){
__android_log_print(ANDROID_LOG_DEBUG,
"native-lib",
"动态注册原生函数 发生错误"
);
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
需要注意的两点:
函数签名
与 Dalvik 字节码中的类型描述符是一样的
![](https://img.haomeiwen.com/i14168438/3823cd4ae52facbb.png)
举例: 函数类型 int fun(int,String); -> 签名 (ILjava/lang/String;)I
函数类型 boolean fun2(char,char) -》 (CC)Z
FindClass 的参数-类描述符
类描述符,间隔符应该是/ 而不是.
举例: com/bluelesson/hellondk/MainActivity
Android so 文件格式-ELF 格式
和 PE 文件类似,比 PE 文件简单,我们可以用 NDK 中提供的一个工具 readelf 去查看 ELF
文件主要表信息。
目录:D:\Android\sdk\ndk-bundle\toolchains\x86-4.9\prebuilt\windows-x86_64\bin
![](https://img.haomeiwen.com/i14168438/dfcf9bfcec6081f2.png)
常见命令:
-h 查看文件头
-S 查看 Section header Table
-l 查看程序头表 Progrm Header Table
-s 查看符号表
-r 查看重定位表
-d 查看动态加载信息
![](https://img.haomeiwen.com/i14168438/6a2a5d48468aef1a.png)
文件头信息
![](https://img.haomeiwen.com/i14168438/c5d4fb4d3f11ac12.png)
段表信息,段表信息描述的是文件中每个区段的信息
![](https://img.haomeiwen.com/i14168438/898b25bd738e45bd.png)
程序头表信息
表示的是在内存中的区段信息。LOAD 类型区段会被加载到内存中。一般对齐方式 0x1000.
![](https://img.haomeiwen.com/i14168438/af13f9782a73321b.png)
so 文件加载执行流程
![](https://img.haomeiwen.com/i14168438/90868a0940b9c438.png)
① Init 段的 init_proc 函数(linker)
② Init_array 段的构造数组函数(linker)
③ Jni_Onload(libdvm.so)
使用 readelf 可以查看 init 段和 init_array 段的地址
![](https://img.haomeiwen.com/i14168438/3fef7604e416fcba.png)
Android so 文件动态调试
工具:IDA Pro , Android monitor、adb
① 上传 IDA 调试服务端
![](https://img.haomeiwen.com/i14168438/c789747880a1af97.png)
-
上传到 android 机器中
adb push android_server /data/local/tmp/ands -
修改文件权限,测试运行
image.png
-
带参数,修改端口运行
image.png
② 打开 monitor,以调试方式启动 apk
-
打开 monitor,待程序启动之后,选中调试进程
image.png
-
以调试方式启动 apk
adb shell am start -D -n 包名/入口类名
image.png
-
设置端口转发
adb forward tcp:23456 tcp:23456
③ 启动 IDA,附加调试 apk
image.png
image.png
image.png
image.png
④ 启动 jdb,附加 apk,启动 APK
jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8700
网友评论