Sviluppatori: è semplicissimo aggirare le restrizioni API nascoste di Android

click fraud protection

Android 9 Pie e Android 10 lanciano avvisi o bloccano completamente l'accesso alle API nascoste. Ecco come gli sviluppatori possono aggirare le restrizioni API nascoste.

Flashback a più di un anno fa e siamo tutti entusiasti di vedere cosa accadrà nelle beta di Android P. Gli utenti attendono con ansia nuove funzionalità e gli sviluppatori attendono con ansia alcuni nuovi strumenti per migliorare le loro app. Sfortunatamente per alcuni di questi sviluppatori, la prima beta di Android P è arrivata con una brutta sorpresa: restrizioni API nascoste. Prima di approfondire cosa significa esattamente, lasciatemi spiegare un po’ il suo contesto.

Di cosa si tratta?

Gli sviluppatori di app Android non devono iniziare da zero quando creano un'app. Google fornisce strumenti per rendere lo sviluppo di app più semplice e meno ripetitivo. Uno di questi strumenti è l'SDK di Android. L'SDK è essenzialmente un riferimento a tutte le funzioni che gli sviluppatori possono utilizzare in sicurezza nelle loro app. Queste funzioni sono standard su tutte le varianti di Android approvate da Google. Tuttavia, l'SDK non è esaustivo. Esistono alcune parti "nascoste" del framework Android che non fanno parte dell'SDK.

Queste parti "nascoste" possono essere incredibilmente utili per cose più complicate o di basso livello. Ad esempio, il mio Applicazione cassetto widget utilizza una funzione non SDK per rilevare quando un utente avvia un'app da un widget in modo che il drawer possa chiudersi automaticamente. Potresti pensare: "Perché non rendere queste funzioni non SDK parte dell'SDK?" Ebbene, il problema è che il loro funzionamento non è del tutto prevedibile. Google non può garantire che ogni singola parte del framework funzioni su ogni singolo dispositivo che approva, quindi vengono selezionati metodi più importanti da verificare. Google non garantisce che il resto del framework rimarrà coerente. I produttori possono modificare o rimuovere completamente queste funzioni nascoste. Anche in diverse versioni di AOSP, non si sa mai se una funzione nascosta esisterà ancora o funzionerà come prima.

Se ti stai chiedendo perché ho utilizzato la parola "nascosto", è perché queste funzioni non fanno nemmeno parte dell'SDK. Normalmente, se provi a utilizzare un metodo o una classe nascosta in un'app, la compilazione non riuscirà. Il loro utilizzo richiede riflessione o un SDK modificato.

Con Android P, Google ha deciso che nasconderli non era sufficiente. Quando è stata rilasciata la prima beta, è stato annunciato la maggior parte (non tutte) le funzioni nascoste non erano più disponibili per l'utilizzo da parte degli sviluppatori di app. La prima beta ti avviserebbe quando la tua app utilizzava una funzione nella lista nera. Le prossime beta hanno semplicemente bloccato la tua app. Almeno per me, questa lista nera è stata piuttosto fastidiosa. Non solo si è rotto un bel po' Gesti di navigazione, ma poiché le funzioni nascoste sono inserite nella lista nera per impostazione predefinita (Google deve inserirne manualmente alcune nella whitelist per ogni versione), ho avuto molti problemi a far funzionare Widget Drawer.

Ora, c'erano alcuni modi per aggirare la lista nera. Il primo era semplicemente mantenere la tua app destinata all'API 27 (Android 8.1), poiché la lista nera si applicava solo alle app destinate all'API più recente. Sfortunatamente, con Google requisiti API minimi per la pubblicazione sul Play Store, sarebbe possibile prendere di mira l'API 27 solo per così tanto tempo. A partire dal 1 novembre 2019, tutti gli aggiornamenti delle app nel Play Store devono avere come target l'API 28 o successiva.

La seconda soluzione alternativa è in realtà qualcosa che Google ha integrato in Android. È possibile eseguire un comando ADB per disabilitare completamente la lista nera. È ottimo per uso personale e test, ma posso dirti in prima persona che cercare di convincere gli utenti finali a configurare ed eseguire ADB è un incubo.

Allora dove ci porta questo? Non possiamo più scegliere come target l'API 27 se vogliamo caricare sul Play Store e il metodo ADB semplicemente non è praticabile. Ciò non significa che siamo senza opzioni, però.

