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

Android逆向分析的过程中免不了碰到Android so被加固的情况,要对被加固的Android so进行脱壳处理,就需要先了解Android so的加载流程,进而了解Android so的加固原理。学习Android so加固的思路和学习Android dex文件加固的思路是类似的,下面就以Android so加固的源头System.loadLibrary函数为入口点进行学习,这里的源码分析以Android 4.4.4 r1版本的源码为基础。

1.System.loadLibrary函数和System.load函数的调用区别。

关于Android系统中System.loadLibrary函数和System.load函数的调用区别,可以参考System.loadLibrary函数和System.load函数的注释说明。System.loadLibrary函数调用传入的参数为Android so库文件的文件名称的约定简写,而System.load函数调用传入的参数为Android
so库文件的文件全路径,这就是调用的区别。

System.load函数的调用参数的说明:

System.loadLibrary函数的调用参数的说明:

2.System.loadLibrary函数和System.load函数仅仅在调用参数上有一些区别(java层的代码实现上有一些差别),具体的底层函数实现是一样的,System.loadLibrary函数和System.load函数最终底层都是调用的Native函数Runtime.nativeLoad来实现。

3.System.loadLibrary函数的java层代码实现的调用流程图,如下:

4.System.load函数的java层代码实现的调用流程图,如下:

5.System.loadLibrary函数是在Android 4.4.4 r1源码的文件路径  /libcore/luni/src/main/java/java/lang/System.java 中实现的,在System.loadLibrary函数实现中调用Runtime类的函数getRuntime获取Runtime类的实例对象,调用类VMStack的Native函数getCallingClassLoader获取当前进程调用者的class
loader(类加载器),然后调用Runtime类的函数loadLibrary对so库文件进行查找和加载。

http://androidxref.com/4.4.4_r1/xref/libcore/luni/src/main/java/java/lang/System.java

6.Runtime类的函数loadLibrary,在Android
4.4.4 r1源码的文件路径 /libcore/luni/src/main/java/java/lang/Runtime.java 中实现,比较深层的代码原理分析可以参考老罗的博客《Dalvik虚拟机JNI方法的注册过程分析》。Runtime类的函数loadLibrary实现中对于类加载器ClassLoader实例对象loader不为null的情况,调用loader的成员函数findLibrary获取需要加载so库文件的绝对路径,调用Runtime类的函数doLoad进行so库文件的加载;当类加载器ClassLoader实例对象loader为null的情况时,在Android系统预定义范围lib文件目录下进行so库文件的查找,查找需要加载的so库文件的名称如:lib<name>.so,则调用Runtime类的函数doLoad进行so库文件的加载;如果找不到该so库文件所在的文件路径再会抛出异常。

