44

Starting with Android Pie, access to certain hidden classes, methods and fields was restricted. Before Pie, it was pretty easy to use these hidden non-SDK components by simply using reflection.

Now, however, apps targeting API 28 (Pie) or later will be met with ClassNotFoundException, NoSuchMethodError or NoSuchFieldException when trying to access components such as Activity#createDialog(). For most people this is fine, but as someone who likes to hack around the API, it can make things difficult.

How can I work around these restrictions?

TheWanderer
  • 16,775
  • 6
  • 49
  • 63
  • 3
    Hy, I'm the maintainer of a library intended to access hidden API also **API 30**. Please consider: [Technical description](https://androidreverse.wordpress.com/2020/05/02/android-api-restriction-bypass-for-all-android-versions/) [Github for Instructions](https://github.com/ChickenHook/RestrictionBypass) Because this is a very new library please provide feedback. – SarotecK May 02 '20 at 00:32
  • Would you be able to describe a little bit about what you did to bypass Android 11's hardened restrictions? Copying the relevant parts from your links would be enough. It'd also be nice to know why it needs to be done in JNI. – TheWanderer May 02 '20 at 01:48

4 Answers4

93

There are actually a few ways to do this.


Secure Settings

Google built in a way to disable the hidden API restrictions globally on a given Android device, for testing purposes. The section in the link in the question titled How can I enable access to non-SDK interfaces? says the following:

You can enable access to non-SDK interfaces on development devices by changing the API enforcement policy using the following adb commands:

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

To reset the API enforcement policy to the default settings, use the following commands:

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

These commands do not require a rooted device.

You can set the integer in the API enforcement policy to one of the following values:

  • 0: Disable all detection of non-SDK interfaces. Using this setting disables all log messages for non-SDK interface usage and prevents you from testing your app using the StrictMode API. This setting is not recommended.
  • 1: Enable access to all non-SDK interfaces, but print log messages with warnings for any non-SDK interface usage. Using this setting also allows you to test your app using the StrictMode API.
  • 2: Disallow usage of non-SDK interfaces that belong to either the blacklist or the greylist and are restricted for your target API level.
  • 3: Disallow usage of non-SDK interfaces that belong to the blacklist, but allow usage of interfaces that belong to the greylist and are restricted for your target API level.

(On the Q betas, there seems to be only one key now: hidden_api_policy.)

(In my testing, after changing this setting, your app needs to be fully restarted—process killed–for it to take effect.)

You can even change this from inside an app with Settings.Global.putInt(ContentResolver, String, Int). However, it requires the app to hold the WRITE_SECURE_SETTINGS permission, which is only automatically granted to signature-level or privileged apps. It can be manually granted through ADB.


JNI

All APIs Including API 30 and Later

The previous method is only functional for apps targeting API 29 and below. For apps targeting API 30 and above, use this library: https://github.com/ChickenHook/RestrictionBypass.

I don't fully understand how this works, but it seems to abuse the creation of Java Threads inside the JNI to set the current app's hidden API exemption policy to allow access to all hidden APIs.

Here's a full description of how it works: https://androidreverse.wordpress.com/2020/05/02/android-api-restriction-bypass-for-all-android-versions/.

The usage is simple. Make sure you have JitPack added to your repositories (in the project-level build.gradle):

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

Then implement the library:

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

It will automatically remove the API restrictions for you.

API 29 and Earlier

The secure settings method is good for testing or for personal apps, but if your app is meant to be distributed to devices you don't control, trying to instruct end users on how to use ADB can be a nightmare, and even if they already know what to do, it's inconvenient.

Luckily, there is actually a way to disable the API restrictions for your app, using some clever tricks in native code.

Inside your JNI_OnLoad() method, you can do the following:

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);

  ...
}

This will disable the hidden API checks for you, without any special permissions.

Source

There's also a library you can use that will do this for you: https://github.com/tiann/FreeReflection/


Pure Java/Kotlin

JNI isn't for everyone (including me). It also needs you to have separate versions of your app for different architectures. Luckily, there are also pure-Java solutions.

All APIs Including API 30 and Later

The team behind LSPosed, a replacement for the popular Xposed framework, has come up with a pure-Java solution for bypassing hidden API restrictions for apps targeting API 28 or later.

The library is over at their GitHub: https://github.com/LSPosed/AndroidHiddenApiBypass.

The explanation is in Chinese, but the gist of it seems to be this. The library uses Java's Unsafe API as an alternative to reflection. It then works very similarly to the method for API 29 and earlier, allowing the user to set the hidden API exemptions.

To use it, just implement the library:

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

And then set the hidden API exemptions when your application starts:

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

API 29 and Earlier

Android's hidden API restrictions only apply to third party apps that aren't signed by the platform signature and aren't manually whitelisted in /system/etc/sysconfig/. What this means is that the framework (obviously) can access any hidden methods it wants, which is what this method takes advantage of.

The solution here is to use double-reflection (or "meta-reflection," as the translated source calls it). Here's an example, retrieving a hidden method (in 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)

Now, this could stand as a good enough solution on its own, but it can be taken a step further. The class dalvik.system.VMRuntime has a method: setHiddenApiExemptions(vararg methods: String). Simply passing "L" to this method will exempt all hidden APIs, and we can do that with double-reflection.

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"))

