Προγραμματιστές: Είναι εξαιρετικά εύκολο να παρακάμψετε τους κρυφούς περιορισμούς API του Android

click fraud protection

Το Android 9 Pie και το Android 10 εκπέμπουν προειδοποιήσεις ή αποκλείουν οριστικά την πρόσβαση σε κρυφά API. Δείτε πώς οι προγραμματιστές μπορούν να παρακάμψουν τους κρυφούς περιορισμούς API.

Αναδρομή πριν από περισσότερο από ένα χρόνο, και είμαστε όλοι ενθουσιασμένοι που θα δούμε τι θα ακολουθήσει στις beta του Android P. Οι χρήστες ανυπομονούν για νέες δυνατότητες και οι προγραμματιστές περιμένουν με ανυπομονησία κάποια νέα εργαλεία για να κάνουν τις εφαρμογές τους καλύτερες. Δυστυχώς για ορισμένους από αυτούς τους προγραμματιστές, η πρώτη έκδοση beta του Android P ήρθε με μια δυσάρεστη έκπληξη: κρυφούς περιορισμούς API. Πριν βουτήξω στο τι ακριβώς σημαίνει αυτό, επιτρέψτε μου να εξηγήσω λίγο για το περιεχόμενό του.

Τι είναι αυτό;

Οι προγραμματιστές εφαρμογών Android δεν χρειάζεται να ξεκινούν από την αρχή όταν δημιουργούν μια εφαρμογή. Η Google παρέχει εργαλεία για να κάνει την ανάπτυξη εφαρμογών ευκολότερη και λιγότερο επαναλαμβανόμενη. Ένα από αυτά τα εργαλεία είναι το Android SDK. Το SDK είναι ουσιαστικά μια αναφορά σε όλες τις λειτουργίες που μπορούν να χρησιμοποιήσουν οι προγραμματιστές με ασφάλεια στις εφαρμογές τους. Αυτές οι λειτουργίες διατίθενται στάνταρ σε όλες τις παραλλαγές του Android που εγκρίνει η Google. Ωστόσο, το SDK δεν είναι εξαντλητικό. Υπάρχουν αρκετά "κρυφά" τμήματα του πλαισίου του Android που δεν αποτελούν μέρος του SDK.

Αυτά τα «κρυμμένα» μέρη μπορεί να είναι απίστευτα χρήσιμα για πιο χακαρισμένα ή χαμηλού επιπέδου πράγματα. Για παράδειγμα, μου Εφαρμογή συρταριού widget χρησιμοποιεί μια λειτουργία που δεν είναι SDK για να ανιχνεύει πότε ένας χρήστης εκκινεί μια εφαρμογή από ένα γραφικό στοιχείο, ώστε το συρτάρι να μπορεί να κλείσει αυτόματα. Ίσως σκέφτεστε: "Γιατί να μην κάνετε απλώς αυτές τις λειτουργίες που δεν είναι SDK μέρος του SDK;" Λοιπόν, το πρόβλημα είναι ότι η λειτουργία τους δεν είναι πλήρως προβλέψιμη. Η Google δεν μπορεί να διασφαλίσει ότι κάθε τμήμα του πλαισίου λειτουργεί σε κάθε συσκευή που εγκρίνει, επομένως επιλέγονται πιο σημαντικές μέθοδοι για επαλήθευση. Η Google δεν εγγυάται ότι το υπόλοιπο πλαίσιο θα παραμείνει συνεπές. Οι κατασκευαστές μπορούν να αλλάξουν ή να αφαιρέσουν εντελώς αυτές τις κρυφές λειτουργίες. Ακόμη και σε διαφορετικές εκδόσεις του AOSP, ποτέ δεν ξέρεις αν μια κρυφή συνάρτηση θα εξακολουθεί να υπάρχει ή να λειτουργεί όπως παλιά.

Αν αναρωτιέστε γιατί χρησιμοποιώ τη λέξη "κρυφό", είναι επειδή αυτές οι λειτουργίες δεν αποτελούν καν μέρος του SDK. Κανονικά, εάν προσπαθήσετε να χρησιμοποιήσετε μια κρυφή μέθοδο ή κλάση σε μια εφαρμογή, θα αποτύχει η μεταγλώττιση. Η χρήση τους απαιτεί αντανάκλαση ή ένα τροποποιημένο SDK.

