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

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

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

so共享库文件的高级特性

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

  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. [2] c++全局对象初始化,其构造函数(对象)被自动执行

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

  1. extern "C" void _init(void) { } -------》编译生成后在.init
  2. __attribute__((constructor)) void _init(void) { } -------》编译生成后在.init_array
  3. 说明下,带构造函数的全局对象生成的时在在.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:

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

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

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

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

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

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

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

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

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

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

  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. TRACE("[ Calling %s (size %d) @ %p for '%s' ]", array_name, count, functions, name);
  6. int begin = reverse ? (count - 1) : 0;
  7. int end = reverse ? -1 : count;
  8. int step = reverse ? -1 : 1;
  9. // 循环遍历调用.init_arrayt段中每个函数
  10. for (int i = begin; i != end; i += step) {
  11. TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);
  12. // .init_arrayt段中,每个函数指针的调用和上面的.init段的构造函数的实现是一样的
  13. CallFunction("function", functions[i]);
  14. }
  15. TRACE("[ Done calling %s for '%s' ]", array_name, name);
  16. }

从.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

  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. // 调用Runtime.loadLibrary函数实现libxxx.so库文件的加载
  14. Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
  15. }

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

  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. * Searches for a library, then loads and links it without security checks.
  16. */
  17. void loadLibrary(String libraryName, ClassLoader loader) {
  18. if (loader != null) {
  19. String filename = loader.findLibrary(libraryName);
  20. if (filename == null) {
  21. throw new UnsatisfiedLinkError("Couldn't load " + libraryName +
  22. " from loader " + loader +
  23. ": findLibrary returned null");
  24. }
  25. String error = doLoad(filename, loader);
  26. if (error != null) {
  27. throw new UnsatisfiedLinkError(error);
  28. }
  29. return;
  30. }
  31. String filename = System.mapLibraryName(libraryName);
  32. List<String> candidates = new ArrayList<String>();
  33. String lastError = null;
  34. for (String directory : mLibPaths) {
  35. String candidate = directory + filename;
  36. candidates.add(candidate);
  37. if (IoUtils.canOpenReadOnly(candidate)) {
  38. // 调用doLoad函数加载so库文件
  39. String error = doLoad(candidate, loader);
  40. if (error == null) {
  41. return; // We successfully loaded the library. Job done.
  42. }
  43. lastError = error;
  44. }
  45. }
  46. if (lastError != null) {
  47. throw new UnsatisfiedLinkError(lastError);
  48. }
  49. throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
  50. }

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

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

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

  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. assert(fileNameObj != NULL);
  20. char* fileName = dvmCreateCstrFromString(fileNameObj);
  21. if (ldLibraryPathObj != NULL) {
  22. char* ldLibraryPath = dvmCreateCstrFromString(ldLibraryPathObj);
  23. void* sym = dlsym(RTLD_DEFAULT, "android_update_LD_LIBRARY_PATH");
  24. if (sym != NULL) {
  25. typedef void (*Fn)(const char*);
  26. Fn android_update_LD_LIBRARY_PATH = reinterpret_cast<Fn>(sym);
  27. (*android_update_LD_LIBRARY_PATH)(ldLibraryPath);
  28. } else {
  29. ALOGE("android_update_LD_LIBRARY_PATH not found; .so dependencies will not work!");
  30. }
  31. free(ldLibraryPath);
  32. }
  33. StringObject* result = NULL;
  34. char* reason = NULL;
  35. // 调用dvmLoadNativeCode函数加载so库文件
  36. bool success = dvmLoadNativeCode(fileName, classLoader, &reason);
  37. if (!success) {
  38. const char* msg = (reason != NULL) ? reason : "unknown failure";
  39. result = dvmCreateStringFromCstr(msg);
  40. dvmReleaseTrackedAlloc((Object*) result, NULL);
  41. }
  42. free(reason);
  43. free(fileName);
  44. RETURN_PTR(result);
  45. }

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

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

感谢连接:

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中进行分析

  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

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

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

  1. pandaos@pandaos:~/elf1$ gcc main.cpp -o elf1
  2. pandaos@pandaos:~/elf1$ ./elf1 libdanmu.so
  3. offset : 1399f0
  4. INIT ARRAY OFFSET:13a9c0(RVA)
  5. INTI NUM:11
  6. init table:
  7. fun 0 :9eb9
  8. fun 1 :9fa9
  9. fun 2 :a099
  10. fun 3 :a1bd
  11. fun 4 :a2e1
  12. fun 5 :a815
  13. fun 6 :a895
  14. fun 7 :a8d1
  15. fun 8 :a8e1
  16. fun 9 :a9bd
  17. fun 10 :aa99
  18. 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中进行分析,然后使用特征字符串搜索的方法进行定位。

  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/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

