Outils pour utilisateurs

Outils du site


apk-hacking

Hacking d'​applications Android

Introduction

J'utilise ici le terme "hacking" dans son sens noble: Comprendre le fonctionnement et modifier pour l'adapter à ses besoins.

Je vais donc décrire ici la manière d'examiner et modifier une application Android (fichier .apk). La motivation est double:

  • S'amuser à bidouiller et comprendre le fonctionnement des applications (en particulier le code machine Dalvik d'Android).
  • Protéger sa vie privée en coupant certaines fonctionnalités des applications (géolocalisation, etc.)

L'ingénierie inverse de fichiers APK

APK est le format standard de packaging d'applications Android. N'importe quel fichier APK est installable sur un système Android tant que vous avez autorisé l'installation d'applications tierces (dans la configuration d'Android). En fait, les fichiers APK sont de simples fichiers ZIP contenant un certain nombre de fichier (fichiers XML descriptifs, fichier de données, fichiers .dex contenant les programmes…)

Les applications Android sont développées en Java, mais compilées pour une machine virtuelle différente de celle de Java: Au lieu d'utiliser la JVM, Android utilise une machine virtuelle appelée Dalvik. Son code machine est bien entendu différent de celui de la JVM.

  • En java, un fichier .java compilé donnera un fichier .class (contenant le bytecode Java). Les fichiers .class et autres ressources sont rassemblées dans un fichier .jar
  • Pour Android, un fichier .java compilé donnera un fichier .dex (contenant le bytecode Dalvik). Les fichiers .dex et autres ressources sont rassemblées dans un fichier .apk

Un même code Java donnant habituellement à peu près les mêmes bytecodes, il existe des outils (comme JAD) capables de "décompiler" des .class en .java, mais cette décompilation n'est pas toujours possible et dans la quasi-totalité des cas, les fichiers java ne sont de toute manière pas recompilables.

Il en va de même pour les .dex: Les fichiers java obtenus à partir des .dex ne sont généralement pas recompilables. Ce n'est donc pas une bonne piste pour modifier des applications.

La bonne manière de procéder est donc d'aller modifier directement le code Dalvik.

Nous n'allons pas ici faire des cours de Java ou d'assembleur, mais la connaissances de ces langages est un plus pour comprendre ce qui suit.

PS: J'ai effectué ce hack sans aucune connaissance préalable du langage machine Dalvik.

Outils

Voici les outils que nous allons utiliser:

  • dex2jar, un outils pour convertir les .apk en .jar
  • JD-GUI, une interface graphique pour décompiler les .jar
  • APK-Studio, pour modifier le bytecode .dex des apk, regénérer les APK et re-calculer automatiquement les signatures d'APK (parce que je suis un feignant). ( Lien de téléchargement)

Pourquoi:

  • Convertir les .apk en .jar et les lire dans JD-GUI facilitera l'examen du code: On obtiendra (pour la plupart des .dex) les sources Java correspondant, ce qui est bien plus facile à lire que des instructions machine Dalvik. Cela sera bien plus facile pour comprendre le code.
  • Une fois le code à modifier localisé, on utilisera APK-Studio pour aller modifier directement le code Dalvik.

APK-Studio permet de montrer le code machine Dalvik sous forme de source Smali. Smali est une sorte de langage assembleur qui donne du bytecode Dalvik. Le code Smali n'est pas très difficile à comprendre et se compare facilement au source Java.

Cas pratique

Récupérez le fichier APK de l'application à modifier, soit en copiant-collant l'URL GooglePlay de l'application dans cette page, soit en utilisant cette application de backup. Transférez l'APK sur votre ordinateur.

A titre d'exemple, nous allons supprimer la géolocalisation systématique de l'application Crédit Agricole Alsace Vosges (com.cavo.MonCAAlsace.apk). Chaque fois que vous voulez consulter vos comptes, l'application essaie de vous géolocaliser. Il n'existe aucune option pour le désactiver. Ce comportement ne me semble pas acceptable.

Étape 1 : Convertir l'APK en Jar

Faites:

./d2j-dex2jar.sh com.cavo.MonCAAlsace.apk

ou

d2j-dex2jar.bat com.cavo.MonCAAlsace.apk

Cela va créer le fichier com.cavo.MonCAAlsace-dex2jar.jar.

Étape 2 : Extraire les sources Java décompilées

Lancez JD-GUI et ouvrez le fichier .jar précédemment créé, puis allez dans le menu "Fichiers" > "Save all sources…".

  • Si JD-GUI ne démarre plus (sous Java 9 par exemple), avec le message d'erreur:
    Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make jdk.internal.loader.ClassLoaders$AppClassLoader(jdk.internal.loader.ClassLoaders$PlatformClassLoader,jdk.internal.loader.URLClassPath) accessible: module java.base does not "opens jdk.internal.loader" to unnamed module @5b87ed94
  • Il vous suffit de démarrer JD-GUI avec la ligne de commande suivante:
    java --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens jdk.zipfs/jdk.nio.zipfs=ALL-UNNAMED -jar jd-gui-1.4.0.jar

Cela va créer le fichier com.cavo.MonCAAlsace-dex2jar.src.zip qui contient les sources Java décompilées de l'application.

Notes:

  • Pas de miracle: Ces sources java ne sont pas recompilables.
  • Certaines classes ou méthodes ne seront pas décompilées (parce-que JAD n'y sera pas parvenu). Dans ce cas, JD-GUI affiche directement le code Dalvik.

Nous n'allons pas utiliser ces sources java pour les modifier. Nous allons juste les utiliser pour trouver que ce nous cherchons.

Dézippez le fichier, ce qui vous permettra ensuite rechercher dans les sources (Je n'utilise pas la recherche interne de JD-GUI car je ne la trouve pas fiable).

Étape 3 : Rechercher ce que nous voulons modifier

Note importante: Un certain pourcentage d'applications Android ont leur code "obfusqué": Toutes les méthodes et attributs sont renommés (a/aa/ab/ac/….) afin de rendre le code incompréhensible. Cela complique en effet la lecture du code, mais ces applications doivent faire appel aux API Android qui doivent - elles - porter des noms explicites. On arrive donc généralement à retrouver ses petits.

Dans notre cas, vous voulons empêcher l'application de faire appel aux fonctions GPS pour nous localiser. L'opération est donc assez simple: Localiser les appels aux API GPS et les neutraliser.

Dans le SDK Android, les fonctions GPS sont gérées par le LocationManager. Nous allons donc simplement rechercher ce mot dans les sources. On les trouve là:

  • \com\cavo\MonCAAlsace\Accueil\accueil.java
  • \com\cavo\MonCAAlsace\Bibliotheques\ARLayout.java
  • \com\cavo\MonCAAlsace\Quotidien\agences.java
  • \com\cavo\MonCAAlsace\Quotidien\geoloc_agence.java
  • \com\cavo\MonCAAlsace\Quotidien\ra_agences.java
  • \com\flurry\android\monolithic\sdk\impl\in.java

Prenons en exemple la première, accueil.java:

On trouve l'appel au LocationManager dans l'initialisation:

      ...
      Global.Tablette = isTablet(this);
      AppRater.app_launched(getParent());
      ((LocationManager)getSystemService("location")).requestLocationUpdates("network", 10000L, 0.0F, this);
      this.progressDialog = new ProgressDialog(getParent());
      this.progressDialog.setCancelable(false);
      ...

Ils instancient un LocationManager, puis lui demandent une geolocalisation rapide (par le réseau), et quand c'est fait, appeler la callback onLocationChanged que possède this.

Pour cette classe, il suffit donc de neutraliser cette ligne. Allons-voir dans le source Dalvik. Accrochez-vous à votre caleçon, on plonge.

Étape 4 : Modifier le code Smali/Dalvik

Lancez APK-Studio, menu File > APK. Dans la fenêtre qui s'affiche:

  • Project name: Entrez ce que vous voulez. Par exemple 'CA'.
  • APK Path: indiquez-lui votre fichier APK.
  • Project location: Créez un répertoire vide et indiquez-le là. APK-Studio y placera ses fichiers de travail.

Cliquez sur le bouton "Create" et laissez-le mouliner.

Dans APK-Studio, ouvrez CA/smali/android/com/cavo/MonCAAlsace/Accueil/accueil.smali. Vous devez voir un code qui commence comme cela:

.class public Lcom/cavo/MonCAAlsace/Accueil/accueil;
.super Landroid/app/Activity;
.source "accueil.java"

# interfaces
.implements Landroid/location/LocationListener;


# static fields
.field private static final ALERT_DIALOG:I

.field public static Actus:Landroid/widget/Button;

.field public static Agences:Landroid/widget/Button;

...

Il s'agit de la classe Accueil.java que nous voulons modifier.

Que fait La ligne qui nous intéresse ?

((LocationManager)getSystemService("location")).requestLocationUpdates("network", 10000L, 0.0F, this);
  • Elle appelle la fonction getSystemService (en passant la valeur "location").
  • Elle caste le résultat en LocationManager.
  • Elle appelle la méthode requestLocationUpdates en passant les paramètres "network", 10000, 0 et this.

Voyons si vous vous y retrouvez dans le code Dalvik correspondant:

    .line 146
    const-string v5, "location"
    move-object/from16 v0, p0
    invoke-virtual {v0, v5}, Lcom/cavo/MonCAAlsace/Accueil/accueil;->getSystemService(Ljava/lang/String;)Ljava/lang/Object;
    move-result-object v2
    check-cast v2, Landroid/location/LocationManager;
    
    .line 147
    .local v2, "locMan":Landroid/location/LocationManager;
    const-string v3, "network"
    const-wide/16 v4, 0x2710
    const/4 v6, 0x0
    move-object/from16 v7, p0
    invoke-virtual/range {v2 .. v7}, Landroid/location/LocationManager;->requestLocationUpdates(Ljava/lang/String;JFLandroid/location/LocationListener;)V

Pas si compliqué, hein ?

Vous pouvez voir, dans la section .line 146, l'appel (invoke) à la fonction getSystemService(), le stockage du résultat dans le registre v2, puis le casting en LocationManager. Et dans la section .line 147, l'appel à la méthode requestLocationUpdates() avec différents paramètres.

  • Dalvik utilise des registres (v0,v1,v2…) dans lesquels il charge les valeurs à traiter.
  • p1,p2,p3… sont les paramètres passés à la méthode. p0 est équivalent à this.
  • Les constantes sont signalées simplement avec leur type et leur valeur.
  • Les appels de méthode sont invoke-*
  • Les variables locales déclarées avec .local
  • etc.
  • Je ne vais pas rappeler ici l'intégralité de ce langage assembleur (voir ), mais vous pouvez constater qu'on arrive assez facilement à retrouver la correspondance entre les instructions java et les instructions Dalvik.

Note: Vous ne devez pas supprimer les directives .lines. L'assembleur Smali/Dalvik s'en sert pour se repérer.

Pour annuler complètement la ligne, on va effectuer une opération classique: la 'NOPer'. (NOP est une instruction qui ne fait rien: "NOP" = "No OPeration").

Le code devient:

    .line 146
    nop

    .line 147
    nop

(Ne touchez pas ce qu'il y a avant .line 146 ni à ce qu'il y a après .line 148.)

Il va nous falloir trouver tous les autres appels au LocationManager et les neutraliser.

Voici les autres fichiers modifiés:

  • \CA\smali\com\cavo\MonCAAlsace\Bibliotheques\ARLayout.smali:
    .line 50
    iget-object v0, p0, Lcom/cavo/MonCAAlsace/Bibliotheques/ARLayout;->locMan:Landroid/location/LocationManager;
    const-string v1, "network"
    const-wide/16 v2, 0x64
    const/high16 v4, 0x3f800000
    move-object v5, p0
    invoke-virtual/range {v0 .. v5}, Landroid/location/LocationManager;->requestLocationUpdates(Ljava/lang/String;JFLandroid/location/LocationListener;)V
    .line 50
    nop
  • \CA\smali\com\cavo\MonCAAlsace\Quotidien\geoloc_agence.smali:
    .line 101
    iget-object v0, p0, Lcom/cavo/MonCAAlsace/Quotidien/geoloc_agence;->lm:Landroid/location/LocationManager;
    const-string v1, "network"
    const-wide/16 v2, 0x2710
    const/4 v4, 0x0
    move-object v5, p0
    invoke-virtual/range {v0 .. v5}, Landroid/location/LocationManager;->requestLocationUpdates(Ljava/lang/String;JFLandroid/location/LocationListener;)V
    .line 101
    nop
  • \CA\smali\com\cavo\MonCAAlsace\Quotidien\ra_agences.smali:
    .line 195
    sget-object v1, Lcom/cavo/MonCAAlsace/Quotidien/ra_agences;->ctx:Landroid/content/Context;
    const-string v2, "location"
    invoke-virtual {v1, v2}, Landroid/content/Context;->getSystemService(Ljava/lang/String;)Ljava/lang/Object;
    move-result-object v0
    check-cast v0, Landroid/location/LocationManager;

    .line 196
    .local v0, "locMan":Landroid/location/LocationManager;
    const-string v1, "network"
    const-wide/16 v2, 0x2710
    const/4 v4, 0x0
    iget-object v5, p0, Lcom/cavo/MonCAAlsace/Quotidien/ra_agences;->gpsListener:Landroid/location/LocationListener;
    invoke-virtual/range {v0 .. v5}, Landroid/location/LocationManager;->requestLocationUpdates(Ljava/lang/String;JFLandroid/location/LocationListener;)V
    .line 195
    nop

    .line 196
    .local v0, "locMan":Landroid/location/LocationManager;
    nop
  • \CA\smali\com\flurry\android\monolithic\sdk\impl\in.smali:
    .line 161
    invoke-static {p1}, Landroid/text/TextUtils;->isEmpty(Ljava/lang/CharSequence;)Z
    move-result v0
    if-nez v0, :cond_0

    .line 162
    iget-object v0, p0, Lcom/flurry/android/monolithic/sdk/impl/in;->f:Landroid/location/LocationManager;
    const-wide/32 v2, 0x1b7740
    const/4 v4, 0x0
    iget-object v5, p0, Lcom/flurry/android/monolithic/sdk/impl/in;->i:Lcom/flurry/android/monolithic/sdk/impl/io;
    invoke-static {}, Landroid/os/Looper;->getMainLooper()Landroid/os/Looper;
    move-result-object v6
    move-object v1, p1
    invoke-virtual/range {v0 .. v6}, Landroid/location/LocationManager;->requestLocationUpdates(Ljava/lang/String;JFLandroid/location/LocationListener;Landroid/os/Looper;)V
    .line 161
    nop

    .line 162
    nop

Étape 5: Re-packager l'APK

Une fois les modifications apportées aux différentes classes dans APK-Studio, rien de plus simple pour générer l'APK correspondant:

  • Sauvegardez vos modifications: Menu Files > Save all.
  • Re-générez l'APK: Menu Project > Build.

L'opération prendra un certain temps.

Regardez ensuite dans votre répertoire projet /CA/build/rebuilt.apk. Voilà votre fichier .apk modifié prêt à être installé.

Dé-installez l'application d'origine et installez ce nouvel APK. (Si l'application plante, c'est que vos modification ne sont pas correctes.)

Dans notre cas, le lancement de l'application "Crédit Agricole" ne déclenchera plus l'appel au GPS (même si ce dernier n'est pas désactivé).

Notes

  • Là nous avons neutralisé les appels aux fonctions de géolocalisation car l'application accepte de fonctionner sans, mais certaines applications refuseront. Dans ce cas, il vous faudra changer de stratégie: Leur fournir malgré tout les données, mais avec des latitutes/longitudes bidon (par exemple en allant modifier la callback onLocationChanged() du programme.)
  • Dans notre exemple nous avons juste NOPé le code qui nous embêtait, mais dans certains cas il vous faudra faire des choses plus complexes (renvoyer null directement (return-void), instancier un objet du bon type (non initialisé) et le renvoyer, shunter un saut conditionnel, etc.)
  • Cette méthode vous obligera à re-faire votre hack à chaque mise à jour de l'application en question.
  • Les choses peuvent être plus compliquées pour les applications qui chargent dynamiquement du code.
  • L'obfuscation peut compliquer la compréhension du code. Dans ce cas il peut être utile de charger les sources générées par JD-GUI dans un IDE (IntelliJ, Eclipse…) afin de faire un peu de refactoring (renommage des méthodes et attributs), ce qui aidera grandement à comprendre le code.

Liens divers

Je conserve ici des liens divers en rapport avec le bidouillage d'APK:

apk-hacking.txt · Dernière modification : 2019/01/22 08:26 de sebsauvage