Come elevare le autorizzazioni dell'app utilizzando la libreria Shizuku

Il rooting non è per tutti. Ecco come puoi ottenere autorizzazioni elevate a livello di shell nella tua app utilizzando la libreria Shizuku.

Esistono molti motivi per cui le autorizzazioni normalmente concesse alla tua app potrebbero non essere sufficienti. Forse sei come me e ti piace creare app hackerate che abusano dell'API Android. Alcune delle API che utilizzo sono bloccate da autorizzazioni speciali. A volte solo l'utente shell (ADB) o il sistema possono accedervi. C'è una soluzione però: Shizuku.

Shizuku ti consente di chiamare le API di sistema quasi direttamente e interamente in Java o Kotlin. Questa guida ti mostrerà come implementare e utilizzare Shizuku.

Cos'è Shizuku?

Prima di iniziare a usare Shizuku, potrebbe essere utile sapere di cosa si tratta esattamente. Se hai familiarità con Magisk, allora Shizuku è simile. Ma invece di gestire l'accesso root, gestisce l'accesso alla shell.

Shizuku esegue il proprio processo con autorizzazioni a livello di shell. Il modo in cui l'utente attiva tale processo dipende dal dispositivo, dalla versione di Android e dalla scelta. Shizuku può essere attivato tramite ADB, tramite ADB wireless sul dispositivo (

su Android 11 e versioni successive) o tramite l'accesso root. Le app che implementano Shizuku possono quindi richiedere l'autorizzazione per utilizzare tale processo per eseguire operazioni con privilegi elevati.

ShizukuSviluppatore: Xingchen e Rikka

Prezzo: gratuito.

4.1.

Scaricamento

Perchè Shizuku?

Mentre l'accesso a livello di shell al sistema non ti consente di fare tanto quanto radice, ti dà comunque più accesso di quanto farebbe una normale app. Oltre a ciò, il modo in cui funziona Shizuku ti consente di utilizzare le API Android quasi come al solito. Non devi fare affidamento sui comandi della shell (anche se puoi farlo se lo desideri).

Se la tua app necessita di autorizzazioni speciali che possono essere concesse solo tramite ADB (o root), Shizuku e Android 11 costituiscono un ottimo abbinamento. Puoi semplicemente utilizzare Shizuku per concedere autorizzazioni speciali completamente sul dispositivo.

Anche con i dispositivi che non dispongono di Android 11, Shizuku può essere utile. L'app fornisce istruzioni e script per gli utenti, così non devi farlo tu.

Integrazione

Aggiungere Shizuku alla tua app non è la cosa più semplice, ma non è nemmeno difficile. Sfortunatamente, il documentazione dello sviluppatore non è esattamente completo, ma questo articolo ti copre. Ecco come integrare Shizuku nella tua app.

Dipendenze

Il primo passo è aggiungere le dipendenze Shizuku. Nel build.gradle a livello di modulo, aggiungi quanto segue al blocco delle dipendenze.

def shizuku_version = '11.0.3'

implementation "dev.rikka.shizuku: api:$shizuku_version"
implementation "dev.rikka.shizuku: provider:$shizuku_version"

Assicurati di aggiornare la versione se necessario. 11.0.3 è l'ultima al momento in cui scrivo.

Fornitore

Affinché Shizuku funzioni, devi aggiungere un blocco provider al manifest della tua app. Apri AndroidManifest.xml e aggiungi quanto segue all'interno del blocco dell'applicazione.

 android: name="rikka.shizuku.ShizukuProvider"
android: authorities="${applicationId}.shizuku"
android: multiprocess="false"
android: enabled="true"
android: exported="true"
android: permission="android.permission.INTERACT_ACROSS_USERS_FULL" />

Autorizzazione

Per l'autorizzazione, Shizuku utilizza un'autorizzazione di runtime. Tra poco parleremo della concessione di tale autorizzazione. Per ora, aggiungilo ad AndroidManifest.xml all'interno del blocco manifest.

Ora che tutto ciò è stato aggiunto, l'integrazione di base è completata. Lascia che Gradle esegua una sincronizzazione del progetto e continua con Utilizzo.


Utilizzo

Controllando la disponibilità

Prima di spiegare come utilizzare Shizuku, parliamo di come assicurarci che sia effettivamente disponibile per l'uso.

Prima di verificare se l'autorizzazione è stata concessa e prima di effettuare chiamate API tramite Shizuku, puoi assicurarti che tali controlli e chiamate abbiano esito positivo con il seguente metodo:

Shizuku.pingBinder()

Se Shizuku è installato e in esecuzione, verrà restituito VERO. Altrimenti restituirà false.

Concessione dell'autorizzazione

