Bypass Android's Hidden API Restrictions

Bypass Android's hidden API restrictions

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

What does @UnsupportedAppUsage annotation depict

If we go to the annotation source:

/**
* Indicates that a class member, that is not part of the SDK, is used by apps.
* Since the member is not part of the SDK, such use is not supported.
*
* <p>This annotation acts as a heads up that changing a given method or field
* may affect apps, potentially breaking them when the next Android version is
* released. In some cases, for members that are heavily used, this annotation
* may imply restrictions on changes to the member.
*
* <p>This annotation also results in access to the member being permitted by the
* runtime, with a warning being generated in debug builds.
*
* <p>For more details, see go/UnsupportedAppUsage.
*
* {@hide}
*/

Basically, it means it's being used by apps, even though it's not technically part of the SDK, and thus isn't supported. It seems to be more of a warning for anyone contributing to AOSP rather than something you need to worry about too much.

What does @hide mean in the Android source code?

Android has two types of APIs that are not accessible via SDK.

The first one is located in package com.android.internal. The second API type is a collection of classes and methods that are marked with the @hide Javadoc attribute.

Starting from Android 9 (API level 28), Google introduces new restrictions on the use of non-SDK interfaces, whether directly, via reflection, or via JNI. These restrictions are applied whenever an app references a non-SDK interface or attempts to obtain its handle using reflection or JNI.

But before API level 28, the hidden methods could still be accessed via Java reflection. The @hide attribute is just part of Javadoc (droiddoc also), so the @hide just simply means the method/class/field is excluded from the API docs.

For example, the checkUidPermission() method in ActivityManager.java uses @hide:

/** @hide */
public static int checkUidPermission(String permission, int uid) {
try {
return AppGlobals.getPackageManager()
.checkUidPermission(permission, uid);
} catch (RemoteException e) {
// Should never happen, but if it does... deny!
Slog.e(TAG, "PackageManager is dead?!?", e);
}
return PackageManager.PERMISSION_DENIED;
}

However, we can call it by reflection:

Class c;
c = Class.forName("android.app.ActivityManager");
Method m = c.getMethod("checkUidPermission", new Class[] {String.class, int.class});
Object o = m.invoke(null, new Object[]{"android.permission.READ_CONTACTS", 10010});

How do you secure project-wide API keys in a mobile app

TL/DR: You don't.

Long answer:

Any key that is distributed with the app can be read by the app for it to use it. The app therefore has what it needs to read the key, even if it is encrypted or obfuscated. An attacker can use the same technique that the app would use, to obtain the key.

Equally, fetching the key form an external source does not protect it. Again an attacker can use the same channel to obtain a copy of the key.

Besides attacking the channel by which the app obtains the key (from an encrypted store inside the package, or from an external source), an attacker can also obtain it from the app's memory or by intercepting network transmissions.

The only secure solution is to never have a copy of the key on the end user device.

The key should be kept on a well secured server which will act as a middle-man between the user's device and the end service. Any requests by the client device to the end service needs to be routed via this server.

The server, having the "global project keys", should make the requests to the end service on the behalf of the end user, and return the result (and never any keys) to the client. For the client to use this server, a per-user authenticated session must be used. The server must validate this session for every request prior to forwarding the request on to the end service.

Summary:

Use a secure server between the client and the end service to make requests on behalf of clients using the global key.

EDIT:
Side note: There is a distinction that needs to be made between per-user keys and keys that are project-wide. It is acceptable to keep keys that are specific to one individual person on that user's device.



Related Topics



Leave a reply



Submit