Με το Android P, η Google αποφάσισε ότι η απόκρυψή τους δεν αρκούσε. Όταν κυκλοφόρησε η πρώτη beta, ανακοινώθηκε ότι Οι περισσότερες (όχι όλες) κρυφές λειτουργίες δεν ήταν πλέον διαθέσιμες για χρήση στους προγραμματιστές εφαρμογών. Η πρώτη beta θα σας προειδοποιούσε όταν η εφαρμογή σας χρησιμοποιούσε μια λειτουργία στη μαύρη λίστα. Οι επόμενες beta απλώς διέκοψαν την εφαρμογή σας. Τουλάχιστον για μένα, αυτή η μαύρη λίστα ήταν αρκετά ενοχλητική. Όχι μόνο έσπασε αρκετά Χειρονομίες πλοήγησης, αλλά επειδή οι κρυφές συναρτήσεις περιλαμβάνονται στη μαύρη λίστα από προεπιλογή (η Google πρέπει να εισάγει με μη αυτόματο τρόπο κάποιες επιτρεπόμενες ανά κυκλοφορία), δυσκολεύτηκα πολύ να λειτουργήσει το Συρτάρι γραφικών στοιχείων.

Τώρα, υπήρχαν μερικοί τρόποι για να επιλύσετε τη μαύρη λίστα. Το πρώτο ήταν να διατηρήσετε απλώς την εφαρμογή σας στο API 27 (Android 8.1), καθώς η μαύρη λίστα ίσχυε μόνο για εφαρμογές που στοχεύουν το πιο πρόσφατο API. Δυστυχώς, με τη Google ελάχιστες απαιτήσεις API για δημοσίευση στο Play Store, θα ήταν δυνατή μόνο η στόχευση του API 27 για τόσο μεγάλο χρονικό διάστημα. Από 1 Νοεμβρίου 2019, όλες οι ενημερώσεις εφαρμογών στο Play Store πρέπει να στοχεύουν το API 28 ή μεταγενέστερο.

Η δεύτερη λύση είναι στην πραγματικότητα κάτι που η Google έχει ενσωματώσει στο Android. Είναι δυνατό να εκτελέσετε μια εντολή ADB για να απενεργοποιήσετε εντελώς τη μαύρη λίστα. Αυτό είναι εξαιρετικό για προσωπική χρήση και δοκιμές, αλλά μπορώ να σας πω από πρώτο χέρι ότι η προσπάθεια να πείσετε τους τελικούς χρήστες να εγκαταστήσουν και να εκτελέσουν το ADB είναι ένας εφιάλτης.

Πού μας αφήνει λοιπόν αυτό; Δεν μπορούμε πλέον να στοχεύσουμε το API 27 αν θέλουμε να ανεβάσουμε στο Play Store και η μέθοδος ADB απλώς δεν είναι βιώσιμη. Ωστόσο, αυτό δεν σημαίνει ότι δεν έχουμε επιλογές.

Η κρυφή μαύρη λίστα API ισχύει μόνο για εφαρμογές χρηστών που δεν είναι στη λίστα επιτρεπόμενων. Οι εφαρμογές συστήματος, οι εφαρμογές που έχουν υπογραφεί με την υπογραφή της πλατφόρμας και οι εφαρμογές που καθορίζονται σε ένα αρχείο διαμόρφωσης εξαιρούνται από τη μαύρη λίστα. Περιέργως, η σουίτα Υπηρεσιών Google Play καθορίζεται σε αυτό το αρχείο διαμόρφωσης. Υποθέτω ότι η Google είναι καλύτερη από εμάς τους υπόλοιπους.

Τέλος πάντων, ας συνεχίσουμε να μιλάμε για τη μαύρη λίστα. Το κομμάτι που μας ενδιαφέρει σήμερα είναι ότι οι εφαρμογές συστήματος εξαιρούνται. Αυτό σημαίνει, για παράδειγμα, ότι η εφαρμογή System UI μπορεί να χρησιμοποιήσει όλες τις κρυφές λειτουργίες που θέλει, δεδομένου ότι βρίσκεται στο διαμέρισμα συστήματος. Προφανώς, δεν μπορούμε απλώς να προωθήσουμε μια εφαρμογή στο διαμέρισμα συστήματος. Αυτό χρειάζεται root, καλός διαχειριστής αρχείων, γνώση αδειών... Θα ήταν ευκολότερο να χρησιμοποιήσετε το ADB. Ωστόσο, δεν είναι ο μόνος τρόπος για να είμαστε μια εφαρμογή συστήματος, τουλάχιστον όσον αφορά την κρυφή μαύρη λίστα API.

Γυρίστε το μυαλό σας σε επτά παραγράφους πριν, όταν ανέφερα τον προβληματισμό. Αν δεν ξέρετε τι είναι η αντανάκλαση, σας προτείνω να διαβάσετε αυτή η σελίδα, αλλά εδώ είναι μια γρήγορη περίληψη. Στην Java, ο προβληματισμός είναι ένας τρόπος πρόσβασης σε κλάσεις, μεθόδους και πεδία που συνήθως δεν είναι προσβάσιμες. Είναι ένα απίστευτα ισχυρό εργαλείο. Όπως είπα σε εκείνη την παράγραφο, ο προβληματισμός ήταν ένας τρόπος πρόσβασης σε λειτουργίες που δεν ήταν SDK. Η μαύρη λίστα API αποκλείει τη χρήση του προβληματισμού, αλλά δεν αποκλείει τη χρήση του διπλό-αντανάκλαση.