Poiché Shizuku utilizza un'autorizzazione di runtime, è necessario concederla alla tua app prima di poter eseguire qualsiasi operazione con l'accesso alla shell. Sono inoltre in circolazione due versioni API, con diverse modalità di concessione. Questa sezione ti mostrerà come gestirli entrambi.

Controllo

Prima di richiedere il permesso, la cosa migliore da fare è verificare se ne sei già in possesso. Se lo fai, puoi continuare con tutto ciò che devi fare. In caso contrario sarà necessario richiederlo prima di proseguire.

Per verificare se disponi dell'autorizzazione per utilizzare Shizuku, puoi utilizzare quanto segue. Questo codice presuppone che tu lo stia eseguendo all'interno di un'attività.

Kotlin:

val isGranted = if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
checkSelfPermission(ShizukuProvider.PERMISSION) == PackageManager.PERMISSION_GRANTED
} else {
Shizuku.checkSelfPermission() = PackageManager.PERMISSION_GRANTED
}

Giava:

boolean isGranted;
if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
isGranted = checkSelfPermission(ShizukuProvider.PERMISSION) == PackageManager.PERMISSION_GRANTED;
} else {
isGranted = Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED;
}

Richiedente

Se devi richiedere l'autorizzazione per utilizzare Shizuku, ecco come fare.

IL SHIZUKU_CODICE la variabile utilizzata di seguito deve essere un numero intero con un valore costante (variabile statica).

Kotlin:

if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
requestPermissions(arrayOf(ShizukuProvider.PERMISSION), SHIZUKU_CODE)
} else {
Shizuku.requestPermission(SHIZUKU_CODE)
}

Giava:

if (Shizuku.isPreV11() || Shizuku.getVersion() < 11) {
requestPermissions(newString[] { ShizukuProvider.PERMISSION }, SHIZUKU_CODE);
} else {
Shizuku.requestPermissions(SHIZUKU_CODE);
}

Per ascoltare il risultato, dovrai sovrascrivere Activity's onRequestPermissionsResult() metodo. Dovrai anche implementare Shizuku. OnRequestPermissionResultListener. L'esempio che mostrerò presuppone che la tua Activity implementi quell'interfaccia, ma puoi implementarla in una variabile se lo desideri.

Kotlin:

classExampleActivity : AppCompatActivity, Shizuku.OnRequestPermissionResultListener {
...

override void onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
permissions.forEachIndexed { index, permission ->
if (permission == ShizukuProvider.PERMISSION) {
onRequestPermissionResult(requestCode, grantResults[index])
}
}
}

override voidonRequestPermissionResult(requestCode: Int, grantResult: Int){
val isGranted = grantResult == PackageManager.PERMISSION_GRANTED
//Dostuff based on the result.

}
}

Giava:

publicclassExampleActivityextendsAppCompatActivityimplementsShizuku.OnRequestPermissionResultListener{
...

@Override
publicvoidonRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
int result = grantResults[i];

if (permission.equals(ShizukuProvider.PERMISSION) {
onRequestPermissionResult(requestCode, result);
}
}
}

@Override
publicvoidonRequestPermissionResult(int requestCode, int grantResult){
boolean isGranted = grantResult == PackageManager.PERMISSION_GRANTED;
//Dostuff based on the result.
}
}

Utilizzo delle API

Ora che Shizuku è configurato e le autorizzazioni sono concesse, puoi iniziare a chiamare effettivamente le API utilizzando Shizuku. Il processo qui è leggermente diverso da quello a cui potresti essere abituato. Invece di chiamare getSystemService() e lanciare qualcosa del genere WindowManager, dovrai invece utilizzare le API interne per questi (ad esempio, IWindowManager).

Shizuku include un bypass per la lista nera delle API nascoste di Android, quindi non dovrai preoccuparti di questo quando lo usi. Se sei curioso di sapere come aggirarlo da solo, dai un'occhiata al mio tutorial precedente.

Ecco un rapido esempio (utilizzando la riflessione) che mostra come ottenere un'istanza di IPackageManager e usarlo per concedere a un'app un'autorizzazione di runtime.

Kotlin:

val iPmClass = Class.forName("android.content.pm.IPackageManager")
val iPmStub = Class.forName("android.content.pm.IPackageManager\$Stub")
val asInterfaceMethod = iPmStub.getMethod("asInterface", IBinder::class.java)
val grantRuntimePermissionMethod = iPmClass.getMethod("grantRuntimePermission", String:: class.java /* package name */, String:: class.java /* permission name */, Int:: class.java /* user ID */)

