Programeri: Izuzetno je jednostavno zaobići Androidova skrivena API ograničenja

click fraud protection

Android 9 Pie i Android 10 šalju upozorenja ili izravno blokiraju pristup skrivenim API-jima. Evo kako programeri mogu zaobići skrivena ograničenja API-ja.

Prisjetimo se vremena prije više od godinu dana i svi smo uzbuđeni što ćemo vidjeti što dolazi u beta verzijama Androida P. Korisnici se raduju novim značajkama, a programeri se raduju nekim novim alatima koji će poboljšati njihove aplikacije. Nažalost za neke od tih programera, prva beta verzija Androida P došla je s malim gadnim iznenađenjem: skrivenim ograničenjima API-ja. Prije nego što zaronim u to što to točno znači, dopustite mi da malo objasnim njegov kontekst.

O čemu se ovdje radi?

Razvojni programeri aplikacija za Android ne moraju početi od nule kada izrađuju aplikaciju. Google nudi alate za lakši i manje ponavljajući razvoj aplikacija. Jedan od tih alata je Android SDK. SDK je u biti referenca na sve funkcije koje razvojni programeri mogu sigurno koristiti u svojim aplikacijama. Ove funkcije dolaze standardno na svim varijantama Androida koje Google odobri. SDK ipak nije iscrpan. Postoji dosta "skrivenih" dijelova Androidovog okvira koji nisu dio SDK-a.

Ovi "skriveni" dijelovi mogu biti nevjerojatno korisni za hakirane stvari ili stvari niske razine. Na primjer, moj Aplikacija Widget Drawer koristi funkciju koja nije SDK za otkrivanje kada korisnik pokrene aplikaciju iz widgeta kako bi se ladica mogla automatski zatvoriti. Možda mislite: "Zašto jednostavno ne učinite ove funkcije koje nisu SDK dijelom SDK-a?" Pa, problem je što njihov rad nije u potpunosti predvidljiv. Google ne može osigurati da svaki pojedini dio okvira radi na svakom pojedinom uređaju koji odobri, pa se odabiru važnije metode za provjeru. Google ne jamči da će ostatak okvira ostati dosljedan. Proizvođači mogu promijeniti ili potpuno ukloniti te skrivene funkcije. Čak i u različitim verzijama AOSP-a, nikad ne znate hoće li skrivena funkcija i dalje postojati ili će raditi kao prije.

Ako se pitate zašto koristim riječ "skriveno", to je zato što te funkcije nisu niti dio SDK-a. Obično, ako pokušate koristiti skrivenu metodu ili klasu u aplikaciji, ona se neće uspjeti prevesti. Njihova uporaba zahtijeva odraz ili modificirani SDK.

Uz Android P, Google je odlučio da samo skrivanje nije dovoljno. Kada je objavljena prva beta verzija, objavljeno je da većina (ne sve) skrivenih funkcija više nije bila dostupna za upotrebu razvojnim programerima aplikacija. Prva beta bi vas upozorila kada bi vaša aplikacija koristila funkciju na crnoj listi. Sljedeće beta verzije jednostavno su srušile vašu aplikaciju. Barem je meni ova crna lista bila prilično dosadna. Ne samo da se prilično slomio Navigacijske geste, ali budući da su skrivene funkcije prema zadanim postavkama stavljene na crnu listu (Google mora ručno staviti neke na bijelu listu po izdanju), imao sam dosta problema s pokretanjem Widget Drawera.

Postojalo je nekoliko načina da se zaobiđe crna lista. Prvi je bio da vaša aplikacija jednostavno cilja API 27 (Android 8.1), budući da se crna lista odnosi samo na aplikacije koje ciljaju najnoviji API. Nažalost, s Googleom minimalni API zahtjevi za objavljivanje u Trgovini Play bilo bi moguće ciljati samo API 27 toliko dugo. Od 1. studenog 2019, sva ažuriranja aplikacije u Trgovini Play moraju ciljati API 28 ili noviji.

Drugo rješenje zapravo je nešto što je Google ugradio u Android. Moguće je pokrenuti ADB naredbu za potpuno onemogućavanje crne liste. To je odlično za osobnu upotrebu i testiranje, ali mogu vam reći iz prve ruke da je pokušaj natjerati krajnje korisnike da postave i pokrenu ADB noćna mora.

Dakle, gdje nas to ostavlja? Ne možemo više ciljati API 27 ako želimo učitati u Trgovinu Play, a ADB metoda jednostavno nije održiva. No, to ne znači da smo ostali bez mogućnosti.

