【问题标题】:Bypass Android's hidden API restrictions绕过 Android 的隐藏 API 限制
【发布时间】:2019-05-03 12:47:09
【问题描述】:

从 Android Pie 开始,access to certain hidden classes, methods and fields was restricted。在 Pie 之前,只需使用反射即可轻松使用这些隐藏的非 SDK 组件。

但是,现在,针对 API 28 (Pie) 或更高版本的应用程序在尝试访问 Activity#createDialog() 等组件时将遇到 ClassNotFoundException、NoSuchMethodError 或 NoSuchFieldException。对于大多数人来说,这很好,但作为喜欢破解 API 的人,这可能会让事情变得困难。

如何解决这些限制?

【问题讨论】:

  • 嗨,我是一个库的维护者,该库旨在访问隐藏的 API 也 API 30。请考虑:Technical descriptionGithub for Instructions 因为这是一个非常新的库,请提供反馈。
  • 您能否简单介绍一下您为绕过 Android 11 的强化限制所做的工作?从您的链接中复制相关部分就足够了。很高兴知道为什么需要在 JNI 中完成。

标签: android


【解决方案1】:

实际上有几种方法可以做到这一点。


安全设置

出于测试目的,Google 内置了一种在给定 Android 设备上全局禁用隐藏 API 限制的方法。标题为如何启用对非 SDK 接口的访问? 的问题链接中的部分内容如下:

您可以通过使用以下 adb 命令更改 API 强制策略来启用对开发设备上非 SDK 接口的访问:

adb shell settings put global hidden_api_policy_pre_p_apps  1
adb shell settings put global hidden_api_policy_p_apps 1

要将 API 强制策略重置为默认设置,请使用以下命令:

adb shell settings delete global hidden_api_policy_pre_p_apps
adb shell settings delete global hidden_api_policy_p_apps

这些命令不需要根设备。

您可以将 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 手动授予。


JNI

所有 API,包括 API 30 及更高版本

前一种方法仅适用于面向 API 29 及更低版本的应用。对于面向 API 30 及更高版本的应用,请使用此库:https://github.com/ChickenHook/RestrictionBypass

我不完全理解这是如何工作的,但它似乎滥用了在 JNI 中创建 Java 线程来设置当前应用的隐藏 API 豁免政策以允许访问所有隐藏的 API。

以下是其工作原理的完整说明:https://androidreverse.wordpress.com/2020/05/02/android-api-restriction-bypass-for-all-android-versions/

用法很简单。确保您已将 JitPack 添加到您的存储库中(在项目级别 build.gradle):

allprojects {
    repositories {
        [..]
        maven { url "https://jitpack.io" }
    }
}

然后实现库:

implementation 'com.github.ChickenHook:RestrictionBypass:2.2'

它会自动为您解除 API 限制。

API 29 及更早版本

安全设置方法适用于测试或个人应用程序,但如果您的应用程序旨在分发到您无法控制的设备上,试图指导最终用户如何使用 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);

  ...
}

这将为您禁用隐藏的 API 检查,无需任何特殊权限。

Source

您还可以使用一个库来为您执行此操作:https://github.com/tiann/FreeReflection/


纯 Java/Kotlin

JNI 并不适合所有人(包括我)。它还需要您针对不同的架构拥有不同版本的应用程序。幸运的是,还有纯 Java 解决方案。

所有 API,包括 API 30 及更高版本

LSPosed(流行的 Xposed 框架的替代品)背后的团队提出了一种纯 Java 解决方案,用于绕过针对 API 28 或更高版本的应用程序的隐藏 API 限制。

该库位于他们的 GitHub 上:https://github.com/LSPosed/AndroidHiddenApiBypass

解释是中文的,但它的主旨似乎是这样的。该库使用 Java 的 Unsafe API 作为反射的替代方案。然后,它的工作方式与 API 29 及更早版本的方法非常相似,允许用户设置隐藏的 API 豁免。

要使用它,只需实现库:

implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:2.0'

然后在您的应用程序启动时设置隐藏的 API 豁免:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    HiddenApiBypass.addHiddenApiExemptions("L");
}

API 29 及更早版本

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)

现在,这本身可以作为一个足够好的解决方案,但它可以更进一步。 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"))