val iPmInstance = asInterfaceMethod.invoke(null, ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package")))

grantRuntimePermissionMethod.invoke(iPmInstance, "com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)

Giava:

Class< ?>> iPmClass = Class.forName("android.content.pm.IPackageManager");
Class< ?>> iPmStub = Class.forName("android.content.pm.IPackageManager$Stub");
Method asInterfaceMethod = iPmStub.getMethod("asInterface", IBinder.class);
Method grantRuntimePermissionMethod = iPmClass.getMethod("grantRuntimePermission", String.class, String.class, int.class);

val iPmInstance = asInterfaceMethod.invoke(null, new ShizukuBinderWrapper(SystemServiceHelper.getSystemService("package")));

grantRuntimePermissionMethod.invoke(iPmInstance, "com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0);

Il processo per altre API è simile. Ottieni i riferimenti alla classe e alla sua sottoclasse Stub. Ottieni il riferimento a asInterface metodo per la classe Stub. Ottieni i riferimenti ai metodi desiderati dalla classe stessa. Quindi, invoca il asInterface riferimento al metodo che hai, sostituendo "package" sopra con qualunque servizio tu abbia bisogno. Tale istanza può quindi essere passata per le invocazioni del metodo.

Consiglio dell'esperto: puoi evitare del tutto di utilizzare la riflessione se installi un SDK modificato. Dai un'occhiata al repository GitHub di anggrayudi per gli SDK modificati e le istruzioni di installazione. Con questo installato, il codice sopra (in Kotlin) diventa molto più semplice.

val iPm = IPackageManager.Stub.asInterface(ShizukuBinderWrapper(SystemServiceHelper.getService("package"))))
iPm.grantRuntimePermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)

Qualsiasi API basata su AIDL può essere utilizzata con questo metodo da qualsiasi punto della tua app, purché Shizuku sia in esecuzione e la tua app disponga dell'autorizzazione.

Servizio utenti

Sebbene il metodo Binder copra la maggior parte dei casi d'uso, potrebbero esserci momenti in cui è necessaria un'API che non disponga di un'interfaccia Binder diretta. In tal caso, puoi utilizzare la funzione Servizio utente in Shizuku.

Questo metodo funziona in modo simile a un normale servizio in Android. Lo "avvii", comunichi collegandoti ad esso con una ServiceConnection ed esegui la tua logica nella classe di servizio. La differenza è che non stai utilizzando il servizio Android e qualsiasi cosa all'interno del servizio viene eseguita con le autorizzazioni ADB.

Ora ci sono alcune limitazioni. Il servizio utente viene eseguito in un processo e in un utente completamente separati, quindi non puoi interagire con il resto della tua app se non tramite i tuoi callback e raccoglitori AIDL. Poiché inoltre non viene eseguito in un processo dell'app corretto, alcune cose come l'associazione dei servizi Android potrebbero non funzionare correttamente.

Ciò richiede anche Shizuku versione 10 o successiva. Sebbene la maggior parte delle fonti dell'app al momento disponga della versione 11, dovresti comunque includere un controllo della versione, che verrà incluso nell'esempio.

Definire un AIDL

Per iniziare, dovrai creare un nuovo file AIDL. Puoi farlo in Android Studio facendo clic con il pulsante destro del mouse su qualsiasi cosa nella visualizzazione file Android, passando con il mouse sull'opzione "Nuovo" e scegliendo l'opzione "AIDL". Inserisci il nome del file (ad esempio "IUserService") e Android Studio creerà un file modello per te.

Se non hai familiarità con il funzionamento degli AIDL, assicurati di dare un'occhiata La documentazione di Google.

Rimuovere i metodi del modello dall'AIDL e quindi aggiungere a destroy() metodo con l'ID corretto per Shizuku. Questo verrà chiamato quando Shizuku sta uccidendo il servizio utente e dovrebbe essere usato per ripulire eventuali riferimenti o logica in corso che hai.

Esempio AIDL:

packagetk.zwander.shizukudemo;

interfaceIUserService{
voiddestroy()= 16777114;

void grantPermission(String packageName, String permission, int userId) = 1; //You can specify your own method IDs in the AIDL. Check out the documentation for more details on this.
}

Attuazione dell'AIDL

La prossima cosa da fare è effettivamente implementare l'AIDL.

Giava:

publicclassUserServiceextendsIUserService.Stub{
//Make sure to include an empty constructor.
publicUserService(){
}

@Override
publicvoiddestroy(){
//Shizuku wants the service to be killed. Clean up and then exit.
System.exit(0);
}

@Override
publicvoidgrantPermission(String packageName, String permission, int userId){
//No need to use ShizukuBinderWrapper.
IPackageManager.Stub.asInterface(SystemServiceHelper.getService("package")).grantRuntimePermission(packageName, permission, userId);
}
}

Kotlin:

classUserService : IUserService.Stub {
//Include an empty constructor.
constructor() {
}

override fun destroy(){
//Shizuku wants the service to be killed. Clean up and exit.
System.exit(0)
}

override fun grantPermission(packageName: String, permission: String, userId: Int) {
//No need for ShizukuBinderWrapper.
IPackageManager.Stub.asInterface(SystemServiceHelper.getService("package")).grantRuntimePermission(packageName, permission, userId)
}
}

Gli esempi precedenti presuppongono che tu abbia il file API nascosta Android SDK installato.

Configurazione della connessione al servizio

Ora che il servizio utente è definito e implementato, è il momento di configurarlo per l'uso. La prima cosa che dovresti fare è definire una ServiceConnection con cui desideri comunicare (ad esempio, dall'attività principale nella tua app).

