在Android so文件的.init、.init_array上和JNI_OnLoad处下断点

移动端Android安全的发展,催生了各种Android加固的诞生,基于ELF文件的特性,很多的加固厂商在进行Android逆向的对抗的时,都会在Android的so文件中进行动态的对抗,对抗的点一般在so文件的.init段和JNI_OnLoad处。因此,我们在逆向分析各种厂商的加固so时,需要在so文件的.init段和JNI_OnLoad处下断点进行分析,过掉这些加固的so对抗。

 

一、如何向.init和.init_array段添加自定义的函数

so共享库文件的高级特性

 

在so共享库文件动态加载时,有一次执行代码的机会:

 

[plain] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. [1] so加载时构造函数,在函数声明时加上"__attribute__((constructor))"属性  
  2.    void __attribute__((constructor)) init_function(void)  
  3.    {  
  4.        // to do  
  5.    }  
  6.     对应有so卸载时析构函数,在程序exit()或者dlclose()返回前执行  
  7.    void __attribute__((destructor)) fini_function(void)  
  8.    {  
  9.         // to do  
  10.    }  
  11.   
  12. [2] c++全局对象初始化,其构造函数(对象)被自动执行  

 

 

在Android NDK编程中,.init段和.init_array段函数的定义方式

 

[java] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. extern "C" void _init(void) { } -------》编译生成后在.init段  
  2.   
  3. __attribute__((constructor)) void _init(void) { } -------》编译生成后在.init_array段  
  4.   
  5. 说明下,带构造函数的全局对象生成的时在在.init_array段里面。  

 

 

使用IDA工具查看so库文件中.init段和.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:

 

[cpp] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. // dlopen函数调用do_dlopen函数实现so库文件的加载  
  2. void* dlopen(const char* filename, int flags) {  
  3.       
  4.   // 信号互斥量(锁)  
  5.   ScopedPthreadMutexLocker locker(&gDlMutex);  
  6.   // 调用do_dlopen()函数实现so库文件的加载  
  7.   soinfo* result = do_dlopen(filename, flags);  
  8.   // 判断so库文件是否加载成功  
  9.   if (result == NULL) {  
  10.     __bionic_format_dlerror("dlopen failed", linker_get_error_buffer());  
  11.     return NULL;  
  12.   }  
  13.   // 返回加载后so库文件的文件句柄  
  14.   return result;  
  15. }  

 

 

Android4.4.4r1的源码\bionic\linker\linker.cpp:

 

[cpp] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. // 实现对so库文件的加载和执行构造函数  
  2. soinfo* do_dlopen(const char* name, int flags) {  
  3.   
  4.   // 判断加载so文件的flags是否符合要求  
  5.   if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL)) != 0) {  
  6.     DL_ERR("invalid flags to dlopen: %x", flags);  
  7.     return NULL;  
  8.   }  
  9.   // 修改内存属性为可读可写  
  10.   set_soinfo_pool_protection(PROT_READ | PROT_WRITE);  
  11.     
  12.   // find_library会判断so是否已经加载,  
  13.   // 如果没有加载,对so进行加载,完成一些初始化工作  
  14.   soinfo* si = find_library(name);  
  15.   // 判断so库问价是否加载成功  
  16.   if (si != NULL) {  
  17.         
  18.     // ++++++ so加载成功,调用构造函数 ++++++++  
  19.     si->CallConstructors();  
  20.     // ++++++++++++++++++++++++++++++++++++++++  
  21.   }  
  22.     
  23.   // 设置内存属性为可读  
  24.   set_soinfo_pool_protection(PROT_READ);  
  25.   // 返回so内存模块  
  26.   return si;  
  27. }  

 

 

当上面的构造函数 si->CallConstructors() 被调用时,preinit_array-> .init -> .init_array段的函数,会依次按照顺序进行执行并且.init_array段的函数指针数组的执行的实现其实和.init段的函数的执行的实现是一样的。

 

[java] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. 这里的DT_INIT和DT_INIT_ARRAY到底是什么呢?  
  2.   
  3. init_func和init_array都是结构体soinfo的成员变量,在soinfo_link_image加载so的时候进行赋值。  
  4.   
  5. #define DT_INIT  12         /* Address of initialization function */  
  6. #define DT_INIT_ARRAY   25  /* Address of initialization function array */  
  7.   
  8. case DT_INIT:  
  9.     si->init_func = reinterpret_cast<linker_function_t>(base + d->d_un.d_ptr);  
  10.     DEBUG(“%s constructors (DT_INIT) found at %p”, si->name, si->init_func);  
  11.     break;  
  12. case DT_INIT_ARRAY:  
  13.     si->init_array = reinterpret_cast<linker_function_t*>(base + d->d_un.d_ptr);  
  14.     DEBUG(“%s constructors (DT_INIT_ARRAY) found at %p”, si->name, si->init_array);  
  15.     break;  

 

 

先调用.init段的构造函数再调用.init_array段的构造函数

 

