Entwickler: Es ist super einfach, die versteckten API-Einschränkungen von Android zu umgehen

Android 9 Pie und Android 10 geben Warnungen aus oder blockieren den Zugriff auf versteckte APIs komplett. So können Entwickler die versteckten API-Einschränkungen umgehen.

Rückblick auf die Zeit vor über einem Jahr und wir sind alle gespannt, was uns in den Android P-Betas erwartet. Benutzer freuen sich auf neue Funktionen und Entwickler auf einige neue Tools, mit denen sie ihre Apps verbessern können. Unglücklicherweise für einige dieser Entwickler brachte die erste Android P-Beta eine kleine Überraschung mit sich: versteckte API-Einschränkungen. Bevor ich darauf eingehe, was genau das bedeutet, möchte ich etwas über den Kontext erklären.

Worum geht es hier?

Entwickler von Android-Apps müssen nicht bei Null anfangen, wenn sie eine App erstellen. Google stellt Tools bereit, die die App-Entwicklung einfacher und weniger repetitiv machen. Eines dieser Tools ist das Android SDK. Das SDK ist im Wesentlichen eine Referenz auf alle Funktionen, die Entwickler sicher in ihren Apps verwenden können. Diese Funktionen sind Standard bei allen von Google zugelassenen Android-Varianten. Das SDK erhebt jedoch keinen Anspruch auf Vollständigkeit. Es gibt eine ganze Reihe „versteckter“ Teile des Android-Frameworks, die nicht Teil des SDK sind.

Diese „versteckten“ Teile können für Hacker- oder Low-Level-Sachen unglaublich nützlich sein. Zum Beispiel meine Widget-Drawer-App nutzt eine Nicht-SDK-Funktion, um zu erkennen, wann ein Benutzer eine App über ein Widget startet, sodass die Schublade automatisch geschlossen werden kann. Sie denken vielleicht: „Warum machen Sie diese Nicht-SDK-Funktionen nicht einfach zu einem Teil des SDK?“ Das Problem besteht darin, dass ihre Funktionsweise nicht vollständig vorhersehbar ist. Google kann nicht sicherstellen, dass jeder einzelne Teil des Frameworks auf jedem einzelnen genehmigten Gerät funktioniert. Daher werden wichtigere Methoden zur Überprüfung ausgewählt. Google garantiert nicht, dass der Rest des Frameworks konsistent bleibt. Hersteller können diese versteckten Funktionen ändern oder ganz entfernen. Selbst in verschiedenen Versionen von AOSP weiß man nie, ob eine versteckte Funktion noch vorhanden ist oder wie früher funktioniert.

Wenn Sie sich fragen, warum ich das Wort „versteckt“ verwende, liegt das daran, dass diese Funktionen nicht einmal Teil des SDK sind. Wenn Sie versuchen, eine versteckte Methode oder Klasse in einer App zu verwenden, schlägt die Kompilierung normalerweise fehl. Ihre Verwendung erfordert Betrachtung oder ein modifiziertes SDK.

Mit Android P entschied Google, dass es nicht ausreichte, sie einfach zu verstecken. Als die erste Beta veröffentlicht wurde, das wurde bekannt gegeben Die meisten (nicht alle) versteckten Funktionen standen App-Entwicklern nicht mehr zur Verfügung. Die erste Beta würde Sie warnen, wenn Ihre App eine Funktion auf der schwarzen Liste verwendet. Die nächsten Betas haben Ihre App einfach zum Absturz gebracht. Zumindest für mich war diese Blacklist ziemlich nervig. Es ist nicht nur ziemlich viel kaputt gegangen Navigationsgesten, aber da versteckte Funktionen standardmäßig auf der schwarzen Liste stehen (Google muss einige pro Version manuell auf die weiße Liste setzen), hatte ich große Probleme, Widget Drawer zum Laufen zu bringen.

