Izstrādātāji: ir ļoti vienkārši apiet Android slēptos API ierobežojumus

Android 9 Pie un Android 10 izsaka brīdinājumus vai tieši bloķē piekļuvi slēptajām API. Lūk, kā izstrādātāji var apiet slēptos API ierobežojumus.

Atskats uz vairāk nekā gadu atpakaļ, un mēs visi esam satraukti par to, kas gaidāms Android P beta versijā. Lietotāji ar nepacietību gaida jaunas funkcijas, un izstrādātāji gaida dažus jaunus rīkus, lai uzlabotu savas lietotnes. Diemžēl dažiem no šiem izstrādātājiem pirmā Android P beta versija bija saistīta ar nedaudz nepatīkamu pārsteigumu: slēptiem API ierobežojumiem. Pirms iedziļināties tajā, ko tieši tas nozīmē, ļaujiet man mazliet paskaidrot tā kontekstu.

Kas tas ir par?

Android lietotņu izstrādātājiem, veidojot lietotni, nav jāsāk no nulles. Google nodrošina rīkus, kas atvieglo lietotņu izstrādi un mazāk atkārtojas. Viens no šiem rīkiem ir Android SDK. SDK būtībā ir atsauce uz visām funkcijām, kuras izstrādātāji var droši izmantot savās lietotnēs. Šīs funkcijas ir iekļautas visu Google apstiprināto Android variantu standartaprīkojumā. Tomēr SDK nav pilnīgs. Ir diezgan daudz "slēptu" Android sistēmas daļu, kas neietilpst SDK.

Šīs "slēptās" daļas var būt neticami noderīgas, lai iegūtu vairāk hacky vai zema līmeņa lietas. Piemēram, mans Logrīku atvilktnes lietotne izmanto funkciju, kas nav SDK, lai noteiktu, kad lietotājs palaiž lietotni no logrīka, lai atvilktne varētu automātiski aizvērties. Jūs varētu domāt: "Kāpēc gan šīs funkcijas, kas nav SDK, vienkārši neiekļaut SDK?" Problēma ir tā, ka to darbība nav pilnībā paredzama. Google nevar nodrošināt, ka katra ietvara daļa darbojas katrā tā apstiprinātajā ierīcē, tāpēc verifikācijai tiek atlasītas svarīgākas metodes. Google negarantē, ka pārējā ietvara daļa paliks konsekventa. Ražotāji var mainīt vai pilnībā noņemt šīs slēptās funkcijas. Pat dažādās AOSP versijās jūs nekad nezināt, vai slēptā funkcija joprojām pastāvēs vai darbosies kā agrāk.

Ja jums rodas jautājums, kāpēc es izmantoju vārdu "slēpts", tas ir tāpēc, ka šīs funkcijas pat neietilpst SDK. Parasti, ja mēģināt lietotnē izmantot slēptu metodi vai klasi, to neizdosies kompilēt. To izmantošana prasa pārdomas vai modificēts SDK.

Izmantojot Android P, Google nolēma, ka ar to slēpšanu vien nepietiek. Kad tika izlaista pirmā beta versija, tas tika paziņots lielākā daļa (ne visas) slēpto funkciju vairs nebija pieejamas lietotņu izstrādātājiem. Pirmā beta versija brīdinās jūs, kad jūsu lietotnē tika izmantota funkcija, kas iekļauta melnajā sarakstā. Nākamās beta versijas vienkārši avarēja jūsu lietotni. Vismaz man šis melnais saraksts bija diezgan kaitinošs. Tas ne tikai salauza diezgan daudz Navigācijas žesti, taču, tā kā slēptās funkcijas pēc noklusējuma ir iekļautas melnajā sarakstā (Google ir manuāli jāiekļauj baltajā sarakstā katrai laidienai), man bija daudz problēmu, lai logrīku atvilktne darbotos.

Tagad bija daži veidi, kā apiet melno sarakstu. Pirmais bija vienkārši saglabāt lietotnes mērķauditorijas atlasi pēc API 27 (Android 8.1), jo melnais saraksts attiecās tikai uz tām lietotnēm, kuru mērķauditorija ir jaunākā API. Diemžēl ar Google minimālās API prasības publicēšanai Play veikalā, tik ilgi būtu iespējams atlasīt tikai API 27. No 2019. gada 1. novembra, visiem Play veikala lietotņu atjauninājumiem ir jābūt vērstiem uz API 28 vai jaunāku versiju.

Otrais risinājums patiesībā ir kaut kas tāds, ko Google ir iebūvēts Android ierīcē. Ir iespējams palaist ADB komandu, lai pilnībā atspējotu melno sarakstu. Tas ir lieliski piemērots personīgai lietošanai un testēšanai, taču es varu jums uzreiz pateikt, ka mēģinājums likt galalietotājiem iestatīt un palaist ADB ir murgs.

Tātad, kur tas mūs atstāj? Mēs vairs nevaram mērķēt uz API 27, ja vēlamies augšupielādēt Play veikalā, un ADB metode vienkārši nav dzīvotspējīga. Tomēr tas nenozīmē, ka mums vairs nav iespēju.