[cpp] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. // so库文件加载完毕以后调用构造函数  
  2. void soinfo::CallConstructors() {  
  3.       
  4.   if (constructors_called) {  
  5.     return;  
  6.   }  
  7.   
  8.   // We set constructors_called before actually calling the constructors, otherwise it doesn't  
  9.   // protect against recursive constructor calls. One simple example of constructor recursion  
  10.   // is the libc debug malloc, which is implemented in libc_malloc_debug_leak.so:  
  11.   // 1. The program depends on libc, so libc's constructor is called here.  
  12.   // 2. The libc constructor calls dlopen() to load libc_malloc_debug_leak.so.  
  13.   // 3. dlopen() calls the constructors on the newly created  
  14.   //    soinfo for libc_malloc_debug_leak.so.  
  15.   // 4. The debug .so depends on libc, so CallConstructors is  
  16.   //    called again with the libc soinfo. If it doesn't trigger the early-  
  17.   //    out above, the libc constructor will be called again (recursively!).  
  18.   constructors_called = true;  
  19.   
  20.   if ((flags & FLAG_EXE) == 0 && preinit_array != NULL) {  
  21.     // The GNU dynamic linker silently ignores these, but we warn the developer.  
  22.     PRINT("\"%s\": ignoring %d-entry DT_PREINIT_ARRAY in shared library!",  
  23.           name, preinit_array_count);  
  24.   }  
  25.   
  26.   // 调用DT_NEEDED类型段的构造函数  
  27.   if (dynamic != NULL) {  
  28.     for (Elf32_Dyn* d = dynamic; d->d_tag != DT_NULL; ++d) {  
  29.       if (d->d_tag == DT_NEEDED) {  
  30.         const char* library_name = strtab + d->d_un.d_val;  
  31.         TRACE("\"%s\": calling constructors in DT_NEEDED \"%s\"", name, library_name);  
  32.         find_loaded_library(library_name)->CallConstructors();  
  33.       }  
  34.     }  
  35.   }  
  36.   
  37.   TRACE("\"%s\": calling constructors", name);  
  38.   
  39.   // DT_INIT should be called before DT_INIT_ARRAY if both are present.  
  40.   // 先调用.init段的构造函数  
  41.   CallFunction("DT_INIT", init_func);  
  42.   // 再调用.init_array段的构造函数  
  43.   CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);  
  44. }  

 

 

.init段构造函数的调用实现

 

[cpp] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. // 构造函数调用的实现  
  2. void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) {  
  3.   
  4.   // 判断构造函数的调用地址是否符合要求  
  5.   if (function == NULL || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) {  
  6.     return;  
  7.   }  
  8.   
  9.   // function_name被调用的函数名称,function为函数的调用地址  
  10.   // [ Calling %s @ %p for '%s' ] 字符串为在 /system/bin/linker 中查找.init和.init_array段调用函数的关键  
  11.   TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name);  
  12.   // 调用function函数  
  13.   function();  
  14.   TRACE("[ Done calling %s @ %p for '%s' ]", function_name, function, name);  
  15.   
  16.   // The function may have called dlopen(3) or dlclose(3), so we need to ensure our data structures  
  17.   // are still writable. This happens with our debug malloc (see http://b/7941716).  
  18.   set_soinfo_pool_protection(PROT_READ | PROT_WRITE);  
  19. }  


.init_arrayt段构造函数的调用实现

 

 

[cpp] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. void soinfo::CallArray(const char* array_name UNUSED, linker_function_t* functions, size_t count, bool reverse) {  
  2.   if (functions == NULL) {  
  3.     return;  
  4.   }  
  5.   
  6.   TRACE("[ Calling %s (size %d) @ %p for '%s' ]", array_name, count, functions, name);  
  7.   
  8.   int begin = reverse ? (count - 1) : 0;  
  9.   int end = reverse ? -1 : count;  
  10.   int step = reverse ? -1 : 1;  
  11.   
  12.   // 循环遍历调用.init_arrayt段中每个函数  
  13.   for (int i = begin; i != end; i += step) {  
  14.     TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);  
  15.       
  16.     // .init_arrayt段中,每个函数指针的调用和上面的.init段的构造函数的实现是一样的  
  17.     CallFunction("function", functions[i]);  
  18.   }  
  19.   
  20.   TRACE("[ Done calling %s for '%s' ]", array_name, name);  
  21. }  

 

 

从.init段和.init_arrayt段构造函数的调用实现来看,最终都是调用的 void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) 函数,因此IDA动态调试so时,只要守住CallFunction函数就可以实现对.init段和.init_arrayt段构造函数调用的监控。

 

 

四、Android jni中JNI_OnLoad函数的执行

Android4.4.4r1的源码/libcore/luni/src/main/java/java/lang/System.java

 

[java] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. /** 
  2.  * Loads and links the library with the specified name. The mapping of the 
  3.  * specified library name to the full path for loading the library is 
  4.  * implementation-dependent. 
  5.  * 
  6.  * @param libName 
  7.  *            the name of the library to load. 
  8.  * @throws UnsatisfiedLinkError 
  9.  *             if the library could not be loaded. 
  10.  */  
  11. // System.loadLibrary函数加载libxxx.so库文件  
  12. public static void loadLibrary(String libName) {  
  13.       
  14.     // 调用Runtime.loadLibrary函数实现libxxx.so库文件的加载  
  15.     Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());  
  16. }  

 

 

Android4.4.4r1的源码/libcore/luni/src/main/java/java/lang/Runtime.java