在Android so文件的.init、.init_array上和JNI_OnLoad处下断点的更多相关文章

  1. ida动态调试so,在init_array和JNI_ONLOAD处下断点

    本文涉及到的apk.请在github下载https://github.com/jltxgcy/AliCrack/AliCrackme_2.apk. 0x00 怎样在JNI_ONLOAD下断点.參考安卓 ...

  2. IDA调试android so文件.init_array和JNI_OnLoad

    我们知道so文件在被加载的时候会首先执行.init_array中的函数,然后再执行JNI_OnLoad()函数.JNI_Onload()函数因为有符号表所以非常容易找到,但是.init_array里的 ...

  3. Android C语言_init函数和constructor属性及.init/.init_array节探索

    本篇文章主要介绍了"Android C语言_init函数和constructor属性及.init/.init_array节探索",主要涉及到Android C语言_init函数和c ...

  4. Android 用adb pull或push 拷贝手机文件到到电脑上,拷贝手机数据库到电脑上,拷贝电脑数据库到手机上

    先说一下adb命令配置,如果遇到adb不是内部或外部命令,也不是可运行的程序或批量文件.配置下环境变量 1.adb不是内部或外部命令,也不是可运行的程序或批量文件. 解决办法:在我的电脑-属性-高级计 ...

  5. 使用.NET框架、Web service实现Android的文件上传(二)

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYUAAAKpCAIAAADcx6fPAAAgAElEQVR4nOydd1hT5+LHg1attbfr1t ...

  6. Android和FTP服务器交互,上传下载文件(实例demo)

    今天同学说他备份了联系人的数据放在一个文件里,想把它存到服务器上,以便之后可以进行下载恢复..于是帮他写了个上传,下载文件的demo 主要是 跟FTP服务器打交道-因为这个东东有免费的可以身亲哈 1. ...

  7. 分析cocos2d-x在Android上的编译过程(1):cocco2d-x是怎样生成的Android的文件夹结构

    当新建完一个cocos2d-x的项目后.进入到项目中的proj.android中,会看到例如以下的文件夹结构 在VS先把它编译,然后导入到Eclipse中,导入完后会看到多了几个文件 watermar ...

  8. Android OkHttp文件上传与下载的进度监听扩展

    http://www.loongwind.com/archives/290.html 上一篇文章介绍了用Retrofit实现文件的上传与下载,但是我们发现没办法监听上传下载的进度,毕竟我们在做开发的时 ...

  9. android中的文件(图片)上传

    android中的文件(图片)上传其实没什么复杂的,主要是对 multipart/form-data 协议要有所了解. 关于 multipart/form-data 协议,在 RFC文档中有详细的描述 ...

随机推荐

  1. 基于CameraLink的逻辑综合和版图设计

    前期接口设计用的是Vivado18.3+Modelsim10.6,逻辑综合及版图生成的环境是Ubuntu16,逻辑综合用的工具Design Compiler,生成版图用的工具是Encounter. 下 ...

  2. HDOJ-6641(欧几里得+异或运算)

    TDL HDOJ-6641 关于题意,就是要找出符合f的第m大的数,而且后面还要满足异或等式. 通过观察题目,可以发现n太大了,所以不能直接枚举.当然因为m比较小,所以可以转换思路k^n,这个数最大不 ...

  3. PBR:基于物理的渲染(Physically Based Rendering)+理论相关

    一: 关于能量守恒 出射光线的能量永远不能超过入射光线的能量(发光面除外).如图示我们可以看到,随着粗糙度的上升镜面反射区域的会增加,但是镜面反射的亮度却会下降.如果不管反射轮廓的大小而让每个像素的镜 ...

  4. 基于4H-SIC的先进集成电路用n型LDMOS晶体管

    基于4H-SIC的先进集成电路用n型LDMOS晶体管 摘要: 通过对具有不同的设计方式的具有减小的表面电场的横向4H-SIC-N型-横向扩散金属氧化物半导体(LDMOS)晶体管进行测量和模拟,得到了得 ...

  5. 用c++解一元二次方程

    解方程 github项目地址 这两天得知初二的表妹学了一元二次方程,听说还不会解,我就想着试试用C语言编写解方程. 一元二次方程 用公式法 这种方法效果很好: #include"funct. ...

  6. 微服务架构Day16-SpringBoot之监控管理

    监控管理使用步骤 通过引入spring-boot-starter-actuator,可以使用SpringBoot提供应用监控和管理的功能.可以通过HTTP,JMX,SSH协议来进行操作,自动得到审计, ...

  7. python网络编程TCP服务多客户端的服务端开发

    #服务多客户端TCP服务端开发 2 #方法说明 3 """ 4 bind(host,port)表示绑定端口号,host是ip地址,ip地址一般不进 行绑定,表示本机的任何 ...

  8. Android学习之在Adapter中调用Fragment

    •前言 在学习<第一行代码>,4.5 小节--一个简易版的新闻应用的时候: 在为 RecyclerView 创建适配器的时候: 作者直接在 NewsTitleFragment.java 中 ...

  9. vue实现日历

    vue实现日历 之前在上家公司做过一个公司人员考勤的东西,里面需要用到日历,当时自己用vue随便写了一个,比较简单 下面代码是删掉了其他功能的代码,只留下日历部分 <template> & ...

  10. java进阶(40)--wait与notify(生产者与消费者模式)

    文档目录: 一.概念 二.wait的作用 三.notify的作用 四.生产者消费者模式 五.举例 ---------------------------------------分割线:正文------ ...