例如,将该代码放入您的 Application 类的 onCreate() 方法中,然后您就可以像平常一样使用隐藏的 API。

有关这方面的完整 Java 示例,请查看 JNI 部分中链接的 FreeReflection 库,或查看下面的源代码。

Source

【讨论】:

  • 这甚至适用于 Android 11 吗?还是再也没有办法了?好像这个比较新:github.com/ChickenHook/RestrictionBypass
  • @androiddeveloper 它链接在答案的顶部
  • 看起来有一种新的纯Java绕过技术github.com/LSPosed/AndroidHiddenApiBypass
  • @DADi590 我认为某些 API 即使对于较旧的目标也受到限制,但我不确定。不过,我知道明确引导用户离开 Play 商店是违反他们的规则的,而且您的列表编辑或更新可能会被拒绝。
  • @Flyview 如果您的设备不是 arm64-v8a、arm-v7、x86 或 x86_64,ChickenHook 库将无法工作。无论如何,对于大多数人来说,LSposed 库可能是一个更好的选择。
【解决方案2】:

只是为了简化 TheWanderer 的答案,以合并链接包并为我工作:

将依赖项添加到您的 build.gradle...

implementation 'me.weishu:free_reflection:2.2.0'

将 attachBaseContext 添加到您的 MainActivity(例如):

@Override
  protected void attachBaseContext(Context base) {
  super.attachBaseContext(base);
  Reflection.unseal(base);
}

或者,在 Kotlin 中:

override fun attachBaseContext(base: Context?) {
  super.attachBaseContext(base)
  Reflection.unseal(base)
}

而且,如果您的导入没有自动添加,

import me.weishu.reflection.Reflection
import android.content.Context

希望对你有帮助!

【讨论】:

  • 对我也有用!很好的答案
【解决方案3】:

限制是如何起作用的?

VM(可能在java_lang_Class.cc)识别调用者以及是否允许他访问隐藏字段或隐藏方法的新方法是遍历堆栈跟踪,直到出现不匹配类型的“跳帧白名单” "(我们暂时称其为“跳过列表”)。意味着如果您只是执行“通过反射进行反射”,则 Method.invoke(...) 位于“跳过列表”中并将被跳过。下一帧将是您的方法,VM 将检查您是否是系统库。否则访问将被拒绝。

到目前为止还好吗?

现在的解决方法:

如果 Stacktrace 不包含除 getMethod() / getDeclaredMethod() 之外的任何 Frame,会发生什么?

回答:VM 将无法验证,因为没有要验证的内容...

好的,但我们怎样才能做到这一点?

好吧,如果我们生成一个新线程,堆栈跟踪将是“空的”,除了调用 Method.invoke(...) 的框架。意味着我们将堆栈跟踪减少到属于我们代码的一帧。

这就是原生进入游戏的地方:

VM 检查 API 限制的接口一般有 3 个:

  • 链接 - VM 在“类加载”期间进行验证? (这里还没有做太多研究)。
  • 反射 - 虚拟机遍历 Java 堆栈跟踪(如前所述)
  • JNI - VM 检查加载调用 loadLibrary(...) 的类的 ClassLoader 在 ClassPath 中是否有 framework.jar。

意味着 JNI 验证器不遍历 Java 堆栈,反射验证器不关心本机。

好的,让我们把它们结合起来。

我们通过调用 std::async(...).get() 方法创建一个“空”堆栈跟踪,并通过 JNI 调用反射 API(是的,我们调用 java.lang.reflect.Class.get*)通过 JNI因为它不是受限制的方法,所以 jni 调用会成功。现在反射验证器遍历堆栈并且找不到任何 Java 框架,因为该线程中没有 Java 框架(Class.get* 调用除外)。

希望我符合要求,不要那么详细,但要准确和正确。

另见:

Github

Description

【讨论】:

  • 顺便说一句。我已经在 4 周前向 Google Bug Tracker 提供了这个。
  • 这好多了,谢谢。如果您添加您拥有的原始链接,它将是完整的。
【解决方案4】:

我正在为@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"}});

【讨论】:

    猜你喜欢
    • 2017-10-23
    • 2013-11-10
    • 1970-01-01
    • 2019-10-20
    • 1970-01-01
    • 2012-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多