[java] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. /** 
  2.      * Loads and links the library with the specified name. The mapping of the 
  3.      * specified library name to the full path for loading the library is 
  4.      * implementation-dependent. 
  5.      * 
  6.      * @param libName 
  7.      *            the name of the library to load. 
  8.      * @throws UnsatisfiedLinkError 
  9.      *             if the library can not be loaded. 
  10.      */  
  11.     public void loadLibrary(String libName) {  
  12.         loadLibrary(libName, VMStack.getCallingClassLoader());  
  13.     }  
  14.   
  15.     /* 
  16.      * Searches for a library, then loads and links it without security checks. 
  17.      */  
  18.     void loadLibrary(String libraryName, ClassLoader loader) {  
  19.         if (loader != null) {  
  20.             String filename = loader.findLibrary(libraryName);  
  21.             if (filename == null) {  
  22.                 throw new UnsatisfiedLinkError("Couldn't load " + libraryName +  
  23.                                                " from loader " + loader +  
  24.                                                ": findLibrary returned null");  
  25.             }  
  26.             String error = doLoad(filename, loader);  
  27.             if (error != null) {  
  28.                 throw new UnsatisfiedLinkError(error);  
  29.             }  
  30.             return;  
  31.         }  
  32.   
  33.         String filename = System.mapLibraryName(libraryName);  
  34.         List<String> candidates = new ArrayList<String>();  
  35.         String lastError = null;  
  36.         for (String directory : mLibPaths) {  
  37.             String candidate = directory + filename;  
  38.             candidates.add(candidate);  
  39.   
  40.             if (IoUtils.canOpenReadOnly(candidate)) {  
  41.                 // 调用doLoad函数加载so库文件  
  42.                 String error = doLoad(candidate, loader);  
  43.                 if (error == null) {  
  44.                     return; // We successfully loaded the library. Job done.  
  45.                 }  
  46.                 lastError = error;  
  47.             }  
  48.         }  
  49.   
  50.         if (lastError != null) {  
  51.             throw new UnsatisfiedLinkError(lastError);  
  52.         }  
  53.         throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);  
  54.     }  

 

看下String doLoad(String name, ClassLoader loader)函数的实现,doLoad函数调用native层实现的nativeLoad函数进行so库文件的加载

 

[java] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. private String doLoad(String name, ClassLoader loader) {  
  2.         // Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH,  
  3.         // which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH.  
  4.   
  5.         // The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load  
  6.         // libraries with no dependencies just fine, but an app that has multiple libraries that  
  7.         // depend on each other needed to load them in most-dependent-first order.  
  8.   
  9.         // We added API to Android's dynamic linker so we can update the library path used for  
  10.         // the currently-running process. We pull the desired path out of the ClassLoader here  
  11.         // and pass it to nativeLoad so that it can call the private dynamic linker API.  
  12.   
  13.         // We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the  
  14.         // beginning because multiple apks can run in the same process and third party code can  
  15.         // use its own BaseDexClassLoader.  
  16.   
  17.         // We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any  
  18.         // dlopen(3) calls made from a .so's JNI_OnLoad to work too.  
  19.   
  20.         // So, find out what the native library search path is for the ClassLoader in question...  
  21.         String ldLibraryPath = null;  
  22.         if (loader != null && loader instanceof BaseDexClassLoader) {  
  23.             // so库文件的文件路径  
  24.             ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();  
  25.         }  
  26.         // nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless  
  27.         // of how many ClassLoaders are in the system, but dalvik doesn't support synchronized  
  28.         // internal natives.  
  29.         synchronized (this) {  
  30.             // 调用native方法nativeLoad加载so库文件  
  31.             return nativeLoad(name, loader, ldLibraryPath);  
  32.         }  
  33.     }  
  34.   
  35.     // TODO: should be synchronized, but dalvik doesn't support synchronized internal natives.  
  36.     // 函数nativeLoad为native方法实现的  
  37.     private static native String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath);  

 

 

nativeLoad函数在Android4.4.4r1源码/dalvik/vm/native/java_lang_Runtime.cpp中的实现

 