Nun gab es einige Möglichkeiten, die schwarze Liste zu umgehen. Die erste bestand darin, Ihre App einfach auf API 27 (Android 8.1) auszurichten, da die Blacklist nur für Apps galt, die auf die neueste API ausgerichtet waren. Leider mit Google Mindest-API-Anforderungen Für die Veröffentlichung im Play Store wäre es nur möglich, API 27 für einen bestimmten Zeitraum anzuvisieren. Stand: 1. November 2019Alle App-Updates für den Play Store müssen auf API 28 oder höher abzielen.

Die zweite Problemumgehung ist tatsächlich etwas, das Google in Android integriert hat. Es ist möglich, einen ADB-Befehl auszuführen, um die Blacklist vollständig zu deaktivieren. Das ist großartig für den persönlichen Gebrauch und zum Testen, aber ich kann Ihnen aus erster Hand sagen, dass der Versuch, Endbenutzer dazu zu bringen, ADB einzurichten und auszuführen, ein Albtraum ist.

Wohin führt uns das? Wir können API 27 nicht mehr als Ziel verwenden, wenn wir in den Play Store hochladen möchten, und die ADB-Methode ist einfach nicht praktikabel. Das bedeutet jedoch nicht, dass wir keine Optionen mehr haben.

Die versteckte API-Blacklist gilt nur für Benutzeranwendungen, die nicht auf der Whitelist stehen. Systemanwendungen, mit der Plattformsignatur signierte Anwendungen und in einer Konfigurationsdatei angegebene Anwendungen sind alle von der Blacklist ausgenommen. Lustigerweise sind alle Google Play Services-Suites in dieser Konfigurationsdatei angegeben. Ich denke, Google ist besser als der Rest von uns.

Wie dem auch sei, reden wir weiter über die schwarze Liste. Der Teil, der uns heute interessiert, ist, dass Systemanwendungen davon ausgenommen sind. Das bedeutet zum Beispiel, dass die System UI-App alle versteckten Funktionen nutzen kann, die sie möchte, da sie sich auf der Systempartition befindet. Natürlich können wir eine App nicht einfach auf die Systempartition verschieben. Das erfordert Root, einen guten Dateimanager, Kenntnisse über Berechtigungen... Es wäre einfacher, ADB zu verwenden. Das ist jedoch nicht die einzige Möglichkeit, eine System-App zu sein, zumindest was die versteckte API-Blacklist betrifft.

Erinnern Sie sich an die Zeit vor sieben Absätzen, als ich das Nachdenken erwähnte. Wenn Sie nicht wissen, was Reflexion ist, empfehle ich die Lektüre diese Seite, aber hier ist eine kurze Zusammenfassung. In Java ist Reflektion eine Möglichkeit, auf normalerweise unzugängliche Klassen, Methoden und Felder zuzugreifen. Es ist ein unglaublich mächtiges Werkzeug. Wie ich in diesem Absatz sagte, war Reflektion früher eine Möglichkeit, auf Nicht-SDK-Funktionen zuzugreifen. Die API-Blacklist blockiert die Verwendung von Reflection, blockiert jedoch nicht die Verwendung von doppelt-Betrachtung.

Jetzt wird es etwas seltsam. Wenn Sie eine versteckte Methode aufrufen möchten, gehen Sie normalerweise folgendermaßen vor (dies ist in Kotlin der Fall, aber Java ist ähnlich):

val someHiddenClass = Class.forName("android.some.hidden.Class")
val someHiddenMethod = someHiddenClass.getMethod("someHiddenMethod", String::class.java)

someHiddenMethod.invoke(null, "some important string")

Dank der API-Blacklist würden Sie jedoch nur eine ClassNotFoundException erhalten. Wenn man jedoch zweimal darüber nachdenkt, funktioniert es gut:

val forName = Class::class.java.getMethod("forName", String:: class.java)
val getMethod = Class::class.java.getMethod("getMethod", String:: class.java, arrayOf>()::class.java)
val someHiddenClass = forName.invoke(null, "android.some.hidden.Class") asClass
val someHiddenMethod = getMethod.invoke(someHiddenClass, "someHiddenMethod", String::class.java)

someHiddenMethod.invoke(null, "some important string")

Verrückt oder? Nun ja, aber auch nein. Die API-Blacklist verfolgt, wer eine Funktion aufruft. Wenn die Quelle nicht ausgenommen ist, stürzt sie ab. Im ersten Beispiel ist die Quelle die App. Im zweiten Beispiel ist die Quelle jedoch das System selbst. Anstatt die Reflexion zu nutzen, um direkt zu bekommen, was wir wollen, nutzen wir sie, um dem System mitzuteilen, dass es bekommen soll, was wir wollen. Da die Quelle des Aufrufs der versteckten Funktion das System ist, hat die Blacklist keinen Einfluss mehr auf uns.

Also sind wir fertig. Wir haben jetzt eine Möglichkeit, die API-Blacklist zu umgehen. Es ist etwas umständlich, aber wir könnten eine Wrapper-Funktion schreiben, damit wir nicht jedes Mal doppelt reflektieren müssen. Es ist nicht großartig, aber es ist besser als nichts. Aber eigentlich sind wir noch nicht fertig. Es gibt eine bessere Möglichkeit, dies zu tun, indem wir normale Reflexion oder ein modifiziertes SDK verwenden, wie in den guten alten Zeiten.

Da die Durchsetzung der Blacklist pro Prozess bewertet wird (was in den meisten Fällen dasselbe ist wie pro App), kann das System möglicherweise auf irgendeine Weise aufzeichnen, ob die betreffende App ausgenommen ist oder nicht. Zum Glück gibt es das, und es ist für uns zugänglich. Mithilfe dieses neu entdeckten Doppelreflexions-Hacks haben wir einen Codeblock für die einmalige Verwendung erhalten:

val forName = Class::class.java.getDeclaredMethod("forName", String:: class.java)
val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod", String:: class.java, arrayOf>()::class.java)

val vmRuntimeClass = forName.invoke(null, "dalvik.system.VMRuntime") asClass
val getRuntime = getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null) as Method
val setHiddenApiExemptions = getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", arrayOf(arrayOf<String>()::class.java)) asMethod

val vmRuntime = getRuntime.invoke(null)

setHiddenApiExemptions.invoke(vmRuntime, arrayOf("L"))

Okay, technisch gesehen sagt das dem System nicht, dass unsere App von der API-Blacklist ausgenommen ist. Es gibt tatsächlich einen weiteren ADB-Befehl, den Sie ausführen können, um Funktionen anzugeben, die nicht auf die schwarze Liste gesetzt werden sollen. Das machen wir uns oben zunutze. Der Code überschreibt grundsätzlich alles, was das System für unsere App als ausgenommen erachtet. Die Übergabe von „L“ am Ende bedeutet, dass alle Methoden ausgenommen sind. Wenn Sie bestimmte Methoden ausschließen möchten, verwenden Sie die Smali-Syntax: Landroid/some/hidden/Class; Landroid/some/other/hidden/Class;.

Jetzt sind wir tatsächlich fertig. Erstellen Sie eine benutzerdefinierte Anwendungsklasse und fügen Sie diesen Code ein onCreate() Methode, und bam, keine Einschränkungen mehr.


Vielen Dank an XDA-Mitglied Weishu, den Entwickler von VirtualXposed und Taichi, für die ursprüngliche Entdeckung dieser Methode. Wir möchten uns auch beim XDA Recognized Developer topjohnwu dafür bedanken, dass er mich auf diese Problemumgehung hingewiesen hat. Hier erfahren Sie mehr darüber, wie es funktioniert, obwohl es auf Chinesisch ist. ich auch schrieb darüber auf Stack Overflow, auch mit einem Beispiel in JNI.