androidNDK逆向初步

知识补充

  1. elf,linux下程序执行,连接等需要详细补充。

原生程序启动流程概述 2013

注意这里是原生可执行程序,不是动态库so的加载运行过程
静态链接出的可执行文件
_start(crtbegin_static.S)调用__libc_init(bionic\lib\bionic\libc_init_static.c)调用main

*动态连接出的可执行文件(.interp段指定加载器默认为system/bin/linker):
linker(begin.S)调用linker_init(linker.c)返回入口函数并执行_start(crtbegin_dynamic.S)调用libc_init(libc_init_dtnamic.c)调用main

动态连接库文件:
on_dlclose(crtbegin_so.c)调用cxa_finalize记录计数

说明:

  1. 目前静态动态_start都一样
  2. __libc_init动态的少点因为大部分工作linker做了

原生文件格式

不同系统、架构下的elf有所不同。android下的elf遵循了arm的标准,不同版本ndk与连接器脚本生成的elf有所不同。具体的格式需要阅读源码:NDK的platforms\android-14\arch-arm\usr\include\sys\exec_elf.h
目前简单百度了下,大概可理解为由头部决定节头和段头的位置,由段头和节头分别决定段与节的位置,节用来存储数据代码等给link用的,而段是内存中用来执行的。

原生c的逆向分析

注意主要用寄存器传参 R0 R1 R2 依次左到右(不同于栈的右到左压,右在高地址)。返回用R0
R11常作为栈帧
ida蓝色表示顺序执行,绿色满足时,红色不满足时执行
全局变量从全局偏移表里取(具体?详细了解elf结构和内存布局后)

switch依旧依靠跳转表

gcc的-O2优化效果好,取消了栈的存储操作,基本全用寄存器,而且逻辑上也省去了许多无用的部分

原生c++的逆向分析

c++的类被解析为结构体的空间分配,而各函数代码都是直接化在流程中了,不是分成块的存储调用方式。
c++的各种特性库slt(如gunslt、stlport、gabi++、system)都分别支持各种不同的c++功能,其中前俩个提供容器类,默认为system自动连接,其它的都分为动态和静态连接方式。详见ndk高级编程中

动态连接时ida可以解析出函数的来源,而静态时ida只能识别出子程序调用,无法识别出具体的函数。因此静态分析难度更大,只有动态分析好用

JNI

而DNK中最为常用的就是jni中的交互函数:JNIEnv和JavaVM
前者是原生与java交互用,后者代表虚拟机常用于多线程中。
c中都是指向函数表结构体的指针,c++中为类对象指针。为了方便ida识别出JNI的结构体,需要导入jni头文件用来看出具体调用的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
在定义完JNI俩个函数指针集合结构体之后
定义c++用的类
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
......定义类的方法调用该结构体。
}

以下为ida解析头文件结构体方法:
进入ida,点击文件->读取文件->解析c头文件
这里选择ndk的jni文件(已修改并存入工具库中),需要删除报错的俩个include和1107的#define JNIEXPORT …改为#define JNIEXPORT
然后结构体选项卡按insert->Add standard structure
之后反汇编窗口中选中env函数偏移的量右键中会出现解析为该结构体的内容

java与c的native函数可以手动映射

因此下坑的地方很多,比如init数组加密操作,javaload加密操作,so中一般名字的native方法不能说明什么。要全部分析

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
static JNINativeMethod methods[] = {
{"initSN", "()V", (void*)n1},
{"saveSN", "(Ljava/lang/String;)V", (void*)n2},
{"work", "()V", (void*)n3}
};
/*
* 库加载时注册native函数
*/
jint JNI_OnLoad(JavaVM* vm, void* reserved){
if(JNI_OK != (*vm)->GetEnv(vm, (void**)&g_env, JNI_VERSION_1_6)){
return -1;
}
LOGV("JNI_OnLoad()");
native_class = (*g_env)->FindClass(g_env, "com/droider/ndkapp/MyApp");
if (JNI_OK ==(*g_env)->RegisterNatives(g_env,
native_class, methods, NELEM(methods))){//把java类的native函数对应到原生函数中,手动。自动的话是按javah生成的对应,这里没有包含javah生成的头文件
LOGV("RegisterNatives() --> nativeMethod() ok");
} else {
LOGE("RegisterNatives() --> nativeMethod() failed");
return -1;
}
return JNI_VERSION_1_6;
}
void JNI_OnUnLoad(JavaVM* vm, void* reserved){
LOGV("JNI_OnUnLoad()");
(*g_env)->UnregisterNatives(g_env, native_class);
LOGV("UnregisterNatives()");
}

init

编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
LOGD("%s" , __FUNCTION__);
return JNI_VERSION_1_6;
}
//init函数定义,必须为_init
extern "C" void _init(void){
LOGD("%s" , __FUNCTION__);
}
__attribute__((constructor,visibility("hidden"))) void init_array_0(void) {
LOGD("%s" , __FUNCTION__);
}
__attribute__((constructor,visibility("hidden"))) void init_array_1(void) {
LOGD("%s" , __FUNCTION__);
}
extern "C" void preinit(int argc, char **argv, char **envp) {
LOGD("preinit_array never be called in so");
}
__attribute__((section(".preinit_array"))) typeof(preinit) *__preinit = preinit;

https://blog.csdn.net/qq1084283172/article/details/54233552