[cpp] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. /* 
  2.  * static String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath) 
  3.  * 
  4.  * Load the specified full path as a dynamic library filled with 
  5.  * JNI-compatible methods. Returns null on success, or a failure 
  6.  * message on failure. 
  7.  */  
  8. /* 
  9.  * 参数args[0]保存的是一个Java层的String对象,这个String对象描述的就是要加载的so文件, 
  10.  * 函数Dalvik_java_lang_Runtime_nativeLoad首先是调用函数dvmCreateCstrFromString来将它转换成一个C++层的字符串fileName, 
  11.  * 然后再调用函数dvmLoadNativeCode来执行加载so文件的操作。 
  12.  */  
  13. static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,  
  14.     JValue* pResult)  
  15. {  
  16.     StringObject* fileNameObj = (StringObject*) args[0];  
  17.     Object* classLoader = (Object*) args[1];  
  18.     StringObject* ldLibraryPathObj = (StringObject*) args[2];  
  19.   
  20.     assert(fileNameObj != NULL);  
  21.     char* fileName = dvmCreateCstrFromString(fileNameObj);  
  22.   
  23.     if (ldLibraryPathObj != NULL) {  
  24.         char* ldLibraryPath = dvmCreateCstrFromString(ldLibraryPathObj);  
  25.         void* sym = dlsym(RTLD_DEFAULT, "android_update_LD_LIBRARY_PATH");  
  26.         if (sym != NULL) {  
  27.             typedef void (*Fn)(const char*);  
  28.             Fn android_update_LD_LIBRARY_PATH = reinterpret_cast<Fn>(sym);  
  29.             (*android_update_LD_LIBRARY_PATH)(ldLibraryPath);  
  30.         } else {  
  31.             ALOGE("android_update_LD_LIBRARY_PATH not found; .so dependencies will not work!");  
  32.         }  
  33.         free(ldLibraryPath);  
  34.     }  
  35.   
  36.     StringObject* result = NULL;  
  37.     char* reason = NULL;  
  38.     // 调用dvmLoadNativeCode函数加载so库文件  
  39.     bool success = dvmLoadNativeCode(fileName, classLoader, &reason);  
  40.     if (!success) {  
  41.         const char* msg = (reason != NULL) ? reason : "unknown failure";  
  42.         result = dvmCreateStringFromCstr(msg);  
  43.         dvmReleaseTrackedAlloc((Object*) result, NULL);  
  44.     }  
  45.   
  46.     free(reason);  
  47.     free(fileName);  
  48.     RETURN_PTR(result);  
  49. }  

 

 

nativeLoad函数的本地方法实现Dalvik_java_lang_Runtime_nativeLoad()函数最终调用Android4.4.4r1源码/dalvik/vm/Native.cpp中的dvmLoadNativeCode()函数,在该函数中先调用dlopen函数加载so库文件到内存中,然后调用dlsym函数获取so库文件中JNI_OnLoad函数的导出地址,然后调用JNI_OnLoad函数执行开发者自定义的代码和实现jni函数的注册。

 

