查看原文
其他

从 Android 源码看 SO 的加载

2017-07-27 Caln 看雪学院

做安卓逆向的时候常常会碰到写在 Shared Object(即 [*.so] 文件,下文 SO)的加固逻辑,碰到 SO 的时候惯性分析方法是:静态找 JNI_Onload / .init / .init_array 这些在加载之初会被调用的代码段或者是指向被调用代码段的指针段。

然而现在的加固基本上都会用各种方式抹掉函数关系、SHT 等导致直接静态分析 SO 时既不能从函数表里找到 JNI_Onload 也不能从 SECTION 里找到 .init,部分逆向人员在这种情况下走了弯路。

#1

Source Code

Android 作为开源的系统,既然可以拿到源码,那么就可以尝试分析加载 SO 时的流程,进而找到对应正确的下断位置来确定 JNI_Onload / .init 的偏移量。下文中所有源码均为 android-4.4.2_r1 版本,各个版本在细节上的实现可能存在差异。

以 [java.lang.Runtime -> load()] 为例子来说明(loadLiabrary() 最后和 load() 殊途同归,有兴趣的可以自行分析),对应的 Android 源码在 [java/lang/Runtime.java],从 320 行开始。

可以看到 load(String pathName) 实际上是调用了 load(String pathName, ClassLoader loader),而后者又调用了 doLoad(pathName, loader),这里的函数调用没有什么实际的意义(仅指对逆向者没有实质的意义,下同)一直在传值。

下面是 doLoad(pathName, loader) 的定义,源码还是在上面的 Runtime.java 里。

主要是检测 loader 的正确性,并带上 LD_LIBRARY_PATH 一起进入 nativeLoad(name, loader, ldLibraryPath),这里开始进入 native 层,nativeLoad 的定义在 vm/native/java_lang_Runtime.cpp # 64 行,如下。

还是传值 + 检查,然后执行 [bool success = dvmLoadNativeCode(fileName, classLoader, &reason);] ,看下 dvmLoadNativeCode(...) 的代码,位于 vm/Native.cpp # 301 行。

做了一些常规的检查,不赘述了,可以看到 [version = (*func)(gDvmJni.jniVm, NULL);] 这里调用了 JNI_OnLoad,上一行是 [ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);],记录一下方便逆向时确定位置。

根据逆向经验 .init(_array) 段定义的内容是在 JNI_OnLoad 之前执行的,而 dlopen 是加载 SO 的函数可能会在这里执行 .init,看一下 dlopen 函数,它的定义在 linker/dlfcn.cpp # 63 行。

其实还是调用了 do_dlopen,do_dlopen 的定义在 linker/linker.cpp # 823 行,代码如下。

做了一些检查,*是否符合调用 dlopen 的格式、*是否属于已经加在过的 SO,如果属于之前没有加在过的 SO 就执行 [si->CallConstructors();],看一下 CallConstructors() 的定义。

重点是最后这的 [CallFunction("DT_INIT", init_func);] 和 [CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);],很明显是执行 .init(_array) 定义的内容,这里不贴 CallArray 的代码了,其实还是循环调用了 CallFunction,下面看看 CallFunction 的代码,linker/linker.cpp # 1172 行。

看到这行代码 [function();],所以可以确定 .init(_array) 定义的内容最终在这执行。同样记录一下 [TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name);] 方便逆向时确定位置。

#2

Reverse Offset

找到具体调用的位置之后下面开始确定这两个位置在 ELF 中的便宜,以便动态调试时下断点,根据上面的分析:

1. JNI_OnLoad 的调用在 vm/Native.cpp 里,对应 /system/lib/libdvm.so。

2. .init(_array) 的调用在 linker/linker.cpp 里,对应 /system/bin/linker。


下面拿 JNI_OnLoad 做例子说明,adb pull 取出手机里的二进制文件 /system/lib/libdvm.so,拉入 IDA,[Shift + F12] 显示所有的字符串,直接找 "[Calling JNI_OnLoad for \"%s\"]" ,找到后如图所示。

跳到 DATA XREF 指向的位置 [dvmLoadNativeCode(char const*,Object *,char **)+1C4 ],汇编码如下,可以看到符合源代码中的调用情况,下图选中的 [BLX R8] 就是调用 JNI_OnLoad 的位置。

可以看到偏移为 [libdvm.so + 0x53A08],所以当载入一个 SO 时只需要在这个偏移量对应的位置下断即可,.inti(_array) 的处理方法也相同,不重复说明了。

#3

结论

在本次分析的版本 4.4.2_r1 中,只需要在 [/system/bin/linker + 0x274C] 和[/system/lib/libdvm.so + 0x53A08] 这两个位置下断,即可成功找到 .inti / .init_array / JNI_OnLoad 并实现断点,不需要符号表,SEGMENT啥的,具体效果可以参考我前两篇文章。

#4

Reference

*  Android安全–linker加载so流程,在.init下断点



本文由看雪论坛 Caln 原创,转载请注明来自看雪社区



热门阅读


点击阅读原文/read,还有更多干货等着你~



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存