在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.          &
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值