Skrivena crna lista API-ja odnosi se samo na korisničke aplikacije koje nisu na popisu dopuštenih. Sistemske aplikacije, aplikacije potpisane potpisom platforme i aplikacije navedene u konfiguracijskoj datoteci izuzete su s crne liste. Zanimljivo, svi paketi Google Play usluga navedeni su u toj konfiguracijskoj datoteci. Pretpostavljam da je Google bolji od nas ostalih.

U svakom slučaju, nastavimo pričati o crnoj listi. Dio koji nas danas zanima jest da su sistemske aplikacije izuzete. To znači, na primjer, da aplikacija System UI može koristiti sve skrivene funkcije koje želi budući da se nalazi na particiji sustava. Očito, ne možemo jednostavno gurnuti aplikaciju na sistemsku particiju. Za to je potreban root, dobar file manager, poznavanje dozvola... Bilo bi lakše koristiti ADB. No, to nije jedini način na koji možemo biti aplikacija sustava, barem što se tiče skrivene crne liste API-ja.

Vratite se u mislima na sedam odlomaka prije kada sam spomenuo razmišljanje. Ako ne znate što je refleksija, preporučam čitanje ova stranica, ali evo kratkog sažetka. U Javi refleksija je način pristupa obično nedostupnim klasama, metodama i poljima. To je nevjerojatno moćan alat. Kao što sam rekao u onom odlomku, refleksija je nekad bila način pristupa funkcijama koje nisu SDK. API crna lista blokira korištenje refleksije, ali ne blokira korištenje dvostruko-odraz.

Sada, evo gdje postaje malo čudno. Obično, ako želite pozvati skrivenu metodu, učinili biste nešto poput ovoga (ovo je u Kotlinu, ali Java je slična):

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

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

Ipak, zahvaljujući API crnoj listi, dobili biste samo ClassNotFoundException. Međutim, ako razmislite dvaput, dobro funkcionira:

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

Čudno zar ne? Pa da, ali i ne. API crna lista prati tko poziva funkciju. Ako izvor nije izuzet, ruši se. U prvom primjeru, izvor je aplikacija. Međutim, u drugom primjeru, izvor je sam sustav. Umjesto da refleksiju koristimo da izravno dobijemo ono što želimo, koristimo je da kažemo sustavu da dobije ono što želimo. Budući da je izvor poziva skrivene funkcije sustav, crna lista više ne utječe na nas.

Dakle, gotovi smo. Sada imamo način da zaobiđemo API crnu listu. Malo je nezgrapno, ali mogli bismo napisati funkciju omotača tako da ne moramo svaki put dvostruko reflektirati. Nije sjajno, ali bolje je nego ništa. Ali zapravo, nismo gotovi. Postoji bolji način za to koji će nam omogućiti korištenje normalnog odraza ili modificiranog SDK-a, kao u dobra stara vremena.

Budući da se provedba crne liste ocjenjuje po procesu (što je u većini slučajeva isto kao i po aplikaciji), možda postoji neki način da sustav zabilježi je li dotična aplikacija izuzeta ili ne. Srećom, postoji i nama je dostupna. Koristeći taj novootkriveni hack dvostrukog odraza, imamo blok koda za jednokratnu upotrebu:

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

U redu, tehnički gledano, ovo ne govori sustavu da je naša aplikacija izuzeta s crne liste API-ja. Zapravo postoji još jedna ADB naredba koju možete pokrenuti da odredite funkcije koje ne bi trebale biti na crnoj listi. To je ono što mi gore iskorištavamo. Kod u osnovi nadjačava sve što sustav smatra izuzetim za našu aplikaciju. Prolazak "L" na kraju znači da su sve metode izuzete. Ako želite izuzeti određene metode, koristite Smali sintaksu: Landroid/some/hidden/Class; Landroid/some/other/hidden/Class;.

Sada smo zapravo gotovi. Napravite prilagođenu klasu aplikacije, stavite taj kod u onCreate() metoda, i bam, nema više ograničenja.


Zahvaljujemo članu XDA weishu, programeru VirtualXposed i Taichiju, što je izvorno otkrio ovu metodu. Također bismo željeli zahvaliti XDA Recognized Developer topjohnwu što mi je ukazao na ovo zaobilazno rješenje. Evo malo više o tome kako to radi, iako je na kineskom. ja također pisao o ovome na Stack Overflowu, s primjerom i u JNI.