Slēptais API melnais saraksts attiecas tikai uz lietotāju lietojumprogrammām, kas nav iekļautas baltajā sarakstā. Sistēmas lietojumprogrammas, lietojumprogrammas, kas parakstītas ar platformas parakstu, un lietojumprogrammas, kas norādītas konfigurācijas failā, ir atbrīvotas no melnā saraksta. Smieklīgi, ka visi Google Play pakalpojumu komplekti ir norādīti šajā konfigurācijas failā. Es domāju, ka Google ir labāks par mums pārējiem.

Jebkurā gadījumā turpināsim runāt par melno sarakstu. Šodien mūs interesē tas, ka sistēmas lietojumprogrammas ir atbrīvotas no nodokļa. Tas nozīmē, piemēram, ka sistēmas lietotāja interfeisa lietotne var izmantot visas vajadzīgās slēptās funkcijas, jo tā atrodas sistēmas nodalījumā. Acīmredzot mēs nevaram vienkārši nospiest lietotni sistēmas nodalījumā. Tam nepieciešama root, labs failu pārvaldnieks, zināšanas par atļaujām... Vienkāršāk būtu izmantot ADB. Tomēr tas nav vienīgais veids, kā mēs varam būt sistēmas lietotne, vismaz attiecībā uz slēpto API melno sarakstu.

Atgriezieties pirms septiņām rindkopām, kad es pieminēju pārdomas. Ja nezini, kas ir refleksija, iesaku izlasīt šo lapu, bet šeit ir īss kopsavilkums. Java programmā refleksija ir veids, kā piekļūt parasti nepieejamām klasēm, metodēm un laukiem. Tas ir neticami spēcīgs instruments. Kā jau teicu šajā rindkopā, refleksija agrāk bija veids, kā piekļūt funkcijām, kas nav SDK. API melnais saraksts bloķē atspoguļošanas izmantošanu, bet nebloķē dubultā- atspulgs.

Lūk, kur tas kļūst mazliet dīvaini. Parasti, ja vēlaties izsaukt slēpto metodi, rīkojieties šādi (tas ir Kotlinā, bet Java ir līdzīga):

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

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

Tomēr, pateicoties API melnajam sarakstam, jūs vienkārši iegūtu ClassNotFoundException. Tomēr, ja pārdomājat divas reizes, tas darbojas labi:

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

Dīvaini vai ne? Nu jā, bet arī nē. API melnais saraksts izseko, kurš izsauc funkciju. Ja avots nav atbrīvots, tas avarē. Pirmajā piemērā avots ir lietotne. Tomēr otrajā piemērā avots ir pati sistēma. Tā vietā, lai izmantotu pārdomas, lai tieši iegūtu to, ko mēs vēlamies, mēs to izmantojam, lai norādītu sistēmai iegūt to, ko mēs vēlamies. Tā kā slēptās funkcijas izsaukuma avots ir sistēma, melnais saraksts mūs vairs neietekmē.

Tātad esam galā. Tagad mums ir veids, kā apiet API melno sarakstu. Tas ir nedaudz neveikls, taču mēs varētu uzrakstīt iesaiņojuma funkciju, lai mums nebūtu katru reizi jāatspoguļo dubultā. Tas nav lieliski, bet tas ir labāk nekā nekas. Bet patiesībā mēs neesam pabeiguši. Ir labāks veids, kā to izdarīt, piemēram, vecajos labajos laikos, izmantojot parasto atspoguļojumu vai modificētu SDK.

Tā kā melnā saraksta izpilde tiek novērtēta katrā procesā (kas vairumā gadījumu ir tāds pats kā katrai lietotnei), sistēmai var būt kāds veids, kā reģistrēt, vai attiecīgā lietotne ir atbrīvota vai nav. Par laimi, ir, un tas mums ir pieejams. Izmantojot šo jaunatklāto dubultās refleksijas uzlaušanu, mums ir vienreiz lietojams koda bloks:

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

Labi, tehniski tas nenozīmē, ka mūsu lietotne ir atbrīvota no API melnā saraksta. Patiesībā ir vēl viena ADB komanda, kuru varat palaist, lai norādītu funkcijas, kuras nevajadzētu iekļaut melnajā sarakstā. Tas ir tas, ko mēs izmantojam iepriekš. Kods pamatā ignorē visu, kas, pēc sistēmas domām, ir atbrīvots no mūsu lietotnes. “L” izlaišana beigās nozīmē, ka visas metodes ir atbrīvotas. Ja vēlaties atbrīvot noteiktas metodes, izmantojiet Smali sintaksi: Landroid/some/hidden/Class; Landroid/some/other/hidden/Class;.

Tagad mēs faktiski esam pabeiguši. Izveidojiet pielāgotu lietojumprogrammu klasi, ievietojiet šo kodu onCreate() metodi, un bam, vairs nekādu ierobežojumu.


Paldies XDA Member Weishu, VirtualXposed un Taichi izstrādātājam, par šīs metodes sākotnējo atklāšanu. Mēs arī vēlamies pateikties XDA Recognized Developer topjohnwu par to, ka viņš man norādīja uz šo risinājumu. Šeit ir nedaudz vairāk par to, kā tas darbojas, lai gan tas ir ķīniešu valodā. ES arī rakstīja par to Stack Overflow, ar piemēru arī JNI.