[cpp] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. typedef int (*OnLoadFunc)(JavaVM*, void*);  
  2.   
  3. /* 
  4.  * Load native code from the specified absolute pathname.  Per the spec, 
  5.  * if we've already loaded a library with the specified pathname, we 
  6.  * return without doing anything. 
  7.  * 
  8.  * TODO? for better results we should absolutify the pathname.  For fully 
  9.  * correct results we should stat to get the inode and compare that.  The 
  10.  * existing implementation is fine so long as everybody is using 
  11.  * System.loadLibrary. 
  12.  * 
  13.  * The library will be associated with the specified class loader.  The JNI 
  14.  * spec says we can't load the same library into more than one class loader. 
  15.  * 
  16.  * Returns "true" on success. On failure, sets *detail to a 
  17.  * human-readable description of the error or NULL if no detail is 
  18.  * available; ownership of the string is transferred to the caller. 
  19.  */  
  20. bool dvmLoadNativeCode(const char* pathName, Object* classLoader,  
  21.         char** detail)  
  22. {  
  23.     SharedLib* pEntry;  
  24.     void* handle;  
  25.     bool verbose;  
  26.   
  27.     /* reduce noise by not chattering about system libraries */  
  28.     verbose = !!strncmp(pathName, "/system", sizeof("/system")-1);  
  29.     verbose = verbose && !!strncmp(pathName, "/vendor", sizeof("/vendor")-1);  
  30.   
  31.     if (verbose)  
  32.         ALOGD("Trying to load lib %s %p", pathName, classLoader);  
  33.   
  34.     *detail = NULL;  
  35.   
  36.     /* 
  37.      * See if we've already loaded it.  If we have, and the class loader 
  38.      * matches, return successfully without doing anything. 
  39.      */  
  40.     pEntry = findSharedLibEntry(pathName);  
  41.     if (pEntry != NULL) {  
  42.         if (pEntry->classLoader != classLoader) {  
  43.             ALOGW("Shared lib '%s' already opened by CL %p; can't open in %p",  
  44.                 pathName, pEntry->classLoader, classLoader);  
  45.             return false;  
  46.         }  
  47.         if (verbose) {  
  48.             ALOGD("Shared lib '%s' already loaded in same CL %p",  
  49.                 pathName, classLoader);  
  50.         }  
  51.         if (!checkOnLoadResult(pEntry))  
  52.             return false;  
  53.         return true;  
  54.     }  
  55.   
  56.     /* 
  57.      * Open the shared library.  Because we're using a full path, the system 
  58.      * doesn't have to search through LD_LIBRARY_PATH.  (It may do so to 
  59.      * resolve this library's dependencies though.) 
  60.      * 
  61.      * Failures here are expected when java.library.path has several entries 
  62.      * and we have to hunt for the lib. 
  63.      * 
  64.      * The current version of the dynamic linker prints detailed information 
  65.      * about dlopen() failures.  Some things to check if the message is 
  66.      * cryptic: 
  67.      *   - make sure the library exists on the device 
  68.      *   - verify that the right path is being opened (the debug log message 
  69.      *     above can help with that) 
  70.      *   - check to see if the library is valid (e.g. not zero bytes long) 
  71.      *   - check config/prelink-linux-arm.map to ensure that the library 
  72.      *     is listed and is not being overrun by the previous entry (if 
  73.      *     loading suddenly stops working on a prelinked library, this is 
  74.      *     a good one to check) 
  75.      *   - write a trivial app that calls sleep() then dlopen(), attach 
  76.      *     to it with "strace -p <pid>" while it sleeps, and watch for 
  77.      *     attempts to open nonexistent dependent shared libs 
  78.      * 
  79.      * This can execute slowly for a large library on a busy system, so we 
  80.      * want to switch from RUNNING to VMWAIT while it executes.  This allows 
  81.      * the GC to ignore us. 
  82.      */  
  83.     Thread* self = dvmThreadSelf();  
  84.     ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);  
  85.   
  86.     // 先调用dlopen函数加载so库文件到内存中  
  87.     handle = dlopen(pathName, RTLD_LAZY);  
  88.     dvmChangeStatus(self, oldStatus);  
  89.   
  90.     if (handle == NULL) {  
  91.         *detail = strdup(dlerror());  
  92.         ALOGE("dlopen(\"%s\") failed: %s", pathName, *detail);  
  93.         return false;  
  94.     }  
  95.   
  96.     /* create a new entry */  
  97.     SharedLib* pNewEntry;  
  98.     pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));  
  99.     pNewEntry->pathName = strdup(pathName);  
  100.     pNewEntry->handle = handle;  
  101.     pNewEntry->classLoader = classLoader;  
  102.     dvmInitMutex(&pNewEntry->onLoadLock);  
  103.     pthread_cond_init(&pNewEntry->onLoadCond, NULL);  
  104.     pNewEntry->onLoadThreadId = self->threadId;  
  105.   
  106.     /* try to add it to the list */  
  107.     SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);  
  108.   
  109.     if (pNewEntry != pActualEntry) {  
  110.         ALOGI("WOW: we lost a race to add a shared lib (%s CL=%p)",  
  111.             pathName, classLoader);  
  112.         freeSharedLibEntry(pNewEntry);  
  113.         return checkOnLoadResult(pActualEntry);  
  114.     } else {  
  115.         if (verbose)  
  116.             ALOGD("Added shared lib %s %p", pathName, classLoader);  
  117.   
  118.         bool result = false;  
  119.         void* vonLoad;  
  120.         int version;  
  121.   
  122.         // 获取前面加载的so库文件中的导出函数JNI_OnLoad的调用地址  
  123.         vonLoad = dlsym(handle, "JNI_OnLoad");  
  124.         // 判断导出函数JNI_OnLoad的调用地址是否为null  
  125.         if (vonLoad == NULL) {  
  126.             ALOGD("No JNI_OnLoad found in %s %p, skipping init", pathName, classLoader);  
  127.             result = true;  
  128.         } else {  
  129.               
  130.             // 获取前面加载的so库文件中的导出函数JNI_OnLoad的调用地址成功  
  131.             /* 
  132.              * Call JNI_OnLoad.  We have to override the current class 
  133.              * loader, which will always be "null" since the stuff at the 
  134.              * top of the stack is around Runtime.loadLibrary().  (See 
  135.              * the comments in the JNI FindClass function.) 
  136.              */  
  137.             // 保存获取到的JNI_OnLoad函数的调用地址  
  138.             OnLoadFunc func = (OnLoadFunc)vonLoad;  
  139.             Object* prevOverride = self->classLoaderOverride;  
  140.   
  141.             self->classLoaderOverride = classLoader;  
  142.             oldStatus = dvmChangeStatus(self, THREAD_NATIVE);  
  143.             if (gDvm.verboseJni) {  
  144.                   
  145.                 // 字符串[Calling JNI_OnLoad for \"%s\"]可以作为查找system/lib/libdvm.so中JNI_OnLoad函数调用地址的依据  
  146.                 ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);  
  147.             }  
  148.               
  149.             // 调用so库文件中的导出函数JNI_OnLoad  
  150.             version = (*func)(gDvmJni.jniVm, NULL);  
  151.             dvmChangeStatus(self, oldStatus);  
  152.             self->classLoaderOverride = prevOverride;  
  153.   
  154.             if (version == JNI_ERR) {  
  155.                 *detail = strdup(StringPrintf("JNI_ERR returned from JNI_OnLoad in \"%s\"",  
  156.                                               pathName).c_str());  
  157.             } else if (dvmIsBadJniVersion(version)) {  
  158.                 *detail = strdup(StringPrintf("Bad JNI version returned from JNI_OnLoad in \"%s\": %d",  
  159.                                               pathName, version).c_str());  
  160.                 /* 
  161.                  * It's unwise to call dlclose() here, but we can mark it 
  162.                  * as bad and ensure that future load attempts will fail. 
  163.                  * 
  164.                  * We don't know how far JNI_OnLoad got, so there could 
  165.                  * be some partially-initialized stuff accessible through 
  166.                  * newly-registered native method calls.  We could try to 
  167.                  * unregister them, but that doesn't seem worthwhile. 
  168.                  */  
  169.             } else {  
  170.                 result = true;  
  171.             }  
  172.             if (gDvm.verboseJni) {  
  173.                 ALOGI("[Returned %s from JNI_OnLoad for \"%s\"]",  
  174.                       (result ? "successfully" : "failure"), pathName);  
  175.             }  
  176.         }  
  177.   
  178.         if (result)  
  179.             pNewEntry->onLoadResult = kOnLoadOkay;  
  180.         else  
  181.             pNewEntry->onLoadResult = kOnLoadFailed;  
  182.   
  183.         pNewEntry->onLoadThreadId = 0;  
  184.   
  185.         /* 
  186.          * Broadcast a wakeup to anybody sleeping on the condition variable. 
  187.          */  
  188.         dvmLockMutex(&pNewEntry->onLoadLock);  
  189.         pthread_cond_broadcast(&pNewEntry->onLoadCond);  
  190.         dvmUnlockMutex(&pNewEntry->onLoadLock);  
  191.         return result;  
  192.     }  
  193. }  

 