http://androidxref.com/4.4.4_r1/xref/libcore/luni/src/main/java/java/lang/Runtime.java

    /*
* Searches for a library, then loads and links it without security checks.
*/
void loadLibrary(String libraryName, ClassLoader loader) {
if (loader != null) {
// 1. 类加载器ClassLoader不为null的情况 // 调用类ClassLoader的类成员函数findLibraryg获取so库文件的全路径
String filename = loader.findLibrary(libraryName);
// 检查是否获取so库文件的绝对全路径成功
if (filename == null) {
throw new UnsatisfiedLinkError("Couldn't load " + libraryName +
" from loader " + loader +
": findLibrary returned null");
}
// 调用Runtime类的成员函数doLoad进行so库文件的加载
String error = doLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
} // 2. 类加载器ClassLoader为null的情况
// 在Android系统预定义的系统范围lib目录下查找so库文件得到so库文件的名称
// 例如:lib<name>.so
String filename = System.mapLibraryName(libraryName);
// 保存so库文件的绝对全路径,供加载so库文件
List<String> candidates = new ArrayList<String>();
String lastError = null;
for (String directory : mLibPaths) {
// 拼接字符串得到so库文件的绝对全路径
String candidate = directory + filename;
candidates.add(candidate);
// 判断so库文件的路径是否可读有效
if (IoUtils.canOpenReadOnly(candidate)) {
// 调用Runtime类的成员函数doLoad进行so库文件的加载
String error = doLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
} // so库文件的文件路径查找或者so库文件加载出现错误的情况
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}

7.Runtime类的函数loadLibrary最终调用Runtime类的成员函数doLoad实现so库文件的加载,doLoad函数先调用ClassLoader的实例成员方法getLdLibraryPath获取当前Android进程运行所需要加载的so库文件的所有文件目录路径的环境变量列表(:隔开类似linux环境变量的字符串),并以此为传入参数之一调用Native函数nativeLoad对目标so库文件进行加载。

    private String doLoad(String name, ClassLoader loader) {
// Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH,
// which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH. // The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load
// libraries with no dependencies just fine, but an app that has multiple libraries that
// depend on each other needed to load them in most-dependent-first order. // We added API to Android's dynamic linker so we can update the library path used for
// the currently-running process. We pull the desired path out of the ClassLoader here
// and pass it to nativeLoad so that it can call the private dynamic linker API. // We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the
// beginning because multiple apks can run in the same process and third party code can
// use its own BaseDexClassLoader. // We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any
// dlopen(3) calls made from a .so's JNI_OnLoad to work too. // So, find out what the native library search path is for the ClassLoader in question...
String ldLibraryPath = null;
if (loader != null && loader instanceof BaseDexClassLoader) {
// 获取当前Android进程运行dex文件需要加载的so库文件所在文件目录路径的环境变量(: 隔开)
ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
}
// nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
// of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
// internal natives.
synchronized (this) {
// 同步处理,调用native方法nativeLoad加载so库文件name
return nativeLoad(name, loader, ldLibraryPath);
}
}

8.类Runtime的成员方法nativeLoad是Native函数,最终调用NDK编写的底层jni方法,并且当前Android进程所运行的虚拟机模式不同,Runtime.nativeLoad函数的实现也会有所差别。在Dalvik虚拟机模式下,Runtime.nativeLoad函数最终调用Android
4.4.4 r1 源码文件 /dalvik/vm/native/java_lang_Runtime.cpp 中的函数Dalvik_java_lang_Runtime_nativeLoad;Art虚拟机模式下,Runtime.nativeLoad函数最终调用的是Android
4.4.4 r1 源码文件 /art/runtime/native/java_lang_Runtime.cc 中的函数Runtime_nativeLoad。

http://androidxref.com/4.4.4_r1/xref/dalvik/vm/native/java_lang_Runtime.cpp#Dalvik_java_lang_Runtime_nativeLoad

http://androidxref.com/4.4.4_r1/xref/art/runtime/native/java_lang_Runtime.cc#95

Dalvik虚拟机模式下,Runtime.nativeLoad函数的Native层实现。

/*
* static String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath)
*
* Load the specified full path as a dynamic library filled with
* JNI-compatible methods. Returns null on success, or a failure
* message on failure.
*/
static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,
JValue* pResult)
{
// 获取需要加载的so库文件的绝对路径(Java层的String对象)
StringObject* fileNameObj = (StringObject*) args[0];
// 获取当前Android进程的类加载器ClassLoader实例对象
Object* classLoader = (Object*) args[1];
// 获取当前Android进程运行所依赖的so库文件所在的so文件目录路径的环境变量字符串(: 隔开的)
StringObject* ldLibraryPathObj = (StringObject*) args[2]; assert(fileNameObj != NULL);
// 将java层的字符串转换C语言类型的字符串
char* fileName = dvmCreateCstrFromString(fileNameObj); // 判断需要加载的依赖so库文件的文件目录路径(lib库文件夹的环境变量)是否为空
if (ldLibraryPathObj != NULL) { // 将java层的字符串转换成为c语言类型的字符串
char* ldLibraryPath = dvmCreateCstrFromString(ldLibraryPathObj);
// 获取当前libdvm.so模块的导出函数android_update_LD_LIBRARY_PATH的调用地址
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更新系统lib库文件的文件目录
(*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;
// 调用dvmLoadNativeCode函数加载目标so库文件fileName
bool success = dvmLoadNativeCode(fileName, classLoader, &reason);
// 检查目标so库文件是否加载成功
if (!success) {
const char* msg = (reason != NULL) ? reason : "unknown failure";
result = dvmCreateStringFromCstr(msg);
dvmReleaseTrackedAlloc((Object*) result, NULL);
} free(reason);
free(fileName);
// 设置函数返回值
RETURN_PTR(result);
}

9.Android 4.4.4 r1 源码文件 /dalvik/vm/native/java_lang_Runtime.cpp 中的函数Dalvik_java_lang_Runtime_nativeLoad,最终调用源码文件 /dalvik/vm/Native.cpp 中的dvmLoadNativeCode函数,并函数dvmLoadNativeCode是libdvm.so库文件中的导出函数。dvmLoadNativeCode函数主要实现是先调用dlopen函数加载目标so库文件,然后调用目标so库文件中的导出函数JNI_OnLoad,实现jni函数的注册以及其他的初始化操作等。

http://androidxref.com/4.4.4_r1/xref/dalvik/vm/Native.cpp#318

typedef int (*OnLoadFunc)(JavaVM*, void*);

/*
* Load native code from the specified absolute pathname. Per the spec,
* if we've already loaded a library with the specified pathname, we
* return without doing anything.
*
* TODO? for better results we should absolutify the pathname. For fully
* correct results we should stat to get the inode and compare that. The
* existing implementation is fine so long as everybody is using
* System.loadLibrary.
*
* The library will be associated with the specified class loader. The JNI
* spec says we can't load the same library into more than one class loader.
*
* Returns "true" on success. On failure, sets *detail to a
* human-readable description of the error or NULL if no detail is
* available; ownership of the string is transferred to the caller.
*/
bool dvmLoadNativeCode(const char* pathName, Object* classLoader,
char** detail)
{
SharedLib* pEntry;
void* handle;
bool verbose; /* reduce noise by not chattering about system libraries */
verbose = !!strncmp(pathName, "/system", sizeof("/system")-1);
verbose = verbose && !!strncmp(pathName, "/vendor", sizeof("/vendor")-1);
// 如果不是Android系统库,打印log
if (verbose)
ALOGD("Trying to load lib %s %p", pathName, classLoader); *detail = NULL; /*
* See if we've already loaded it. If we have, and the class loader
* matches, return successfully without doing anything.
*/
// 通过hash查找,判断当前目标so库文件是否已经被加载过
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);
}
// 上次没有加载so成功,返回失败
if (!checkOnLoadResult(pEntry))
return false;
return true;
} /*
* Open the shared library. Because we're using a full path, the system
* doesn't have to search through LD_LIBRARY_PATH. (It may do so to
* resolve this library's dependencies though.)
*
* Failures here are expected when java.library.path has several entries
* and we have to hunt for the lib.
*
* The current version of the dynamic linker prints detailed information
* about dlopen() failures. Some things to check if the message is
* cryptic:
* - make sure the library exists on the device
* - verify that the right path is being opened (the debug log message
* above can help with that)
* - check to see if the library is valid (e.g. not zero bytes long)
* - check config/prelink-linux-arm.map to ensure that the library
* is listed and is not being overrun by the previous entry (if
* loading suddenly stops working on a prelinked library, this is
* a good one to check)
* - write a trivial app that calls sleep() then dlopen(), attach
* to it with "strace -p <pid>" while it sleeps, and watch for
* attempts to open nonexistent dependent shared libs
*
* This can execute slowly for a large library on a busy system, so we
* want to switch from RUNNING to VMWAIT while it executes. This allows
* the GC to ignore us.
*/
// 获取当前dalvik虚拟机线程的描述结构体
Thread* self = dvmThreadSelf();
// 设置dalvik虚拟机的线程状态为等待
ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT); // 调用dlopen函数动态加载目标so库文件(重点)
handle = dlopen(pathName, RTLD_LAZY);
// 恢复dalvik虚拟机的线程状态
dvmChangeStatus(self, oldStatus); // 判断目标so库文件是否动态加载成功
if (handle == NULL) {
*detail = strdup(dlerror());
ALOGE("dlopen(\"%s\") failed: %s", pathName, *detail);
return false;
} /* create a new entry */
SharedLib* pNewEntry;
// 创建一个新的SharedLib结构体对象描述目标so库文件被加载的信息
pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));
// 被加载目标so库文件的绝对路径
pNewEntry->pathName = strdup(pathName);
// 目标so库文件被加载之后的基地址句柄
pNewEntry->handle = handle;
// 被加载的目标so库文件所属的ClassLoader类加载器
pNewEntry->classLoader = classLoader;
dvmInitMutex(&pNewEntry->onLoadLock);
pthread_cond_init(&pNewEntry->onLoadCond, NULL);
// 加载目标so库文件的dalvik虚拟机的线程tid
pNewEntry->onLoadThreadId = self->threadId; /* try to add it to the list */
// 添加SharedLib对象pNewEntry到gDvm.nativeLibs中保存起来,添加时会先在gDvm.nativeLibs中查询
// 如果当前目标so库文件,没有被其他线程所加载则进行add的添加并返回当前的pNewEntry;
// 如果当前目标so库文件已经被其他线程所加载,则返回其他线程对应的pNewEntry。
SharedLib* pActualEntry = addSharedLibEntry(pNewEntry); // 判断其他线程是否已经加载过当前目标so库文件
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 {
// 当前目标so库文件没有被别的dalvik线程所加载的情况
if (verbose)
ALOGD("Added shared lib %s %p", pathName, classLoader); bool result = false;
void* vonLoad;
int version; // 获取当前目标so库文件中的导出函数JNI_OnLoad的调用地址(重点)
vonLoad = dlsym(handle, "JNI_OnLoad");
// 检查是否获取导出函数JNI_OnLoad的调用地址成功
if (vonLoad == NULL) {
// 当前目标so库文件中没有实现JNI_OnLoad函数
ALOGD("No JNI_OnLoad found in %s %p, skipping init", pathName, classLoader);
result = true;
} else {
/*
* Call JNI_OnLoad. We have to override the current class
* loader, which will always be "null" since the stuff at the
* top of the stack is around Runtime.loadLibrary(). (See
* the comments in the JNI FindClass function.)
*/
// 进行函数指针类型的转换
OnLoadFunc func = (OnLoadFunc)vonLoad;
// 保存当前线程原来的classLoaderOverride
Object* prevOverride = self->classLoaderOverride; // 暂时修改当前dalvik线程的classLoaderOverride
self->classLoaderOverride = classLoader;
// 修改当前dalvik虚拟机线程的状态
oldStatus = dvmChangeStatus(self, THREAD_NATIVE);
if (gDvm.verboseJni) {
ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);
}
// 调用JNI_OnLoad函数进行jni函数的注册等操作
version = (*func)(gDvmJni.jniVm, NULL);
// 恢复dalvik虚拟机线程的状态
dvmChangeStatus(self, oldStatus);
// 恢复dalvik虚拟机的classLoaderOverride
self->classLoaderOverride = prevOverride; // 检查是否调用JNI_OnLoad函数成功
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());
/*
* It's unwise to call dlclose() here, but we can mark it
* as bad and ensure that future load attempts will fail.
*
* We don't know how far JNI_OnLoad got, so there could
* be some partially-initialized stuff accessible through
* newly-registered native method calls. We could try to
* unregister them, but that doesn't seem worthwhile.
*/
} else {
// 调用成功,设置标记
result = true;
}
if (gDvm.verboseJni) {
ALOGI("[Returned %s from JNI_OnLoad for \"%s\"]",
(result ? "successfully" : "failure"), pathName);
}
} // 根据JNI_OnLoad函数调用成功的结果,设置目标so库文件是否加载成功的标记
if (result)
// 目标so库文件动态加载成功(jni函数注册成功等)
pNewEntry->onLoadResult = kOnLoadOkay;
else
pNewEntry->onLoadResult = kOnLoadFailed; pNewEntry->onLoadThreadId = 0; /*
* Broadcast a wakeup to anybody sleeping on the condition variable.
*/
// 状态的恢复
dvmLockMutex(&pNewEntry->onLoadLock);
pthread_cond_broadcast(&pNewEntry->onLoadCond);
dvmUnlockMutex(&pNewEntry->onLoadLock);
return result;
}
}

