从Android Pie开始,限制了对某些隐藏类,方法和字段的访问。在使用Pie之前,仅通过反射即可使用这些隐藏的非SDK组件非常容易。
但是,现在,尝试访问诸如的组件时,面向API 28(Pie)或更高版本的应用将遇到ClassNotFoundException,NoSuchMethodError或NoSuchFieldException异常Activity#createDialog()。对于大多数人来说,这很好,但是作为喜欢使用该API的人,这可能会使事情变得困难。
如何解决这些限制?
The*_*rer 39
实际上,有几种方法可以做到这一点。
Google建立了一种方法来全局禁用给定Android设备上的隐藏API限制,以进行测试。问题中链接中标题为“ 如何启用对非SDK接口的访问”的部分?说以下内容:
您可以使用以下adb命令来更改API实施策略,从而在开发设备上启用对非SDK接口的访问:
Run Code Online (Sandbox Code Playgroud)adb shell settings put global hidden_api_policy_pre_p_apps 1 adb shell settings put global hidden_api_policy_p_apps 1要将API强制策略重置为默认设置,请使用以下命令:
Run Code Online (Sandbox Code Playgroud)adb shell settings delete global hidden_api_policy_pre_p_apps adb shell settings delete global hidden_api_policy_p_apps这些命令不需要root用户的设备。
您可以在API强制策略中将整数设置为以下值之一:
- 0:禁用所有非SDK接口检测。使用此设置将禁用所有非SDK接口使用的日志消息,并阻止您使用StrictMode API测试应用。不建议使用此设置。
- 1:启用对所有非SDK接口的访问,但会打印日志消息,并带有警告,说明任何非SDK接口的使用情况。使用此设置还可以使您使用StrictMode API测试应用。
- 2:禁止使用属于黑名单或灰名单且受目标API级别限制的非SDK接口。
- 3:禁止使用属于黑名单的非SDK接口,但允许使用属于灰名单且受目标API级别限制的接口。
(在Q测试版中,现在似乎只有一把钥匙:hidden_api_policy。)
(在我的测试中,更改此设置后,您的应用需要完全重新启动(进程已终止)才能生效。)
您甚至可以使用在应用程序内部更改此设置Settings.Global.putInt(ContentResolver, String, Int)。但是,它要求应用程序拥有WRITE_SECURE_SETTINGS权限,该权限只会自动授予签名级或特权应用程序。可以通过ADB手动授予它。
安全设置方法非常适合测试或个人应用程序,但是如果您打算将应用程序分发到不受您控制的设备上,则试图指导最终用户如何使用ADB可能是一场噩梦,即使他们已经知道该怎么办,很不方便。
幸运的是,实际上,有一种方法可以使用本机代码中的一些巧妙技巧来禁用应用程序的API限制。
在JNI_OnLoad()方法内部,您可以执行以下操作:
static art::Runtime* runtime = nullptr;
extern "C" jint JNI_OnLoad(JavaVM *vm, void *reserved) {
...
runtime = reinterpret_cast<art::JavaVMExt*>(vm)->GetRuntime();
runtime->SetHiddenApiEnforcementPolicy(art::hiddenapi::EnforcementPolicy::kNoChecks);
...
}
Run Code Online (Sandbox Code Playgroud)
这将为您禁用隐藏的API检查,没有任何特殊权限。
您还可以使用一个可以为您完成此操作的库:https : //github.com/tiann/FreeReflection/
JNI并不适合所有人(包括我)。它还需要您为不同的体系结构使用单独的应用程序版本。幸运的是,还有一个纯Java解决方案。
Android的隐藏API限制仅适用于未由平台签名签名且未在中手动列入白名单的第三方应用/system/etc/sysconfig/。这意味着该框架(显然)可以访问它想要的任何隐藏方法,而这正是该方法所利用的。
此处的解决方案是使用双反射(或“元反射”,翻译后的源称其为“元反射”)。这是一个示例,它检索一个隐藏方法(在Kotlin中):
val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod", String::class.java, arrayOf<Class<*>>()::class.java)
val someHiddenMethod = getDeclaredMethod.invoke(SomeClass::class.java, "someHiddenMethod", Param1::class.java, Param2::class.java)
val result = someHiddenMethod.invoke(someClassInstance, param1, param2)
Run Code Online (Sandbox Code Playgroud)
现在,它本身可以作为一个足够好的解决方案,但是可以采取进一步的措施。该类dalvik.system.VMRuntime具有一个方法:setHiddenApiExemptions(vararg methods: String)。只需传递"L"给此方法即可免除所有隐藏的API,我们可以通过两次反射来做到这一点。
val forName = Class::class.java.getDeclaredMethod("forName", String::class.java)
val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod", String::class.java, arrayOf<Class<*>>()::class.java)
val vmRuntimeClass = forName.invoke(null, "dalvik.system.VMRuntime") as Class<*>
val getRuntime = getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null) as Method
val setHiddenApiExemptions = getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", arrayOf(arrayOf<String>()::class.java)) as Method
val vmRuntime = getRuntime.invoke(null)
setHiddenApiExemptions.invoke(vmRuntime, arrayOf("L"))
Run Code Online (Sandbox Code Playgroud)
onCreate()例如,将代码放入Application类的方法中,然后您就可以像平常一样使用隐藏的API。
有关此示例的完整Java示例,请查看JNI部分中链接的FreeReflection库,或遵循下面的源代码。
限制如何发挥作用?
VM(可能在java_lang_Class.cc中)识别调用者以及是否允许他访问隐藏字段或隐藏方法的新方法是遍历堆栈跟踪,直到出现一个不匹配的“跳帧白名单”类型的帧(让我们现在称之为“跳过列表”)。意味着如果您只是执行“通过反射进行反射”,则Method.invoke(...)位于“跳过列表”中并且将被跳过。下一帧将是你的方法,VM 将检查你是否是系统库。如果不这样做,访问将被拒绝。
到目前为止还好吗?
现在解决方法:
如果 Stacktrace 除了 getMethod() / getDeclaredMethod() 之外不包含任何 Frame,会发生什么情况?
答:虚拟机将无法验证,因为没有什么可验证的......
好的,但是我们怎样才能实现这一目标呢?
好吧,如果我们生成一个新线程,除了调用 Method.invoke(...) 的帧之外,堆栈跟踪将是“空”。意味着我们将堆栈跟踪减少到属于我们代码的一帧。
这就是本地人进入游戏的地方:
一般来说,VM 检查 API 限制的接口有以下三个:
意味着 JNI 验证器不会遍历 Java 堆栈,并且反射验证器不关心本机。
好吧,让我们把它们结合起来。
我们通过调用 std::async(...).get() 方法创建一个“空”堆栈跟踪,并通过 JNI 调用 Reflection API(是的,我们调用 java.lang.reflect.Class.get*),因为它不是受限制的方法 jni 调用将会成功。现在,反射验证器遍历堆栈,不会找到任何 Java 框架,因为该线程中没有 Java 框架(除了 Class.get* 调用)。
希望我符合的要求不要那么详细,但也要准确、正确。
也可以看看:
只是为了以合并链接包并为我工作的方式简化 TheWanderer 的答案:
将依赖项添加到您的 build.gradle...
implementation 'me.weishu:free_reflection:2.2.0'
Run Code Online (Sandbox Code Playgroud)
将 AttachBaseContext 添加到您的 MainActivity (例如):
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Reflection.unseal(base);
}
Run Code Online (Sandbox Code Playgroud)
或者,在 Kotlin 中:
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
Reflection.unseal(base)
}
Run Code Online (Sandbox Code Playgroud)
而且,如果您的导入没有自动添加,
import me.weishu.reflection.Reflection
import android.content.Context
Run Code Online (Sandbox Code Playgroud)
希望能帮助到你!
我正在为 @TheWanderer 惊人的答案添加 java 实现:
Method forName = Class.class.getDeclaredMethod("forName", String.class);
Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
Class vmRuntimeClass = (Class) forName.invoke(null, "dalvik.system.VMRuntime");
Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null);
Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[] {String[].class});
Object vmRuntime = getRuntime.invoke(null);
setHiddenApiExemptions.invoke(vmRuntime, new String[][]{new String[]{"L"}});
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3052 次 |
| 最近记录: |