背景
之前说过适配一下android 14屏幕关闭,我开始以为就是找到对应对象通过反射调用对应的方法就可以了,但发现无法调用反射方法,出现调用异常情况。
过程
- 通过android 源代码找到了displaycontrol这个类(可以看上一篇文章分析这个)
- 无法反射这个类,没有找到这个类
- 分析发现displaycontrol,这个是services.jar这个类
- 于是想通过DexClassLoader加载位于系统的类,能够LocadClass 到DispalyControl
- 但调用具体函数的报错,发现nativie函数没有实现,到这里就断了,不知道怎么弄了,因为错误信息只是提示调用异常,其他信息都没有(可能有的手机会更详细错误,但我的手机默认没有,后面发现可以设置加载错误等级,打印错误)
- 这个时候只能查看scrcpy源代码,他通过ClassLoaderFactory,加载Services.jar这个,然后反射Runtimer.LoadLibrary0 ,加载android_servers 动态库
- 现在问题变成为什么要用ClassLoaderFactor 和 为什么要反射LoadLibrary0?
为什么要反射ClassLoaderFactor和 为什么反射LoadLibrary0?
ClassLoaderFactor 通过这个关键词搜索,找到很多关于app_prcess的文章加载分析,自己看了好多文章大概知道,android 启动SystemSerive(android 重要服务,我们常见通过serviceManager获取一些服务就是从这些服务)和APK创建的CladdLoader都是用这个。
通过android源代码分析,我发现在创建SystemService,会通过System.LoadLibrary("android_servers"),APK不会加载,因为用不到,同时系统也限制我们调用系统私有库,这个时候我还是不能知道为什么要用ClassLoaderFactor。
我通过DexClassLoader创建Class然后用RunTimer.LoadLibrary0,发现也是无法调用,我用DexClassLoader和System.loadLibrary("android_servers"),发现也是无法调用,那么说明Runtimer.LoadLibrary0 为了加载android_servers 动态库,默认系统不给我们加载android_servers动态库,于是通过无法加载动态库等关键词搜索找到 so命名空间这个概念。
于是开始找到许多有关so命名空间的内容,但没有那么理解,只是知道android会限制app加载系统私有的库,再结合一些资料这里用ClassLoaderFactory 还是为isShared 设置true,可以从其他空间共享找到动态库,默认是false,同时必须LoadLibrary0 对应那一个才能使用别的空间,可以看具体代码介绍
Runtime.java - Android Code Search
void loadLibrary0(Class<?> fromClass, String libname) {
ClassLoader classLoader = ClassLoader.getClassLoader(fromClass);
loadLibrary0(classLoader, fromClass, libname);
}
//这个主要反射,获取反射对应的类的ClassLoader,
/**
* Loads the shared library {@code libname} in the context of {@code loader} and
* {@code callerClass}.
*
* @param loader the class loader that initiated the loading. Used by the
* underlying linker to determine linker namespace. A {@code null}
* value represents the boot class loader.
* @param callerClass the class that initiated the loading. When not
* {@code null}, it is also used to determine the linker
* namespace from the class's dex file location (which is in an
* apk or dex jar).
* @param libname the name of the library.
*/
private synchronized void loadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) {
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
}
String libraryName = libname;
// Android-note: BootClassLoader doesn't implement findLibrary(). http://b/111850480
// Android's class.getClassLoader() can return BootClassLoader where the RI would
// have returned null; therefore we treat BootClassLoader the same as null here.
if (loader != null && !(loader instanceof BootClassLoader)) {
String filename = loader.findLibrary(libraryName);
if (filename == null &&
(loader.getClass() == PathClassLoader.class ||
loader.getClass() == DelegateLastClassLoader.class)) {
// Don't give up even if we failed to find the library in the native lib paths.
// The underlying dynamic linker might be able to find the lib in one of the linker
// namespaces associated with the current linker namespace. In order to give the
// dynamic linker a chance, proceed to load the library with its soname, which
// is the fileName.
// Note that we do this only for PathClassLoader and DelegateLastClassLoader to
// minimize the scope of this behavioral change as much as possible, which might
// cause problem like b/143649498. These two class loaders are the only
// platform-provided class loaders that can load apps. See the classLoader attribute
// of the application tag in app manifest.
filename = System.mapLibraryName(libraryName);
}
if (filename == null) {
// It's not necessarily true that the ClassLoader used
// System.mapLibraryName, but the default setup does, and it's
// misleading to say we didn't find "libMyLibrary.so" when we
// actually searched for "liblibMyLibrary.so.so".
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\"");
}
String error = nativeLoad(filename, loader, callerClass);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
// We know some apps use mLibPaths directly, potentially assuming it's not null.
// Initialize it here to make sure apps see a non-null value.
getLibPaths();
String filename = System.mapLibraryName(libraryName);
String error = nativeLoad(filename, loader, callerClass);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
}
callerClass the class that initiated the loading. When not {@code null}, it is also used to determine the linker namespace from the class's dex file location (which is in an apk or dex jar).
这句话大概参数不会空会链接到这个类的命名空间(这个跟命名空间规则结合来了),这个非常重要,我如果用其他loadLibrary0
官方命名规则
网址:链接器命名空间 | Android Open Source Project
重点几个地方
namespace.name.link.other.allow_all_shared_libs
一个布尔值,用于指示当无法在 name 命名空间中找到共享库时,能否在 other 命名空间中搜索到所有这些共享库。
此属性无法与 namespace.name.link.other.shared_libs 一起使用。
namespace.vndk.link.sphal.allow_all_shared_libs = true
这表示所有库名称都可以遍历从 vndk 到 sphal 命名空间的回退链接。
这个是官方说明,所以这个bool大概对应代码allow_all_shared_libs 对应ClassFactory IsShared 这值,所以必须用ClassFacotry来设置这个值为true
解决方案
- 通过scrcpy 这种方式实现
- LD_PRELOAD 提前加载系统库,直接绕过,然后java里面调用System.load("android_services"),那么系统就可以加载了
- 系统库放到自己apk包里面,直接加载(网上说的)
- 不反射,直接用binder 有关类进行系统通知,貌似下面问题,对应最新的源代码就是这么用的,但他给例子用LD_PRELOAD 这种方式
LD_PRELOAD 补充
相关的网站:Android 14 turn screen off issue · Issue #3927 · Genymobile/scrcpy · GitHub
命令:
adb shell LD_PRELOAD="/system/lib64/libandroid_servers.so" CLASSPATH=/sdcard/test.apk:/system/framework/services.jar app_process / 启动类
查资料LD_PRELOAD,可以用hook so,这个是linux的特性,对于我来说相当于提前加载so而已,绕过java检测。
日志命令
setprop debug.ld.all dlopen,dlerror,dlsym
这样子link报错就非常更多日志,方便我们分析
资料
自己分析时候找到一些有关的资料,方便理解
Android 9.0 System.loadLibrary 的源码解析_android9 system.load-CSDN博客
Android 类加载机制 | Earth Guardian (redspider110.github.io)
如何从另一个应用程序加载私有本机库?-腾讯云开发者社区-腾讯云 (tencent.com)
Android 动态链接库 So 的加载 | yxhuang
基于命名空间的动态链接—— 隔离 Android 中应用程序和系统的本地库_in namespace classloader-namespace-CSDN博客
so加载 - Linker跟NameSpace知识 (上篇)so库的加载可是我们日常开发都会用到的,因此系统也提供了非常 - 掘金 (juejin.cn)
浅谈Android系统编译apk后so文件在dlopen时出现linker权限问题_needed or dlopened by-CSDN博客
总结
通过代码修改和关键词进行搜索找到有关资料,不断进行验证,目前来说不一定百分百正确,但基本差不多了,因为自己不是专门android开发,所以不想花更多精力来验证。
这里学习很多android启动资料,孵化也是从app_process启动,只是通过参数来标记而已,启动类不一样而已,app_process 创建java启动器,同时vm调用java main类,进入java环境,app_process,默认引用ilbrum_time.so,感觉这里可用app_process 模拟APK启动,这样子用来多开(android一般hook apk 这边),自己构建ClassLoader ,自己写代码模拟APK,这个就是真正掌握程序入口,因为就自己的类。