感谢连接:

http://blog.csdn.net/luoshengyang/article/details/8923483

http://blog.csdn.net/myarrow/article/details/9718677

http://www.cnblogs.com/vendanner/p/4979177.html

http://bbs.pediy.com/showthread.php?t=211764

 

 

五、在.init和.init_array段的函数上下断点(基于Android4.4.4版本)

方法一:在上面已经分析了.init和.init_array段构造函数的执行,很显然我们想在.init和.init_array段构造函数上下断点也必须根据这些执行的流程来。由于Android系统的/system/bin/linker文件中上面提到的很多so库文件加载过程的函数没有被导出设置为隐藏,在进行so库文件的动态调试后不好通过查找关键流程函数的方法来查找.init和.init_array段构造函数。根据.init和.init_array段构造函数的调用的特点,最终的构造函数的调用都是在CallFunction函数并且在调用.init和.init_array段构造函数之前有明显的特征字符串 [ Calling %s @ %p for '%s' ],因此我们使用IDA工具,通过在/system/bin/linker文件中搜索特征字符串[ Calling %s @ %p for '%s' ] 来查找到 .init和.init_array段构造函数调用的地方。

 

将手机设备中的/system/bin/linker文件导出来,拖入到IDA中进行分析

 

[cpp] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. adb pull /system/bin/linker  

 

 

通过IDA工具在/system/bin/linker文件中,查找特征字符串 [ Calling %s @ %p for '%s' ]

 

根据字符串 [ Calling %s @ %p for '%s' ] 引用查询到.init和.init_array段构造函数调用的代码调用位置即 0x0000274C  BLX  R4处,0x0000274C即为.init和.init_array段构造函数调用地址(RVA)。

 

再开一个IDA对该so库文件进行Android应用的附加调试,设置IDA调试时断在so库文件加载的位置,更保险的方法就是 在system/lib/libdvm.so库文件的导出函数dvmLoadNativeCode()处下断点 ,然后通过IDA工具获取/system/bin/linker的模块加载基址linker_base(RA),因此 inker_base+0x0000274C 即为.init和.init_array段构造函数被调用的位置(VA),在此处下断点F7跟进 即可进入.init和.init_array段构造函数的实际调用地址VA处,实现监控.init和.init_array段构造函数的代码行为。

 

这里就不动态调试操作了,直接网上借一张图片显示效果,下面图即为.init和.init_array段构造函数被调用的位置, F7 跟进进行分析即可:

 

方法二:使用作者无名侠 【原创】执行视图 解析init_array 提供的工具,静态的解析so库文件的可执行试图,获取到.init_array段构造函数的调用地址(不是被调用的位置)的相对虚拟地址偏移fun_rva,加上该so模块加载基址so_base即 so_base+fun_rva 即为.init_array段构造函数的直接函数调用地址VA。代码下载地址为:https://github.com/Chenyuxin/elf_initarray.git

 