Put that code in your Application class' onCreate() method, for example, and then you'll be able to use hidden APIs like normal.

For a full Java example of this, check out the FreeReflection library linked in the JNI section, or follow through the source below.

Source

TheWanderer
  • 16,775
  • 6
  • 49
  • 63
  • Great explanation. as a follow up, do you know if one has granted hidden API access to his application. can he enable or disable internet access even though no permission was granted in Manifest? – DeJaVo May 01 '20 at 17:23
  • @DeJaVo hidden APIs are still subject to permissions. Google's decision to block access to these APIs isn't a security measure, but for stability. – TheWanderer May 01 '20 at 18:11
  • 1
    This works even on Android 11 ? Or no way to do it anymore? Seems this one is newer: https://github.com/ChickenHook/RestrictionBypass – android developer Jun 20 '20 at 19:20
  • 1
    @androiddeveloper it's linked at the top of the answer – TheWanderer Jun 20 '20 at 19:21
  • @TheWanderer Oh sorry. Usually edits are at the bottom, because people read from top to bottom, so old stuff is at the top. – android developer Jun 21 '20 at 07:03
  • @TheWanderer "Using reflection for accessing manufacturer-supplied libraries – the kind that you need to use for in the manifest" does this reflection restriction effects such libraries? How to over come in android 11 device and target is android 11. – NitZRobotKoder Feb 18 '21 at 21:43
  • 1
    Looks like there's a new pure Java bypass technique https://github.com/LSPosed/AndroidHiddenApiBypass. – jhilliar Apr 11 '21 at 02:10
  • Thank you for this! But a question: so the permanent way is to just put the target SDK as 27 at most, right? (Except Google won't let people publish the apps on Play Store, but I can always publish one with the newest SDK API there and warn users to download the app from a different place. But still, is what I asked correct? Everything will work with lower target SDK APIs?) – Edw590 May 28 '21 at 22:50
  • 1
    @DADi590 I think some APIs are restricted even for older targets, but I'm not sure. I do know that explicitly directing users away from the Play Store is against their rules, though, and your listing edit or update may be rejected. – TheWanderer May 29 '21 at 01:55
  • Oh. I didn't know about that (both things, actually). Never published an app on Play Store. It's something I'm planning to do some time. Damn... Not cool. I'll read their rules when I decide to pay the 25 bucks and publish it. Thank you. – Edw590 May 29 '21 at 11:42
  • I'm trying to use ChickenHook RestrictionBypass on Android 11 and it isn't working. I'm still getting the logcat warning about being denied. Is all that's really required is to include it in the gradle dependencies?! – Flyview Aug 31 '21 at 21:24
  • 1
    @Flyview if your device isn't arm64-v8a, arm-v7, x86, or x86_64, the ChickenHook library won't work. The LSposed library is probably a better option for most people anyway. – TheWanderer Aug 31 '21 at 22:25
  • https://github.com/LSPosed/AndroidHiddenApiBypass works well, thanks! – Flyview Sep 01 '21 at 04:54
5

Just to simplify TheWanderer's answer in a way that incorporated the linked package and worked for me:

Add dependency to your build.gradle...

implementation 'me.weishu:free_reflection:2.2.0'

Add attachBaseContext to your MainActivity (e.g.):

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

Or, in Kotlin:

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

And, if your imports weren't added automatically,

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

Hope it helps!

kwishnu
  • 1,730
  • 19
  • 17
5

How does the restiction work?

The new way how the VM (maynly in java_lang_Class.cc) identifies the caller and if he's allowed to access hidden field or hidden method is to walk through the Stacktrace until a frame appears not Matching kind of a "whitelist for skipping frames" (let's call it "skipping list" for now). Means if you just do "Reflection via Reflection" the Method.invoke(...) is in "skipping list" and will be skipped. Next frame will be your method and the VM will check whether you're a system library or not. If not access will be denied.

Ok so far?

Now the Workaround:

What happens if the Stacktrace doesn't contain any Frame except of the getMethod() / getDeclaredMethod()?

Answer: The VM will not be able to verify because there is nothing to verify...

Ok but how can we achive that?

Well, if we spawn a new thread the stacktrace will be "empty" except of the frame calling the Method.invoke(...). Means we reduced the stacktrace to one frame that belongs to our code.

And here is where native comes into the game:

In general there are three interfaces where the VM checks API restrictions:

  • linkage - VM verifies during "Classloading" ? (didn't research much here yet).
  • Reflection - VM walks through the Java stacktrace (like described aboth)
  • JNI - VM checks if ClassLoader that loaded the Class calling the loadLibrary(...) has framework.jar in ClassPath.

Means the JNI verifier doesn't walk through the Java stack and the Reflection verifier doesn't care about native.

Ok let's combine those.

We create an "empty" stacktrace by calling std::async(...).get() method and call the Reflection API via JNI (yes we call java.lang.reflect.Class.get*) via JNI because it's not a restricted method the jni call will succeed. And now the Reflection verifier walks through the stack and won't find any Java Frame because there was no Java Frame in this thread (except of the Class.get* call).

Hope I matched the requirements to not be that detailed but also beeing accurate and telling correctly.

See also:

Github

Description

SarotecK
  • 186
  • 1
  • 6
5

I'm adding the java implementation for @TheWanderer amazing answer:

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"}});
gkpln3
  • 1,317
  • 10
  • 24