Τώρα, εδώ είναι που γίνεται λίγο περίεργο. Κανονικά, αν θέλατε να καλέσετε μια κρυφή μέθοδο, θα κάνατε κάτι σαν αυτό (αυτό είναι στο Kotlin, αλλά η Java είναι παρόμοια):

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

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

Χάρη στη μαύρη λίστα API, ωστόσο, θα λάβετε απλώς ένα ClassNotFoundException. Ωστόσο, αν αντανακλάτε δύο φορές, λειτουργεί καλά:

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

Παράξενο σωστά; Λοιπόν, ναι, αλλά και όχι. Η μαύρη λίστα API παρακολουθεί ποιος καλεί μια συνάρτηση. Εάν η πηγή δεν εξαιρείται, διακόπτεται. Στο πρώτο παράδειγμα, η πηγή είναι η εφαρμογή. Ωστόσο, στο δεύτερο παράδειγμα, η πηγή είναι το ίδιο το σύστημα. Αντί να χρησιμοποιούμε τον προβληματισμό για να πάρουμε αυτό που θέλουμε άμεσα, τον χρησιμοποιούμε για να πούμε στο σύστημα να πάρει αυτό που θέλουμε. Δεδομένου ότι η πηγή της κλήσης προς την κρυφή συνάρτηση είναι το σύστημα, η μαύρη λίστα δεν μας επηρεάζει πλέον.

Οπότε τελειώσαμε. Έχουμε έναν τρόπο να παρακάμψουμε τη μαύρη λίστα API τώρα. Είναι λίγο αδέξιο, αλλά θα μπορούσαμε να γράψουμε μια συνάρτηση περιτυλίγματος, ώστε να μην χρειάζεται να αντανακλούμε διπλά κάθε φορά. Δεν είναι υπέροχο, αλλά είναι καλύτερο από το τίποτα. Αλλά στην πραγματικότητα, δεν τελειώσαμε. Υπάρχει ένας καλύτερος τρόπος για να το κάνετε αυτό που θα μας επιτρέψει να χρησιμοποιήσουμε κανονική αντανάκλαση ή ένα τροποποιημένο SDK, όπως στις παλιές καλές μέρες.

Δεδομένου ότι η επιβολή της μαύρης λίστας αξιολογείται ανά διαδικασία (η οποία είναι ίδια με την εφαρμογή ανά εφαρμογή στις περισσότερες περιπτώσεις), ενδέχεται να υπάρχει κάποιος τρόπος για να καταγράψει το σύστημα εάν η εν λόγω εφαρμογή εξαιρείται ή όχι. Ευτυχώς, υπάρχει και είναι προσβάσιμο σε εμάς. Χρησιμοποιώντας αυτό το νέο hack διπλής αντανάκλασης, έχουμε ένα μπλοκ κώδικα μίας χρήσης:

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

Εντάξει, τεχνικά, αυτό δεν λέει στο σύστημα ότι η εφαρμογή μας εξαιρείται από τη μαύρη λίστα API. Υπάρχει στην πραγματικότητα μια άλλη εντολή ADB που μπορείτε να εκτελέσετε για να καθορίσετε συναρτήσεις που δεν πρέπει να περιλαμβάνονται στη μαύρη λίστα. Αυτό εκμεταλλευόμαστε παραπάνω. Ο κώδικας ουσιαστικά παρακάμπτει οτιδήποτε το σύστημα πιστεύει ότι εξαιρείται για την εφαρμογή μας. Περνώντας "L" στο τέλος σημαίνει ότι όλες οι μέθοδοι εξαιρούνται. Εάν θέλετε να εξαιρέσετε συγκεκριμένες μεθόδους, χρησιμοποιήστε τη σύνταξη Smali: Landroid/some/hidden/Class; Landroid/some/other/hidden/Class;.

Τώρα τελειώσαμε. Δημιουργήστε μια προσαρμοσμένη κλάση εφαρμογής, βάλτε αυτόν τον κωδικό στο onCreate() μέθοδο, και μπαμ, δεν υπάρχουν άλλοι περιορισμοί.


Ευχαριστούμε το XDA Member weishu, τον προγραμματιστή των VirtualXposed και Taichi, για την αρχική ανακάλυψη αυτής της μεθόδου. Θα θέλαμε επίσης να ευχαριστήσουμε τον XDA Recognized Developer topjohnwu που μου υπέδειξε αυτήν τη λύση. Εδώ είναι λίγο περισσότερα για το πώς λειτουργεί, αν και είναι στα κινέζικα. εγώ επίσης έγραψε για αυτό στο Stack Overflow, με παράδειγμα και στο JNI.