移动端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段里面。
使用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:
-
- void* dlopen(const char* filename, int flags) {
-
-
- ScopedPthreadMutexLocker locker(&gDlMutex);
-
- soinfo* result = do_dlopen(filename, flags);
-
- if (result == NULL) {
- __bionic_format_dlerror("dlopen failed", linker_get_error_buffer());
- return NULL;
- }
-
- return result;
- }
Android4.4.4r1的源码\bionic\linker\linker.cpp:
-
- soinfo* do_dlopen(const char* name, int 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);
-
-
-
- soinfo* si = find_library(name);
-
- if (si != NULL) {
-
-
- si->CallConstructors();
-
- }
-
-
- set_soinfo_pool_protection(PROT_READ);
-
- return si;
- }
当上面的构造函数 si->CallConstructors() 被调用时,preinit_array-> .init -> .init_array段的函数,会依次按照顺序进行执行并且
.init_array段的函数指针数组的执行的实现其实和.init段的函数的执行的实现是一样的。
- 这里的DT_INIT和DT_INIT_ARRAY到底是什么呢?
-
- init_func和init_array都是结构体soinfo的成员变量,在soinfo_link_image加载so的时候进行赋值。
-
- #define DT_INIT 12
- #define DT_INIT_ARRAY 25
-
- 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段的构造函数
-
- void soinfo::CallConstructors() {
-
- if (constructors_called) {
- return;
- }
-
-
-
-
-
-
-
-
-
-
-
- constructors_called = true;
-
- if ((flags & FLAG_EXE) == 0 && preinit_array != NULL) {
-
- PRINT("\"%s\": ignoring %d-entry DT_PREINIT_ARRAY in shared library!",
- name, preinit_array_count);
- }
-
-
- 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);
-
-
-
- CallFunction("DT_INIT", init_func);
-
- CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
- }
.init段构造函数的调用实现
-
- 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;
- }
-
-
-
- TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name);
-
- function();
- TRACE("[ Done calling %s @ %p for '%s' ]", function_name, function, name);
-
-
-
- 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;
-
-
- for (int i = begin; i != end; i += step) {
- TRACE("[ %s[%d] == %p ]", array_name, i, functions[i]);
-
-
- CallFunction("function", functions[i]);
- }
-
- TRACE("[ Done calling %s for '%s' ]", array_name, name);
- }
从
.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
-
-
-
-
-
-
-
-
-
-
-
- public static void loadLibrary(String libName) {
-
-
- Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
- }
Android4.4.4r1的源码/libcore/luni/src/main/java/java/lang/Runtime.java
-
-
-
-
-
-
-
-
-
-
- public void loadLibrary(String libName) {
- loadLibrary(libName, VMStack.getCallingClassLoader());
- }
-
-
-
-
- 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)) {
-
- String error = doLoad(candidate, loader);
- if (error == null) {
- return;
- }
- lastError = error;
- }
- }
-
- if (lastError != null) {
- throw new UnsatisfiedLinkError(lastError);
- }
- throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
- }
看下String doLoad(String name, ClassLoader loader)函数的实现,
doLoad函数调用native层实现的nativeLoad函数进行so库文件的加载
- private String doLoad(String name, ClassLoader loader) {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- String ldLibraryPath = null;
- if (loader != null && loader instanceof BaseDexClassLoader) {
-
- ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
- }
-
-
-
- synchronized (this) {
-
- return nativeLoad(name, loader, ldLibraryPath);
- }
- }
-
-
-
- private static native String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath);
nativeLoad函数在Android4.4.4r1源码/dalvik/vm/native/java_lang_Runtime.cpp中的实现
-
-
-
-
-
-
-
-
-
-
-
-
- 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);
- (*android_update_LD_LIBRARY_PATH)(ldLibraryPath);
- } else {
- ALOGE("android_update_LD_LIBRARY_PATH not found; .so dependencies will not work!");
- }
- free(ldLibraryPath);
- }
-
- StringObject* result = NULL;
- char* reason = NULL;
-
- bool success = dvmLoadNativeCode(fileName, classLoader, &reason);
- if (!success) {
- const char* msg = (reason != NULL) ? reason : "unknown failure";
- result = dvmCreateStringFromCstr(msg);
- dvmReleaseTrackedAlloc((Object*) result, NULL);
- }
-
- free(reason);
- free(fileName);
- RETURN_PTR(result);
- }
nativeLoad函数的本地方法实现Dalvik_java_lang_Runtime_nativeLoad()函数最终调用Android4.4.4r1源码/dalvik/vm/Native.cpp中的dvmLoadNativeCode()函数,在该函数中先调用dlopen函数加载so库文件到内存中,然后调用dlsym函数获取so库文件中JNI_OnLoad函数的导出地址,然后调用JNI_OnLoad函数执行开发者自定义的代码和实现jni函数的注册。
- typedef int (*OnLoadFunc)(JavaVM*, void*);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- bool dvmLoadNativeCode(const char* pathName, Object* classLoader,
- char** detail)
- {
- SharedLib* pEntry;
- void* handle;
- bool verbose;
-
-
- verbose = !!strncmp(pathName, "/system", sizeof("/system")-1);
- verbose = verbose && !!strncmp(pathName, "/vendor", sizeof("/vendor")-1);
-
- if (verbose)
- ALOGD("Trying to load lib %s %p", pathName, classLoader);
-
- *detail = NULL;
-
-
-
-
-
- pEntry = findSharedLibEntry(pathName);
- if (pEntry != NULL) {
- if (pEntry->classLoader != classLoader) {
- ALOGW("Shared lib '%s' already opened by CL %p; can't open in %p",
- pathName, pEntry->classLoader, classLoader);
- return false;
- }
- if (verbose) {
- ALOGD("Shared lib '%s' already loaded in same CL %p",
- pathName, classLoader);
- }
- if (!checkOnLoadResult(pEntry))
- return false;
- return true;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Thread* self = dvmThreadSelf();
- ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);
-
-
- handle = dlopen(pathName, RTLD_LAZY);
- dvmChangeStatus(self, oldStatus);
-
- if (handle == NULL) {
- *detail = strdup(dlerror());
- ALOGE("dlopen(\"%s\") failed: %s", pathName, *detail);
- return false;
- }
-
-
- SharedLib* pNewEntry;
- pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));
- pNewEntry->pathName = strdup(pathName);
- pNewEntry->handle = handle;
- pNewEntry->classLoader = classLoader;
- dvmInitMutex(&pNewEntry->onLoadLock);
- pthread_cond_init(&pNewEntry->onLoadCond, NULL);
- pNewEntry->onLoadThreadId = self->threadId;
-
-
- SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);
-
- if (pNewEntry != pActualEntry) {
- ALOGI("WOW: we lost a race to add a shared lib (%s CL=%p)",
- pathName, classLoader);
- freeSharedLibEntry(pNewEntry);
- return checkOnLoadResult(pActualEntry);
- } else {
- if (verbose)
- ALOGD("Added shared lib %s %p", pathName, classLoader);
-
- bool result = false;
- void* vonLoad;
- int version;
-
-
- vonLoad = dlsym(handle, "JNI_OnLoad");
-
- if (vonLoad == NULL) {
- ALOGD("No JNI_OnLoad found in %s %p, skipping init", pathName, classLoader);
- result = true;
- } else {
-
-
-
-
-
-
-
-
-
- OnLoadFunc func = (OnLoadFunc)vonLoad;
- Object* prevOverride = self->classLoaderOverride;
-
- self->classLoaderOverride = classLoader;
- oldStatus = dvmChangeStatus(self, THREAD_NATIVE);
- if (gDvm.verboseJni) {
-
-
- ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);
- }
-
-
- version = (*func)(gDvmJni.jniVm, NULL);
- dvmChangeStatus(self, oldStatus);
- self->classLoaderOverride = prevOverride;
-
- if (version == JNI_ERR) {
- *detail = strdup(StringPrintf("JNI_ERR returned from JNI_OnLoad in \"%s\"",
- pathName).c_str());
- } else if (dvmIsBadJniVersion(version)) {
- *detail = strdup(StringPrintf("Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
- pathName, version).c_str());
-
-
-
-
-
-
-
-
-
- } else {
- result = true;
- }
- if (gDvm.verboseJni) {
- ALOGI("[Returned %s from JNI_OnLoad for \"%s\"]",
- (result ? "successfully" : "failure"), pathName);
- }
- }
-
- if (result)
- pNewEntry->onLoadResult = kOnLoadOkay;
- else
- pNewEntry->onLoadResult = kOnLoadFailed;
-
- pNewEntry->onLoadThreadId = 0;
-
-
-
-
- dvmLockMutex(&pNewEntry->onLoadLock);
- pthread_cond_broadcast(&pNewEntry->onLoadCond);
- dvmUnlockMutex(&pNewEntry->onLoadLock);
- return result;
- }
- }
感谢连接:
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中进行分析
- 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。
-
-
-
- #include <stdio.h>
- #include <elf.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
-
-
-
-
-
-
-
-
-
-
-
- Elf32_Addr VaToFa(int fd,Elf32_Addr rva)
- {
-
-
-
-
-
- int old;
- int pnum;
- Elf32_Ehdr ehdr;
- Elf32_Addr result;
-
- old = lseek(fd, 0, SEEK_CUR);
- lseek(fd, 0, SEEK_SET);
- read(fd,&ehdr,sizeof(Elf32_Ehdr));
-
- pnum = ehdr.e_phnum;
- result = rva;
-
- for(int i = 0; i < pnum; i++)
- {
- Elf32_Phdr phdr;
- read(fd,&phdr, sizeof(Elf32_Phdr));
- if(rva >= phdr.p_vaddr && rva < phdr.p_vaddr+phdr.p_memsz)
- result = rva-phdr.p_vaddr+phdr.p_offset;
- }
-
- lseek(fd,old,SEEK_SET);
-
- return result;
- }
-
-
- int main(int argc, char const *argv[]) {
-
- int fp;
- Elf32_Ehdr ehdr;
- int phnum;
-
-
- if(argc!=2)
- {
- printf("Please input elf file!\n");
- return -1;
- }
-
-
- fp = open(argv[1], O_RDONLY);
- if(!fp)
- {
- printf("error:can't open %s \n",argv[1] );
- return -1;
- }
-
-
- read(fp, &ehdr,sizeof(Elf32_Ehdr));
-
- if(memcmp(ehdr.e_ident, ELFMAG, SELFMAG))
- {
- printf("bad magic.\n");
- close(fp);
-
- return -1;
- }
-
-
- phnum = ehdr.e_phnum;
-
- for(int i = 0; i < phnum; i++)
- {
- Elf32_Phdr phdr;
-
-
- read(fp, &phdr,sizeof(Elf32_Phdr));
-
-
- if(phdr.p_type==PT_DYNAMIC)
- {
- Elf32_Dyn dyn;
- Elf32_Addr initaddr;
- Elf32_Word initsize;
-
-
- int cnt = 0;
-
-
- printf("offset : %x\n",phdr.p_offset);
-
- lseek(fp,phdr.p_offset, SEEK_SET);
-
-
-
- do {
-
-
- read(fp,&dyn,sizeof(Elf32_Dyn));
-
-
- if(dyn.d_tag == DT_INIT_ARRAY)
-
- initaddr = dyn.d_un.d_ptr;
- else if(dyn.d_tag == DT_INIT_ARRAYSZ)
- {
-
- initsize = dyn.d_un.d_val;
- break;
- }
-
-
- } while(dyn.d_tag != DT_NULL);
-
-
- initsize/=4;
- initsize-=1;
-
-
- printf("INIT ARRAY OFFSET:%x(RVA)\nINTI NUM:%d\ninit table:\n", initaddr, initsize);
-
-
- initaddr = VaToFa(fp, initaddr);
-
-
- lseek(fp, initaddr, SEEK_SET);
-
-
- for(int i = 0;i < initsize;i++)
- {
- Elf32_Addr fun;
-
-
- read(fp, &fun, 4);
-
-
- printf("fun %d :%x\n", i, fun);
- }
-
- }
- }
-
- return 0;
- }
作者无名侠的代码使用方法以及测试:
- pandaos@pandaos:~/elf1$ gcc main.cpp -o elf1
-
- pandaos@pandaos:~/elf1$ ./elf1 libdanmu.so
- offset : 1399f0
- INIT ARRAY OFFSET:13a9c0(RVA)
- INTI NUM:11
- init table:
- fun 0 :9eb9
- fun 1 :9fa9
- fun 2 :a099
- fun 3 :a1bd
- fun 4 :a2e1
- fun 5 :a815
- fun 6 :a895
- fun 7 :a8d1
- fun 8 :a8e1
- fun 9 :a9bd
- fun 10 :aa99
- 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中进行分析,然后使用特征字符串搜索的方法进行定位。
- 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