[cpp] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. /* 
  2.   Code By:无名侠 
  3. */  
  4. #include <stdio.h>  
  5. #include <elf.h>  
  6. #include <fcntl.h>  
  7. #include <stdlib.h>  
  8. #include <string.h>  
  9. #include <unistd.h>  
  10.   
  11. /*** 
  12.  *  
  13.  * 需要注意的是Elf32_Dyn中解析出的init_array 地址是RVA, 
  14.  * 有些时候段装载地址可能和文件偏移不同(也就是p_vaddr!= p_offset),  
  15.  * 如果想直接从文件解析该数组需要做转换.转换方法是查表. 
  16.  *  
  17.  ***/  
  18.   
  19.   
  20. // 将相对地址偏移RVA转换为elf文件的文件偏移FA  
  21. Elf32_Addr VaToFa(int fd,Elf32_Addr rva)  
  22. {  
  23.   /*顾名思义 
  24.     fd - 打开的so文件句柄 
  25.     rva - 欲转换的地址 
  26.     return - rva的文件偏移 
  27.   */  
  28.   int old;  
  29.   int pnum;  
  30.   Elf32_Ehdr ehdr;  
  31.   Elf32_Addr result;  
  32.     
  33.   old = lseek(fd, 0, SEEK_CUR);  
  34.   lseek(fd, 0, SEEK_SET);  
  35.   read(fd,&ehdr,sizeof(Elf32_Ehdr));  
  36.     
  37.   pnum = ehdr.e_phnum;  
  38.   result = rva;  
  39.     
  40.   for(int i = 0; i < pnum; i++)  
  41.   {  
  42.     Elf32_Phdr phdr;  
  43.     read(fd,&phdr, sizeof(Elf32_Phdr));  
  44.     if(rva >= phdr.p_vaddr && rva < phdr.p_vaddr+phdr.p_memsz)  
  45.       result =  rva-phdr.p_vaddr+phdr.p_offset;  
  46.   }  
  47.     
  48.   lseek(fd,old,SEEK_SET);  
  49.     
  50.   return result;  
  51. }  
  52.   
  53. // elf可执行程序的主函数  
  54. int main(int argc, char const *argv[]) {  
  55.       
  56.   int  fp;  
  57.   Elf32_Ehdr ehdr;  
  58.   int phnum;  
  59.     
  60.   // 对输入的函数参数的个数进行校验  
  61.   if(argc!=2)  
  62.   {  
  63.     printf("Please input elf file!\n");  
  64.     return -1;  
  65.   }  
  66.     
  67.   // 打开静态的so文件  
  68.   fp = open(argv[1], O_RDONLY);  
  69.   if(!fp)  
  70.   {  
  71.     printf("error:can't open %s \n",argv[1] );  
  72.     return -1;  
  73.   }  
  74.     
  75.   // 读取elf32文件的文件头  
  76.   read(fp, &ehdr,sizeof(Elf32_Ehdr));  
  77.   // 对文件的格式进行简单的判断  
  78.   if(memcmp(ehdr.e_ident, ELFMAG, SELFMAG))  
  79.   {  
  80.    printf("bad magic.\n");  
  81.    close(fp);  
  82.      
  83.    return -1;  
  84.   }  
  85.    
  86.  // 获取elf文件中程序头表的个数  
  87.  phnum = ehdr.e_phnum;  
  88.  // 遍历程序头表  
  89.  for(int i = 0; i < phnum; i++)  
  90.  {  
  91.    Elf32_Phdr phdr;  
  92.    // elf文件的文件头的后面就是elf文件的程序头表  
  93.    // 读取elf文件的程序头表  
  94.    read(fp, &phdr,sizeof(Elf32_Phdr));  
  95.      
  96.    // 对程序头表保存的数据的类型是否为.dynamic段  
  97.    if(phdr.p_type==PT_DYNAMIC)  
  98.    {  
  99.      Elf32_Dyn dyn;  
  100.      Elf32_Addr initaddr;  
  101.      Elf32_Word initsize;  
  102.   
  103.      // 该程序段为PT_DYNAMIC类型的.dynamic段  
  104.      int cnt = 0;  
  105.        
  106.      // 打印该程序段在elf文件中文件偏移RVA  
  107.      printf("offset : %x\n",phdr.p_offset);  
  108.      // 设置文件的偏移,定位到该程序的文件内容处  
  109.      lseek(fp,phdr.p_offset, SEEK_SET);  
  110.        
  111.      // 该程序段的实际数据为多个Elf32_Dyn结构体  
  112.      // 遍历该程序段的Elf32_Dyn结构体查找到.init_array段  
  113.      do {  
  114.       
  115.        // 读取Elf32_Dyn结构体的数据  
  116.        read(fp,&dyn,sizeof(Elf32_Dyn));  
  117.          
  118.        // 判断Elf32_Dyn结构体保存的数据是否为.init_array段的  
  119.        if(dyn.d_tag == DT_INIT_ARRAY)  
  120.         // 获取.init段的初始化函数跳转表起始相对地址  
  121.         initaddr = dyn.d_un.d_ptr;  
  122.        else if(dyn.d_tag == DT_INIT_ARRAYSZ)   
  123.        {  
  124.           // 获取DT_INIT_ARRAY的大小(占用字节数)  
  125.          initsize = dyn.d_un.d_val;  
  126.          break;  
  127.         }  
  128.   
  129.           
  130.      } while(dyn.d_tag != DT_NULL);  
  131.        
  132.      // 获取.init_array段有效初始函数调用地址的个数  
  133.      initsize/=4;  
  134.      initsize-=1;  
  135.        
  136.      // 打印.init_array段初始化函数的起始相对地址RVA和初始化函数的个数  
  137.      printf("INIT ARRAY OFFSET:%x(RVA)\nINTI NUM:%d\ninit table:\n", initaddr, initsize);  
  138.        
  139.      // 将.init_array段初始化函数的起始相对地址RVA转换为文件偏移的FA  
  140.      initaddr = VaToFa(fp, initaddr);  
  141.        
  142.      // 定位到elf文件的保存.init_array段初始化函数位置  
  143.      lseek(fp, initaddr, SEEK_SET);  
  144.        
  145.      // 遍历读取.init_array段初始化函数的相对调用地址RVA  
  146.      for(int i = 0;i < initsize;i++)  
  147.      {  
  148.         Elf32_Addr fun;  
  149.           
  150.         // 读取.init_array段的初始函数的相对调用地址  
  151.         read(fp, &fun, 4);  
  152.           
  153.         // 打印读取到的.init_array段的初始函数的相对调用地址  
  154.         printf("fun %d :%x\n", i, fun);  
  155.      }  
  156.        
  157.     }  
  158.   }  
  159.     
  160.   return 0;  
  161. }  


作者无名侠的代码使用方法以及测试:

 