Giava:

private IUserService binder = null;

privatefinal ServiceConnection connection = new ServiceConnection() {
@Override
publicvoidonServiceConnected(ComponentName componentName, IBinder binder){
if (binder != null && binder.pingBinder()) {
binder = IUserService.Stub.asInterface(binder);
}
}

@Override
publicvoidonServiceDisconnected(ComponentName componentName){
binder = null;
}
}

Kotlin:

privatevar binder: IUserService? = null

private val connection = object: ServiceConnection {
override fun onServiceConnected(componentName: ComponentName, binder: IBinder?){
if (binder != null && binder.pingBinder()) {
binder = IUserService.Stub.asInterface(binder)
}
}

override fun onServiceDisconnected(componentName: ComponentName){
binder = null;
}
}

IL binder variabile è ciò che utilizzerai per comunicare con il servizio utente dalla tua app. Per verificare se è disponibile per l'uso, controlla semplicemente che non sia null e così pingBinder() ritorna VERO, proprio come nell'esempio di codice sopra.

Creazione degli argomenti del servizio utente

Prima di poter controllare il servizio utente, dovrai definire alcuni argomenti che Shizuku utilizzerà all'avvio e all'arresto. Questi includono cose come dire a Shizuku il nome della classe del servizio, specificare il suffisso del processo, se è possibile eseguire il debug o meno e quale versione è.

Giava:

privatefinal Shizuku.UserServiceArgs serviceArgs = new Shizuku.UserServiceArgs(
newComponentName(BuildConfig.APPLICATION_ID, UserService.class.getName()))
.processNameSuffix("user_service")
.debuggable(BuildConfig.DEBUG)
.version(BuildConfig.VERSION_CODE);

Kotlin:

private val serviceArgs = Shizuku.UserServiceArgs(
ComponentName(BuildConfig.APPLICATION_ID, UserService.class::java.getName()))
.processNameSuffix("user_service")
.debuggable(BuildCOnfig.DEBUG)
.version(BuildConfig.VERSION_CODE)

Avvio, arresto e collegamento del servizio utente

Le azioni di avvio e collegamento e le azioni di arresto e separazione sono unificate in Shizuku. Non esistono metodi di avvio e associazione separati o metodi di arresto e separazione.

Ecco come fare iniziare e legare il Servizio Utenti.

Giava:

if (Shizuku.getVersion >= 10) {
//This only works on Shizuku 10 or later.
Shizuku.bindUserService(serviceArgs, connection);
} else {
//Tell the user to upgrade Shizuku.
}

Kotlin:

if (Shizuku.getVersion() >= 10) {
//This only works on Shizuku 10 or later.
Shizuku.bindUserService(serviceArgs, connection)
} else {
//Tell the user to upgrade Shizuku.
}

Ecco come fare fermati e sciogliti il Servizio Utenti.

Giava:

if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true);
}

Kotlin:

if (Shizuku.getVersion >= 10) {
Shizuku.unbindUserService(serviceArgs, connection, true)
}

Invocazione del servizio utente

Una volta avviato il servizio utente, puoi iniziare a utilizzarlo. Controlla semplicemente se il binder la variabile è diversa da zero ed è possibile eseguire il ping, quindi effettuare la chiamata al metodo.

Giava:

if (binder != null && binder.pingBinder()) {
binder.grantPermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0);
}

Kotlin:

if (binder?.pingBinder() == true) {
binder?.grantPermission("com.zacharee1.systemuituner", android.Manifest.permission.WRITE_SECURE_SETTINGS, 0)
}

Conclusione

Se hai seguito tutto ciò, ora dovresti avere un'integrazione Shizuku funzionante. Ricorda solo di dire ai tuoi utenti di installare Shizuku e di verificare adeguatamente che Shizuku sia disponibile prima di provare a usarlo.