绕过链接器命名空间限制访问libart.so

还是以通过dlopen获取libart.so句柄为出发点,由于android 7.0之后的链接器命名空间限制包括libart.so的一些私有库被限制访问。(源码分析基于android 10.0.0_r7)

应用进程类加载器的命名空间初始化

在应用程序对应的可执行文件app_process根据/system/etc/ld.config.{api}.txt配置文件初始化命名空间之后,每当应用程序创建一个类加载器classloader并调用System.loadLibrary加载so库时都会创建一个与此类加载器对应的命名空间(name为classloader-namespace)。从源码角度分析一下这个过程,System.loadLibrary函数最后会调用OpenNativeLibrary函数。

OpenNativeLibrary

System.loadLibrary()-->nativeLoad()-->Runtime.c::Runtime_nativeLoad()-->JVM_NativeLoad()-->Openjdkjvm.cc::JVM_NativeLoad()-->java_vm_ext.cc::LoadNativeLibrary()-->native_loader.cpp::OpenNativeLibrary(),也就是java层的System.loadLibrary()最终会调用libnativeloader.so的OpenNativeLibrary函数。

  • OpenNativeLibrary先判断classloader类加载器是否为空,如果为空直接调用android_dlopen_ext加载库文件
  • 如果判断classloader类加载器不为空,并且classloader类加载器没有对应的命名空间(第一次调用System.loadLibrary)就调用LibraryNamespaces::Create创建新的命名空间。

LibraryNamespaces::Create

  • 先调用android_create_namespace创建一个名称为classloader-namespace的命名空间。

  • 调用android_linker_namespace设置新创建的命名空间链接到default和runtime等命名空间。

  • 设置链接到default命名空间的共享库为system_exposed_libraries,设置链接到runtime命名空间的共享库为runtime_exposed_libraries。

查看system_exposed_libraries的值等于system_public_libraries,而system_public_libraries的值为/system/etc/public.libraries.txt配置文件中所有的库名称。所以新创建的类加载器命名空间链接到default命名空间并设置的共享库存放在/system/etc/public.libraries.txt配置文件中。

查看runtime_exposed_libraries的值等于kRuntimePublicLibraries,而kRuntimePublicLibraries中只包含了libicuuc.so和libicui18n.so。所以新创建的类加载器命名空间链接到runtime命名空间并设置的共享库为kRuntimePublicLibraries。

libart.so加载runtime命名空间的过程

app进程都是从zygote进程fork的,zygote进程会调用AndroidRuntime::start做一些初始化其中就会调用libnativehelper.so中的JniInvocationImpl::Init函数加载libart.so文件。

AndroidRuntime::start

libart加载到runtime

由之前分析的linker的初始化过程可知,ELF可执行文件默认位于default命名空间中。https://www.cnblogs.com/revercc/p/16299712.html。查看/system/etc/ld.config.29.txt文件发现default空间会链接到runtime空间中并设置共享库包含libnativehelper.so,所以当app_process加载libnativehelper.so失败时会通过runtime命名空间加载。当app_process进程调用AndroidRuntime::start函数并进一步调用libnativehelper.so中的JniInvocationImpl::Init函数,JniInvocationImpl::Init函数通过dlopen加载libart.so文件。因为dlopen调用者libnativehelper.so被加载到了runtime命名空间中,所以其调用dlopen加载libart.so也会加载到runtime命名空间中。

绕过链接器命名空间限制访问libart.so

查看/system/etc/ld.config.29.txt文件system标签下的runtime命名空间发现其库搜索路径为/apex/com.android.runtime/lib(64),而libart.so就在此目录中。所以app_process在初始化命名空间后加载libart.so时会将此其加载到runtime命名空间中。当app应用程序创建类加载器时在第一次调用Sytem.loadLibrary时会创建一个classloader-namespace命名空间,此命名空间会链接到default和runtime等命名空间中,其中链接到default命名空间中的共享库保存在/system/etc/public.libraries.txt配置文件中,链接到runtime命名空间中的共享库保存在kRuntimePublicLibraries中。而kRuntimePublicLibraries中对于android 10_0.0_r7这个版本而言只有libicuuc.so和libicui18n.so这两个库,并没有libart.so所以调用dlopen是没有权限在runtime命名空间中搜索加载libart.so的。

因为kRuntimePublicLibraries中对于android 10_0.0_r7这个版本而言只有libicuuc.so和libicui18n.so这两个库,那么说在dlopen是可以有权限在runtime命名空间中搜索加载这连个库并返回对应的handle的。native中调用lopen("libicuuc.so", RTLD_NOW);确实可以获取到对应的handle。

网上的帖子有的人说将libart.so加入到public.libraries.txt中就可以通过dlopen得到libart.so的handle是不对的,因为此文件中保存的是类加载器命名空间链接到default命名空间中共享库列表并不是链接到runtime命名空间的共享库列表。你设置public.libraries.txt中加入libart.so当无法在类加载器命名空间中加载libart.so时会去default命名空间中搜索加载此库,但libart.so是被加载到runtime命名空间中并不在default空间中,所以还是找不到。正确的做法是在kRuntimePublicLibraries中加入libart.so并重新编译源码。

当然还有其他很多方法可以绕过,本文只是从类加载器命名空间初始化的角度分析。例如可以通过直接修改相关命名空间策略源码,或者修改/system/etc/ld.config.29.txt文件中runtime命名空间namespace.runtime.isolated = false也就是非严格隔离,这样其就可以通过dlopen并传入绝对路径加载libart.so。最好的方法就是通过在内存上直接操作得到libart.so的基地址和符号信息(通过map表得到libart基地址并解析符号表得到相关符号偏移),这是因为命名空间限制都是针对调用dlopen和dlsym等接口api设置的不使用这些接口自然没有什么命名空间隔离。

参考:
https://www.jianshu.com/p/1672b52548ce
http://zjh.wiki/2020-09-07-libart-so.html
https://blog.seeflower.dev/archives/79/
https://www.52pojie.cn/thread-948942-1-1.html
https://developer.aliyun.com/article/748570

posted @ 2023-01-03 00:09  怎么可以吃突突  阅读(1168)  评论(0编辑  收藏  举报