[cpp] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. pandaos@pandaos:~/elf1$ gcc main.cpp -o elf1  
  2.   
  3. pandaos@pandaos:~/elf1$ ./elf1 libdanmu.so   
  4. offset : 1399f0  
  5. INIT ARRAY OFFSET:13a9c0(RVA)  
  6. INTI NUM:11  
  7. init table:  
  8. fun 0 :9eb9  
  9. fun 1 :9fa9  
  10. fun 2 :a099  
  11. fun 3 :a1bd  
  12. fun 4 :a2e1  
  13. fun 5 :a815  
  14. fun 6 :a895  
  15. fun 7 :a8d1  
  16. fun 8 :a8e1  
  17. fun 9 :a9bd  
  18. fun 10 :aa99  
  19. pandaos@pandaos:~/elf1$   

 

 

自己动手的测试的结果:

 

.init_array段构造函数的调用地址的RVA获取到了,只要通过 方法一 中的IDA调试so库的方法获取到该.init_array段所在so文件的内存加载基址 so_base ,因此 so_base+.init_array段构造函数的调用地址的RVA 即为.init_array段构造函数的调用地址的VA也就是.init_array段构造函数的动态实际调用地址,我们只要在这个地址处下断点即可。

 

感谢连接:

http://bbs.pediy.com/showthread.php?t=212374

https://github.com/Chenyuxin/elf_initarray.git

 

 

六、在so库文件的JNI_OnLoad上下断点(基于Android4.4.4版本的Dalvik模式)

方法一:由于JNI_OnLoad函数在被调用时是在函数dvmLoadNativeCode()中,并且JNI_OnLoad函数在被调用时也有特征字符串,如 [Calling JNI_OnLoad for \"%s\"] 和 "JNI_OnLoad" 等根据自己的喜欢选一个就行。因此,我们可以将手机设备中的system/lib/libdvm.so文件导出来,拖到IDA中进行分析,然后使用特征字符串搜索的方法进行定位。

 

[cpp] view plain copy

  在CODE上查看代码片派生到我的代码片

  1. adb pull system/lib/libdvm.so  

 

 

详细的步骤可以参考作者【原创】JNI_OnLoad与init_array下断方法整理  的帖子

 

方法二:前面的作者可能是已经被特征字符串搜索的方法思维定式了,其实在JNI_OnLoad上下断点很容易的,不需要这么麻烦。

adb pull system/lib/libdvm.so将Android手机设备的libdvm.so文件导出来,拖到IDA中进行分析,可以发现libdvm.so库文件中 dvmLoadNativeCode() 是导出的,意味着我们在使用IDA动态调试so库文件时,可以在函数dvmLoadNativeCode()上下断点,很高兴的是JNI_OnLoad函数的调用就是在函数dvmLoadNativeCode()中,因此通过 _Z17dvmLoadNativeCodePKcP6ObjectPPc 即dvmLoadNativeCode()函数就可以定位到JNI_OnLoad函数的调用的位置。

 

通过 _Z17dvmLoadNativeCodePKcP6ObjectPPc 即dvmLoadNativeCode()函数就可以定位到JNI_OnLoad函数的调用的位置(这里是静态的查找示意图,动态查找的方法一样,等目标App应用的so库文件加载了,然后在动态加载的system/lib/libdvm.so中查找 _Z17dvmLoadNativeCodePKcP6ObjectPPc 函数,然后在函数_Z17dvmLoadNativeCodePKcP6ObjectPPc中查找到JNI_OnLoad函数的调用位置[ BLX  R8 ]),F7 跟进JNI_OnLoad函数的实现即可分析JNI_OnLoad函数的代码行为。

 

这里给出的实例是Dalvik模式下的,Art模式下在JNI_OnLoad函数上下断点方法一样。

 

 

七、在Android so文件的.init、.init_array上和JNI_OnLoad处下断点的方法总结

由用于调试的Android设备的Androd系统的版本,找到该Android系统版本对应的Android源码,查看和弄明白.init、.init_array和JNI_OnLoad的执行流程和原理,找到能用于搜索的有效特征字符串,导出用于调试的Android设备的Androd系统的/system/bin/linker文件、system/lib/libdvm.so或system/lib/libartso文件,使用IDA工具进行分析,通过前面的特征字符串搜索找到.init、.init_array和JNI_OnLoad被调用位置的RVA,然后IDA调试so获取相应的system/lib/libdvm.so或system/lib/libartso文件的动态内存加载基址linker_base、libdvm_base或者libartso_base,因此IDA动态调试时.init、.init_array被调用的位置VA为 linker_base+RVA;JNI_OnLoad被调用的位置的VA为 libdvm_base或者libartso_base + RVA,我们在动态调试分析的时候,只要在这两个关键点处下断点即可。

 

本文博客地址:http://blog.csdn.net/qq1084283172/article/details/54233552

 

感谢连接:

http://blog.csdn.net/luoshengyang/article/details/8923483

http://blog.csdn.net/myarrow/article/details/9718677

http://blog.chinaunix.net/uid-1835494-id-2831799.html

http://bbs.pediy.com/showthread.php?t=211764

http://bbs.pediy.com/showthread.php?t=212374

http://www.ibm.com/developerworks/cn/linux/l-elf/part1/

http://bbs.pediy.com/showthread.php?p=1365423

http://www.blogfshare.com/linker-load-so.html

http://www.cnblogs.com/vendanner/p/4979177.html

https://github.com/Chenyuxin/elf_initarray

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值