La lista nera delle API nascoste si applica solo alle applicazioni utente non autorizzate. Le applicazioni di sistema, le applicazioni firmate con la firma della piattaforma e le applicazioni specificate in un file di configurazione sono tutte esenti dalla lista nera. Stranamente, la suite Google Play Services è tutta specificata in quel file di configurazione. Immagino che Google sia migliore di tutti noi.

Comunque continuiamo a parlare della lista nera. La parte che ci interessa oggi è che le applicazioni di sistema sono esenti. Ciò significa, ad esempio, che l'app System UI può utilizzare tutte le funzioni nascoste che desidera poiché si trova sulla partizione di sistema. Ovviamente, non possiamo semplicemente inviare un'app alla partizione di sistema. Ciò richiede root, un buon file manager, conoscenza dei permessi... Sarebbe più semplice usare ADB. Questo non è l'unico modo in cui possiamo essere un'app di sistema, almeno per quanto riguarda la lista nera delle API nascoste.

Riporta la tua mente a sette paragrafi fa, quando ho menzionato la riflessione. Se non sai cos'è la riflessione, ti consiglio di leggere questa pagina, ma ecco un breve riassunto. In Java, la riflessione è un modo per accedere a classi, metodi e campi normalmente inaccessibili. È uno strumento incredibilmente potente. Come ho detto in quel paragrafo, la riflessione era un modo per accedere a funzioni non SDK. La lista nera dell'API blocca l'uso della riflessione, ma non blocca l'uso di Doppio-riflessione.

Ora, ecco dove diventa un po' strano. Normalmente, se volessi chiamare un metodo nascosto, faresti qualcosa del genere (questo è in Kotlin, ma Java è simile):

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

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

Grazie alla lista nera dell'API, tuttavia, otterresti semplicemente una ClassNotFoundException. Tuttavia, se rifletti due volte, funziona bene:

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

Strano vero? Ebbene sì, ma anche no. La lista nera dell'API tiene traccia di chi sta chiamando una funzione. Se la fonte non è esente, si blocca. Nel primo esempio, l'origine è app. Tuttavia, nel secondo esempio, la fonte è il sistema stesso. Invece di usare la riflessione per ottenere direttamente ciò che vogliamo, la usiamo per dire al sistema di ottenere ciò che vogliamo. Poiché la fonte della chiamata alla funzione nascosta è il sistema, la lista nera non ci riguarda più.

Quindi abbiamo finito. Ora abbiamo un modo per aggirare la lista nera delle API. È un po' macchinoso, ma potremmo scrivere una funzione wrapper in modo da non dover riflettere ogni volta due volte. Non è eccezionale, ma è meglio di niente. Ma in realtà non abbiamo finito. Esiste un modo migliore per farlo che ci consentirà di utilizzare la riflessione normale o un SDK modificato, come ai vecchi tempi.

Poiché l'applicazione della lista nera viene valutata per processo (che nella maggior parte dei casi è la stessa cosa per app), potrebbe esserci un modo per il sistema di registrare se l'app in questione è esente o meno. Fortunatamente esiste ed è accessibile a noi. Utilizzando questo nuovo hack a doppia riflessione, abbiamo un blocco di codice monouso:

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

Ok, tecnicamente questo non significa dire al sistema che la nostra app è esente dalla lista nera delle API. In realtà esiste un altro comando ADB che puoi eseguire per specificare le funzioni che non dovrebbero essere inserite nella lista nera. Questo è ciò di cui stiamo approfittando sopra. Il codice sostanzialmente sovrascrive qualunque cosa il sistema ritenga esente per la nostra app. Passare "L" alla fine significa che tutti i metodi sono esenti. Se vuoi esentare metodi specifici, usa la sintassi Smali: Landroid/some/hidden/Class; Landroid/some/other/hidden/Class;.

Adesso abbiamo davvero finito. Crea una classe Application personalizzata, inserisci quel codice nel file onCreate() metodo e bam, niente più restrizioni.


Grazie al membro XDA weishu, lo sviluppatore di VirtualXposed e Taichi, per aver scoperto questo metodo. Vorremmo anche ringraziare lo sviluppatore riconosciuto XDA topjohnwu per avermi segnalato questa soluzione alternativa. Ecco qualcosa in più su come funziona, anche se è in cinese. anche io ha scritto a riguardo su Stack Overflow, con un esempio anche in JNI.