10.Dalvik模式下System.loadLibrary函数的执行流程基本分析完了,理解的不是很透彻,可以结合老罗的博客《Dalvik虚拟机JNI方法的注册过程分析》进行深入的学习。System.loadLibrary函数的执行流程中还有两个重点函数需要深入分析:dlopen函数动态加载so库文件,JNI_OnLoad函数调用,实现jni函数的注册,后面我再深入学习。

Dalvik模式下System.loadLibrary函数的执行流程分析的更多相关文章

  1. Dalvik模式下基于Android运行时类加载的函数dexFindClass脱壳

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78003184 前段时间在看雪论坛发现了<发现一个安卓万能脱壳方法>这篇 ...

  2. Android平台dalvik模式下java Hook框架ddi的分析(2)--dex文件的注入和调用

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/77942585 前面的博客<Android平台dalvik模式下java Ho ...

  3. Android平台dalvik模式下java Hook框架ddi的分析(1)

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/75710411 一.前 言 在前面的博客中已经学习了作者crmulliner编写的, ...

  4. 基于dalvik模式下的Xposed Hook开发的某加固脱壳工具

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/77966109 这段时间好好的学习了一下Android加固相关的知识和流程也大致把A ...

  5. Dalvik模式下在Android so库文件.init段、.init_array段构造函数上下断点

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78244766 在前面的博客<在Android so文件的.init..ini ...

  6. 在Debug模式下中断, 在Release模式下跳出当前函数的断言

    在Debug模式下中断, 在Release模式下跳出当前函数的断言 #ifdef DEBUG #define __breakPoint_on_debug asm("int3") # ...

  7. 报时机器人的rasa shell执行流程分析

      本文以报时机器人为载体,介绍了报时机器人的对话能力范围.配置文件功能和训练和运行命令,重点介绍了rasa shell命令启动后的程序执行过程. 一.报时机器人项目结构 1.对话能力范围 (1)能够 ...

  8. 严格模式下顶层箭头函数this指向的是全局对象

    我们知道普通函数调用,this在非严格模式下指向全局对象,在严格模式下是undefined.那箭头函数呢?我们知道,箭头函数没有自己的this,它的this是最近外层非箭头函数的this,那直接在顶层 ...

  9. Java基础毕向东day05 对象与对象的区别,匿名内部类,函数的执行流程。

    1.Car c = new Car(); Car c2 = new Car(); 1> c 和 c2之间的区别? public static void main(String[] args) { ...

随机推荐

  1. 25个关键技术点,带你熟悉Python

    摘要:本文收纳了Python学习者经常使用的库和包,并介绍了Python使用中热门的问题. 01.Python 简介 什么是 Python 一种面向对象的高级动态可解释型脚本语言. Python 解释 ...

  2. 五十:代码审计-PHP无框架项目SQL注入挖掘技巧

    代码审计教学计划: 审计项目漏洞Demo->审计思路->完整源码框架->验证并利用漏洞 代码审计教学内容: PHP,JAVA网站应用,引入框架类开发源码,相关审计工具及插件使用 代码 ...

  3. Python2021哔哩哔哩视频爬取

    一.找到想要爬取的视频,进入网页源代码 在网页源代码里面可以很容易的找到视频各种清晰度的源地址 二.对地址发送请求 如果对视频源地址发送get请求会返回403 通过按F12进入开发者工具分析 发现并不 ...

  4. Linux系统用户与用户组管理

    一.用户和用户组的管理 1.新增组 groupadd 命令 格式:groupadd 组名 2.删除组 groupdel 格式:groupdel 组名 3.增加用用户命令 useradd   格式:us ...

  5. Vulkan移植GpuImage(一)高斯模糊与自适应阈值

    自适应阈值效果图 demo 这几天抽空看了下GpuImage的filter,移植了高斯模糊与自适应阈值的vulkan compute shader实现,一个是基本的图像处理,一个是组合基础图像处理聚合 ...

  6. .zip爆破

    .zip爆破 Python的优化问题 Python在计算密集型任务方面没有明显的多线程优化,多线程更加适合用于处理I/O密集型任务(如网络请求).爆破任务使用顺序执行即可. 编写Python脚本 一个 ...

  7. 基于sinc的音频重采样(一):原理

    我在前面的文章<音频开源代码中重采样算法的评估与选择>中说过sinc方法是较好的音频重采样方法,缺点是运算量大.https://ccrma.stanford.edu/~jos/resamp ...

  8. C语言数组寻址

    C语言数组 数组的定义 数组是用来存放数据类型相同且逻辑意义相同的数据 数组的大小 数组的大小不能是变量,必须是常量或者常量表达式,常量表达式由编译器编译时自动求值. 也可以不指定数组大小,但必须对数 ...

  9. Azure Front Door(三)启用 Web Application Firewall (WAF) 保护Web 应用程序,拒绝恶意攻击

    一,引言 上一篇我们利用 Azure Front Door 为后端 VM 部署提供流量的负载均衡.因为是演示实例,也没有实际的后端实例代码,只有一个 "Index.html" 的静 ...

  10. 从本质彻底精通Git——4个模型1个周期1个史观1个工作流

    一.什么是Git? Git是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目. Git是Linus Torvalds为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软 ...