移动端Android安全的发展,催生了各种Android加固的诞生,基于ELF文件的特性,很多的加固厂商在进行Android逆向的对抗的时,都会在Android的so文件中进行动态的对抗,对抗的点一般在so文件的.init段和JNI_OnLoad处。因此,我们在逆向分析各种厂商的加固so时,需要在so文件的.init段和JNI_OnLoad处下断点进行分析,过掉这些加固的so对抗。
一、如何向.init和.init_array段添加自定义的函数
so共享库文件的高级特性
在so共享库文件动态加载时,有一次执行代码的机会:
- [1] so加载时构造函数,在函数声明时加上"__attribute__((constructor))"属性
- void __attribute__((constructor)) init_function(void)
- {
- // to do
- }
- 对应有so卸载时析构函数,在程序exit()或者dlclose()返回前执行
- void __attribute__((destructor)) fini_function(void)
- {
- // to do
- }
- [2] c++全局对象初始化,其构造函数(对象)被自动执行
在Android NDK编程中,.init段和.init_array段函数的定义方式:
- extern "C" void _init(void) { } -------》编译生成后在.init段
- __attribute__((constructor)) void _init(void) { } -------》编译生成后在.init_array段
- 说明下,带构造函数的全局对象生成的时在在.init_array段里面。
参考连接:
《UNIX系统编程手册》
【求助】JNI编程,怎么在native中定义_init段呢?
http://www.blogfshare.com/linker-load-so.html
http://blog.csdn.net/qq1084283172/article/details/54095995
http://blog.csdn.net/l173864930/article/details/38456313
二、向Android JNI的JNI_OnLoad添加自定义的代码
在Android的jni编程中,native函数实现的jni映射,既可以根据jni函数的编写协议编写jni函数,让Java虚拟机在加载so库文件时,根据函数签名逐一检索,将各个native方法与相应的java本地函数映射起来(增加运行的时间,降低运行的效率)也可以调用jni机制提供的RegisterNatives()函数手动将jni本地方法和java类的本地方法直接映射起来,需要开发者自定义实现JNI_OnLoad()函数;当so库文件被加载时,JNI_OnLoad()函数会被调用,实现jni本地方法和java类的本地方法的直接映射。
根据jni函数的编写协议,实现java本地方法和jni本地方法的映射
使用JNI_OnLoad的执行,调用RegisterNatives()函数实现java本地方法和jni本地方法的映射
三、在so库文件中定义的.init和.init_array段处函数的执行
Android4.4.4r1的源码\bionic\linker\dlfcn.cpp:
- // dlopen函数调用do_dlopen函数实现so库文件的加载
- void* dlopen(const char* filename, int flags) {
- // 信号互斥量(锁)
- ScopedPthreadMutexLocker locker(&gDlMutex);
- // 调用do_dlopen()函数实现so库文件的加载
- soinfo* result = do_dlopen(filename, flags);
- // 判断so库文件是否加载成功
- if (result == NULL) {
- __bionic_format_dlerror("dlopen failed", linker_get_error_buffer());
- return NULL;
- }
- // 返回加载后so库文件的文件句柄
- return result;
- }
- // 实现对so库文件的加载和执行构造函数
- soinfo* do_dlopen(const char* name, int flags) {
- // 判断加载so文件的flags是否符合要求
- if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL)) != 0) {
- DL_ERR("invalid flags to dlopen: %x", flags);
- return NULL;
- }
- // 修改内存属性为可读可写
- set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
- // find_library会判断so是否已经加载,
- // 如果没有加载,对so进行加载,完成一些初始化工作
- soinfo* si = find_library(name);
- // 判断so库问价是否加载成功
- if (si != NULL) {
- // ++++++ so加载成功,调用构造函数 ++++++++
- si->CallConstructors();
- // ++++++++++++++++++++++++++++++++++++++++
- }
- // 设置内存属性为可读
- set_soinfo_pool_protection(PROT_READ);
- // 返回so内存模块
- return si;
- }
- 这里的DT_INIT和DT_INIT_ARRAY到底是什么呢?
- init_func和init_array都是结构体soinfo的成员变量,在soinfo_link_image加载so的时候进行赋值。
- #define DT_INIT 12 /* Address of initialization function */
- #define DT_INIT_ARRAY 25 /* Address of initialization function array */
- case DT_INIT:
- si->init_func = reinterpret_cast<linker_function_t>(base + d->d_un.d_ptr);
- DEBUG(“%s constructors (DT_INIT) found at %p”, si->name, si->init_func);
- break;
- case DT_INIT_ARRAY:
- si->init_array = reinterpret_cast<linker_function_t*>(base + d->d_un.d_ptr);
- DEBUG(“%s constructors (DT_INIT_ARRAY) found at %p”, si->name, si->init_array);
- break;
先调用.init段的构造函数再调用.init_array段的构造函数
- // so库文件加载完毕以后调用构造函数
- void soinfo::CallConstructors() {
- if (constructors_called) {
- return;
- }
- // We set constructors_called before actually calling the constructors, otherwise it doesn't
- // protect against recursive constructor calls. One simple example of constructor recursion
- // is the libc debug malloc, which is implemented in libc_malloc_debug_leak.so:
- // 1. The program depends on libc, so libc's constructor is called here.
- // 2. The libc constructor calls dlopen() to load libc_malloc_debug_leak.so.
- // 3. dlopen() calls the constructors on the newly created
- // soinfo for libc_malloc_debug_leak.so.
- // 4. The debug .so depends on libc, so CallConstructors is
- // called again with the libc soinfo. If it doesn't trigger the early-
- // out above, the libc constructor will be called again (recursively!).
- constructors_called = true;
- if ((flags & FLAG_EXE) == 0 && preinit_array != NULL) {
- // The GNU dynamic linker silently ignores these, but we warn the developer.
- PRINT("\"%s\": ignoring %d-entry DT_PREINIT_ARRAY in shared library!",
- name, preinit_array_count);
- }
- // 调用DT_NEEDED类型段的构造函数
- if (dynamic != NULL) {
- for (Elf32_Dyn* d = dynamic; d->d_tag != DT_NULL; ++d) {
- if (d->d_tag == DT_NEEDED) {
- const char* library_name = strtab + d->d_un.d_val;
- TRACE("\"%s\": calling constructors in DT_NEEDED \"%s\"", name, library_name);
- find_loaded_library(library_name)->CallConstructors();
- }
- }
- }
- TRACE("\"%s\": calling constructors", name);
- // DT_INIT should be called before DT_INIT_ARRAY if both are present.
- // 先调用.init段的构造函数
- CallFunction("DT_INIT", init_func);
- // 再调用.init_array段的构造函数
- CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
- }
- // 构造函数调用的实现
- void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) {
- // 判断构造函数的调用地址是否符合要求
- if (function == NULL || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) {
- return;
- }
- // function_name被调用的函数名称,function为函数的调用地址
- // [ Calling %s @ %p for '%s' ] 字符串为在 /system/bin/linker 中查找.init和.init_array段调用函数的关键
- TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name);
- // 调用function函数
- function();
- TRACE("[ Done calling %s @ %p for '%s' ]", function_name, function, name);
- // The function may have called dlopen(3) or dlclose(3), so we need to ensure our data structures
- // are still writable. This happens with our debug malloc (see http://b/7941716).
- set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
- }
.init_arrayt段构造函数的调用实现
- void soinfo::CallArray(const char* array_name UNUSED, linker_function_t* functions, size_t count, bool reverse) {
- if (functions == NULL) {
- return;
- }
- TRACE("[ Calling %s (size %d) @ %p for '%s' ]", array_name, count, functions, name);
- int begin = reverse ? (count - 1) : 0;
- int end = reverse ? -1 : count;
- int step = reverse ? -1 : 1;
- // 循环遍历调用.init_arrayt段中每个函数
- for (int i = begin; i != end; i += step) {
- TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);
- // .init_arrayt段中,每个函数指针的调用和上面的.init段的构造函数的实现是一样的
- CallFunction("function", functions[i]);
- }
- TRACE("[ Done calling %s for '%s' ]", array_name, name);
- }
四、Android jni中JNI_OnLoad函数的执行
Android4.4.4r1的源码/libcore/luni/src/main/java/java/lang/System.java
- /**
- * Loads and links the library with the specified name. The mapping of the
- * specified library name to the full path for loading the library is
- * implementation-dependent.
- *
- * @param libName
- * the name of the library to load.
- * @throws UnsatisfiedLinkError
- * if the library could not be loaded.
- */
- // System.loadLibrary函数加载libxxx.so库文件
- public static void loadLibrary(String libName) {
- // 调用Runtime.loadLibrary函数实现libxxx.so库文件的加载
- Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
- }
Android4.4.4r1的源码/libcore/luni/src/main/java/java/lang/Runtime.java
- /**
- * Loads and links the library with the specified name. The mapping of the
- * specified library name to the full path for loading the library is
- * implementation-dependent.
- *
- * @param libName
- * the name of the library to load.
- * @throws UnsatisfiedLinkError
- * if the library can not be loaded.
- */
- public void loadLibrary(String libName) {
- loadLibrary(libName, VMStack.getCallingClassLoader());
- }
- /*
- * Searches for a library, then loads and links it without security checks.
- */
- void loadLibrary(String libraryName, ClassLoader loader) {
- if (loader != null) {
- String filename = loader.findLibrary(libraryName);
- if (filename == null) {
- throw new UnsatisfiedLinkError("Couldn't load " + libraryName +
- " from loader " + loader +
- ": findLibrary returned null");
- }
- String error = doLoad(filename, loader);
- if (error != null) {
- throw new UnsatisfiedLinkError(error);
- }
- return;
- }
- String filename = System.mapLibraryName(libraryName);
- List<String> candidates = new ArrayList<String>();
- String lastError = null;
- for (String directory : mLibPaths) {
- String candidate = directory + filename;
- candidates.add(candidate);
- if (IoUtils.canOpenReadOnly(candidate)) {
- // 调用doLoad函数加载so库文件
- String error = doLoad(candidate, loader);
- if (error == null) {
- return; // We successfully loaded the library. Job done.
- }
- lastError = error;
- }
- }
- if (lastError != null) {
- throw new UnsatisfiedLinkError(lastError);
- }
- throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
- }
- private String doLoad(String name, ClassLoader loader) {
- // Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH,
- // which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH.
- // The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load
- // libraries with no dependencies just fine, but an app that has multiple libraries that
- // depend on each other needed to load them in most-dependent-first order.
- // We added API to Android's dynamic linker so we can update the library path used for
- // the currently-running process. We pull the desired path out of the ClassLoader here
- // and pass it to nativeLoad so that it can call the private dynamic linker API.
- // We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the
- // beginning because multiple apks can run in the same process and third party code can
- // use its own BaseDexClassLoader.
- // We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any
- // dlopen(3) calls made from a .so's JNI_OnLoad to work too.
- // So, find out what the native library search path is for the ClassLoader in question...
- String ldLibraryPath = null;
- if (loader != null && loader instanceof BaseDexClassLoader) {
- // so库文件的文件路径
- ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
- }
- // nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
- // of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
- // internal natives.
- synchronized (this) {
- // 调用native方法nativeLoad加载so库文件
- return nativeLoad(name, loader, ldLibraryPath);
- }
- }
- // TODO: should be synchronized, but dalvik doesn't support synchronized internal natives.
- // 函数nativeLoad为native方法实现的
- private static native String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath);
nativeLoad函数在Android4.4.4r1源码/dalvik/vm/native/java_lang_Runtime.cpp中的实现
- /*
- * static String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath)
- *
- * Load the specified full path as a dynamic library filled with
- * JNI-compatible methods. Returns null on success, or a failure
- * message on failure.
- */
- /*
- * 参数args[0]保存的是一个Java层的String对象,这个String对象描述的就是要加载的so文件,
- * 函数Dalvik_java_lang_Runtime_nativeLoad首先是调用函数dvmCreateCstrFromString来将它转换成一个C++层的字符串fileName,
- * 然后再调用函数dvmLoadNativeCode来执行加载so文件的操作。
- */
- static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,
- JValue* pResult)
- {
- StringObject* fileNameObj = (StringObject*) args[0];
- Object* classLoader = (Object*) args[1];
- StringObject* ldLibraryPathObj = (StringObject*) args[2];
- assert(fileNameObj != NULL);
- char* fileName = dvmCreateCstrFromString(fileNameObj);
- if (ldLibraryPathObj != NULL) {
- char* ldLibraryPath = dvmCreateCstrFromString(ldLibraryPathObj);
- void* sym = dlsym(RTLD_DEFAULT, "android_update_LD_LIBRARY_PATH");
- if (sym != NULL) {
- typedef void (*Fn)(const char*);
- Fn android_update_LD_LIBRARY_PATH = reinterpret_cast<Fn>(sym);
- &